diff options
Diffstat (limited to 'editor/plugins')
170 files changed, 26002 insertions, 14211 deletions
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index 36a814c30a..a7d7c0145a 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,7 +33,10 @@ #include "canvas_item_editor_plugin.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" +#include "scene/gui/separator.h" bool AbstractPolygon2DEditor::Vertex::operator==(const AbstractPolygon2DEditor::Vertex &p_vertex) const { return polygon == p_vertex.polygon && vertex == p_vertex.vertex; @@ -147,12 +150,16 @@ void AbstractPolygon2DEditor::_menu_option(int p_option) { void AbstractPolygon2DEditor::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + button_create->set_icon(get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + button_edit->set_icon(get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + button_delete->set_icon(get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + } break; + case NOTIFICATION_READY: { disable_polygon_editing(false, String()); - button_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); - button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); - button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); button_edit->set_pressed(true); get_tree()->connect("node_removed", callable_mp(this, &AbstractPolygon2DEditor::_node_removed)); @@ -242,14 +249,18 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) return false; } + if (!_get_node()->is_visible_in_tree()) { + return false; + } + Ref<InputEventMouseButton> mb = p_event; if (!_has_resource()) { - if (mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { create_resource->set_text(String("No polygon resource on this node.\nCreate and assign one?")); create_resource->popup_centered(); } - return (mb.is_valid() && mb->get_button_index() == 1); + return (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT); } CanvasItemEditor::Tool tool = CanvasItemEditor::get_singleton()->get_current_tool(); @@ -264,7 +275,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) Vector2 cpoint = _get_node()->to_local(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mb->get_position()))); if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) { return false; @@ -283,15 +294,14 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) _commit_action(); return true; } else { - Vector<Vector2> vertices2 = _get_polygon(insert.polygon); - pre_move_edit = vertices2; edited_point = PosVertex(insert.polygon, insert.vertex + 1, xform.affine_inverse().xform(insert.pos)); - vertices2.insert(edited_point.vertex, edited_point.pos); + vertices.insert(edited_point.vertex, edited_point.pos); + pre_move_edit = vertices; selected_point = Vertex(edited_point.polygon, edited_point.vertex); edge_point = PosVertex(); undo_redo->create_action(TTR("Insert Point")); - _action_set_polygon(insert.polygon, vertices2); + _action_set_polygon(insert.polygon, vertices); _commit_action(); return true; } @@ -326,7 +336,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) return true; } } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && !edited_point.valid()) { + } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && !edited_point.valid()) { const PosVertex closest = closest_point(gpoint); if (closest.valid()) { @@ -335,7 +345,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) } } } else if (mode == MODE_DELETE) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { const PosVertex closest = closest_point(gpoint); if (closest.valid()) { @@ -346,7 +356,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) } if (mode == MODE_CREATE) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { if (_is_line()) { // for lines, we don't have a wip mode, and we can undo each single add point. Vector<Vector2> vertices = _get_polygon(0); @@ -384,7 +394,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) return true; } } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) { + } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && wip_active) { _wip_cancel(); } } @@ -395,7 +405,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) if (mm.is_valid()) { Vector2 gpoint = mm->get_position(); - if (edited_point.valid() && (wip_active || (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT))) { + if (edited_point.valid() && (wip_active || (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE)) { Vector2 cpoint = _get_node()->to_local(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint))); //Move the point in a single axis. Should only work when editing a polygon and while holding shift. @@ -443,10 +453,10 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) Ref<InputEventKey> k = p_event; if (k.is_valid() && k->is_pressed()) { - if (k->get_keycode() == KEY_DELETE || k->get_keycode() == KEY_BACKSPACE) { + if (k->get_keycode() == Key::KEY_DELETE || k->get_keycode() == Key::BACKSPACE) { if (wip_active && selected_point.polygon == -1) { if (wip.size() > selected_point.vertex) { - wip.remove(selected_point.vertex); + wip.remove_at(selected_point.vertex); _wip_changed(); selected_point = wip.size() - 1; canvas_item_editor->update_viewport(); @@ -460,9 +470,9 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) return true; } } - } else if (wip_active && k->get_keycode() == KEY_ENTER) { + } else if (wip_active && k->get_keycode() == Key::ENTER) { _wip_close(); - } else if (wip_active && k->get_keycode() == KEY_ESCAPE) { + } else if (wip_active && k->get_keycode() == Key::ESCAPE) { _wip_cancel(); } } @@ -475,6 +485,10 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl return; } + if (!_get_node()->is_visible_in_tree()) { + return; + } + Transform2D xform = canvas_item_editor->get_canvas_transform() * _get_node()->get_global_transform(); // All polygon points are sharp, so use the sharp handle icon const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); @@ -553,8 +567,8 @@ 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); - p_overlay->draw_string(font, point - num_size * 0.5, num, HALIGN_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); + Size2 num_size = font->get_string_size(num, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); + 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)); } } } @@ -599,7 +613,7 @@ void AbstractPolygon2DEditor::remove_point(const Vertex &p_vertex) { Vector<Vector2> vertices = _get_polygon(p_vertex.polygon); if (vertices.size() > (_is_line() ? 2 : 3)) { - vertices.remove(p_vertex.vertex); + vertices.remove_at(p_vertex.vertex); undo_redo->create_action(TTR("Edit Polygon (Remove Point)")); _action_set_polygon(p_vertex.polygon, vertices); @@ -690,12 +704,9 @@ AbstractPolygon2DEditor::PosVertex AbstractPolygon2DEditor::closest_edge_point(c return closest; } -AbstractPolygon2DEditor::AbstractPolygon2DEditor(EditorNode *p_editor, bool p_wip_destructive) { - canvas_item_editor = nullptr; - editor = p_editor; +AbstractPolygon2DEditor::AbstractPolygon2DEditor(bool p_wip_destructive) { undo_redo = EditorNode::get_undo_redo(); - wip_active = false; edited_point = PosVertex(); wip_destructive = p_wip_destructive; @@ -707,26 +718,24 @@ AbstractPolygon2DEditor::AbstractPolygon2DEditor(EditorNode *p_editor, bool p_wi button_create = memnew(Button); button_create->set_flat(true); add_child(button_create); - button_create->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option), varray(MODE_CREATE)); + button_create->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_CREATE)); button_create->set_toggle_mode(true); button_edit = memnew(Button); button_edit->set_flat(true); add_child(button_edit); - button_edit->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option), varray(MODE_EDIT)); + button_edit->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_EDIT)); button_edit->set_toggle_mode(true); button_delete = memnew(Button); button_delete->set_flat(true); add_child(button_delete); - button_delete->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option), varray(MODE_DELETE)); + button_delete->connect("pressed", callable_mp(this, &AbstractPolygon2DEditor::_menu_option).bind(MODE_DELETE)); button_delete->set_toggle_mode(true); create_resource = memnew(ConfirmationDialog); add_child(create_resource); - create_resource->get_ok_button()->set_text(TTR("Create")); - - mode = MODE_EDIT; + create_resource->set_ok_button_text(TTR("Create")); } void AbstractPolygon2DEditorPlugin::edit(Object *p_object) { @@ -746,9 +755,8 @@ void AbstractPolygon2DEditorPlugin::make_visible(bool p_visible) { } } -AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(EditorNode *p_node, AbstractPolygon2DEditor *p_polygon_editor, String p_class) : +AbstractPolygon2DEditorPlugin::AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, String p_class) : polygon_editor(p_polygon_editor), - editor(p_node), klass(p_class) { CanvasItemEditor::get_singleton()->add_control_to_menu_panel(polygon_editor); polygon_editor->hide(); diff --git a/editor/plugins/abstract_polygon_2d_editor.h b/editor/plugins/abstract_polygon_2d_editor.h index 4f9adfff25..696fd7b637 100644 --- a/editor/plugins/abstract_polygon_2d_editor.h +++ b/editor/plugins/abstract_polygon_2d_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,18 +31,18 @@ #ifndef ABSTRACT_POLYGON_2D_EDITOR_H #define ABSTRACT_POLYGON_2D_EDITOR_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/polygon_2d.h" +#include "scene/gui/box_container.h" class CanvasItemEditor; class AbstractPolygon2DEditor : public HBoxContainer { GDCLASS(AbstractPolygon2DEditor, HBoxContainer); - Button *button_create; - Button *button_edit; - Button *button_delete; + Button *button_create = nullptr; + Button *button_edit = nullptr; + Button *button_delete = nullptr; struct Vertex { Vertex() {} @@ -80,15 +80,14 @@ class AbstractPolygon2DEditor : public HBoxContainer { Vector<Vector2> pre_move_edit; Vector<Vector2> wip; - bool wip_active; - bool wip_destructive; + bool wip_active = false; + bool wip_destructive = false; - bool _polygon_editing_enabled; + bool _polygon_editing_enabled = false; - CanvasItemEditor *canvas_item_editor; - EditorNode *editor; - Panel *panel; - ConfirmationDialog *create_resource; + CanvasItemEditor *canvas_item_editor = nullptr; + Panel *panel = nullptr; + ConfirmationDialog *create_resource = nullptr; protected: enum { @@ -98,15 +97,14 @@ protected: MODE_CONT, }; - int mode; + int mode = MODE_EDIT; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; virtual void _menu_option(int p_option); void _wip_changed(); void _wip_close(); void _wip_cancel(); - bool _delete_point(const Vector2 &p_gpoint); void _notification(int p_what); void _node_removed(Node *p_node); @@ -145,14 +143,13 @@ public: void forward_canvas_draw_over_viewport(Control *p_overlay); void edit(Node *p_polygon); - AbstractPolygon2DEditor(EditorNode *p_editor, bool p_wip_destructive = true); + AbstractPolygon2DEditor(bool p_wip_destructive = true); }; class AbstractPolygon2DEditorPlugin : public EditorPlugin { GDCLASS(AbstractPolygon2DEditorPlugin, EditorPlugin); - AbstractPolygon2DEditor *polygon_editor; - EditorNode *editor; + AbstractPolygon2DEditor *polygon_editor = nullptr; String klass; public: @@ -165,7 +162,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - AbstractPolygon2DEditorPlugin(EditorNode *p_node, AbstractPolygon2DEditor *p_polygon_editor, String p_class); + AbstractPolygon2DEditorPlugin(AbstractPolygon2DEditor *p_polygon_editor, String p_class); ~AbstractPolygon2DEditorPlugin(); }; diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index ad2d9866fa..32d97c65a9 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,10 @@ #include "animation_blend_space_1d_editor.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "scene/animation/animation_blend_tree.h" StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const { @@ -42,7 +45,7 @@ StringName AnimationNodeBlendSpace1DEditor::get_blend_position_path() const { void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_DELETE && !k->is_echo()) { + if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_point != -1) { _erase_selected(); accept_event(); @@ -51,7 +54,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && tool_create->is_pressed()))) { + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (mb->get_button_index() == MouseButton::LEFT && tool_create->is_pressed()))) { menu->clear(); animations_menu->clear(); animations_to_add.clear(); @@ -81,12 +84,12 @@ 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; } int idx = menu->get_item_count(); - menu->add_item(vformat("Add %s", name), idx); + menu->add_item(vformat(TTR("Add %s"), name), idx); menu->set_item_metadata(idx, E); } @@ -98,7 +101,8 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven menu->add_separator(); menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - menu->set_position(blend_space_draw->get_screen_transform().xform(mb->get_position())); + menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); + menu->reset_size(); menu->popup(); add_point_pos = (mb->get_position() / blend_space_draw->get_size()).x; @@ -110,7 +114,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { blend_space_draw->update(); // why not // try to see if a point can be selected @@ -132,7 +136,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT) { if (dragging_selected) { // move float point = blend_space->get_blend_point_position(selected_point); @@ -161,7 +165,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven } // *set* the blend - if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { float blend_pos = mb->get_position().x / blend_space_draw->get_size().x; blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); blend_pos += blend_space->get_min_space(); @@ -184,7 +188,7 @@ void AnimationNodeBlendSpace1DEditor::_blend_space_gui_input(const Ref<InputEven _update_edited_point_pos(); } - if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && tool_blend->is_pressed() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { float blend_pos = mm->get_position().x / blend_space_draw->get_size().x; blend_pos *= blend_space->get_max_space() - blend_space->get_min_space(); blend_pos += blend_space->get_min_space(); @@ -212,7 +216,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; @@ -221,9 +225,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_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height(font_size) + font->get_ascent(font_size)), "0", HALIGN_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 - 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, Math::round(EDSCALE)); } if (snap->is_pressed()) { @@ -237,7 +241,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; @@ -294,10 +298,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)); } } @@ -311,6 +315,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()); @@ -326,13 +332,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(); @@ -528,39 +536,42 @@ void AnimationNodeBlendSpace1DEditor::_open_editor() { } void AnimationNodeBlendSpace1DEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); - tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); - tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); - open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - } - - if (p_what == NOTIFICATION_PROCESS) { - String error; - - if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_PROCESS: { + String error; + + if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { + error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); + } - if (error != error_label->get_text()) { - error_label->set_text(error); - if (error != String()) { - error_panel->show(); - } else { - error_panel->hide(); + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); + } } - } - } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - set_process(is_visible_in_tree()); + case NOTIFICATION_VISIBILITY_CHANGED: { + set_process(is_visible_in_tree()); + } break; } } @@ -588,7 +599,6 @@ AnimationNodeBlendSpace1DEditor *AnimationNodeBlendSpace1DEditor::singleton = nu AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { singleton = this; - updating = false; HBoxContainer *top_hb = memnew(HBoxContainer); add_child(top_hb); @@ -603,7 +613,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); @@ -611,7 +621,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); @@ -619,7 +629,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); @@ -645,6 +655,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)); @@ -660,7 +676,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(); @@ -739,9 +755,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 fe98a91ab3..9b06f3248f 100644 --- a/editor/plugins/animation_blend_space_1d_editor.h +++ b/editor/plugins/animation_blend_space_1d_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,14 +31,13 @@ #ifndef ANIMATION_BLEND_SPACE_1D_EDITOR_H #define ANIMATION_BLEND_SPACE_1D_EDITOR_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_blend_space_1d.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" #include "scene/gui/popup.h" +#include "scene/gui/separator.h" #include "scene/gui/tree.h" class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { @@ -46,36 +45,38 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { Ref<AnimationNodeBlendSpace1D> blend_space; - HBoxContainer *goto_parent_hb; - Button *goto_parent; + HBoxContainer *goto_parent_hb = nullptr; + Button *goto_parent = nullptr; - PanelContainer *panel; - Button *tool_blend; - Button *tool_select; - Button *tool_create; - VSeparator *tool_erase_sep; - Button *tool_erase; - Button *snap; - SpinBox *snap_value; + PanelContainer *panel = nullptr; + Button *tool_blend = nullptr; + Button *tool_select = nullptr; + Button *tool_create = nullptr; + VSeparator *tool_erase_sep = nullptr; + Button *tool_erase = nullptr; + Button *snap = nullptr; + SpinBox *snap_value = nullptr; - LineEdit *label_value; - SpinBox *max_value; - SpinBox *min_value; + LineEdit *label_value = nullptr; + SpinBox *max_value = nullptr; + SpinBox *min_value = nullptr; - HBoxContainer *edit_hb; - SpinBox *edit_value; - Button *open_editor; + CheckBox *sync = nullptr; - int selected_point; + HBoxContainer *edit_hb = nullptr; + SpinBox *edit_value = nullptr; + Button *open_editor = nullptr; - Control *blend_space_draw; + int selected_point = -1; - PanelContainer *error_panel; - Label *error_label; + Control *blend_space_draw = nullptr; - bool updating; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; - UndoRedo *undo_redo; + bool updating = false; + + UndoRedo *undo_redo = nullptr; static AnimationNodeBlendSpace1DEditor *singleton; @@ -88,14 +89,14 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { void _labels_changed(String); void _snap_toggled(); - PopupMenu *menu; - PopupMenu *animations_menu; + PopupMenu *menu = nullptr; + PopupMenu *animations_menu = nullptr; Vector<String> animations_to_add; - float add_point_pos; + float add_point_pos = 0.0f; Vector<real_t> points; - bool dragging_selected_attempt; - bool dragging_selected; + bool dragging_selected_attempt = false; + bool dragging_selected = false; Vector2 drag_from; Vector2 drag_ofs; @@ -109,9 +110,7 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { void _edit_point_pos(double); void _open_editor(); - void _goto_parent(); - - EditorFileDialog *open_file; + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 49fcac512b..dc764725dd 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,7 +35,10 @@ #include "core/io/resource_loader.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "scene/animation/animation_blend_tree.h" #include "scene/animation/animation_player.h" #include "scene/gui/menu_button.h" @@ -70,7 +73,7 @@ StringName AnimationNodeBlendSpace2DEditor::get_blend_position_path() const { void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; - if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_DELETE && !k->is_echo()) { + if (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_point != -1 || selected_triangle != -1) { _erase_selected(); accept_event(); @@ -79,7 +82,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && tool_create->is_pressed()))) { + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (mb->get_button_index() == MouseButton::LEFT && tool_create->is_pressed()))) { menu->clear(); animations_menu->clear(); animations_to_add.clear(); @@ -105,11 +108,11 @@ 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(); - menu->add_item(vformat("Add %s", name), idx); + menu->add_item(vformat(TTR("Add %s"), name), idx); menu->set_item_metadata(idx, E); } @@ -121,7 +124,8 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven menu->add_separator(); menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - menu->set_position(blend_space_draw->get_screen_transform().xform(mb->get_position())); + menu->set_position(blend_space_draw->get_screen_position() + mb->get_position()); + menu->reset_size(); menu->popup(); add_point_pos = (mb->get_position() / blend_space_draw->get_size()); add_point_pos.y = 1.0 - add_point_pos.y; @@ -129,12 +133,11 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven add_point_pos += blend_space->get_min_space(); if (snap->is_pressed()) { - add_point_pos.x = Math::snapped(add_point_pos.x, blend_space->get_snap().x); - add_point_pos.y = Math::snapped(add_point_pos.y, blend_space->get_snap().y); + add_point_pos = add_point_pos.snapped(blend_space->get_snap()); } } - if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { blend_space_draw->update(); //update anyway //try to see if a point can be selected selected_point = -1; @@ -174,13 +177,13 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_triangle->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { blend_space_draw->update(); //update anyway //try to see if a point can be selected selected_point = -1; for (int i = 0; i < points.size(); i++) { - if (making_triangle.find(i) != -1) { + if (making_triangle.has(i)) { continue; } @@ -209,14 +212,13 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven } } - if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && dragging_selected_attempt && mb->get_button_index() == MouseButton::LEFT) { if (dragging_selected) { //move Vector2 point = blend_space->get_blend_point_position(selected_point); point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } updating = true; @@ -236,7 +238,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven blend_space_draw->update(); } - if (mb.is_valid() && mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && tool_blend->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { Vector2 blend_pos = (mb->get_position() / blend_space_draw->get_size()); blend_pos.y = 1.0 - blend_pos.y; blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); @@ -270,7 +272,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven blend_space_draw->update(); } - if (mm.is_valid() && tool_blend->is_pressed() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && tool_blend->is_pressed() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { Vector2 blend_pos = (mm->get_position() / blend_space_draw->get_size()); blend_pos.y = 1.0 - blend_pos.y; blend_pos *= (blend_space->get_max_space() - blend_space->get_min_space()); @@ -406,22 +408,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_string(font, Point2(2 * EDSCALE, y - font->get_height(font_size) + font->get_ascent(font_size)), "0", HALIGN_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(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, 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_string(font, Point2(x + 2 * EDSCALE, s.height - 2 * EDSCALE - font->get_height(font_size) + font->get_ascent(font_size)), "0", HALIGN_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 - 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, Math::round(EDSCALE)); } if (snap->is_pressed()) { @@ -434,7 +436,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; @@ -448,7 +450,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; @@ -467,8 +469,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { if (dragging_selected && selected_point == point_idx) { point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -478,7 +479,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; @@ -490,10 +491,11 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { color.a *= 0.2; } - Vector<Color> colors; - colors.push_back(color); - colors.push_back(color); - colors.push_back(color); + Vector<Color> colors = { + color, + color, + color + }; blend_space_draw->draw_primitive(points, colors, Vector<Vector2>()); } @@ -503,8 +505,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { if (dragging_selected && selected_point == i) { point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -533,9 +534,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 +565,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 +596,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 +622,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"); @@ -702,8 +706,7 @@ void AnimationNodeBlendSpace2DEditor::_update_edited_point_pos() { if (dragging_selected) { pos += drag_ofs; if (snap->is_pressed()) { - pos.x = Math::snapped(pos.x, blend_space->get_snap().x); - pos.y = Math::snapped(pos.y, blend_space->get_snap().y); + pos = pos.snapped(blend_space->get_snap()); } } updating = true; @@ -732,49 +735,52 @@ void AnimationNodeBlendSpace2DEditor::_edit_point_pos(double) { } void AnimationNodeBlendSpace2DEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); - tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); - tool_triangle->set_icon(get_theme_icon(SNAME("ToolTriangle"), SNAME("EditorIcons"))); - tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); - open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - auto_triangles->set_icon(get_theme_icon(SNAME("AutoTriangle"), SNAME("EditorIcons"))); - interpolation->clear(); - interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0); - interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1); - interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2); - } - - if (p_what == NOTIFICATION_PROCESS) { - String error; - - if (!AnimationTreeEditor::get_singleton()->get_tree()) { - error = TTR("BlendSpace2D does not belong to an AnimationTree node."); - } else if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - } else if (blend_space->get_triangle_count() == 0) { - error = TTR("No triangles exist, so no blending can take place."); - } + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + tool_blend->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("EditKey"), SNAME("EditorIcons"))); + tool_triangle->set_icon(get_theme_icon(SNAME("ToolTriangle"), SNAME("EditorIcons"))); + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + snap->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + open_editor->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + auto_triangles->set_icon(get_theme_icon(SNAME("AutoTriangle"), SNAME("EditorIcons"))); + interpolation->clear(); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), "", 0); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), "", 1); + interpolation->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), "", 2); + } break; + + case NOTIFICATION_PROCESS: { + String error; + + if (!AnimationTreeEditor::get_singleton()->get_tree()) { + error = TTR("BlendSpace2D does not belong to an AnimationTree node."); + } else if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { + error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); + } else if (blend_space->get_triangle_count() == 0) { + error = TTR("No triangles exist, so no blending can take place."); + } - if (error != error_label->get_text()) { - error_label->set_text(error); - if (error != String()) { - error_panel->show(); - } else { - error_panel->hide(); + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); + } } - } - } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - set_process(is_visible_in_tree()); + case NOTIFICATION_VISIBILITY_CHANGED: { + set_process(is_visible_in_tree()); + } break; } } @@ -827,7 +833,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); @@ -835,7 +841,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); @@ -843,7 +849,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); @@ -851,7 +857,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); @@ -897,6 +903,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); @@ -921,7 +934,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 3b8b78b2b5..26471df051 100644 --- a/editor/plugins/animation_blend_space_2d_editor.h +++ b/editor/plugins/animation_blend_space_2d_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,14 +31,13 @@ #ifndef ANIMATION_BLEND_SPACE_2D_EDITOR_H #define ANIMATION_BLEND_SPACE_2D_EDITOR_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_blend_space_2d.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" #include "scene/gui/popup.h" +#include "scene/gui/separator.h" #include "scene/gui/tree.h" class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { @@ -46,43 +45,44 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { Ref<AnimationNodeBlendSpace2D> blend_space; - PanelContainer *panel; - Button *tool_blend; - Button *tool_select; - Button *tool_create; - Button *tool_triangle; - VSeparator *tool_erase_sep; - Button *tool_erase; - Button *snap; - SpinBox *snap_x; - SpinBox *snap_y; - OptionButton *interpolation; - - Button *auto_triangles; - - LineEdit *label_x; - LineEdit *label_y; - SpinBox *max_x_value; - SpinBox *min_x_value; - SpinBox *max_y_value; - SpinBox *min_y_value; - - HBoxContainer *edit_hb; - SpinBox *edit_x; - SpinBox *edit_y; - Button *open_editor; + PanelContainer *panel = nullptr; + Button *tool_blend = nullptr; + Button *tool_select = nullptr; + Button *tool_create = nullptr; + Button *tool_triangle = nullptr; + VSeparator *tool_erase_sep = nullptr; + Button *tool_erase = nullptr; + Button *snap = nullptr; + SpinBox *snap_x = nullptr; + SpinBox *snap_y = nullptr; + CheckBox *sync = nullptr; + OptionButton *interpolation = nullptr; + + Button *auto_triangles = nullptr; + + LineEdit *label_x = nullptr; + LineEdit *label_y = nullptr; + SpinBox *max_x_value = nullptr; + SpinBox *min_x_value = nullptr; + SpinBox *max_y_value = nullptr; + SpinBox *min_y_value = nullptr; + + HBoxContainer *edit_hb = nullptr; + SpinBox *edit_x = nullptr; + SpinBox *edit_y = nullptr; + Button *open_editor = nullptr; int selected_point; int selected_triangle; - Control *blend_space_draw; + Control *blend_space_draw = nullptr; - PanelContainer *error_panel; - Label *error_label; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; bool updating; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; static AnimationNodeBlendSpace2DEditor *singleton; @@ -95,8 +95,8 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { void _labels_changed(String); void _snap_toggled(); - PopupMenu *menu; - PopupMenu *animations_menu; + PopupMenu *menu = nullptr; + PopupMenu *animations_menu = nullptr; Vector<String> animations_to_add; Vector2 add_point_pos; Vector<Vector2> points; @@ -124,7 +124,7 @@ class AnimationNodeBlendSpace2DEditor : public AnimationTreeNodeEditorPlugin { StringName get_blend_position_path() const; - EditorFileDialog *open_file; + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 030d90eeca..79be2d04b3 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,12 +34,16 @@ #include "core/input/input.h" #include "core/io/resource_loader.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_inspector.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "scene/animation/animation_player.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel.h" #include "scene/gui/progress_bar.h" +#include "scene/gui/view_panner.h" #include "scene/main/window.h" void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const Ref<Script> &p_script) { @@ -58,7 +62,7 @@ void AnimationNodeBlendTreeEditor::add_custom_type(const String &p_name, const R void AnimationNodeBlendTreeEditor::remove_custom_type(const Ref<Script> &p_script) { for (int i = 0; i < add_options.size(); i++) { if (add_options[i].script == p_script) { - add_options.remove(i); + add_options.remove_at(i); return; } } @@ -66,9 +70,13 @@ void AnimationNodeBlendTreeEditor::remove_custom_type(const Ref<Script> &p_scrip _update_options_menu(); } -void AnimationNodeBlendTreeEditor::_update_options_menu() { +void AnimationNodeBlendTreeEditor::_update_options_menu(bool p_has_input_ports) { add_node->get_popup()->clear(); + add_node->get_popup()->reset_size(); for (int i = 0; i < add_options.size(); i++) { + if (p_has_input_ports && add_options[i].input_port_count == 0) { + continue; + } add_node->get_popup()->add_item(add_options[i].name, i); } @@ -79,7 +87,7 @@ void AnimationNodeBlendTreeEditor::_update_options_menu() { } add_node->get_popup()->add_separator(); add_node->get_popup()->add_item(TTR("Load..."), MENU_LOAD_FILE); - use_popup_menu_position = false; + use_position_from_popup_menu = false; } Size2 AnimationNodeBlendTreeEditor::get_minimum_size() const { @@ -99,7 +107,7 @@ void AnimationNodeBlendTreeEditor::_property_changed(const StringName &p_propert } void AnimationNodeBlendTreeEditor::_update_graph() { - if (updating) { + if (updating || blend_tree.is_null()) { return; } @@ -140,11 +148,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++) { @@ -172,7 +180,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)); @@ -180,7 +188,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); } @@ -190,7 +198,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); } @@ -229,7 +237,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")); @@ -257,6 +265,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) { @@ -288,14 +298,14 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { anode = EditorSettings::get_singleton()->get_resource_clipboard(); ERR_FAIL_COND(!anode.is_valid()); base_name = anode->get_class(); - } else if (add_options[p_idx].type != String()) { + } else if (!add_options[p_idx].type.is_empty()) { AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(add_options[p_idx].type)); ERR_FAIL_COND(!an); anode = Ref<AnimationNode>(an); base_name = add_options[p_idx].name; } else { ERR_FAIL_COND(add_options[p_idx].script.is_null()); - String base_type = add_options[p_idx].script->get_instance_base_type(); + StringName base_type = add_options[p_idx].script->get_instance_base_type(); AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(base_type)); ERR_FAIL_COND(!an); anode = Ref<AnimationNode>(an); @@ -309,9 +319,14 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { return; } + if (!from_node.is_empty() && anode->get_input_count() == 0) { + from_node = ""; + return; + } + Point2 instance_pos = graph->get_scroll_ofs(); - if (use_popup_menu_position) { - instance_pos += popup_menu_position; + if (use_position_from_popup_menu) { + instance_pos += position_from_popup_menu; } else { instance_pos += graph->get_size() * 0.5; } @@ -328,11 +343,52 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { undo_redo->create_action(TTR("Add Node to BlendTree")); undo_redo->add_do_method(blend_tree.ptr(), "add_node", name, anode, instance_pos / EDSCALE); undo_redo->add_undo_method(blend_tree.ptr(), "remove_node", name); + + if (!from_node.is_empty()) { + undo_redo->add_do_method(blend_tree.ptr(), "connect_node", name, 0, from_node); + from_node = ""; + } + if (!to_node.is_empty() && to_slot != -1) { + undo_redo->add_do_method(blend_tree.ptr(), "connect_node", to_node, to_slot, name); + to_node = ""; + to_slot = -1; + } + undo_redo->add_do_method(this, "_update_graph"); undo_redo->add_undo_method(this, "_update_graph"); undo_redo->commit_action(); } +void AnimationNodeBlendTreeEditor::_popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position) { + _update_options_menu(p_has_input_ports); + use_position_from_popup_menu = true; + position_from_popup_menu = p_node_position; + add_node->get_popup()->set_position(p_popup_position); + add_node->get_popup()->reset_size(); + add_node->get_popup()->popup(); +} + +void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) { + _popup(false, graph->get_screen_position() + graph->get_local_mouse_position(), p_position); +} + +void AnimationNodeBlendTreeEditor::_connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position) { + Ref<AnimationNode> node = blend_tree->get_node(p_from); + if (node.is_valid()) { + from_node = p_from; + _popup(true, p_release_position, graph->get_global_mouse_position()); + } +} + +void AnimationNodeBlendTreeEditor::_connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position) { + Ref<AnimationNode> node = blend_tree->get_node(p_to); + if (node.is_valid()) { + to_node = p_to; + to_slot = p_to_slot; + _popup(false, p_release_position, graph->get_global_mouse_position()); + } +} + void AnimationNodeBlendTreeEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, const StringName &p_which) { updating = true; undo_redo->create_action(TTR("Node Moved")); @@ -406,16 +462,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()) { @@ -431,14 +493,6 @@ void AnimationNodeBlendTreeEditor::_delete_nodes_request() { undo_redo->commit_action(); } -void AnimationNodeBlendTreeEditor::_popup_request(const Vector2 &p_position) { - _update_options_menu(); - use_popup_menu_position = true; - popup_menu_position = graph->get_local_mouse_position(); - add_node->get_popup()->set_position(p_position); - add_node->get_popup()->popup(); -} - void AnimationNodeBlendTreeEditor::_node_selected(Object *p_node) { GraphNode *gn = Object::cast_to<GraphNode>(p_node); ERR_FAIL_COND(!gn); @@ -512,8 +566,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); @@ -550,15 +604,15 @@ 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++) { String name = path.get_name(i); - if (accum != String()) { + if (!accum.is_empty()) { accum += "/"; } accum += name; @@ -647,11 +701,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 += "]"; @@ -689,75 +744,95 @@ void AnimationNodeBlendTreeEditor::_removed_from_graph() { } } +void AnimationNodeBlendTreeEditor::_update_editor_settings() { + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + graph->set_warped_panning(bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning"))); +} + +void AnimationNodeBlendTreeEditor::_update_theme() { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); +} + void AnimationNodeBlendTreeEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + _update_editor_settings(); + _update_theme(); + } break; - if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { - _update_graph(); - } - } + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + _update_editor_settings(); + } break; - if (p_what == NOTIFICATION_PROCESS) { - String error; + case NOTIFICATION_THEME_CHANGED: { + _update_theme(); - if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - } + if (is_visible_in_tree()) { + _update_graph(); + } + } break; - if (error != error_label->get_text()) { - error_label->set_text(error); - if (error != String()) { - error_panel->show(); - } else { - error_panel->hide(); + case NOTIFICATION_PROCESS: { + String error; + + if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { + error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); } - } - List<AnimationNodeBlendTree::NodeConnection> conns; - blend_tree->get_node_connections(&conns); - for (const AnimationNodeBlendTree::NodeConnection &E : conns) { - float activity = 0; - StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - if (AnimationTreeEditor::get_singleton()->get_tree() && !AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - activity = AnimationTreeEditor::get_singleton()->get_tree()->get_connection_activity(path, E.input_index); + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); + } } - graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity); - } - AnimationTree *graph_player = AnimationTreeEditor::get_singleton()->get_tree(); - AnimationPlayer *player = nullptr; - if (graph_player->has_node(graph_player->get_animation_player())) { - player = Object::cast_to<AnimationPlayer>(graph_player->get_node(graph_player->get_animation_player())); - } + List<AnimationNodeBlendTree::NodeConnection> conns; + blend_tree->get_node_connections(&conns); + for (const AnimationNodeBlendTree::NodeConnection &E : conns) { + float activity = 0; + StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; + if (AnimationTreeEditor::get_singleton()->get_tree() && !AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { + activity = AnimationTreeEditor::get_singleton()->get_tree()->get_connection_activity(path, E.input_index); + } + graph->set_connection_activity(E.output_node, 0, E.input_node, E.input_index, activity); + } - if (player) { - for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) { - Ref<AnimationNodeAnimation> an = blend_tree->get_node(E->key()); - if (an.is_valid()) { - if (player->has_animation(an->get_animation())) { - Ref<Animation> anim = player->get_animation(an->get_animation()); - if (anim.is_valid()) { - E->get()->set_max(anim->get_length()); - //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E->key()) + "/time"; - E->get()->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); + AnimationTree *graph_player = AnimationTreeEditor::get_singleton()->get_tree(); + AnimationPlayer *player = nullptr; + if (graph_player->has_node(graph_player->get_animation_player())) { + player = Object::cast_to<AnimationPlayer>(graph_player->get_node(graph_player->get_animation_player())); + } + + if (player) { + for (const KeyValue<StringName, ProgressBar *> &E : animations) { + Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key); + if (an.is_valid()) { + if (player->has_animation(an->get_animation())) { + Ref<Animation> anim = player->get_animation(an->get_animation()); + if (anim.is_valid()) { + E.value->set_max(anim->get_length()); + //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; + StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; + E.value->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); + } } } } } - } - for (int i = 0; i < visible_properties.size(); i++) { - visible_properties[i]->update_property(); - } - } + for (int i = 0; i < visible_properties.size(); i++) { + visible_properties[i]->update_property(); + } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - set_process(is_visible_in_tree()); + case NOTIFICATION_VISIBILITY_CHANGED: { + set_process(is_visible_in_tree()); + } break; } } @@ -779,13 +854,13 @@ AnimationNodeBlendTreeEditor *AnimationNodeBlendTreeEditor::singleton = nullptr; void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<AnimationNode> p_node) { String prev_name = blend_tree->get_node_name(p_node); - ERR_FAIL_COND(prev_name == String()); + ERR_FAIL_COND(prev_name.is_empty()); GraphNode *gn = Object::cast_to<GraphNode>(graph->get_node(prev_name)); ERR_FAIL_COND(!gn); const String &new_name = p_text; - ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1); + ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/")); if (new_name == prev_name) { return; //nothing to do @@ -838,8 +913,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; @@ -850,6 +925,9 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima } void AnimationNodeBlendTreeEditor::_node_renamed_focus_out(Node *le, Ref<AnimationNode> p_node) { + if (le == nullptr) { + return; // The text_submitted signal triggered the graph update and freed the LineEdit. + } _node_renamed(le->call("get_text"), p_node); } @@ -877,21 +955,25 @@ void AnimationNodeBlendTreeEditor::edit(const Ref<AnimationNode> &p_node) { AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { singleton = this; updating = false; - use_popup_menu_position = false; + use_position_from_popup_menu = false; graph = memnew(GraphEdit); add_child(graph); graph->add_valid_right_disconnect_type(0); graph->add_valid_left_disconnect_type(0); graph->set_v_size_flags(SIZE_EXPAND_FILL); - graph->connect("connection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_request), varray(), CONNECT_DEFERRED); - graph->connect("disconnection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_disconnection_request), varray(), CONNECT_DEFERRED); + graph->connect("connection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_request), CONNECT_DEFERRED); + graph->connect("disconnection_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_disconnection_request), CONNECT_DEFERRED); graph->connect("node_selected", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_selected)); graph->connect("scroll_offset_changed", callable_mp(this, &AnimationNodeBlendTreeEditor::_scroll_changed)); graph->connect("delete_nodes_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_delete_nodes_request)); graph->connect("popup_request", callable_mp(this, &AnimationNodeBlendTreeEditor::_popup_request)); + graph->connect("connection_to_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_to_empty)); + graph->connect("connection_from_empty", callable_mp(this, &AnimationNodeBlendTreeEditor::_connection_from_empty)); float graph_minimap_opacity = EditorSettings::get_singleton()->get("editors/visual_editors/minimap_opacity"); 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); @@ -902,16 +984,16 @@ 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)); + 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")); - add_options.push_back(AddOption("Add2", "AnimationNodeAdd2")); - add_options.push_back(AddOption("Add3", "AnimationNodeAdd3")); - add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2")); - add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3")); - add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek")); - add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale")); + add_options.push_back(AddOption("OneShot", "AnimationNodeOneShot", 2)); + add_options.push_back(AddOption("Add2", "AnimationNodeAdd2", 2)); + add_options.push_back(AddOption("Add3", "AnimationNodeAdd3", 3)); + add_options.push_back(AddOption("Blend2", "AnimationNodeBlend2", 2)); + add_options.push_back(AddOption("Blend3", "AnimationNodeBlend3", 3)); + add_options.push_back(AddOption("Seek", "AnimationNodeTimeSeek", 1)); + add_options.push_back(AddOption("TimeScale", "AnimationNodeTimeScale", 1)); add_options.push_back(AddOption("Transition", "AnimationNodeTransition")); add_options.push_back(AddOption("BlendTree", "AnimationNodeBlendTree")); add_options.push_back(AddOption("BlendSpace1D", "AnimationNodeBlendSpace1D")); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.h b/editor/plugins/animation_blend_tree_editor_plugin.h index 9f09069719..18199676b8 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,10 +31,8 @@ #ifndef ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H #define ANIMATION_BLEND_TREE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_blend_tree.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" @@ -42,44 +40,51 @@ #include "scene/gui/tree.h" class ProgressBar; +class EditorFileDialog; class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeBlendTreeEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeBlendTree> blend_tree; - GraphEdit *graph; - MenuButton *add_node; - Vector2 popup_menu_position; - bool use_popup_menu_position; + GraphEdit *graph = nullptr; + MenuButton *add_node = nullptr; + Vector2 position_from_popup_menu; + bool use_position_from_popup_menu; - PanelContainer *error_panel; - Label *error_label; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; - AcceptDialog *filter_dialog; - Tree *filters; - CheckBox *filter_enabled; + AcceptDialog *filter_dialog = nullptr; + Tree *filters = nullptr; + CheckBox *filter_enabled = nullptr; - Map<StringName, ProgressBar *> animations; + HashMap<StringName, ProgressBar *> animations; Vector<EditorProperty *> visible_properties; + String to_node = ""; + int to_slot = -1; + String from_node = ""; + void _update_graph(); struct AddOption { String name; String type; Ref<Script> script; - AddOption(const String &p_name = String(), const String &p_type = String()) : + int input_port_count; + AddOption(const String &p_name = String(), const String &p_type = String(), int p_input_port_count = 0) : name(p_name), - type(p_type) { + type(p_type), + input_port_count(p_input_port_count) { } }; Vector<AddOption> add_options; void _add_node(int p_idx); - void _update_options_menu(); + void _update_options_menu(bool p_has_input_ports = false); static AnimationNodeBlendTreeEditor *singleton; @@ -97,8 +102,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 _popup_request(const Vector2 &p_position); + void _delete_nodes_request(const TypedArray<StringName> &p_nodes); bool _update_filters(const Ref<AnimationNode> &anode); void _edit_filters(const String &p_which); @@ -106,10 +110,18 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { void _filter_toggled(); Ref<AnimationNode> _filter_edit; + void _popup(bool p_has_input_ports, const Vector2 &p_popup_position, const Vector2 &p_node_position); + void _popup_request(const Vector2 &p_position); + void _connection_to_empty(const String &p_from, int p_from_slot, const Vector2 &p_release_position); + void _connection_from_empty(const String &p_to, int p_to_slot, const Vector2 &p_release_position); + void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); void _removed_from_graph(); - EditorFileDialog *open_file; + void _update_editor_settings(); + void _update_theme(); + + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); diff --git a/editor/plugins/animation_library_editor.cpp b/editor/plugins/animation_library_editor.cpp new file mode 100644 index 0000000000..c36ae1c521 --- /dev/null +++ b/editor/plugins/animation_library_editor.cpp @@ -0,0 +1,786 @@ +/*************************************************************************/ +/* animation_library_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "animation_library_editor.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" + +void AnimationLibraryEditor::set_animation_player(Object *p_player) { + player = p_player; +} + +void AnimationLibraryEditor::_add_library() { + add_library_dialog->set_title(TTR("Library Name:")); + add_library_name->set_text(""); + add_library_dialog->popup_centered(); + add_library_name->grab_focus(); + adding_animation = false; + adding_animation_to_library = StringName(); + _add_library_validate(""); +} + +void AnimationLibraryEditor::_add_library_validate(const String &p_name) { + String error; + + if (adding_animation) { + Ref<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library); + ERR_FAIL_COND(al.is_null()); + if (p_name == "") { + error = TTR("Animation name can't be empty."); + } else if (!AnimationLibrary::is_valid_animation_name(p_name)) { + error = TTR("Animation name contains invalid characters: '/', ':', ',' or '['."); + } else if (al->has_animation(p_name)) { + error = TTR("Animation with the same name already exists."); + } + } else { + if (p_name == "" && bool(player->call("has_animation_library", ""))) { + error = TTR("Enter a library name."); + } else if (!AnimationLibrary::is_valid_library_name(p_name)) { + error = TTR("Library name contains invalid characters: '/', ':', ',' or '['."); + } else if (bool(player->call("has_animation_library", p_name))) { + error = TTR("Library with the same name already exists."); + } + } + + if (error != "") { + add_library_validate->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + add_library_validate->set_text(error); + add_library_dialog->get_ok_button()->set_disabled(true); + } else { + if (adding_animation) { + add_library_validate->set_text(TTR("Animation name is valid.")); + } else { + if (p_name == "") { + add_library_validate->set_text(TTR("Global library will be created.")); + } else { + add_library_validate->set_text(TTR("Library name is valid.")); + } + } + add_library_validate->add_theme_color_override("font_color", get_theme_color(SNAME("success_color"), SNAME("Editor"))); + add_library_dialog->get_ok_button()->set_disabled(false); + } +} + +void AnimationLibraryEditor::_add_library_confirm() { + if (adding_animation) { + String anim_name = add_library_name->get_text(); + UndoRedo *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()); + + Ref<Animation> anim; + anim.instantiate(); + + undo_redo->create_action(vformat(TTR("Add Animation to Library: %s"), anim_name)); + undo_redo->add_do_method(al.ptr(), "add_animation", anim_name, anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + } else { + String lib_name = add_library_name->get_text(); + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + Ref<AnimationLibrary> al; + al.instantiate(); + + undo_redo->create_action(vformat(TTR("Add Animation Library: %s"), lib_name)); + undo_redo->add_do_method(player, "add_animation_library", lib_name, al); + undo_redo->add_undo_method(player, "remove_animation_library", lib_name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } +} + +void AnimationLibraryEditor::_load_library() { + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &extensions); + + file_dialog->set_title(TTR("Load Animation")); + file_dialog->clear_filters(); + for (const String &K : extensions) { + file_dialog->add_filter("*." + K); + } + + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + file_dialog->set_current_file(""); + file_dialog->popup_centered_ratio(); + + file_dialog_action = FILE_DIALOG_ACTION_OPEN_LIBRARY; +} + +void AnimationLibraryEditor::_file_popup_selected(int p_id) { + Ref<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library); + Ref<Animation> anim; + if (file_dialog_animation != StringName()) { + anim = al->get_animation(file_dialog_animation); + ERR_FAIL_COND(anim.is_null()); + } + switch (p_id) { + case FILE_MENU_SAVE_LIBRARY: { + if (al->get_path().is_resource_file() && !FileAccess::exists(al->get_path() + ".import")) { + EditorNode::get_singleton()->save_resource(al); + break; + } + [[fallthrough]]; + } + case FILE_MENU_SAVE_AS_LIBRARY: { + // Check if we're allowed to save this + { + String al_path = al->get_path(); + if (!al_path.is_resource_file()) { + int srpos = al_path.find("::"); + if (srpos != -1) { + String base = al_path.substr(0, srpos); + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + error_dialog->set_text(TTR("This animation library can't be saved because it does not belong to the edited scene. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } else { + if (FileAccess::exists(al_path + ".import")) { + error_dialog->set_text(TTR("This animation library can't be saved because it was imported from another file. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } + + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_dialog->set_title(TTR("Save Library")); + if (al->get_path().is_resource_file()) { + file_dialog->set_current_path(al->get_path()); + } else { + file_dialog->set_current_file(String(file_dialog_library) + ".res"); + } + file_dialog->clear_filters(); + List<String> exts; + ResourceLoader::get_recognized_extensions_for_type("AnimationLibrary", &exts); + for (const String &K : exts) { + file_dialog->add_filter("*." + K); + } + + file_dialog->popup_centered_ratio(); + file_dialog_action = FILE_DIALOG_ACTION_SAVE_LIBRARY; + } break; + case FILE_MENU_MAKE_LIBRARY_UNIQUE: { + StringName lib_name = file_dialog_library; + + Ref<AnimationLibrary> ald = al->duplicate(); + + // TODO: should probably make all foreign animations assigned to this library + // unique too. + + UndoRedo *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); + undo_redo->add_undo_method(player, "remove_animation_library", lib_name); + undo_redo->add_undo_method(player, "add_animation_library", lib_name, al); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + update_tree(); + + } break; + case FILE_MENU_EDIT_LIBRARY: { + EditorNode::get_singleton()->push_item(al.ptr()); + } break; + + case FILE_MENU_SAVE_ANIMATION: { + if (anim->get_path().is_resource_file() && !FileAccess::exists(anim->get_path() + ".import")) { + EditorNode::get_singleton()->save_resource(anim); + break; + } + [[fallthrough]]; + } + case FILE_MENU_SAVE_AS_ANIMATION: { + // Check if we're allowed to save this + { + String anim_path = al->get_path(); + if (!anim_path.is_resource_file()) { + int srpos = anim_path.find("::"); + if (srpos != -1) { + String base = anim_path.substr(0, srpos); + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + error_dialog->set_text(TTR("This animation can't be saved because it does not belong to the edited scene. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } else { + if (FileAccess::exists(anim_path + ".import")) { + error_dialog->set_text(TTR("This animation can't be saved because it was imported from another file. Make it unique first.")); + error_dialog->popup_centered(); + return; + } + } + } + + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + file_dialog->set_title(TTR("Save Animation")); + if (anim->get_path().is_resource_file()) { + file_dialog->set_current_path(anim->get_path()); + } else { + file_dialog->set_current_file(String(file_dialog_animation) + ".res"); + } + file_dialog->clear_filters(); + List<String> exts; + ResourceLoader::get_recognized_extensions_for_type("Animation", &exts); + for (const String &K : exts) { + file_dialog->add_filter("*." + K); + } + + file_dialog->popup_centered_ratio(); + file_dialog_action = FILE_DIALOG_ACTION_SAVE_ANIMATION; + } break; + case FILE_MENU_MAKE_ANIMATION_UNIQUE: { + StringName anim_name = file_dialog_animation; + + Ref<Animation> animd = anim->duplicate(); + + UndoRedo *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); + undo_redo->add_undo_method(al.ptr(), "remove_animation", anim_name); + undo_redo->add_undo_method(al.ptr(), "add_animation", anim_name, anim); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + update_tree(); + } break; + case FILE_MENU_EDIT_ANIMATION: { + EditorNode::get_singleton()->push_item(anim.ptr()); + } break; + } +} +void AnimationLibraryEditor::_load_file(String p_path) { + switch (file_dialog_action) { + case FILE_DIALOG_ACTION_OPEN_LIBRARY: { + Ref<AnimationLibrary> al = ResourceLoader::load(p_path); + if (al.is_null()) { + error_dialog->set_text(TTR("Invalid AnimationLibrary file.")); + error_dialog->popup_centered(); + return; + } + + TypedArray<StringName> libs = player->call("get_animation_library_list"); + for (int i = 0; i < libs.size(); i++) { + const StringName K = libs[i]; + Ref<AnimationLibrary> al2 = player->call("get_animation_library", K); + if (al2 == al) { + error_dialog->set_text(TTR("This library is already added to the player.")); + error_dialog->popup_centered(); + + return; + } + } + + String name = AnimationLibrary::validate_library_name(p_path.get_file().get_basename()); + + int attempt = 1; + + while (bool(player->call("has_animation_library", name))) { + attempt++; + name = p_path.get_file().get_basename() + " " + itos(attempt); + } + + UndoRedo *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); + undo_redo->add_undo_method(player, "remove_animation_library", name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + case FILE_DIALOG_ACTION_OPEN_ANIMATION: { + Ref<Animation> anim = ResourceLoader::load(p_path); + if (anim.is_null()) { + error_dialog->set_text(TTR("Invalid Animation file.")); + error_dialog->popup_centered(); + return; + } + + Ref<AnimationLibrary> al = player->call("get_animation_library", adding_animation_to_library); + List<StringName> anims; + al->get_animation_list(&anims); + for (const StringName &K : anims) { + Ref<Animation> a2 = al->get_animation(K); + if (a2 == anim) { + error_dialog->set_text(TTR("This animation is already added to the library.")); + error_dialog->popup_centered(); + return; + } + } + + String name = p_path.get_file().get_basename(); + + int attempt = 1; + + while (al->has_animation(name)) { + attempt++; + name = p_path.get_file().get_basename() + " " + itos(attempt); + } + + UndoRedo *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); + undo_redo->add_undo_method(al.ptr(), "remove_animation", name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + + case FILE_DIALOG_ACTION_SAVE_LIBRARY: { + Ref<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library); + String prev_path = al->get_path(); + EditorNode::get_singleton()->save_resource_in_path(al, p_path); + + if (al->get_path() != prev_path) { // Save successful. + UndoRedo *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()); + undo_redo->add_undo_method(al.ptr(), "set_path", prev_path); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } + + } break; + case FILE_DIALOG_ACTION_SAVE_ANIMATION: { + Ref<AnimationLibrary> al = player->call("get_animation_library", file_dialog_library); + Ref<Animation> anim; + if (file_dialog_animation != StringName()) { + anim = al->get_animation(file_dialog_animation); + ERR_FAIL_COND(anim.is_null()); + } + String prev_path = anim->get_path(); + EditorNode::get_singleton()->save_resource_in_path(anim, p_path); + if (anim->get_path() != prev_path) { // Save successful. + UndoRedo *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()); + undo_redo->add_undo_method(anim.ptr(), "set_path", prev_path); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } + } break; + } +} + +void AnimationLibraryEditor::_item_renamed() { + TreeItem *ti = tree->get_edited(); + String text = ti->get_text(0); + String old_text = ti->get_metadata(0); + bool restore_text = false; + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + if (String(text).contains("/") || String(text).contains(":") || String(text).contains(",") || String(text).contains("[")) { + restore_text = true; + } else { + if (ti->get_parent() == tree->get_root()) { + // Renamed library + + if (player->call("has_animation_library", text)) { + restore_text = true; + } else { + undo_redo->create_action(vformat(TTR("Rename Animation Library: %s"), text)); + undo_redo->add_do_method(player, "rename_animation_library", old_text, text); + undo_redo->add_undo_method(player, "rename_animation_library", text, old_text); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + updating = true; + undo_redo->commit_action(); + updating = false; + ti->set_metadata(0, text); + if (text == "") { + ti->set_suffix(0, TTR("[Global]")); + } else { + ti->set_suffix(0, ""); + } + } + } else { + // Renamed anim + StringName library = ti->get_parent()->get_metadata(0); + Ref<AnimationLibrary> al = player->call("get_animation_library", library); + + if (al.is_valid()) { + if (al->has_animation(text)) { + restore_text = true; + } else { + undo_redo->create_action(vformat(TTR("Rename Animation: %s"), text)); + undo_redo->add_do_method(al.ptr(), "rename_animation", old_text, text); + undo_redo->add_undo_method(al.ptr(), "rename_animation", text, old_text); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + updating = true; + undo_redo->commit_action(); + updating = false; + + ti->set_metadata(0, text); + } + } else { + restore_text = true; + } + } + } + + if (restore_text) { + ti->set_text(0, old_text); + } +} + +void AnimationLibraryEditor::_button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button) { + if (p_item->get_parent() == tree->get_root()) { + // Library + StringName lib_name = p_item->get_metadata(0); + Ref<AnimationLibrary> al = player->call("get_animation_library", lib_name); + switch (p_id) { + case LIB_BUTTON_ADD: { + add_library_dialog->set_title(TTR("Animation Name:")); + add_library_name->set_text(""); + add_library_dialog->popup_centered(); + add_library_name->grab_focus(); + adding_animation = true; + adding_animation_to_library = p_item->get_metadata(0); + _add_library_validate(""); + } break; + case LIB_BUTTON_LOAD: { + adding_animation_to_library = p_item->get_metadata(0); + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions); + + file_dialog->clear_filters(); + for (const String &K : extensions) { + file_dialog->add_filter("*." + K); + } + + file_dialog->set_title(TTR("Load Animation")); + file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + file_dialog->set_current_file(""); + file_dialog->popup_centered_ratio(); + + file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION; + + } break; + case LIB_BUTTON_PASTE: { + Ref<Animation> anim = EditorSettings::get_singleton()->get_resource_clipboard(); + if (!anim.is_valid()) { + error_dialog->set_text(TTR("No animation resource in clipboard!")); + error_dialog->popup_centered(); + return; + } + + anim = anim->duplicate(); // Users simply dont care about referencing, so making a copy works better here. + + String base_name; + if (anim->get_name() != "") { + base_name = anim->get_name(); + } else { + base_name = TTR("Pasted Animation"); + } + + String name = base_name; + int attempt = 1; + while (al->has_animation(name)) { + attempt++; + name = base_name + " (" + itos(attempt) + ")"; + } + + UndoRedo *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); + undo_redo->add_undo_method(al.ptr(), "remove_animation", name); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + + } break; + case LIB_BUTTON_FILE: { + file_popup->clear(); + file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_LIBRARY); + file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_LIBRARY); + file_popup->add_separator(); + file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_LIBRARY_UNIQUE); + file_popup->add_separator(); + file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_LIBRARY); + Rect2 pos = tree->get_item_rect(p_item, 1, 0); + Vector2 popup_pos = tree->get_screen_position() + pos.position + Vector2(0, pos.size.height); + file_popup->popup(Rect2(popup_pos, Size2())); + + file_dialog_animation = StringName(); + file_dialog_library = lib_name; + } break; + case LIB_BUTTON_DELETE: { + UndoRedo *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); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + } + + } else { + // Animation + StringName lib_name = p_item->get_parent()->get_metadata(0); + StringName anim_name = p_item->get_metadata(0); + Ref<AnimationLibrary> al = player->call("get_animation_library", lib_name); + Ref<Animation> anim = al->get_animation(anim_name); + ERR_FAIL_COND(!anim.is_valid()); + switch (p_id) { + case ANIM_BUTTON_COPY: { + if (anim->get_name() == "") { + anim->set_name(anim_name); // Keep the name around + } + EditorSettings::get_singleton()->set_resource_clipboard(anim); + } break; + case ANIM_BUTTON_FILE: { + file_popup->clear(); + file_popup->add_item(TTR("Save"), FILE_MENU_SAVE_ANIMATION); + file_popup->add_item(TTR("Save As"), FILE_MENU_SAVE_AS_ANIMATION); + file_popup->add_separator(); + file_popup->add_item(TTR("Make Unique"), FILE_MENU_MAKE_ANIMATION_UNIQUE); + file_popup->add_separator(); + file_popup->add_item(TTR("Open in Inspector"), FILE_MENU_EDIT_ANIMATION); + Rect2 pos = tree->get_item_rect(p_item, 1, 0); + Vector2 popup_pos = tree->get_screen_position() + pos.position + Vector2(0, pos.size.height); + file_popup->popup(Rect2(popup_pos, Size2())); + + file_dialog_animation = anim_name; + file_dialog_library = lib_name; + + } break; + case ANIM_BUTTON_DELETE: { + UndoRedo *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); + undo_redo->add_do_method(this, "_update_editor", player); + undo_redo->add_undo_method(this, "_update_editor", player); + undo_redo->commit_action(); + } break; + } + } +} + +void AnimationLibraryEditor::update_tree() { + if (updating) { + return; + } + + tree->clear(); + ERR_FAIL_COND(!player); + + Color ss_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); + + TreeItem *root = tree->create_item(); + TypedArray<StringName> libs = player->call("get_animation_library_list"); + + for (int i = 0; i < libs.size(); i++) { + const StringName K = libs[i]; + TreeItem *libitem = tree->create_item(root); + libitem->set_text(0, K); + if (K == StringName()) { + libitem->set_suffix(0, TTR("[Global]")); + } else { + libitem->set_suffix(0, ""); + } + + Ref<AnimationLibrary> al = player->call("get_animation_library", K); + bool animation_library_is_foreign = false; + String al_path = al->get_path(); + if (!al_path.is_resource_file()) { + libitem->set_text(1, TTR("[built-in]")); + libitem->set_tooltip(1, al_path); + int srpos = al_path.find("::"); + if (srpos != -1) { + String base = al_path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + animation_library_is_foreign = true; + libitem->set_text(1, TTR("[foreign]")); + } + } else { + if (FileAccess::exists(base + ".import")) { + animation_library_is_foreign = true; + libitem->set_text(1, TTR("[imported]")); + } + } + } + } else { + if (FileAccess::exists(al_path + ".import")) { + animation_library_is_foreign = true; + libitem->set_text(1, TTR("[imported]")); + } else { + libitem->set_text(1, al_path.get_file()); + } + } + + libitem->set_editable(0, !animation_library_is_foreign); + libitem->set_metadata(0, K); + libitem->set_icon(0, get_theme_icon("AnimationLibrary", "EditorIcons")); + + libitem->add_button(0, get_theme_icon("Add", "EditorIcons"), LIB_BUTTON_ADD, animation_library_is_foreign, TTR("Add Animation to Library")); + libitem->add_button(0, get_theme_icon("Load", "EditorIcons"), LIB_BUTTON_LOAD, animation_library_is_foreign, TTR("Load animation from file and add to library")); + libitem->add_button(0, get_theme_icon("ActionPaste", "EditorIcons"), LIB_BUTTON_PASTE, animation_library_is_foreign, TTR("Paste Animation to Library from clipboard")); + + libitem->add_button(1, get_theme_icon("Save", "EditorIcons"), LIB_BUTTON_FILE, false, TTR("Save animation library to resource on disk")); + libitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), LIB_BUTTON_DELETE, false, TTR("Remove animation library")); + + libitem->set_custom_bg_color(0, ss_color); + + List<StringName> animations; + al->get_animation_list(&animations); + for (const StringName &L : animations) { + TreeItem *anitem = tree->create_item(libitem); + anitem->set_text(0, L); + anitem->set_editable(0, !animation_library_is_foreign); + anitem->set_metadata(0, L); + anitem->set_icon(0, get_theme_icon("Animation", "EditorIcons")); + anitem->add_button(0, get_theme_icon("ActionCopy", "EditorIcons"), ANIM_BUTTON_COPY, animation_library_is_foreign, TTR("Copy animation to clipboard")); + + Ref<Animation> anim = al->get_animation(L); + String anim_path = anim->get_path(); + if (!anim_path.is_resource_file()) { + anitem->set_text(1, TTR("[built-in]")); + anitem->set_tooltip(1, anim_path); + int srpos = anim_path.find("::"); + if (srpos != -1) { + String base = anim_path.substr(0, srpos); + if (ResourceLoader::get_resource_type(base) == "PackedScene") { + if (!get_tree()->get_edited_scene_root() || get_tree()->get_edited_scene_root()->get_scene_file_path() != base) { + anitem->set_text(1, TTR("[foreign]")); + } + } else { + if (FileAccess::exists(base + ".import")) { + anitem->set_text(1, TTR("[imported]")); + } + } + } + } else { + if (FileAccess::exists(anim_path + ".import")) { + anitem->set_text(1, TTR("[imported]")); + } else { + anitem->set_text(1, anim_path.get_file()); + } + } + anitem->add_button(1, get_theme_icon("Save", "EditorIcons"), ANIM_BUTTON_FILE, animation_library_is_foreign, TTR("Save animation to resource on disk")); + anitem->add_button(1, get_theme_icon("Remove", "EditorIcons"), ANIM_BUTTON_DELETE, animation_library_is_foreign, TTR("Remove animation from Library")); + } + } +} + +void AnimationLibraryEditor::show_dialog() { + update_tree(); + popup_centered_ratio(0.5); +} + +void AnimationLibraryEditor::_update_editor(Object *p_player) { + emit_signal("update_editor", p_player); +} + +void AnimationLibraryEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_editor", "player"), &AnimationLibraryEditor::_update_editor); + ADD_SIGNAL(MethodInfo("update_editor")); +} + +AnimationLibraryEditor::AnimationLibraryEditor() { + set_title(TTR("Edit Animation Libraries")); + + file_dialog = memnew(EditorFileDialog); + add_child(file_dialog); + file_dialog->connect("file_selected", callable_mp(this, &AnimationLibraryEditor::_load_file)); + + add_library_dialog = memnew(ConfirmationDialog); + VBoxContainer *dialog_vb = memnew(VBoxContainer); + add_library_name = memnew(LineEdit); + dialog_vb->add_child(add_library_name); + add_library_name->connect("text_changed", callable_mp(this, &AnimationLibraryEditor::_add_library_validate)); + add_child(add_library_dialog); + + add_library_validate = memnew(Label); + dialog_vb->add_child(add_library_validate); + add_library_dialog->add_child(dialog_vb); + add_library_dialog->connect("confirmed", callable_mp(this, &AnimationLibraryEditor::_add_library_confirm)); + add_library_dialog->register_text_enter(add_library_name); + + VBoxContainer *vb = memnew(VBoxContainer); + HBoxContainer *hb = memnew(HBoxContainer); + hb->add_spacer(true); + Button *b = memnew(Button(TTR("Add Library"))); + b->connect("pressed", callable_mp(this, &AnimationLibraryEditor::_add_library)); + hb->add_child(b); + b = memnew(Button(TTR("Load Library"))); + b->connect("pressed", callable_mp(this, &AnimationLibraryEditor::_load_library)); + hb->add_child(b); + vb->add_child(hb); + tree = memnew(Tree); + vb->add_child(tree); + + tree->set_columns(2); + tree->set_column_titles_visible(true); + tree->set_column_title(0, TTR("Resource")); + tree->set_column_title(1, TTR("Storage")); + tree->set_column_expand(0, true); + tree->set_column_custom_minimum_width(1, EDSCALE * 250); + tree->set_column_expand(1, false); + tree->set_hide_root(true); + tree->set_hide_folding(true); + tree->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + tree->connect("item_edited", callable_mp(this, &AnimationLibraryEditor::_item_renamed)); + tree->connect("button_clicked", callable_mp(this, &AnimationLibraryEditor::_button_pressed)); + + file_popup = memnew(PopupMenu); + add_child(file_popup); + file_popup->connect("id_pressed", callable_mp(this, &AnimationLibraryEditor::_file_popup_selected)); + + add_child(vb); + + error_dialog = memnew(AcceptDialog); + error_dialog->set_title(TTR("Error:")); + add_child(error_dialog); +} diff --git a/editor/plugins/animation_library_editor.h b/editor/plugins/animation_library_editor.h new file mode 100644 index 0000000000..6e214860b8 --- /dev/null +++ b/editor/plugins/animation_library_editor.h @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* animation_library_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 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 ANIMATION_LIBRARY_EDITOR_H +#define ANIMATION_LIBRARY_EDITOR_H + +#include "editor/animation_track_editor.h" +#include "editor/editor_plugin.h" +#include "scene/animation/animation_player.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/tree.h" + +class EditorFileDialog; + +class AnimationLibraryEditor : public AcceptDialog { + GDCLASS(AnimationLibraryEditor, AcceptDialog) + + enum { + LIB_BUTTON_ADD, + LIB_BUTTON_LOAD, + LIB_BUTTON_PASTE, + LIB_BUTTON_FILE, + LIB_BUTTON_DELETE, + }; + enum { + ANIM_BUTTON_COPY, + ANIM_BUTTON_FILE, + ANIM_BUTTON_DELETE, + }; + + enum FileMenuAction { + FILE_MENU_SAVE_LIBRARY, + FILE_MENU_SAVE_AS_LIBRARY, + FILE_MENU_MAKE_LIBRARY_UNIQUE, + FILE_MENU_EDIT_LIBRARY, + + FILE_MENU_SAVE_ANIMATION, + FILE_MENU_SAVE_AS_ANIMATION, + FILE_MENU_MAKE_ANIMATION_UNIQUE, + FILE_MENU_EDIT_ANIMATION, + }; + + enum FileDialogAction { + FILE_DIALOG_ACTION_OPEN_LIBRARY, + FILE_DIALOG_ACTION_SAVE_LIBRARY, + FILE_DIALOG_ACTION_OPEN_ANIMATION, + FILE_DIALOG_ACTION_SAVE_ANIMATION, + }; + + FileDialogAction file_dialog_action = FILE_DIALOG_ACTION_OPEN_ANIMATION; + + StringName file_dialog_animation; + StringName file_dialog_library; + + AcceptDialog *error_dialog = nullptr; + bool adding_animation = false; + StringName adding_animation_to_library; + EditorFileDialog *file_dialog = nullptr; + ConfirmationDialog *add_library_dialog = nullptr; + LineEdit *add_library_name = nullptr; + Label *add_library_validate = nullptr; + PopupMenu *file_popup = nullptr; + + Tree *tree = nullptr; + + Object *player = nullptr; + + void _add_library(); + void _add_library_validate(const String &p_name); + void _add_library_confirm(); + void _load_library(); + void _load_file(String p_path); + + void _item_renamed(); + void _button_pressed(TreeItem *p_item, int p_column, int p_id, MouseButton p_button); + + void _file_popup_selected(int p_id); + + bool updating = false; + +protected: + void _update_editor(Object *p_player); + static void _bind_methods(); + +public: + void set_animation_player(Object *p_player); + void show_dialog(); + void update_tree(); + AnimationLibraryEditor(); +}; + +#endif // ANIMATION_LIBRARY_EDITOR_H diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 830b010d01..516079673d 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,21 +35,27 @@ #include "core/io/resource_loader.h" #include "core/io/resource_saver.h" #include "core/os/keyboard.h" -#include "editor/animation_track_editor.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning. #include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning. +#include "editor/scene_tree_dock.h" #include "scene/main/window.h" +#include "scene/resources/animation.h" +#include "scene/scene_string_names.h" #include "servers/rendering_server.h" +/////////////////////////////////// + void AnimationPlayerEditor::_node_removed(Node *p_node) { if (player && player == p_node) { player = nullptr; set_process(false); - track_editor->set_animation(Ref<Animation>()); + track_editor->set_animation(Ref<Animation>(), true); track_editor->set_root(nullptr); track_editor->show_select_node_warning(true); _update_player(); @@ -72,7 +78,7 @@ void AnimationPlayerEditor::_notification(int p_what) { if (player->has_animation(animname)) { Ref<Animation> anim = player->get_animation(animname); if (!anim.is_null()) { - frame->set_max(anim->get_length()); + frame->set_max((double)anim->get_length()); } } } @@ -90,6 +96,7 @@ void AnimationPlayerEditor::_notification(int p_what) { last_active = player->is_playing(); updating = false; } break; + case NOTIFICATION_ENTER_TREE: { tool_anim->get_popup()->connect("id_pressed", callable_mp(this, &AnimationPlayerEditor::_animation_tool_menu)); @@ -99,11 +106,13 @@ void AnimationPlayerEditor::_notification(int p_what) { get_tree()->connect("node_removed", callable_mp(this, &AnimationPlayerEditor::_node_removed)); - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); + add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); + add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); } break; + case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { @@ -120,13 +129,13 @@ void AnimationPlayerEditor::_notification(int p_what) { Ref<Image> autoplay_img = autoplay_icon->get_image(); Ref<Image> reset_img = reset_icon->get_image(); Ref<Image> autoplay_reset_img; - Size2 icon_size = Size2(autoplay_img->get_width(), autoplay_img->get_height()); + 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"))); @@ -141,14 +150,14 @@ void AnimationPlayerEditor::_notification(int p_what) { #define ITEM_ICON(m_item, m_icon) tool_anim->get_popup()->set_item_icon(tool_anim->get_popup()->get_item_index(m_item), get_theme_icon(SNAME(m_icon), SNAME("EditorIcons"))) ITEM_ICON(TOOL_NEW_ANIM, "New"); - ITEM_ICON(TOOL_LOAD_ANIM, "Load"); - ITEM_ICON(TOOL_SAVE_ANIM, "Save"); - ITEM_ICON(TOOL_SAVE_AS_ANIM, "Save"); + ITEM_ICON(TOOL_ANIM_LIBRARY, "AnimationLibrary"); ITEM_ICON(TOOL_DUPLICATE_ANIM, "Duplicate"); ITEM_ICON(TOOL_RENAME_ANIM, "Rename"); ITEM_ICON(TOOL_EDIT_TRANSITIONS, "Blend"); ITEM_ICON(TOOL_EDIT_RESOURCE, "Edit"); ITEM_ICON(TOOL_REMOVE_ANIM, "Remove"); + + _update_animation_list_icons(); } break; } } @@ -157,7 +166,7 @@ void AnimationPlayerEditor::_autoplay_pressed() { if (updating) { return; } - if (animation->get_item_count() == 0) { + if (animation->has_selectable_items() == 0) { return; } @@ -183,12 +192,9 @@ void AnimationPlayerEditor::_autoplay_pressed() { } void AnimationPlayerEditor::_play_pressed() { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); - if (current != "") { + if (!current.is_empty()) { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } @@ -200,12 +206,9 @@ void AnimationPlayerEditor::_play_pressed() { } void AnimationPlayerEditor::_play_from_pressed() { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); - if (current != "") { + if (!current.is_empty()) { float time = player->get_current_animation_position(); if (current == player->get_assigned_animation() && player->is_playing()) { @@ -220,13 +223,16 @@ void AnimationPlayerEditor::_play_from_pressed() { stop->set_pressed(false); } -void AnimationPlayerEditor::_play_bw_pressed() { +String AnimationPlayerEditor::_get_current() const { String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { + if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count() && !animation->is_item_separator(animation->get_selected())) { current = animation->get_item_text(animation->get_selected()); } - - if (current != "") { + return current; +} +void AnimationPlayerEditor::_play_bw_pressed() { + String current = _get_current(); + if (!current.is_empty()) { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } @@ -238,12 +244,9 @@ void AnimationPlayerEditor::_play_bw_pressed() { } void AnimationPlayerEditor::_play_bw_from_pressed() { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); - if (current != "") { + if (!current.is_empty()) { float time = player->get_current_animation_position(); if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself @@ -273,48 +276,71 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { } // when selecting an animation, the idea is that the only interesting behavior // ui-wise is that it should play/blend the next one if currently playing - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); - if (current != "") { + if (!current.is_empty()) { player->set_assigned_animation(current); Ref<Animation> anim = player->get_animation(current); { - track_editor->set_animation(anim); + bool animation_library_is_foreign = 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); } } - frame->set_max(anim->get_length()); + frame->set_max((double)anim->get_length()); } else { - track_editor->set_animation(Ref<Animation>()); + track_editor->set_animation(Ref<Animation>(), true); track_editor->set_root(nullptr); } autoplay->set_pressed(current == player->get_autoplay()); - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); - EditorNode::get_singleton()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); _animation_key_editor_seek(timeline_position, false); } void AnimationPlayerEditor::_animation_new() { - renaming = false; - 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; } @@ -322,103 +348,41 @@ void AnimationPlayerEditor::_animation_new() { break; } - name->set_text(base); + _update_name_dialog_library_dropdown(); + + name_dialog_op = TOOL_NEW_ANIM; + name_dialog->set_title(TTR("Create New Animation")); name_dialog->popup_centered(Size2(300, 90)); + name_title->set_text(TTR("New Animation Name:")); + name->set_text(base); name->select_all(); name->grab_focus(); } void AnimationPlayerEditor::_animation_rename() { - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } int selected = animation->get_selected(); String selected_name = animation->get_item_text(selected); + // Remove library prefix if present. + if (selected_name.contains("/")) { + selected_name = selected_name.get_slice("/", 1); + } + + name_dialog->set_title(TTR("Rename Animation")); name_title->set_text(TTR("Change Animation Name:")); name->set_text(selected_name); - renaming = true; + name_dialog_op = TOOL_RENAME_ANIM; name_dialog->popup_centered(Size2(300, 90)); name->select_all(); name->grab_focus(); -} - -void AnimationPlayerEditor::_animation_load() { - ERR_FAIL_COND(!player); - file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); - file->clear_filters(); - List<String> extensions; - - ResourceLoader::get_recognized_extensions_for_type("Animation", &extensions); - for (const String &E : extensions) { - file->add_filter("*." + E + " ; " + E.to_upper()); - } - - file->popup_file_dialog(); -} - -void AnimationPlayerEditor::_animation_save_in_path(const Ref<Resource> &p_resource, const String &p_path) { - int flg = 0; - if (EditorSettings::get_singleton()->get("filesystem/on_save/compress_binary_resources")) { - flg |= ResourceSaver::FLAG_COMPRESS; - } - - String path = ProjectSettings::get_singleton()->localize_path(p_path); - Error err = ResourceSaver::save(path, p_resource, flg | ResourceSaver::FLAG_REPLACE_SUBRESOURCE_PATHS); - - if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Error saving resource!")); - return; - } - - ((Resource *)p_resource.ptr())->set_path(path); - editor->emit_signal(SNAME("resource_saved"), p_resource); -} - -void AnimationPlayerEditor::_animation_save(const Ref<Resource> &p_resource) { - if (p_resource->get_path().is_resource_file()) { - _animation_save_in_path(p_resource, p_resource->get_path()); - } else { - _animation_save_as(p_resource); - } -} - -void AnimationPlayerEditor::_animation_save_as(const Ref<Resource> &p_resource) { - file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - - List<String> extensions; - ResourceSaver::get_recognized_extensions(p_resource, &extensions); - file->clear_filters(); - for (int i = 0; i < extensions.size(); i++) { - file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); - } - - String path; - //file->set_current_path(current_path); - if (p_resource->get_path() != "") { - path = p_resource->get_path(); - if (extensions.size()) { - if (extensions.find(p_resource->get_path().get_extension().to_lower()) == nullptr) { - path = p_resource->get_path().get_base_dir() + p_resource->get_name() + "." + extensions.front()->get(); - } - } - } else { - if (extensions.size()) { - if (p_resource->get_name() != "") { - path = p_resource->get_name() + "." + extensions.front()->get().to_lower(); - } else { - String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore(); - path = "new_" + resource_name_snake_case + "." + extensions.front()->get().to_lower(); - } - } - } - file->set_current_path(path); - file->set_title(TTR("Save Resource As...")); - file->popup_file_dialog(); + library->hide(); } void AnimationPlayerEditor::_animation_remove() { - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } @@ -432,6 +396,13 @@ void AnimationPlayerEditor::_animation_remove_confirmed() { String current = animation->get_item_text(animation->get_selected()); Ref<Animation> anim = player->get_animation(current); + Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim)); + ERR_FAIL_COND(al.is_null()); + + // For names of form lib_name/anim_name, remove library name prefix. + if (current.contains("/")) { + current = current.get_slice("/", 1); + } undo_redo->create_action(TTR("Remove Animation")); if (player->get_autoplay() == current) { undo_redo->add_do_method(player, "set_autoplay", ""); @@ -439,11 +410,11 @@ void AnimationPlayerEditor::_animation_remove_confirmed() { // Avoid having the autoplay icon linger around if there is only one animation in the player. undo_redo->add_do_method(this, "_animation_player_changed", player); } - undo_redo->add_do_method(player, "remove_animation", current); - undo_redo->add_undo_method(player, "add_animation", current, anim); + undo_redo->add_do_method(al.ptr(), "remove_animation", current); + undo_redo->add_undo_method(al.ptr(), "add_animation", current, anim); undo_redo->add_do_method(this, "_animation_player_changed", player); undo_redo->add_undo_method(this, "_animation_player_changed", player); - if (animation->get_item_count() == 1) { + if (animation->has_selectable_items() && animation->get_selectable_item(false) == animation->get_selectable_item(true)) { // Last item remaining. undo_redo->add_do_method(this, "_stop_onion_skinning"); undo_redo->add_undo_method(this, "_start_onion_skinning"); } @@ -474,7 +445,7 @@ double AnimationPlayerEditor::_get_editor_step() const { ERR_FAIL_COND_V(!anim.is_valid(), 0.0); // Use more precise snapping when holding Shift - return Input::get_singleton()->is_key_pressed(KEY_SHIFT) ? anim->get_step() * 0.25 : anim->get_step(); + return Input::get_singleton()->is_key_pressed(Key::SHIFT) ? anim->get_step() * 0.25 : anim->get_step(); } return 0.0; @@ -484,61 +455,158 @@ void AnimationPlayerEditor::_animation_name_edited() { player->stop(); String new_name = name->get_text(); - if (new_name == "" || new_name.find(":") != -1 || new_name.find("/") != -1) { + if (!AnimationLibrary::is_valid_animation_name(new_name)) { error_dialog->set_text(TTR("Invalid animation name!")); error_dialog->popup_centered(); return; } - if (renaming && animation->get_item_count() > 0 && animation->get_item_text(animation->get_selected()) == new_name) { + if (name_dialog_op == TOOL_RENAME_ANIM && animation->has_selectable_items() && animation->get_item_text(animation->get_selected()) == new_name) { name_dialog->hide(); return; } - if (player->has_animation(new_name)) { - error_dialog->set_text(TTR("Animation name already exists!")); + String test_name_prefix = ""; + if (library->is_visible() && library->get_selected_id() != -1) { + test_name_prefix = library->get_item_metadata(library->get_selected_id()); + test_name_prefix += (test_name_prefix != "") ? "/" : ""; + } + + if (player->has_animation(test_name_prefix + new_name)) { + error_dialog->set_text(vformat(TTR("Animation '%s' already exists!"), test_name_prefix + new_name)); error_dialog->popup_centered(); return; } - if (renaming) { - String current = animation->get_item_text(animation->get_selected()); - Ref<Animation> anim = player->get_animation(current); + switch (name_dialog_op) { + case TOOL_RENAME_ANIM: { + String current = animation->get_item_text(animation->get_selected()); + Ref<Animation> anim = player->get_animation(current); - undo_redo->create_action(TTR("Rename Animation")); - undo_redo->add_do_method(player, "rename_animation", current, new_name); - undo_redo->add_do_method(anim.ptr(), "set_name", new_name); - undo_redo->add_undo_method(player, "rename_animation", new_name, current); - undo_redo->add_undo_method(anim.ptr(), "set_name", current); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); + Ref<AnimationLibrary> al = player->get_animation_library(player->find_animation_library(anim)); + ERR_FAIL_COND(al.is_null()); - _select_anim_by_name(new_name); + // Extract library prefix if present. + String new_library_prefix = ""; + if (current.contains("/")) { + new_library_prefix = current.get_slice("/", 0) + "/"; + current = current.get_slice("/", 1); + } - } else { - Ref<Animation> new_anim = Ref<Animation>(memnew(Animation)); - new_anim->set_name(new_name); + undo_redo->create_action(TTR("Rename Animation")); + undo_redo->add_do_method(al.ptr(), "rename_animation", current, new_name); + undo_redo->add_do_method(anim.ptr(), "set_name", new_name); + undo_redo->add_undo_method(al.ptr(), "rename_animation", new_name, current); + undo_redo->add_undo_method(anim.ptr(), "set_name", current); + undo_redo->add_do_method(this, "_animation_player_changed", player); + undo_redo->add_undo_method(this, "_animation_player_changed", player); + undo_redo->commit_action(); - undo_redo->create_action(TTR("Add Animation")); - undo_redo->add_do_method(player, "add_animation", new_name, new_anim); - undo_redo->add_undo_method(player, "remove_animation", new_name); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - if (animation->get_item_count() == 0) { - undo_redo->add_do_method(this, "_start_onion_skinning"); - undo_redo->add_undo_method(this, "_stop_onion_skinning"); - } - undo_redo->commit_action(); + _select_anim_by_name(new_library_prefix + new_name); + } break; + + case TOOL_NEW_ANIM: { + Ref<Animation> new_anim = Ref<Animation>(memnew(Animation)); + new_anim->set_name(new_name); + String library_name; + Ref<AnimationLibrary> al; + if (library->is_visible()) { + library_name = library->get_item_metadata(library->get_selected()); + // It's possible that [Global] was selected, but doesn't exist yet. + if (player->has_animation_library(library_name)) { + al = player->get_animation_library(library_name); + } + + } else { + if (player->has_animation_library("")) { + al = player->get_animation_library(""); + library_name = ""; + } + } + + undo_redo->create_action(TTR("Add Animation")); + + bool lib_added = false; + if (al.is_null()) { + al.instantiate(); + lib_added = true; + undo_redo->add_do_method(player, "add_animation_library", "", al); + library_name = ""; + } + + undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name); + undo_redo->add_do_method(this, "_animation_player_changed", player); + undo_redo->add_undo_method(this, "_animation_player_changed", player); + if (!animation->has_selectable_items()) { + undo_redo->add_do_method(this, "_start_onion_skinning"); + undo_redo->add_undo_method(this, "_stop_onion_skinning"); + } + if (lib_added) { + undo_redo->add_undo_method(player, "remove_animation_library", ""); + } + undo_redo->commit_action(); + + if (library_name != "") { + library_name = library_name + "/"; + } + _select_anim_by_name(library_name + new_name); + + } break; + + case TOOL_DUPLICATE_ANIM: { + String current = animation->get_item_text(animation->get_selected()); + Ref<Animation> anim = player->get_animation(current); + + Ref<Animation> new_anim = _animation_clone(anim); + new_anim->set_name(new_name); + + String library_name; + Ref<AnimationLibrary> al; + if (library->is_visible()) { + library_name = library->get_item_metadata(library->get_selected()); + // It's possible that [Global] was selected, but doesn't exist yet. + if (player->has_animation_library(library_name)) { + al = player->get_animation_library(library_name); + } + } else { + if (player->has_animation_library("")) { + al = player->get_animation_library(""); + library_name = ""; + } + } + + undo_redo->create_action(TTR("Duplicate Animation")); - _select_anim_by_name(new_name); + bool lib_added = false; + if (al.is_null()) { + al.instantiate(); + lib_added = true; + undo_redo->add_do_method(player, "add_animation_library", "", al); + library_name = ""; + } + + undo_redo->add_do_method(al.ptr(), "add_animation", new_name, new_anim); + undo_redo->add_undo_method(al.ptr(), "remove_animation", new_name); + undo_redo->add_do_method(this, "_animation_player_changed", player); + undo_redo->add_undo_method(this, "_animation_player_changed", player); + if (lib_added) { + undo_redo->add_undo_method(player, "remove_animation_library", ""); + } + undo_redo->commit_action(); + + if (library_name != "") { + library_name = library_name + "/"; + } + _select_anim_by_name(library_name + new_name); + } break; } name_dialog->hide(); } void AnimationPlayerEditor::_blend_editor_next_changed(const int p_idx) { - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } @@ -559,7 +627,7 @@ void AnimationPlayerEditor::_animation_blend() { blend_editor.tree->clear(); - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } @@ -614,7 +682,7 @@ void AnimationPlayerEditor::_blend_edited() { return; } - if (animation->get_item_count() == 0) { + if (!animation->has_selectable_items()) { return; } @@ -673,7 +741,7 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) { if (Object::cast_to<AnimationPlayer>(n) && EditorNode::get_singleton()->get_editor_selection()->is_selected(n)) { player = Object::cast_to<AnimationPlayer>(n); _update_player(); - editor->make_bottom_panel_item_visible(this); + EditorNode::get_singleton()->make_bottom_panel_item_visible(this); set_process(true); ensure_visibility(); @@ -693,74 +761,51 @@ void AnimationPlayerEditor::set_state(const Dictionary &p_state) { } void AnimationPlayerEditor::_animation_resource_edit() { - if (animation->get_item_count()) { - String current = animation->get_item_text(animation->get_selected()); + String current = _get_current(); + if (current != String()) { Ref<Animation> anim = player->get_animation(current); - editor->edit_resource(anim); + EditorNode::get_singleton()->edit_resource(anim); } } void AnimationPlayerEditor::_animation_edit() { - if (animation->get_item_count()) { - String current = animation->get_item_text(animation->get_selected()); + String current = _get_current(); + if (current != String()) { Ref<Animation> anim = player->get_animation(current); - track_editor->set_animation(anim); + + bool animation_library_is_foreign = 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); } } -void AnimationPlayerEditor::_save_animation(String p_file) { - String current = animation->get_item_text(animation->get_selected()); - if (current != "") { - Ref<Animation> anim = player->get_animation(current); - - ERR_FAIL_COND(!Object::cast_to<Resource>(*anim)); - - RES current_res = RES(Object::cast_to<Resource>(*anim)); - - _animation_save_in_path(current_res, p_file); - } -} - -void AnimationPlayerEditor::_load_animations(Vector<String> p_files) { - ERR_FAIL_COND(!player); - - for (int i = 0; i < p_files.size(); i++) { - String file = p_files[i]; - - Ref<Resource> res = ResourceLoader::load(file, "Animation"); - ERR_FAIL_COND_MSG(res.is_null(), "Cannot load Animation from file '" + file + "'."); - ERR_FAIL_COND_MSG(!res->is_class("Animation"), "Loaded resource from file '" + file + "' is not Animation."); - if (file.rfind("/") != -1) { - file = file.substr(file.rfind("/") + 1, file.length()); - } - if (file.rfind("\\") != -1) { - file = file.substr(file.rfind("\\") + 1, file.length()); - } - - if (file.find(".") != -1) { - file = file.substr(0, file.find(".")); - } - - undo_redo->create_action(TTR("Load Animation")); - undo_redo->add_do_method(player, "add_animation", file, res); - undo_redo->add_undo_method(player, "remove_animation", file); - if (player->has_animation(file)) { - undo_redo->add_undo_method(player, "add_animation", file, player->get_animation(file)); - } - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); - } -} - void AnimationPlayerEditor::_scale_changed(const String &p_scale) { player->set_speed_scale(p_scale.to_float()); } @@ -795,79 +840,139 @@ void AnimationPlayerEditor::_update_animation() { void AnimationPlayerEditor::_update_player() { updating = true; - List<StringName> animlist; - if (player) { - player->get_animation_list(&animlist); - } animation->clear(); -#define ITEM_DISABLED(m_item, m_disabled) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), m_disabled) - - ITEM_DISABLED(TOOL_SAVE_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_SAVE_AS_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_DUPLICATE_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_RENAME_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_EDIT_TRANSITIONS, animlist.size() == 0); - ITEM_DISABLED(TOOL_COPY_ANIM, animlist.size() == 0); - ITEM_DISABLED(TOOL_REMOVE_ANIM, animlist.size() == 0); - - stop->set_disabled(animlist.size() == 0); - play->set_disabled(animlist.size() == 0); - play_bw->set_disabled(animlist.size() == 0); - play_bw_from->set_disabled(animlist.size() == 0); - play_from->set_disabled(animlist.size() == 0); - frame->set_editable(animlist.size() != 0); - animation->set_disabled(animlist.size() == 0); - autoplay->set_disabled(animlist.size() == 0); - tool_anim->set_disabled(player == nullptr); - onion_toggle->set_disabled(animlist.size() == 0); - onion_skinning->set_disabled(animlist.size() == 0); - pin->set_disabled(player == nullptr); - if (!player) { - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); - EditorNode::get_singleton()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); return; } + List<StringName> libraries; + if (player) { + player->get_animation_library_list(&libraries); + } + int active_idx = -1; - for (const StringName &E : animlist) { - Ref<Texture2D> icon; - if (E == player->get_autoplay()) { - if (E == "RESET") { - icon = autoplay_reset_icon; + bool no_anims_found = true; + bool foreign_global_anim_lib = false; + + for (const StringName &K : libraries) { + if (K != StringName()) { + animation->add_separator(K); + } + + // 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 { - icon = autoplay_icon; + if (FileAccess::exists(library->get_path() + ".import")) { + foreign_global_anim_lib = true; + } } - } else if (E == "RESET") { - icon = reset_icon; } - animation->add_icon_item(icon, E); - if (player->get_assigned_animation() == E) { - active_idx = animation->get_item_count() - 1; + List<StringName> animlist; + library->get_animation_list(&animlist); + + for (const StringName &E : animlist) { + String path = K; + if (path != "") { + path += "/"; + } + path += E; + animation->add_item(path); + if (player->get_assigned_animation() == path) { + active_idx = animation->get_selectable_item(true); + } + no_anims_found = false; } } +#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), foreign_global_anim_lib) + + ITEM_CHECK_DISABLED(TOOL_NEW_ANIM); + +#undef ITEM_CHECK_DISABLED + +#define ITEM_CHECK_DISABLED(m_item) tool_anim->get_popup()->set_item_disabled(tool_anim->get_popup()->get_item_index(m_item), no_anims_found || foreign_global_anim_lib) + + ITEM_CHECK_DISABLED(TOOL_DUPLICATE_ANIM); + ITEM_CHECK_DISABLED(TOOL_RENAME_ANIM); + ITEM_CHECK_DISABLED(TOOL_EDIT_TRANSITIONS); + ITEM_CHECK_DISABLED(TOOL_REMOVE_ANIM); + ITEM_CHECK_DISABLED(TOOL_EDIT_RESOURCE); + +#undef ITEM_CHECK_DISABLED + + stop->set_disabled(no_anims_found); + play->set_disabled(no_anims_found); + play_bw->set_disabled(no_anims_found); + play_bw_from->set_disabled(no_anims_found); + play_from->set_disabled(no_anims_found); + frame->set_editable(!no_anims_found); + animation->set_disabled(no_anims_found); + autoplay->set_disabled(no_anims_found); + tool_anim->set_disabled(player == nullptr); + onion_toggle->set_disabled(no_anims_found); + onion_skinning->set_disabled(no_anims_found); + pin->set_disabled(player == nullptr); + + _update_animation_list_icons(); updating = false; if (active_idx != -1) { animation->select(active_idx); autoplay->set_pressed(animation->get_item_text(active_idx) == player->get_autoplay()); _animation_selected(active_idx); - - } else if (animation->get_item_count() > 0) { - animation->select(0); - autoplay->set_pressed(animation->get_item_text(0) == player->get_autoplay()); - _animation_selected(0); + } else if (animation->has_selectable_items()) { + int item = animation->get_selectable_item(); + animation->select(item); + autoplay->set_pressed(animation->get_item_text(item) == player->get_autoplay()); + _animation_selected(item); } else { _animation_selected(0); } - if (animation->get_item_count()) { + if (!no_anims_found) { String current = animation->get_item_text(animation->get_selected()); Ref<Animation> anim = player->get_animation(current); - track_editor->set_animation(anim); + + bool animation_library_is_foreign = 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); @@ -877,6 +982,69 @@ void AnimationPlayerEditor::_update_player() { _update_animation(); } +void AnimationPlayerEditor::_update_animation_list_icons() { + for (int i = 0; i < animation->get_item_count(); i++) { + String name = animation->get_item_text(i); + if (animation->is_item_disabled(i) || animation->is_item_separator(i)) { + continue; + } + + Ref<Texture2D> icon; + if (name == player->get_autoplay()) { + if (name == SceneStringNames::get_singleton()->RESET) { + icon = autoplay_reset_icon; + } else { + icon = autoplay_icon; + } + } else if (name == SceneStringNames::get_singleton()->RESET) { + icon = reset_icon; + } + + animation->set_item_icon(i, icon); + } +} + +void AnimationPlayerEditor::_update_name_dialog_library_dropdown() { + StringName current_library_name = 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::edit(AnimationPlayer *p_player) { if (player && pin->is_pressed()) { return; // Ignore, pinned. @@ -887,7 +1055,7 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { _update_player(); if (onion.enabled) { - if (animation->get_item_count() > 0) { + if (animation->has_selectable_items()) { _start_onion_skinning(); } else { _stop_onion_skinning(); @@ -902,9 +1070,11 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_player) { track_editor->show_select_node_warning(true); } + + library_editor->set_animation_player(player); } -void AnimationPlayerEditor::forward_canvas_force_draw_over_viewport(Control *p_overlay) { +void AnimationPlayerEditor::forward_force_draw_over_viewport(Control *p_overlay) { if (!onion.can_overlay) { return; } @@ -955,7 +1125,7 @@ void AnimationPlayerEditor::forward_canvas_force_draw_over_viewport(Control *p_o } void AnimationPlayerEditor::_animation_duplicate() { - if (!animation->get_item_count()) { + if (!animation->has_selectable_items()) { return; } @@ -965,37 +1135,40 @@ void AnimationPlayerEditor::_animation_duplicate() { return; } - Ref<Animation> new_anim = memnew(Animation); - List<PropertyInfo> plist; - anim->get_property_list(&plist); - for (const PropertyInfo &E : plist) { - if (E.usage & PROPERTY_USAGE_STORAGE) { - new_anim->set(E.name, anim->get(E.name)); - } - } - new_anim->set_path(""); - String new_name = current; while (player->has_animation(new_name)) { new_name = new_name + " (copy)"; } - new_anim->set_name(new_name); - undo_redo->create_action(TTR("Duplicate Animation")); - undo_redo->add_do_method(player, "add_animation", new_name, new_anim); - undo_redo->add_undo_method(player, "remove_animation", new_name); - undo_redo->add_do_method(player, "animation_set_next", new_name, player->animation_get_next(current)); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); + if (new_name.contains("/")) { + // Discard library prefix. + new_name = new_name.get_slice("/", 1); + } - for (int i = 0; i < animation->get_item_count(); i++) { - if (animation->get_item_text(i) == new_name) { - animation->select(i); - _animation_selected(i); - return; + _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(); +} + +Ref<Animation> AnimationPlayerEditor::_animation_clone(Ref<Animation> p_anim) { + Ref<Animation> new_anim = memnew(Animation); + List<PropertyInfo> plist; + p_anim->get_property_list(&plist); + + for (const PropertyInfo &E : plist) { + if (E.usage & PROPERTY_USAGE_STORAGE) { + new_anim->set(E.name, p_anim->get(E.name)); } } + new_anim->set_path(""); + + return new_anim; } void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool p_timeline_only) { @@ -1005,7 +1178,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool updating = true; String current = player->get_assigned_animation(); - if (current == "" || !player->has_animation(current)) { + if (current.is_empty() || !player->has_animation(current)) { updating = false; current = ""; return; @@ -1014,7 +1187,7 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool Ref<Animation> anim; anim = player->get_animation(current); - float pos = CLAMP(anim->get_length() * (p_value / frame->get_max()), 0, anim->get_length()); + float pos = CLAMP((double)anim->get_length() * (p_value / frame->get_max()), 0, (double)anim->get_length()); if (track_editor->is_snap_enabled()) { pos = Math::snapped(pos, _get_editor_step()); } @@ -1039,6 +1212,9 @@ void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) { if (blend_editor.dialog->is_visible()) { _animation_blend(); // Update. } + if (library_editor->is_visible()) { + library_editor->update_tree(); + } } } @@ -1078,13 +1254,10 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag, } void AnimationPlayerEditor::_animation_tool_menu(int p_option) { - String current; - if (animation->get_selected() >= 0 && animation->get_selected() < animation->get_item_count()) { - current = animation->get_item_text(animation->get_selected()); - } + String current = _get_current(); Ref<Animation> anim; - if (current != String()) { + if (!current.is_empty()) { anim = player->get_animation(current); } @@ -1092,24 +1265,13 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { case TOOL_NEW_ANIM: { _animation_new(); } break; - case TOOL_LOAD_ANIM: { - _animation_load(); - } break; - case TOOL_SAVE_ANIM: { - if (anim.is_valid()) { - _animation_save(anim); - } - } break; - case TOOL_SAVE_AS_ANIM: { - if (anim.is_valid()) { - _animation_save_as(anim); - } + case TOOL_ANIM_LIBRARY: { + library_editor->set_animation_player(player); + library_editor->show_dialog(); } break; case TOOL_DUPLICATE_ANIM: { _animation_duplicate(); - - [[fallthrough]]; // Allow immediate rename after animation is duplicated - } + } break; case TOOL_RENAME_ANIM: { _animation_rename(); } break; @@ -1119,56 +1281,10 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { case TOOL_REMOVE_ANIM: { _animation_remove(); } break; - case TOOL_COPY_ANIM: { - if (!animation->get_item_count()) { - error_dialog->set_text(TTR("No animation to copy!")); - error_dialog->popup_centered(); - return; - } - - String current2 = animation->get_item_text(animation->get_selected()); - Ref<Animation> anim2 = player->get_animation(current2); - EditorSettings::get_singleton()->set_resource_clipboard(anim2); - } break; - case TOOL_PASTE_ANIM: { - Ref<Animation> anim2 = EditorSettings::get_singleton()->get_resource_clipboard(); - if (!anim2.is_valid()) { - error_dialog->set_text(TTR("No animation resource on clipboard!")); - error_dialog->popup_centered(); - return; - } - - String name = anim2->get_name(); - if (name == "") { - name = TTR("Pasted Animation"); - } - - int idx = 1; - String base = name; - while (player->has_animation(name)) { - idx++; - name = base + " " + itos(idx); - } - - undo_redo->create_action(TTR("Paste Animation")); - undo_redo->add_do_method(player, "add_animation", name, anim2); - undo_redo->add_undo_method(player, "remove_animation", name); - undo_redo->add_do_method(this, "_animation_player_changed", player); - undo_redo->add_undo_method(this, "_animation_player_changed", player); - undo_redo->commit_action(); - - _select_anim_by_name(name); - } break; case TOOL_EDIT_RESOURCE: { - if (!animation->get_item_count()) { - error_dialog->set_text(TTR("No animation to edit!")); - error_dialog->popup_centered(); - return; + if (anim.is_valid()) { + EditorNode::get_singleton()->edit_resource(anim); } - - String current2 = animation->get_item_text(animation->get_selected()); - Ref<Animation> anim2 = player->get_animation(current2); - editor->edit_resource(anim2); } break; } } @@ -1222,13 +1338,13 @@ void AnimationPlayerEditor::_onion_skinning_menu(int p_option) { } } -void AnimationPlayerEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { +void AnimationPlayerEditor::shortcut_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) { switch (k->get_keycode()) { - case KEY_A: { + case Key::A: { if (!k->is_shift_pressed()) { _play_bw_from_pressed(); } else { @@ -1236,11 +1352,11 @@ void AnimationPlayerEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { } accept_event(); } break; - case KEY_S: { + case Key::S: { _stop_pressed(); accept_event(); } break; - case KEY_D: { + case Key::D: { if (!k->is_shift_pressed()) { _play_from_pressed(); } else { @@ -1255,7 +1371,7 @@ void AnimationPlayerEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { } void AnimationPlayerEditor::_editor_visibility_changed() { - if (is_visible() && animation->get_item_count() > 0) { + if (is_visible() && animation->has_selectable_items()) { _start_onion_skinning(); } } @@ -1405,26 +1521,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->has_loop() || (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,15 +1582,15 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { } void AnimationPlayerEditor::_start_onion_skinning() { - // FIXME: Using "idle_frame" makes onion layers update one frame behind the current. - if (!get_tree()->is_connected("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->connect("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + // FIXME: Using "process_frame" makes onion layers update one frame behind the current. + if (!get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->connect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); } } void AnimationPlayerEditor::_stop_onion_skinning() { - if (get_tree()->is_connected("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->disconnect("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + if (get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->disconnect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); _free_onion_layers(); @@ -1485,13 +1601,12 @@ void AnimationPlayerEditor::_stop_onion_skinning() { } void AnimationPlayerEditor::_pin_pressed() { - EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->update_tree(); + SceneTreeDock::get_singleton()->get_tree_editor()->update_tree(); } void AnimationPlayerEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_animation_new"), &AnimationPlayerEditor::_animation_new); ClassDB::bind_method(D_METHOD("_animation_rename"), &AnimationPlayerEditor::_animation_rename); - ClassDB::bind_method(D_METHOD("_animation_load"), &AnimationPlayerEditor::_animation_load); ClassDB::bind_method(D_METHOD("_animation_remove"), &AnimationPlayerEditor::_animation_remove); ClassDB::bind_method(D_METHOD("_animation_blend"), &AnimationPlayerEditor::_animation_blend); ClassDB::bind_method(D_METHOD("_animation_edit"), &AnimationPlayerEditor::_animation_edit); @@ -1512,8 +1627,7 @@ AnimationPlayer *AnimationPlayerEditor::get_player() const { return player; } -AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlayerEditorPlugin *p_plugin) { - editor = p_editor; +AnimationPlayerEditor::AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin) { plugin = p_plugin; singleton = this; @@ -1554,7 +1668,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay 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).")); @@ -1579,20 +1693,16 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay tool_anim->set_text(TTR("Animation")); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/new_animation", TTR("New")), TOOL_NEW_ANIM); tool_anim->get_popup()->add_separator(); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation", TTR("Load")), TOOL_LOAD_ANIM); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_animation", TTR("Save")), TOOL_SAVE_ANIM); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/save_as_animation", TTR("Save As...")), TOOL_SAVE_AS_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/animation_libraries", TTR("Manage Animations...")), TOOL_ANIM_LIBRARY); tool_anim->get_popup()->add_separator(); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/copy_animation", TTR("Copy")), TOOL_COPY_ANIM); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/paste_animation", TTR("Paste")), TOOL_PASTE_ANIM); - tool_anim->get_popup()->add_separator(); - tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate")), TOOL_DUPLICATE_ANIM); + tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/duplicate_animation", TTR("Duplicate...")), TOOL_DUPLICATE_ANIM); tool_anim->get_popup()->add_separator(); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/rename_animation", TTR("Rename...")), TOOL_RENAME_ANIM); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/edit_transitions", TTR("Edit Transitions...")), TOOL_EDIT_TRANSITIONS); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/open_animation_in_inspector", TTR("Open in Inspector")), TOOL_EDIT_RESOURCE); tool_anim->get_popup()->add_separator(); tool_anim->get_popup()->add_shortcut(ED_SHORTCUT("animation_player_editor/remove_animation", TTR("Remove")), TOOL_REMOVE_ANIM); + tool_anim->set_disabled(true); hb->add_child(tool_anim); animation = memnew(OptionButton); @@ -1618,18 +1728,20 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay 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(onion_skinning->get_popup()->get_item_count() - 1, true); + onion_skinning->get_popup()->set_item_checked(-1, true); + // TRANSLATORS: Opposite of "Past", refers to a direction in animation onion skinning. onion_skinning->get_popup()->add_check_item(TTR("Future"), ONION_SKINNING_FUTURE); onion_skinning->get_popup()->add_separator(TTR("Depth")); onion_skinning->get_popup()->add_radio_check_item(TTR("1 step"), ONION_SKINNING_1_STEP); - onion_skinning->get_popup()->set_item_checked(onion_skinning->get_popup()->get_item_count() - 1, true); + onion_skinning->get_popup()->set_item_checked(-1, true); onion_skinning->get_popup()->add_radio_check_item(TTR("2 steps"), ONION_SKINNING_2_STEPS); onion_skinning->get_popup()->add_radio_check_item(TTR("3 steps"), ONION_SKINNING_3_STEPS); onion_skinning->get_popup()->add_separator(); @@ -1660,12 +1772,18 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay name_title = memnew(Label(TTR("Animation Name:"))); vb->add_child(name_title); + HBoxContainer *name_hb = memnew(HBoxContainer); name = memnew(LineEdit); - vb->add_child(name); + name_hb->add_child(name); + name->set_h_size_flags(SIZE_EXPAND_FILL); + library = memnew(OptionButton); + name_hb->add_child(library); + library->hide(); + vb->add_child(name_hb); name_dialog->register_text_enter(name); error_dialog = memnew(ConfirmationDialog); - error_dialog->get_ok_button()->set_text(TTR("Close")); + error_dialog->set_ok_button_text(TTR("Close")); error_dialog->set_title(TTR("Error!")); add_child(error_dialog); @@ -1673,7 +1791,7 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay blend_editor.dialog = memnew(AcceptDialog); add_child(blend_editor.dialog); - blend_editor.dialog->get_ok_button()->set_text(TTR("Close")); + blend_editor.dialog->set_ok_button_text(TTR("Close")); blend_editor.dialog->set_hide_on_ok(true); VBoxContainer *blend_vb = memnew(VBoxContainer); blend_editor.dialog->add_child(blend_vb); @@ -1697,16 +1815,13 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected)); - file->connect("file_selected", callable_mp(this, &AnimationPlayerEditor::_save_animation)); - file->connect("files_selected", callable_mp(this, &AnimationPlayerEditor::_load_animations)); - frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true, false)); + frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed).bind(true, false)); scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed)); - renaming = false; last_active = false; timeline_position = 0; - set_process_unhandled_key_input(true); + set_process_shortcut_input(true); add_child(track_editor); track_editor->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1715,6 +1830,10 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay _update_player(); + library_editor = memnew(AnimationLibraryEditor); + add_child(library_editor); + library_editor->connect("update_editor", callable_mp(this, &AnimationPlayerEditor::_animation_player_changed)); + // Onion skinning. track_editor->connect("visibility_changed", callable_mp(this, &AnimationPlayerEditor::_editor_visibility_changed)); @@ -1772,11 +1891,39 @@ AnimationPlayerEditor::~AnimationPlayerEditor() { void AnimationPlayerEditorPlugin::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { + Node3DEditor::get_singleton()->connect("transform_key_request", callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request)); + InspectorDock::get_inspector_singleton()->connect("property_keyed", callable_mp(this, &AnimationPlayerEditorPlugin::_property_keyed)); + anim_editor->get_track_editor()->connect("keying_changed", callable_mp(this, &AnimationPlayerEditorPlugin::_update_keying)); + InspectorDock::get_inspector_singleton()->connect("edited_object_changed", callable_mp(anim_editor->get_track_editor(), &AnimationTrackEditor::update_keying)); set_force_draw_over_forwarding_enabled(); } break; } } +void AnimationPlayerEditorPlugin::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) { + if (!anim_editor->get_track_editor()->has_keying()) { + return; + } + anim_editor->get_track_editor()->insert_value_key(p_keyed, p_value, p_advance); +} + +void AnimationPlayerEditorPlugin::_transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key) { + if (!anim_editor->get_track_editor()->has_keying()) { + return; + } + Node3D *s = Object::cast_to<Node3D>(sp); + if (!s) { + return; + } + anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_POSITION_3D, p_key.origin); + anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_ROTATION_3D, p_key.basis.get_rotation_quaternion()); + anim_editor->get_track_editor()->insert_transform_key(s, p_sub, Animation::TYPE_SCALE_3D, p_key.basis.get_scale()); +} + +void AnimationPlayerEditorPlugin::_update_keying() { + InspectorDock::get_inspector_singleton()->set_keying(anim_editor->get_track_editor()->has_keying()); +} + void AnimationPlayerEditorPlugin::edit(Object *p_object) { anim_editor->set_undo_redo(&get_undo_redo()); if (!p_object) { @@ -1791,17 +1938,16 @@ bool AnimationPlayerEditorPlugin::handles(Object *p_object) const { void AnimationPlayerEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - editor->make_bottom_panel_item_visible(anim_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(anim_editor); anim_editor->set_process(true); anim_editor->ensure_visibility(); } } -AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin(EditorNode *p_node) { - editor = p_node; - anim_editor = memnew(AnimationPlayerEditor(editor, this)); +AnimationPlayerEditorPlugin::AnimationPlayerEditorPlugin() { + anim_editor = memnew(AnimationPlayerEditor(this)); anim_editor->set_undo_redo(EditorNode::get_undo_redo()); - editor->add_bottom_panel_item(TTR("Animation"), anim_editor); + EditorNode::get_singleton()->add_bottom_panel_item(TTR("Animation"), anim_editor); } AnimationPlayerEditorPlugin::~AnimationPlayerEditorPlugin() { diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index be80b7f4e3..3b1de070fa 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,35 +31,31 @@ #ifndef ANIMATION_PLAYER_EDITOR_PLUGIN_H #define ANIMATION_PLAYER_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/animation_track_editor.h" #include "editor/editor_plugin.h" +#include "editor/plugins/animation_library_editor.h" #include "scene/animation/animation_player.h" #include "scene/gui/dialogs.h" #include "scene/gui/slider.h" #include "scene/gui/spin_box.h" #include "scene/gui/texture_button.h" +#include "scene/gui/tree.h" -class AnimationTrackEditor; class AnimationPlayerEditorPlugin; class AnimationPlayerEditor : public VBoxContainer { GDCLASS(AnimationPlayerEditor, VBoxContainer); - EditorNode *editor; - AnimationPlayerEditorPlugin *plugin; - AnimationPlayer *player; + AnimationPlayerEditorPlugin *plugin = nullptr; + AnimationPlayer *player = nullptr; enum { TOOL_NEW_ANIM, - TOOL_LOAD_ANIM, - TOOL_SAVE_ANIM, - TOOL_SAVE_AS_ANIM, + TOOL_ANIM_LIBRARY, TOOL_DUPLICATE_ANIM, TOOL_RENAME_ANIM, TOOL_EDIT_TRANSITIONS, TOOL_REMOVE_ANIM, - TOOL_COPY_ANIM, - TOOL_PASTE_ANIM, TOOL_EDIT_RESOURCE }; @@ -87,31 +83,35 @@ class AnimationPlayerEditor : public VBoxContainer { RESOURCE_SAVE }; - OptionButton *animation; - Button *stop; - Button *play; - Button *play_from; - Button *play_bw; - Button *play_bw_from; - Button *autoplay; - - MenuButton *tool_anim; - Button *onion_toggle; - MenuButton *onion_skinning; - Button *pin; - SpinBox *frame; - LineEdit *scale; - LineEdit *name; - Label *name_title; - UndoRedo *undo_redo; + OptionButton *animation = nullptr; + Button *stop = nullptr; + Button *play = nullptr; + Button *play_from = nullptr; + Button *play_bw = nullptr; + Button *play_bw_from = nullptr; + Button *autoplay = nullptr; + + MenuButton *tool_anim = nullptr; + Button *onion_toggle = nullptr; + MenuButton *onion_skinning = nullptr; + Button *pin = nullptr; + SpinBox *frame = nullptr; + LineEdit *scale = nullptr; + LineEdit *name = nullptr; + OptionButton *library = nullptr; + Label *name_title = nullptr; + UndoRedo *undo_redo = nullptr; + Ref<Texture2D> autoplay_icon; Ref<Texture2D> reset_icon; Ref<ImageTexture> autoplay_reset_icon; bool last_active; float timeline_position; - EditorFileDialog *file; - ConfirmationDialog *delete_dialog; + EditorFileDialog *file = nullptr; + ConfirmationDialog *delete_dialog = nullptr; + + AnimationLibraryEditor *library_editor = nullptr; struct BlendEditor { AcceptDialog *dialog = nullptr; @@ -120,14 +120,15 @@ class AnimationPlayerEditor : public VBoxContainer { } blend_editor; - ConfirmationDialog *name_dialog; - ConfirmationDialog *error_dialog; - bool renaming; + ConfirmationDialog *name_dialog = nullptr; + ConfirmationDialog *error_dialog = nullptr; + int name_dialog_op = TOOL_NEW_ANIM; bool updating; bool updating_blends; - AnimationTrackEditor *track_editor; + AnimationTrackEditor *track_editor = nullptr; + static AnimationPlayerEditor *singleton; // Onion skinning. struct { @@ -171,28 +172,23 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_new(); void _animation_rename(); void _animation_name_edited(); - void _animation_load(); - - void _animation_save_in_path(const Ref<Resource> &p_resource, const String &p_path); - void _animation_save(const Ref<Resource> &p_resource); - void _animation_save_as(const Ref<Resource> &p_resource); void _animation_remove(); void _animation_remove_confirmed(); void _animation_blend(); void _animation_edit(); void _animation_duplicate(); + Ref<Animation> _animation_clone(const Ref<Animation> p_anim); void _animation_resource_edit(); void _scale_changed(const String &p_scale); - void _save_animation(String p_file); - void _load_animations(Vector<String> p_files); - void _seek_frame_changed(const String &p_frame); void _seek_value_changed(float p_value, bool p_set = false, bool p_timeline_only = false); void _blend_editor_next_changed(const int p_idx); void _list_changed(); void _update_animation(); void _update_player(); + void _update_animation_list_icons(); + void _update_name_dialog_library_dropdown(); void _blend_edited(); void _animation_player_changed(Object *p_pl); @@ -200,7 +196,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only = false); void _animation_key_editor_anim_len_changed(float p_len); - virtual void unhandled_key_input(const Ref<InputEvent> &p_ev) override; + virtual void shortcut_input(const Ref<InputEvent> &p_ev) override; void _animation_tool_menu(int p_option); void _onion_skinning_menu(int p_option); @@ -215,8 +211,8 @@ class AnimationPlayerEditor : public VBoxContainer { void _stop_onion_skinning(); void _pin_pressed(); + String _get_current() const; - AnimationPlayerEditor(); ~AnimationPlayerEditor(); protected: @@ -226,7 +222,8 @@ protected: public: AnimationPlayer *get_player() const; - static AnimationPlayerEditor *singleton; + + static AnimationPlayerEditor *get_singleton() { return singleton; } bool is_pinned() const { return pin->is_pressed(); } void unpin() { pin->set_pressed(false); } @@ -238,20 +235,23 @@ public: void set_undo_redo(UndoRedo *p_undo_redo) { undo_redo = p_undo_redo; } void edit(AnimationPlayer *p_player); - void forward_canvas_force_draw_over_viewport(Control *p_overlay); + void forward_force_draw_over_viewport(Control *p_overlay); - AnimationPlayerEditor(EditorNode *p_editor, AnimationPlayerEditorPlugin *p_plugin); + AnimationPlayerEditor(AnimationPlayerEditorPlugin *p_plugin); }; class AnimationPlayerEditorPlugin : public EditorPlugin { GDCLASS(AnimationPlayerEditorPlugin, EditorPlugin); - AnimationPlayerEditor *anim_editor; - EditorNode *editor; + AnimationPlayerEditor *anim_editor = nullptr; protected: void _notification(int p_what); + void _property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance); + void _transform_key_request(Object *sp, const String &p_sub, const Transform3D &p_key); + void _update_keying(); + public: virtual Dictionary get_state() const override { return anim_editor->get_state(); } virtual void set_state(const Dictionary &p_state) override { anim_editor->set_state(p_state); } @@ -262,9 +262,10 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_canvas_force_draw_over_viewport(p_overlay); } + virtual void forward_canvas_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } + virtual void forward_spatial_force_draw_over_viewport(Control *p_overlay) override { anim_editor->forward_force_draw_over_viewport(p_overlay); } - AnimationPlayerEditorPlugin(EditorNode *p_node); + AnimationPlayerEditorPlugin(); ~AnimationPlayerEditorPlugin(); }; diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index a1f96f21bf..473450b292 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,11 +35,16 @@ #include "core/io/resource_loader.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "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) { @@ -53,7 +58,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(); } @@ -66,71 +74,40 @@ 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_DELETE && !k->is_echo()) { - if (selected_node != StringName() || selected_transition_to != StringName() || selected_transition_from != StringName()) { + 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_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() == MOUSE_BUTTON_RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_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("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_transform().xform(mb->get_position())); - 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() == MOUSE_BUTTON_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 @@ -143,15 +120,14 @@ 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; edit_rect.position -= line_sb->get_offset(); edit_rect.size += line_sb->get_minimum_size(); - name_edit_popup->set_position(state_machine_draw->get_screen_transform().xform(edit_rect.position)); + name_edit_popup->set_position(state_machine_draw->get_screen_position() + edit_rect.position); name_edit_popup->set_size(edit_rect.size); name_edit->set_text(node_rects[i].node_name); name_edit_popup->popup(); @@ -170,6 +146,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(); @@ -206,23 +188,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 - if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { + // 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(); @@ -236,8 +248,8 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv state_machine_draw->update(); } - //connect nodes - if (mb.is_valid() && ((tool_select->is_pressed() && mb->is_shift_pressed()) || tool_connect->is_pressed()) && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + // Connect nodes + if (mb.is_valid() && ((tool_select->is_pressed() && mb->is_shift_pressed()) || tool_connect->is_pressed()) && mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected connecting = true; @@ -249,47 +261,63 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } } - //end connecting nodes - if (mb.is_valid() && connecting && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { + // 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 - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) { + // 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(); @@ -303,7 +331,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; @@ -343,29 +371,56 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv state_machine_draw->update(); } - //put ibeam (text cursor) over names to make it clearer that they are editable + // 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(); - bool over_text_now = false; String new_over_node = StringName(); int new_over_node_what = -1; if (tool_select->is_pressed()) { - for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order + for (int i = node_rects.size() - 1; i >= 0; i--) { // Inverse to draw order. - if (node_rects[i].name.has_point(mm->get_position())) { - over_text_now = true; - break; + if (!state_machine->can_edit_node(node_rects[i].node_name)) { + continue; // start/end node can't be edited } if (node_rects[i].node.has_point(mm->get_position())) { new_over_node = node_rects[i].node_name; if (node_rects[i].play.has_point(mm->get_position())) { new_over_node_what = 0; - } - if (node_rects[i].edit.has_point(mm->get_position())) { + } else if (node_rects[i].edit.has_point(mm->get_position())) { new_over_node_what = 1; } + break; } } } @@ -376,14 +431,41 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv state_machine_draw->update(); } - if (over_text != over_text_now) { - if (over_text_now) { - state_machine_draw->set_default_cursor_shape(CURSOR_IBEAM); - } else { - state_machine_draw->set_default_cursor_shape(CURSOR_ARROW); + // set tooltip for transition + if (tool_select->is_pressed()) { + int closest = -1; + float closest_d = 1e20; + for (int i = 0; i < transition_lines.size(); i++) { + Vector2 s[2] = { + transition_lines[i].from, + transition_lines[i].to + }; + Vector2 cpoint = Geometry2D::get_closest_point_to_segment(mm->get_position(), s); + float d = cpoint.distance_to(mm->get_position()); + if (d > transition_lines[i].width) { + continue; + } + + if (d < closest_d) { + closest = i; + closest_d = d; + } } - over_text = over_text_now; + if (closest >= 0) { + String from = String(transition_lines[closest].from_node); + String to = String(transition_lines[closest].to_node); + String tooltip = from + " -> " + to; + + for (int i = 0; i < transition_lines[closest].multi_transitions.size(); i++) { + from = String(transition_lines[closest].multi_transitions[i].from_node); + to = String(transition_lines[closest].multi_transitions[i].to_node); + tooltip += "\n" + from + " -> " + to; + } + state_machine_draw->set_tooltip(tooltip); + } else { + state_machine_draw->set_tooltip(""); + } } } @@ -394,6 +476,475 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } } +Control::CursorShape AnimationNodeStateMachineEditor::get_cursor_shape(const Point2 &p_pos) const { + // Put ibeam (text cursor) over names to make it clearer that they are editable. + Transform2D xform = panel->get_transform() * state_machine_draw->get_transform(); + Point2 pos = xform.xform_inv(p_pos); + Control::CursorShape cursor_shape = get_default_cursor_shape(); + + for (int i = node_rects.size() - 1; i >= 0; i--) { // Inverse to draw order. + if (node_rects[i].node.has_point(pos)) { + if (node_rects[i].name.has_point(pos)) { + cursor_shape = Control::CURSOR_IBEAM; + } + break; + } + } + return cursor_shape; +} + +void AnimationNodeStateMachineEditor::_group_selected_nodes() { + if (!selected_nodes.is_empty()) { + if (selected_nodes.size() == 1 && (*selected_nodes.begin() == state_machine->start_node || *selected_nodes.begin() == state_machine->end_node)) + return; + + Ref<AnimationNodeStateMachine> group_sm = memnew(AnimationNodeStateMachine); + Vector2 group_position; + + Vector<NodeUR> nodes_ur; + Vector<TransitionUR> transitions_ur; + + int base = 1; + String base_name = group_sm->get_caption(); + String group_name = base_name; + + while (state_machine->has_node(group_name) && !selected_nodes.has(group_name)) { + base++; + group_name = base_name + " " + itos(base); + } + + updating = true; + 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()) { @@ -437,7 +988,7 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) { return; } - if (base_name == String()) { + if (base_name.is_empty()) { base_name = node->get_class().replace_first("AnimationNode", ""); } @@ -454,6 +1005,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; @@ -466,7 +1019,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)) { @@ -480,13 +1033,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")); @@ -497,7 +1095,7 @@ void AnimationNodeStateMachineEditor::_connection_draw(const Vector2 &p_from, co accent.a *= 0.6; } - Ref<Texture2D> icons[6] = { + const Ref<Texture2D> icons[6] = { get_theme_icon(SNAME("TransitionImmediateBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionSyncBig"), SNAME("EditorIcons")), get_theme_icon(SNAME("TransitionEndBig"), SNAME("EditorIcons")), @@ -514,39 +1112,46 @@ 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()); } -void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) { - if (r_to == r_from) { +void AnimationNodeStateMachineEditor::_clip_src_line_to_rect(Vector2 &r_from, const Vector2 &p_to, const Rect2 &p_rect) { + if (p_to == r_from) { return; } //this could be optimized... - Vector2 n = (r_to - r_from).normalized(); + Vector2 n = (p_to - r_from).normalized(); while (p_rect.has_point(r_from)) { r_from += n; } } -void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect) { - if (r_to == r_from) { +void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(const Vector2 &p_from, Vector2 &r_to, const Rect2 &p_rect) { + if (r_to == p_from) { return; } //this could be optimized... - Vector2 n = (r_to - r_from).normalized(); + Vector2 n = (r_to - p_from).normalized(); while (p_rect.has_point(r_to)) { r_to -= n; } @@ -555,20 +1160,27 @@ void AnimationNodeStateMachineEditor::_clip_dst_line_to_rect(Vector2 &r_from, Ve 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_selectedframe"), SNAME("GraphNode")); + Ref<StyleBoxFlat> style = get_theme_stylebox(SNAME("state_machine_frame"), SNAME("GraphNode")); + Ref<StyleBoxFlat> style_selected = get_theme_stylebox(SNAME("state_machine_selected_frame"), SNAME("GraphNode")); Ref<Font> font = get_theme_font(SNAME("title_font"), SNAME("GraphNode")); int font_size = get_theme_font_size(SNAME("title_font_size"), SNAME("GraphNode")); Color font_color = get_theme_color(SNAME("title_color"), SNAME("GraphNode")); Ref<Texture2D> play = get_theme_icon(SNAME("Play"), SNAME("EditorIcons")); - Ref<Texture2D> auto_play = get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons")); Ref<Texture2D> edit = get_theme_icon(SNAME("Edit"), SNAME("EditorIcons")); Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); Color linecolor = get_theme_color(SNAME("font_color"), SNAME("Label")); linecolor.a *= 0.3; Ref<StyleBox> playing_overlay = get_theme_stylebox(SNAME("position"), SNAME("GraphNode")); + Ref<StyleBoxFlat> start_overlay = style->duplicate(); + start_overlay->set_border_width_all(1 * EDSCALE); + start_overlay->set_border_color(Color::html("#80f6cf")); + + Ref<StyleBoxFlat> end_overlay = style->duplicate(); + end_overlay->set_border_width_all(1 * EDSCALE); + end_overlay->set_border_color(Color::html("#f26661")); + bool playing = false; StringName current; StringName blend_from; @@ -609,23 +1221,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(); @@ -664,7 +1279,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")); @@ -673,13 +1288,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(); @@ -688,62 +1308,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; + tl.selected = selected_transition_from == tl.from_node && selected_transition_to == tl.to_node; - bool travel = false; - - if (blend_from == tl.from_node && current == tl.to_node) { - travel = true; + if (blend_from == local_from && current == local_to) { + tl.travel = true; } 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; @@ -754,18 +1391,16 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { //now scroll it to draw state_machine_draw->draw_style_box(sb, nr.node); - if (playing && (blend_from == name || current == name || travel_path.find(name) != -1)) { - state_machine_draw->draw_style_box(playing_overlay, nr.node); + if (state_machine->start_node == name) { + state_machine_draw->draw_style_box(sb == style_selected ? style_selected : start_overlay, nr.node); } - bool onstart = state_machine->get_start_node() == name; - if (onstart) { - state_machine_draw->draw_string(font, offset + Vector2(0, -font->get_height(font_size) - 3 * EDSCALE + font->get_ascent(font_size)), TTR("Start"), HALIGN_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"), HALIGN_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; @@ -773,19 +1408,20 @@ 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(); nr.name.size = Vector2(strsize, font->get_height(font_size)); - state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent(font_size)), name, HALIGN_LEFT, -1, font_size, font_color); + state_machine_draw->draw_string(font, nr.name.position + Vector2(0, font->get_ascent(font_size)), name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); offset.x += strsize + sep; if (needs_editor) { @@ -797,10 +1433,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; @@ -827,6 +1467,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()) { @@ -881,169 +1525,174 @@ void AnimationNodeStateMachineEditor::_update_graph() { } void AnimationNodeStateMachineEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_LAYOUT_DIRECTION_CHANGED || p_what == NOTIFICATION_TRANSLATION_CHANGED) { - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - - tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_create->set_icon(get_theme_icon(SNAME("ToolAddNode"), SNAME("EditorIcons"))); - tool_connect->set_icon(get_theme_icon(SNAME("ToolConnect"), SNAME("EditorIcons"))); - - transition_mode->clear(); - transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionImmediate"), SNAME("EditorIcons")), TTR("Immediate")); - transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionSync"), SNAME("EditorIcons")), TTR("Sync")); - transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionEnd"), SNAME("EditorIcons")), TTR("At End")); - - tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - tool_autoplay->set_icon(get_theme_icon(SNAME("AutoPlay"), SNAME("EditorIcons"))); - tool_end->set_icon(get_theme_icon(SNAME("AutoEnd"), SNAME("EditorIcons"))); - - play_mode->clear(); - play_mode->add_icon_item(get_theme_icon(SNAME("PlayTravel"), SNAME("EditorIcons")), TTR("Travel")); - play_mode->add_icon_item(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")), TTR("Immediate")); - } - - if (p_what == NOTIFICATION_PROCESS) { - String error; - - Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); - - if (error_time > 0) { - error = error_text; - error_time -= get_process_delta_time(); - } else if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { - error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); - } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { - error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); - /*} else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) { - if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) { - error = TTR("Start and end nodes are needed for a sub-transition."); - }*/ - } else if (playback.is_null()) { - error = vformat(TTR("No playback resource set at path: %s."), AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); - } - - if (error != error_label->get_text()) { - error_label->set_text(error); - if (error != String()) { - error_panel->show(); - } else { - error_panel->hide(); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: + case NOTIFICATION_TRANSLATION_CHANGED: { + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + + tool_select->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_create->set_icon(get_theme_icon(SNAME("ToolAddNode"), SNAME("EditorIcons"))); + tool_connect->set_icon(get_theme_icon(SNAME("ToolConnect"), SNAME("EditorIcons"))); + + transition_mode->clear(); + transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionImmediate"), SNAME("EditorIcons")), TTR("Immediate")); + transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionSync"), SNAME("EditorIcons")), TTR("Sync")); + transition_mode->add_icon_item(get_theme_icon(SNAME("TransitionEnd"), SNAME("EditorIcons")), TTR("At End")); + + tool_erase->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + tool_group->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + tool_ungroup->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + + play_mode->clear(); + play_mode->add_icon_item(get_theme_icon(SNAME("PlayTravel"), SNAME("EditorIcons")), TTR("Travel")); + play_mode->add_icon_item(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")), TTR("Immediate")); + } break; + + case NOTIFICATION_PROCESS: { + String error; + + Ref<AnimationNodeStateMachinePlayback> playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); + + if (error_time > 0) { + error = error_text; + error_time -= get_process_delta_time(); + } else if (!AnimationTreeEditor::get_singleton()->get_tree()->is_active()) { + error = TTR("AnimationTree is inactive.\nActivate to enable playback, check node warnings if activation fails."); + } else if (AnimationTreeEditor::get_singleton()->get_tree()->is_state_invalid()) { + error = AnimationTreeEditor::get_singleton()->get_tree()->get_invalid_state_reason(); + /*} else if (state_machine->get_parent().is_valid() && state_machine->get_parent()->is_class("AnimationNodeStateMachine")) { + if (state_machine->get_start_node() == StringName() || state_machine->get_end_node() == StringName()) { + error = TTR("Start and end nodes are needed for a sub-transition."); + }*/ + } else if (playback.is_null()) { + error = vformat(TTR("No playback resource set at path: %s."), AnimationTreeEditor::get_singleton()->get_base_path() + "playback"); } - } - for (int i = 0; i < transition_lines.size(); i++) { - int tidx = -1; - for (int j = 0; j < state_machine->get_transition_count(); j++) { - if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) { - tidx = j; - break; + if (error != error_label->get_text()) { + error_label->set_text(error); + if (!error.is_empty()) { + error_panel->show(); + } else { + error_panel->hide(); } } - if (tidx == -1) { //missing transition, should redraw - state_machine_draw->update(); - break; - } + for (int i = 0; i < transition_lines.size(); i++) { + int tidx = -1; + for (int j = 0; j < state_machine->get_transition_count(); j++) { + if (transition_lines[i].from_node == state_machine->get_transition_from(j) && transition_lines[i].to_node == state_machine->get_transition_to(j)) { + tidx = j; + break; + } + } - if (transition_lines[i].disabled != state_machine->get_transition(tidx)->is_disabled()) { - state_machine_draw->update(); - break; - } + if (tidx == -1) { //missing transition, should redraw + state_machine_draw->update(); + break; + } - if (transition_lines[i].auto_advance != state_machine->get_transition(tidx)->has_auto_advance()) { - state_machine_draw->update(); - break; - } + if (transition_lines[i].disabled != state_machine->get_transition(tidx)->is_disabled()) { + state_machine_draw->update(); + break; + } - if (transition_lines[i].advance_condition_name != state_machine->get_transition(tidx)->get_advance_condition_name()) { - state_machine_draw->update(); - break; - } + if (transition_lines[i].auto_advance != state_machine->get_transition(tidx)->has_auto_advance()) { + state_machine_draw->update(); + break; + } - if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) { - state_machine_draw->update(); - break; - } + if (transition_lines[i].advance_condition_name != state_machine->get_transition(tidx)->get_advance_condition_name()) { + state_machine_draw->update(); + break; + } - bool acstate = transition_lines[i].advance_condition_name != StringName() && bool(AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + String(transition_lines[i].advance_condition_name))); + if (transition_lines[i].mode != state_machine->get_transition(tidx)->get_switch_mode()) { + state_machine_draw->update(); + break; + } - if (transition_lines[i].advance_condition_state != acstate) { - state_machine_draw->update(); - break; + bool acstate = transition_lines[i].advance_condition_name != StringName() && bool(AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + String(transition_lines[i].advance_condition_name))); + + if (transition_lines[i].advance_condition_state != acstate) { + state_machine_draw->update(); + break; + } } - } - bool same_travel_path = true; - Vector<StringName> tp; - bool is_playing = false; - StringName current_node; - StringName blend_from_node; - play_pos = 0; - current_length = 0; - - if (playback.is_valid()) { - tp = playback->get_travel_path(); - is_playing = playback->is_playing(); - current_node = playback->get_current_node(); - blend_from_node = playback->get_blend_from_node(); - play_pos = playback->get_current_play_pos(); - current_length = playback->get_current_length(); - } + bool same_travel_path = true; + Vector<StringName> tp; + bool is_playing = false; + StringName current_node; + StringName blend_from_node; + play_pos = 0; + current_length = 0; + + if (playback.is_valid()) { + tp = playback->get_travel_path(); + is_playing = playback->is_playing(); + current_node = playback->get_current_node(); + blend_from_node = playback->get_blend_from_node(); + play_pos = playback->get_current_play_pos(); + current_length = playback->get_current_length(); + } - { - if (last_travel_path.size() != tp.size()) { - same_travel_path = false; - } else { - for (int i = 0; i < last_travel_path.size(); i++) { - if (last_travel_path[i] != tp[i]) { - same_travel_path = false; - break; + { + if (last_travel_path.size() != tp.size()) { + same_travel_path = false; + } else { + for (int i = 0; i < last_travel_path.size(); i++) { + if (last_travel_path[i] != tp[i]) { + same_travel_path = false; + break; + } } } } - } - //update if travel state changed - if (!same_travel_path || last_active != is_playing || last_current_node != current_node || last_blend_from_node != blend_from_node) { - state_machine_draw->update(); - last_travel_path = tp; - last_current_node = current_node; - last_active = is_playing; - last_blend_from_node = blend_from_node; - state_machine_play_pos->update(); - } + //update if travel state changed + if (!same_travel_path || last_active != is_playing || last_current_node != current_node || last_blend_from_node != blend_from_node) { + state_machine_draw->update(); + last_travel_path = tp; + last_current_node = current_node; + last_active = is_playing; + last_blend_from_node = blend_from_node; + state_machine_play_pos->update(); + } - { - if (current_node != StringName() && state_machine->has_node(current_node)) { - String next = current_node; - Ref<AnimationNodeStateMachine> anodesm = state_machine->get_node(next); - Ref<AnimationNodeStateMachinePlayback> current_node_playback; - - while (anodesm.is_valid()) { - current_node_playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + next + "/playback"); - next += "/" + current_node_playback->get_current_node(); - anodesm = anodesm->get_node(current_node_playback->get_current_node()); - } + { + if (current_node != StringName() && state_machine->has_node(current_node)) { + String next = current_node; + Ref<AnimationNodeStateMachine> anodesm = state_machine->get_node(next); + Ref<AnimationNodeStateMachinePlayback> current_node_playback; - // when current_node is a state machine, use playback of current_node to set play_pos - if (current_node_playback.is_valid()) { - play_pos = current_node_playback->get_current_play_pos(); - current_length = current_node_playback->get_current_length(); + while (anodesm.is_valid()) { + current_node_playback = AnimationTreeEditor::get_singleton()->get_tree()->get(AnimationTreeEditor::get_singleton()->get_base_path() + next + "/playback"); + next += "/" + current_node_playback->get_current_node(); + anodesm = anodesm->get_node(current_node_playback->get_current_node()); + } + + // when current_node is a state machine, use playback of current_node to set play_pos + if (current_node_playback.is_valid()) { + play_pos = current_node_playback->get_current_play_pos(); + current_length = current_node_playback->get_current_length(); + } } } - } - if (last_play_pos != play_pos) { - last_play_pos = play_pos; - state_machine_play_pos->update(); - } - } + if (last_play_pos != play_pos) { + last_play_pos = play_pos; + state_machine_play_pos->update(); + } + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - over_node = StringName(); - set_process(is_visible_in_tree()); + case NOTIFICATION_VISIBILITY_CHANGED: { + over_node = StringName(); + set_process(is_visible_in_tree()); + } break; } } @@ -1058,7 +1707,7 @@ void AnimationNodeStateMachineEditor::_removed_from_graph() { void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) { const String &new_name = p_text; - ERR_FAIL_COND(new_name == "" || new_name.find(".") != -1 || new_name.find("/") != -1); + ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/")); if (new_name == prev_name) { return; // Nothing to do. @@ -1102,94 +1751,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(); } @@ -1197,17 +1873,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); @@ -1221,8 +1899,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); @@ -1230,7 +1908,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); @@ -1238,36 +1916,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); @@ -1279,6 +1956,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { panel = memnew(PanelContainer); panel->set_clip_contents(true); + panel->set_mouse_filter(Control::MOUSE_FILTER_PASS); add_child(panel); panel->set_v_size_flags(SIZE_EXPAND_FILL); @@ -1287,11 +1965,12 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { state_machine_draw->connect("gui_input", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_gui_input)); state_machine_draw->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_state_machine_draw)); state_machine_draw->set_focus_mode(FOCUS_ALL); + state_machine_draw->set_mouse_filter(Control::MOUSE_FILTER_PASS); state_machine_play_pos = memnew(Control); state_machine_draw->add_child(state_machine_play_pos); state_machine_play_pos->set_mouse_filter(MOUSE_FILTER_PASS); //pass all to parent - state_machine_play_pos->set_anchors_and_offsets_preset(PRESET_WIDE); + state_machine_play_pos->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); @@ -1318,17 +1997,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)); @@ -1339,13 +2034,94 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { open_file->connect("file_selected", callable_mp(this, &AnimationNodeStateMachineEditor::_file_opened)); undo_redo = EditorNode::get_undo_redo(); - over_text = false; + delete_window = memnew(ConfirmationDialog); + delete_window->set_flag(Window::FLAG_RESIZE_DISABLED, true); + add_child(delete_window); + + delete_tree = memnew(Tree); + delete_tree->set_hide_root(true); + delete_tree->connect("draw", callable_mp(this, &AnimationNodeStateMachineEditor::_delete_tree_draw)); + delete_window->add_child(delete_tree); + + Button *ok = delete_window->get_cancel_button(); + ok->set_text(TTR("Delete Selected")); + ok->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_delete_selected)); + + Button *delete_all = delete_window->add_button(TTR("Delete All"), true); + delete_all->connect("pressed", callable_mp(this, &AnimationNodeStateMachineEditor::_delete_all)); over_node_what = -1; dragging_selected_attempt = false; connecting = false; + selected_transition_index = -1; last_active = false; error_time = 0; } + +void EditorAnimationMultiTransitionEdit::add_transition(const StringName &p_from, const StringName &p_to, Ref<AnimationNodeStateMachineTransition> p_transition) { + Transition tr; + tr.from = p_from; + tr.to = p_to; + tr.transition = p_transition; + transitions.push_back(tr); +} + +bool EditorAnimationMultiTransitionEdit::_set(const StringName &p_name, const Variant &p_property) { + int index = String(p_name).get_slicec('/', 0).to_int(); + StringName prop = String(p_name).get_slicec('/', 1); + + bool found; + transitions.write[index].transition->set(prop, p_property, &found); + if (found) { + return true; + } + + return false; +} + +bool EditorAnimationMultiTransitionEdit::_get(const StringName &p_name, Variant &r_property) const { + int index = String(p_name).get_slicec('/', 0).to_int(); + StringName prop = String(p_name).get_slicec('/', 1); + + if (prop == "transition_path") { + r_property = String(transitions[index].from) + " -> " + transitions[index].to; + return true; + } + + bool found; + r_property = transitions[index].transition->get(prop, &found); + if (found) { + return true; + } + + return false; +} + +void EditorAnimationMultiTransitionEdit::_get_property_list(List<PropertyInfo> *p_list) const { + for (int i = 0; i < transitions.size(); i++) { + List<PropertyInfo> plist; + transitions[i].transition->get_property_list(&plist, true); + + PropertyInfo prop_transition_path; + prop_transition_path.type = Variant::STRING; + prop_transition_path.name = itos(i) + "/" + "transition_path"; + p_list->push_back(prop_transition_path); + + for (List<PropertyInfo>::Element *F = plist.front(); F; F = F->next()) { + if (F->get().name == "script" || F->get().name == "resource_name" || F->get().name == "resource_path" || F->get().name == "resource_local_to_scene") { + continue; + } + + if (F->get().usage != PROPERTY_USAGE_DEFAULT) { + continue; + } + + PropertyInfo prop = F->get(); + prop.name = itos(i) + "/" + prop.name; + + p_list->push_back(prop); + } + } +} diff --git a/editor/plugins/animation_state_machine_editor.h b/editor/plugins/animation_state_machine_editor.h index a969ddd26b..165940e639 100644 --- a/editor/plugins/animation_state_machine_editor.h +++ b/editor/plugins/animation_state_machine_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,81 +31,96 @@ #ifndef ANIMATION_STATE_MACHINE_EDITOR_H #define ANIMATION_STATE_MACHINE_EDITOR_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/animation_tree_editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_node_state_machine.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" #include "scene/gui/popup.h" #include "scene/gui/tree.h" +class EditorFileDialog; + class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { GDCLASS(AnimationNodeStateMachineEditor, AnimationTreeNodeEditorPlugin); Ref<AnimationNodeStateMachine> state_machine; - Button *tool_select; - Button *tool_create; - Button *tool_connect; - Popup *name_edit_popup; - LineEdit *name_edit; + 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; - Button *tool_erase; - Button *tool_autoplay; - Button *tool_end; + HBoxContainer *tool_erase_hb = nullptr; + Button *tool_erase = nullptr; - OptionButton *transition_mode; - OptionButton *play_mode; + OptionButton *transition_mode = nullptr; + OptionButton *play_mode = nullptr; - PanelContainer *panel; + PanelContainer *panel = nullptr; StringName selected_node; + HashSet<StringName> selected_nodes; - HScrollBar *h_scroll; - VScrollBar *v_scroll; + HScrollBar *h_scroll = nullptr; + VScrollBar *v_scroll = nullptr; - Control *state_machine_draw; - Control *state_machine_play_pos; + Control *state_machine_draw = nullptr; + Control *state_machine_play_pos = nullptr; - PanelContainer *error_panel; - Label *error_label; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; - bool updating; + bool updating = false; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; 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; - PopupMenu *animations_menu; + PopupMenu *menu = nullptr; + PopupMenu *connect_menu = nullptr; + PopupMenu *state_machine_menu = nullptr; + PopupMenu *end_menu = nullptr; + PopupMenu *animations_menu = nullptr; Vector<String> animations_to_add; + Vector<String> nodes_to_connect; Vector2 add_node_pos; - bool dragging_selected_attempt; - bool dragging_selected; + ConfirmationDialog *delete_window; + 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(); @@ -130,16 +145,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); - bool over_text; StringName over_node; - int over_node_what; + int over_node_what = -1; String prev_name; void _name_edited(const String &p_text); @@ -147,26 +183,35 @@ class AnimationNodeStateMachineEditor : public AnimationTreeNodeEditorPlugin { void _open_editor(const String &p_name); void _scroll_changed(double); - void _clip_src_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect); - void _clip_dst_line_to_rect(Vector2 &r_from, Vector2 &r_to, const Rect2 &p_rect); + void _clip_src_line_to_rect(Vector2 &r_from, const Vector2 &p_to, const Rect2 &p_rect); + void _clip_dst_line_to_rect(const Vector2 &p_from, Vector2 &r_to, const Rect2 &p_rect); - void _erase_selected(); + void _erase_selected(const bool p_nested_action = false); void _update_mode(); - void _autoplay_selected(); - void _end_selected(); + void _open_menu(const Vector2 &p_position); + void _open_connect_menu(const Vector2 &p_position); + bool _create_submenu(PopupMenu *p_menu, Ref<AnimationNodeStateMachine> p_nodesm, const StringName &p_name, const StringName &p_path, bool from_root = false, Vector<Ref<AnimationNodeStateMachine>> p_parents = Vector<Ref<AnimationNodeStateMachine>>()); + void _stop_connecting(); + + void _group_selected_nodes(); + void _ungroup_selected_nodes(); - bool last_active; + 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; + EditorFileDialog *open_file = nullptr; Ref<AnimationNode> file_loaded; void _file_opened(const String &p_file); @@ -184,7 +229,30 @@ public: static AnimationNodeStateMachineEditor *get_singleton() { return singleton; } virtual bool can_edit(const Ref<AnimationNode> &p_node) override; virtual void edit(const Ref<AnimationNode> &p_node) override; + virtual CursorShape get_cursor_shape(const Point2 &p_pos) const override; AnimationNodeStateMachineEditor(); }; +class EditorAnimationMultiTransitionEdit : public RefCounted { + GDCLASS(EditorAnimationMultiTransitionEdit, RefCounted); + + struct Transition { + StringName from; + StringName to; + Ref<AnimationNodeStateMachineTransition> transition; + }; + + Vector<Transition> transitions; + +protected: + bool _set(const StringName &p_name, const Variant &p_property); + bool _get(const StringName &p_name, Variant &r_property) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + +public: + void add_transition(const StringName &p_from, const StringName &p_to, Ref<AnimationNodeStateMachineTransition> p_transition); + + EditorAnimationMultiTransitionEdit(){}; +}; + #endif // ANIMATION_STATE_MACHINE_EDITOR_H diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index cd84be0c25..bce4c9de8e 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,6 +39,8 @@ #include "core/io/resource_loader.h" #include "core/math/delaunay_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/animation/animation_blend_tree.h" #include "scene/animation/animation_player.h" @@ -55,7 +57,7 @@ void AnimationTreeEditor::edit(AnimationTree *p_tree) { tree = p_tree; Vector<String> path; - if (tree->has_meta("_tree_edit_path")) { + if (tree && tree->has_meta("_tree_edit_path")) { path = tree->get_meta("_tree_edit_path"); edit_path(path); } else { @@ -84,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); @@ -94,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)); } } @@ -143,19 +145,21 @@ void AnimationTreeEditor::enter_editor(const String &p_path) { } void AnimationTreeEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { - ObjectID root; - if (tree && tree->get_tree_root().is_valid()) { - root = tree->get_tree_root()->get_instance_id(); - } + switch (p_what) { + case NOTIFICATION_PROCESS: { + ObjectID root; + if (tree && tree->get_tree_root().is_valid()) { + root = tree->get_tree_root()->get_instance_id(); + } - if (root != current_root) { - edit_path(Vector<String>()); - } + if (root != current_root) { + edit_path(Vector<String>()); + } - if (button_path.size() != edited_path.size()) { - edit_path(edited_path); - } + if (button_path.size() != edited_path.size()) { + edit_path(edited_path); + } + } break; } } @@ -226,8 +230,7 @@ AnimationTreeEditor::AnimationTreeEditor() { AnimationNodeAnimation::get_editable_animation_list = get_animation_list; path_edit = memnew(ScrollContainer); add_child(path_edit); - path_edit->set_enable_h_scroll(true); - path_edit->set_enable_v_scroll(false); + path_edit->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); path_hb = memnew(HBoxContainer); path_edit->add_child(path_hb); path_hb->add_child(memnew(Label(TTR("Path:")))); @@ -258,23 +261,22 @@ void AnimationTreeEditorPlugin::make_visible(bool p_visible) { //editor->hide_animation_player_editors(); //editor->animation_panel_make_visible(true); button->show(); - editor->make_bottom_panel_item_visible(anim_tree_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(anim_tree_editor); anim_tree_editor->set_process(true); } else { if (anim_tree_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } button->hide(); anim_tree_editor->set_process(false); } } -AnimationTreeEditorPlugin::AnimationTreeEditorPlugin(EditorNode *p_node) { - editor = p_node; +AnimationTreeEditorPlugin::AnimationTreeEditorPlugin() { anim_tree_editor = memnew(AnimationTreeEditor); anim_tree_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("AnimationTree"), anim_tree_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("AnimationTree"), anim_tree_editor); button->hide(); } diff --git a/editor/plugins/animation_tree_editor_plugin.h b/editor/plugins/animation_tree_editor_plugin.h index de3d89ae17..a33d97f62f 100644 --- a/editor/plugins/animation_tree_editor_plugin.h +++ b/editor/plugins/animation_tree_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,15 +31,15 @@ #ifndef ANIMATION_TREE_EDITOR_PLUGIN_H #define ANIMATION_TREE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "editor/property_editor.h" #include "scene/animation/animation_tree.h" #include "scene/gui/button.h" #include "scene/gui/graph_edit.h" #include "scene/gui/popup.h" #include "scene/gui/tree.h" +class EditorFileDialog; + class AnimationTreeNodeEditorPlugin : public VBoxContainer { GDCLASS(AnimationTreeNodeEditorPlugin, VBoxContainer); @@ -51,11 +51,11 @@ public: class AnimationTreeEditor : public VBoxContainer { GDCLASS(AnimationTreeEditor, VBoxContainer); - ScrollContainer *path_edit; - HBoxContainer *path_hb; + ScrollContainer *path_edit = nullptr; + HBoxContainer *path_hb = nullptr; - AnimationTree *tree; - MarginContainer *editor_base; + AnimationTree *tree = nullptr; + MarginContainer *editor_base = nullptr; Vector<String> button_path; Vector<String> edited_path; @@ -95,9 +95,8 @@ public: class AnimationTreeEditorPlugin : public EditorPlugin { GDCLASS(AnimationTreeEditorPlugin, EditorPlugin); - AnimationTreeEditor *anim_tree_editor; - EditorNode *editor; - Button *button; + AnimationTreeEditor *anim_tree_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "AnimationTree"; } @@ -106,7 +105,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - AnimationTreeEditorPlugin(EditorNode *p_node); + AnimationTreeEditorPlugin(); ~AnimationTreeEditorPlugin(); }; diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 5405723d10..8ee162d085 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,13 +32,25 @@ #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" #include "editor/editor_node.h" +#include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/project_settings_editor.h" +static inline void setup_http_request(HTTPRequest *request) { + request->set_use_threads(EDITOR_DEF("asset_library/use_threads", true)); + + const String proxy_host = EDITOR_GET("network/http_proxy/host"); + const int proxy_port = EDITOR_GET("network/http_proxy/port"); + request->set_http_proxy(proxy_host, proxy_port); + request->set_https_proxy(proxy_host, proxy_port); +} + void EditorAssetLibraryItem::configure(const String &p_title, int p_asset_id, const String &p_category, int p_category_id, const String &p_author, int p_author_id, const String &p_cost) { title->set_text(p_title); asset_id = p_asset_id; @@ -57,11 +69,13 @@ void EditorAssetLibraryItem::set_image(int p_type, int p_index, const Ref<Textur } void EditorAssetLibraryItem::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - icon->set_normal_texture(get_theme_icon(SNAME("ProjectIconLoading"), SNAME("EditorIcons"))); - category->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); - author->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); - price->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + icon->set_normal_texture(get_theme_icon(SNAME("ProjectIconLoading"), SNAME("EditorIcons"))); + category->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + author->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + price->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); + } break; } } @@ -147,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 { @@ -184,7 +193,8 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const void EditorAssetLibraryItemDescription::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { previews_bg->add_theme_style_override("panel", previews->get_theme_stylebox(SNAME("normal"), SNAME("TextEdit"))); } break; } @@ -230,7 +240,8 @@ void EditorAssetLibraryItemDescription::configure(const String &p_title, int p_a description->add_text(TTR("View Files")); description->pop(); description->add_text("\n" + TTR("Description:") + "\n\n"); - description->append_bbcode(p_description); + description->append_text(p_description); + description->set_selection_enabled(true); set_title(p_title); } @@ -242,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")); @@ -275,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_expand(true); + 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); @@ -288,14 +302,13 @@ EditorAssetLibraryItemDescription::EditorAssetLibraryItemDescription() { previews = memnew(ScrollContainer); previews_bg->add_child(previews); - previews->set_enable_v_scroll(false); - previews->set_enable_h_scroll(true); + previews->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); preview_hb = memnew(HBoxContainer); 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")); } /////////////////////////////////////////////////////////////////////////////////// @@ -344,7 +357,7 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int if (p_code != 200) { error_text = TTR("Request failed, return code:") + " " + itos(p_code); status->set_text(TTR("Failed:") + " " + itos(p_code)); - } else if (sha256 != "") { + } else if (!sha256.is_empty()) { String download_sha256 = FileAccess::get_sha256(download->get_download_file()); if (sha256 != download_sha256) { error_text = TTR("Bad download hash, assuming file has been tampered with.") + "\n"; @@ -355,23 +368,23 @@ void EditorAssetLibraryItemDownload::_http_download_completed(int p_status, int } break; } - if (error_text != String()) { + if (!error_text.is_empty()) { download_error->set_text(TTR("Asset Download Error:") + "\n" + error_text); download_error->popup_centered(); // Let the user retry the download. - retry->show(); + retry_button->show(); return; } - install->set_disabled(false); - status->set_text(TTR("Success!")); + install_button->set_disabled(false); + status->set_text(TTR("Ready to install!")); // Make the progress bar invisible but don't reflow other Controls around it. progress->set_modulate(Color(0, 0, 0, 0)); set_process(false); // Automatically prompt for installation once the download is completed. - _install(); + install(); } void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asset_id, const Ref<Texture2D> &p_preview, const String &p_download_url, const String &p_sha256_hash) { @@ -388,11 +401,13 @@ void EditorAssetLibraryItemDownload::configure(const String &p_title, int p_asse void EditorAssetLibraryItemDownload::_notification(int p_what) { switch (p_what) { - // FIXME: The editor crashes if 'NOTICATION_THEME_CHANGED' is used. - case NOTIFICATION_ENTER_TREE: { - add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("TabContainer"))); - dismiss->set_normal_texture(get_theme_icon(SNAME("Close"), SNAME("EditorIcons"))); + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("AssetLib"))); + status->add_theme_color_override("font_color", get_theme_color(SNAME("status_color"), SNAME("AssetLib"))); + dismiss_button->set_normal_texture(get_theme_icon(SNAME("dismiss"), SNAME("AssetLib"))); } break; + case NOTIFICATION_PROCESS: { // Make the progress bar visible again when retrying the download. progress->set_modulate(Color(1, 1, 1, 1)); @@ -451,7 +466,11 @@ void EditorAssetLibraryItemDownload::_close() { queue_delete(); } -void EditorAssetLibraryItemDownload::_install() { +bool EditorAssetLibraryItemDownload::can_install() const { + return !install_button->is_disabled(); +} + +void EditorAssetLibraryItemDownload::install() { String file = download->get_download_file(); if (external_install) { @@ -465,7 +484,7 @@ void EditorAssetLibraryItemDownload::_install() { void EditorAssetLibraryItemDownload::_make_request() { // Hide the Retry button if we've just pressed it. - retry->hide(); + retry_button->hide(); download->cancel_request(); download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip"); @@ -483,9 +502,14 @@ void EditorAssetLibraryItemDownload::_bind_methods() { } EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { + panel = memnew(PanelContainer); + add_child(panel); + HBoxContainer *hb = memnew(HBoxContainer); - add_child(hb); + panel->add_child(hb); icon = memnew(TextureRect); + icon->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + icon->set_v_size_flags(0); hb->add_child(icon); VBoxContainer *vb = memnew(VBoxContainer); @@ -498,9 +522,9 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { title_hb->add_child(title); title->set_h_size_flags(Control::SIZE_EXPAND_FILL); - dismiss = memnew(TextureButton); - dismiss->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_close)); - title_hb->add_child(dismiss); + dismiss_button = memnew(TextureButton); + dismiss_button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_close)); + title_hb->add_child(dismiss_button); title->set_clip_text(true); @@ -508,7 +532,6 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { status = memnew(Label(TTR("Idle"))); vb->add_child(status); - status->add_theme_color_override("font_color", Color(0.5, 0.5, 0.5)); progress = memnew(ProgressBar); vb->add_child(progress); @@ -516,32 +539,32 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { vb->add_child(hb2); hb2->add_spacer(); - install = memnew(Button); - install->set_text(TTR("Install...")); - install->set_disabled(true); - install->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_install)); + install_button = memnew(Button); + install_button->set_text(TTR("Install...")); + install_button->set_disabled(true); + install_button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::install)); - retry = memnew(Button); - retry->set_text(TTR("Retry")); - retry->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_make_request)); + retry_button = memnew(Button); + retry_button->set_text(TTR("Retry")); + retry_button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDownload::_make_request)); // Only show the Retry button in case of a failure. - retry->hide(); + retry_button->hide(); - hb2->add_child(retry); - hb2->add_child(install); + hb2->add_child(retry_button); + hb2->add_child(install_button); set_custom_minimum_size(Size2(310, 0) * EDSCALE); download = memnew(HTTPRequest); - add_child(download); + panel->add_child(download); download->connect("request_completed", callable_mp(this, &EditorAssetLibraryItemDownload::_http_download_completed)); - download->set_use_threads(EDITOR_DEF("asset_library/use_threads", true)); + setup_http_request(download); download_error = memnew(AcceptDialog); - add_child(download_error); + panel->add_child(download_error); download_error->set_title(TTR("Download Error")); asset_installer = memnew(EditorAssetInstaller); - add_child(asset_installer); + panel->add_child(asset_installer); asset_installer->connect("confirmed", callable_mp(this, &EditorAssetLibraryItemDownload::_close)); prev_status = -1; @@ -553,12 +576,19 @@ EditorAssetLibraryItemDownload::EditorAssetLibraryItemDownload() { void EditorAssetLibrary::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { + add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("AssetLib"))); + error_label->raise(); + } break; + + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { error_tr->set_texture(get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filter->set_clear_button_enabled(true); - - error_label->raise(); + library_scroll_bg->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + downloads_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + error_label->add_theme_color_override("color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { // Focus the search box automatically when switching to the Templates tab (in the Project Manager) @@ -571,6 +601,7 @@ void EditorAssetLibrary::_notification(int p_what) { } } } break; + case NOTIFICATION_PROCESS: { HTTPClient::Status s = request->get_http_client_status(); const bool loading = s != HTTPClient::STATUS_DISCONNECTED; @@ -587,16 +618,14 @@ void EditorAssetLibrary::_notification(int p_what) { } } break; - case NOTIFICATION_THEME_CHANGED: { - library_scroll_bg->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - downloads_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - error_tr->set_texture(get_theme_icon(SNAME("Error"), SNAME("EditorIcons"))); - filter->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filter->set_clear_button_enabled(true); + + case NOTIFICATION_RESIZED: { + _update_asset_items_columns(); } break; case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { _update_repository_options(); + setup_http_request(request); } break; } } @@ -614,13 +643,13 @@ void EditorAssetLibrary::_update_repository_options() { } } -void EditorAssetLibrary::unhandled_key_input(const Ref<InputEvent> &p_event) { +void EditorAssetLibrary::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); const Ref<InputEventKey> key = p_event; if (key.is_valid() && key->is_pressed()) { - if (key->get_keycode_with_modifiers() == (KEY_MASK_CMD | KEY_F) && is_visible_in_tree()) { + if (key->get_keycode_with_modifiers() == (KeyModifierMask::CMD | Key::F) && is_visible_in_tree()) { filter->grab_focus(); filter->select_all(); accept_event(); @@ -631,14 +660,10 @@ void EditorAssetLibrary::unhandled_key_input(const Ref<InputEvent> &p_event) { void EditorAssetLibrary::_install_asset() { ERR_FAIL_COND(!description); - for (int i = 0; i < downloads_hb->get_child_count(); i++) { - EditorAssetLibraryItemDownload *d = Object::cast_to<EditorAssetLibraryItemDownload>(downloads_hb->get_child(i)); - if (d && d->get_asset_id() == description->get_asset_id()) { - if (EditorNode::get_singleton() != nullptr) { - EditorNode::get_singleton()->show_warning(TTR("Download for this asset is already in progress!")); - } - return; - } + EditorAssetLibraryItemDownload *d = _get_asset_in_progress(description->get_asset_id()); + if (d) { + d->install(); + return; } EditorAssetLibraryItemDownload *download = memnew(EditorAssetLibraryItemDownload); @@ -707,9 +732,8 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB if (use_cache) { String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); - FileAccess *file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ); - - if (file) { + Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ); + if (file.is_valid()) { PackedByteArray cached_data; int len = file->get_32(); cached_data.resize(len); @@ -718,8 +742,6 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB file->get_buffer(w, len); image_data = cached_data; - file->close(); - memdelete(file); } } @@ -763,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; @@ -786,23 +806,17 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons if (headers[i].findn("ETag:") == 0) { // Save etag String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); String new_etag = headers[i].substr(headers[i].find(":") + 1, headers[i].length()).strip_edges(); - FileAccess *file; - - file = FileAccess::open(cache_filename_base + ".etag", FileAccess::WRITE); - if (file) { + Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".etag", FileAccess::WRITE); + if (file.is_valid()) { file->store_line(new_etag); - file->close(); - memdelete(file); } int len = p_data.size(); const uint8_t *r = p_data.ptr(); file = FileAccess::open(cache_filename_base + ".data", FileAccess::WRITE); - if (file) { + if (file.is_valid()) { file->store_32(len); file->store_buffer(r, len); - file->close(); - memdelete(file); } break; @@ -830,28 +844,26 @@ void EditorAssetLibrary::_update_image_queue() { int current_images = 0; List<int> to_delete; - for (Map<int, ImageQueue>::Element *E = image_queue.front(); E; E = E->next()) { - if (!E->get().active && current_images < max_images) { - String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text()); + for (KeyValue<int, ImageQueue> &E : image_queue) { + if (!E.value.active && current_images < max_images) { + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E.value.image_url.md5_text()); Vector<String> headers; if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) { - FileAccess *file = FileAccess::open(cache_filename_base + ".etag", FileAccess::READ); - if (file) { + Ref<FileAccess> file = FileAccess::open(cache_filename_base + ".etag", FileAccess::READ); + if (file.is_valid()) { headers.push_back("If-None-Match: " + file->get_line()); - file->close(); - memdelete(file); } } - Error err = E->get().request->request(E->get().image_url, headers); + Error err = E.value.request->request(E.value.image_url, headers); if (err != OK) { - to_delete.push_back(E->key()); + to_delete.push_back(E.key); } else { - E->get().active = true; + E.value.active = true; } current_images++; - } else if (E->get().active) { + } else if (E.value.active) { current_images++; } } @@ -869,13 +881,13 @@ void EditorAssetLibrary::_request_image(ObjectID p_for, String p_image_url, Imag iq.image_index = p_image_index; iq.image_type = p_type; iq.request = memnew(HTTPRequest); - iq.request->set_use_threads(EDITOR_DEF("asset_library/use_threads", true)); + setup_http_request(iq.request); iq.target = p_for; 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; @@ -886,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"); @@ -922,7 +947,7 @@ void EditorAssetLibrary::_search(int p_page) { support_list += String(support_key[i]) + "+"; } } - if (support_list != String()) { + if (!support_list.is_empty()) { args += "&support=" + support_list.substr(0, support_list.length() - 1); } @@ -935,7 +960,7 @@ void EditorAssetLibrary::_search(int p_page) { args += "&reverse=true"; } - if (filter->get_text() != String()) { + if (!filter->get_text().is_empty()) { args += "&filter=" + filter->get_text().uri_encode(); } @@ -954,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); @@ -975,9 +1004,9 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int hbc->add_theme_constant_override("separation", 5 * EDSCALE); Button *first = memnew(Button); - first->set_text(TTR("First")); + first->set_text(TTR("First", "Pagination")); if (p_page != 0) { - first->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(0)); + first->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(0)); } else { first->set_disabled(true); first->set_focus_mode(Control::FOCUS_NONE); @@ -985,9 +1014,9 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int hbc->add_child(first); Button *prev = memnew(Button); - prev->set_text(TTR("Previous")); + prev->set_text(TTR("Previous", "Pagination")); if (p_page > 0) { - prev->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(p_page - 1)); + prev->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(p_page - 1)); } else { prev->set_disabled(true); prev->set_focus_mode(Control::FOCUS_NONE); @@ -1008,16 +1037,16 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int Button *current = memnew(Button); // Add padding to make page number buttons easier to click. current->set_text(vformat(" %d ", i + 1)); - current->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(i)); + current->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(i)); hbc->add_child(current); } } Button *next = memnew(Button); - next->set_text(TTR("Next")); + next->set_text(TTR("Next", "Pagination")); if (p_page < p_page_count - 1) { - next->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(p_page + 1)); + next->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(p_page + 1)); } else { next->set_disabled(true); next->set_focus_mode(Control::FOCUS_NONE); @@ -1026,9 +1055,9 @@ HBoxContainer *EditorAssetLibrary::_make_pages(int p_page, int p_page_count, int hbc->add_child(next); Button *last = memnew(Button); - last->set_text(TTR("Last")); + last->set_text(TTR("Last", "Pagination")); if (p_page != p_page_count - 1) { - last->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search), varray(p_page_count - 1)); + last->connect("pressed", callable_mp(this, &EditorAssetLibrary::_search).bind(p_page_count - 1)); } else { last->set_disabled(true); last->set_focus_mode(Control::FOCUS_NONE); @@ -1095,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; } @@ -1124,22 +1157,21 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const String name = cat["name"]; int id = cat["id"]; categories->add_item(name); - categories->set_item_metadata(categories->get_item_count() - 1, id); + categories->set_item_metadata(-1, id); category_map[cat["id"]] = name; } } + filter->set_editable(true); + sort->set_disabled(false); + categories->set_disabled(false); + support->set_disabled(false); + _search(); } break; case REQUESTING_SEARCH: { initial_loading = false; - // The loading text only needs to be displayed before the first page is loaded. - // Therefore, we don't need to show it again. - library_loading->hide(); - - library_error->hide(); - if (asset_items) { memdelete(asset_items); } @@ -1178,9 +1210,9 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); - asset_items->set_columns(2); - asset_items->add_theme_constant_override("hseparation", 10 * EDSCALE); - asset_items->add_theme_constant_override("vseparation", 10 * EDSCALE); + _update_asset_items_columns(); + asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); + asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); library_vb->add_child(asset_items); @@ -1188,17 +1220,19 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const library_vb->add_child(asset_bottom_page); if (result.is_empty()) { - if (filter->get_text() != String()) { - library_error->set_text( + if (!filter->get_text().is_empty()) { + 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++) { @@ -1219,7 +1253,7 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const item->connect("author_selected", callable_mp(this, &EditorAssetLibrary::_select_author)); item->connect("category_selected", callable_mp(this, &EditorAssetLibrary::_select_category)); - if (r.has("icon_url") && r["icon_url"] != "") { + if (r.has("icon_url") && !r["icon_url"].operator String().is_empty()) { _request_image(item->get_instance_id(), r["icon_url"], IMAGE_QUEUE_ICON, 0); } } @@ -1256,7 +1290,21 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const description->configure(r["title"], r["asset_id"], category_map[r["category_id"]], r["category_id"], r["author"], r["author_id"], r["cost"], r["version"], r["version_string"], r["description"], r["download_url"], r["browse_url"], r["download_hash"]); - if (r.has("icon_url") && r["icon_url"] != "") { + EditorAssetLibraryItemDownload *download_item = _get_asset_in_progress(description->get_asset_id()); + if (download_item) { + if (download_item->can_install()) { + description->set_ok_button_text(TTR("Install")); + description->get_ok_button()->set_disabled(false); + } else { + description->set_ok_button_text(TTR("Downloading...")); + description->get_ok_button()->set_disabled(true); + } + } else { + description->set_ok_button_text(TTR("Download")); + description->get_ok_button()->set_disabled(false); + } + + if (r.has("icon_url") && !r["icon_url"].operator String().is_empty()) { _request_image(description->get_instance_id(), r["icon_url"], IMAGE_QUEUE_ICON, 0); } @@ -1313,10 +1361,30 @@ void EditorAssetLibrary::_manage_plugins() { ProjectSettingsEditor::get_singleton()->set_plugins_page(); } +EditorAssetLibraryItemDownload *EditorAssetLibrary::_get_asset_in_progress(int p_asset_id) const { + for (int i = 0; i < downloads_hb->get_child_count(); i++) { + EditorAssetLibraryItemDownload *d = Object::cast_to<EditorAssetLibraryItemDownload>(downloads_hb->get_child(i)); + if (d && d->get_asset_id() == p_asset_id) { + return d; + } + } + + return nullptr; +} + void EditorAssetLibrary::_install_external_asset(String p_zip_path, String p_title) { emit_signal(SNAME("install_asset"), p_zip_path, p_title); } +void EditorAssetLibrary::_update_asset_items_columns() { + int new_columns = get_size().x / (450.0 * EDSCALE); + new_columns = MAX(1, new_columns); + + if (new_columns != asset_items->get_columns()) { + asset_items->set_columns(new_columns); + } +} + void EditorAssetLibrary::disable_community_support() { support->get_popup()->set_item_checked(SUPPORT_COMMUNITY, false); } @@ -1331,7 +1399,6 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { initial_loading = true; VBoxContainer *library_main = memnew(VBoxContainer); - add_child(library_main); HBoxContainer *search_hb = memnew(HBoxContainer); @@ -1341,10 +1408,11 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { filter = memnew(LineEdit); if (templates_only) { - filter->set_placeholder(TTR("Search templates, projects, and demos")); + filter->set_placeholder(TTR("Search Templates, Projects, and Demos")); } else { - filter->set_placeholder(TTR("Search assets (excluding templates, projects, and demos)")); + filter->set_placeholder(TTR("Search Assets (Excluding Templates, Projects, and Demos)")); } + filter->set_clear_button_enabled(true); search_hb->add_child(filter); filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); filter->connect("text_changed", callable_mp(this, &EditorAssetLibrary::_search_text_changed)); @@ -1388,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)); @@ -1397,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)); @@ -1410,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)); @@ -1431,8 +1502,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_scroll_bg->set_v_size_flags(Control::SIZE_EXPAND_FILL); library_scroll = memnew(ScrollContainer); - library_scroll->set_enable_v_scroll(true); - library_scroll->set_enable_h_scroll(false); + library_scroll->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); library_scroll_bg->add_child(library_scroll); @@ -1453,22 +1523,30 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_vb_border->add_child(library_vb); - library_loading = memnew(Label(TTR("Loading..."))); - library_loading->set_align(Label::ALIGN_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_align(Label::ALIGN_CENTER); + library_error = memnew(VBoxContainer); library_error->hide(); library_vb->add_child(library_error); + library_error_label = memnew(Label(TTR("Failed to get repository configuration."))); + library_error_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + library_error->add_child(library_error_label); + + library_error_retry = memnew(Button(TTR("Retry"))); + library_error_retry->set_h_size_flags(SIZE_SHRINK_CENTER); + library_error_retry->connect("pressed", callable_mp(this, &EditorAssetLibrary::_request_current_config)); + library_error->add_child(library_error_retry); + asset_top_page = memnew(HBoxContainer); library_vb->add_child(asset_top_page); asset_items = memnew(GridContainer); - asset_items->set_columns(2); - asset_items->add_theme_constant_override("hseparation", 10 * EDSCALE); - asset_items->add_theme_constant_override("vseparation", 10 * EDSCALE); + _update_asset_items_columns(); + asset_items->add_theme_constant_override("h_separation", 10 * EDSCALE); + asset_items->add_theme_constant_override("v_separation", 10 * EDSCALE); library_vb->add_child(asset_items); @@ -1477,7 +1555,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { request = memnew(HTTPRequest); add_child(request); - request->set_use_threads(EDITOR_DEF("asset_library/use_threads", true)); + setup_http_request(request); request->connect("request_completed", callable_mp(this, &EditorAssetLibrary::_http_request_completed)); last_queue_id = 0; @@ -1487,7 +1565,6 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { error_hb = memnew(HBoxContainer); library_main->add_child(error_hb); error_label = memnew(Label); - error_label->add_theme_color_override("color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_hb->add_child(error_label); error_tr = memnew(TextureRect); error_tr->set_v_size_flags(Control::SIZE_SHRINK_CENTER); @@ -1496,11 +1573,10 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { description = nullptr; set_process(true); - set_process_unhandled_key_input(true); // Global shortcuts since there is no main element to be focused. + set_process_shortcut_input(true); // Global shortcuts since there is no main element to be focused. downloads_scroll = memnew(ScrollContainer); - downloads_scroll->set_enable_h_scroll(true); - downloads_scroll->set_enable_v_scroll(false); + downloads_scroll->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); library_main->add_child(downloads_scroll); downloads_hb = memnew(HBoxContainer); downloads_scroll->add_child(downloads_hb); @@ -1508,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)); @@ -1518,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(); @@ -1526,12 +1612,11 @@ void AssetLibraryEditorPlugin::make_visible(bool p_visible) { } } -AssetLibraryEditorPlugin::AssetLibraryEditorPlugin(EditorNode *p_node) { - editor = p_node; +AssetLibraryEditorPlugin::AssetLibraryEditorPlugin() { addon_library = memnew(EditorAssetLibrary); addon_library->set_v_size_flags(Control::SIZE_EXPAND_FILL); - editor->get_main_control()->add_child(addon_library); - addon_library->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + EditorNode::get_singleton()->get_main_control()->add_child(addon_library); + addon_library->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); addon_library->hide(); } diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index 286546f962..070d25e29f 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,6 +39,7 @@ #include "scene/gui/grid_container.h" #include "scene/gui/line_edit.h" #include "scene/gui/link_button.h" +#include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/panel_container.h" #include "scene/gui/progress_bar.h" @@ -52,16 +53,16 @@ class EditorAssetLibraryItem : public PanelContainer { GDCLASS(EditorAssetLibraryItem, PanelContainer); - TextureButton *icon; - LinkButton *title; - LinkButton *category; - LinkButton *author; + TextureButton *icon = nullptr; + LinkButton *title = nullptr; + LinkButton *category = nullptr; + LinkButton *author = nullptr; TextureRect *stars[5]; - Label *price; + Label *price = nullptr; - int asset_id; - int category_id; - int author_id; + int asset_id = 0; + int category_id = 0; + int author_id = 0; void _asset_clicked(); void _category_clicked(); @@ -82,11 +83,11 @@ public: class EditorAssetLibraryItemDescription : public ConfirmationDialog { GDCLASS(EditorAssetLibraryItemDescription, ConfirmationDialog); - EditorAssetLibraryItem *item; - RichTextLabel *description; - ScrollContainer *previews; - HBoxContainer *preview_hb; - PanelContainer *previews_bg; + EditorAssetLibraryItem *item = nullptr; + RichTextLabel *description = nullptr; + ScrollContainer *previews = nullptr; + HBoxContainer *preview_hb = nullptr; + PanelContainer *previews_bg = nullptr; struct Preview { int id = 0; @@ -97,11 +98,11 @@ class EditorAssetLibraryItemDescription : public ConfirmationDialog { }; Vector<Preview> preview_images; - TextureRect *preview; + TextureRect *preview = nullptr; void set_image(int p_type, int p_index, const Ref<Texture2D> &p_image); - int asset_id; + int asset_id = 0; String download_url; String title; String sha256; @@ -126,32 +127,32 @@ public: EditorAssetLibraryItemDescription(); }; -class EditorAssetLibraryItemDownload : public PanelContainer { - GDCLASS(EditorAssetLibraryItemDownload, PanelContainer); +class EditorAssetLibraryItemDownload : public MarginContainer { + GDCLASS(EditorAssetLibraryItemDownload, MarginContainer); - TextureRect *icon; - Label *title; - ProgressBar *progress; - Button *install; - Button *retry; - TextureButton *dismiss; + PanelContainer *panel = nullptr; + TextureRect *icon = nullptr; + Label *title = nullptr; + ProgressBar *progress = nullptr; + Button *install_button = nullptr; + Button *retry_button = nullptr; + TextureButton *dismiss_button = nullptr; - AcceptDialog *download_error; - HTTPRequest *download; + AcceptDialog *download_error = nullptr; + HTTPRequest *download = nullptr; String host; String sha256; - Label *status; + Label *status = nullptr; int prev_status; - int asset_id; + int asset_id = 0; bool external_install; - EditorAssetInstaller *asset_installer; + EditorAssetInstaller *asset_installer = nullptr; void _close(); - void _install(); void _make_request(); void _http_download_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data); @@ -163,6 +164,10 @@ public: void set_external_install(bool p_enable) { external_install = p_enable; } int get_asset_id() { return asset_id; } void configure(const String &p_title, int p_asset_id, const Ref<Texture2D> &p_preview, const String &p_download_url, const String &p_sha256_hash); + + bool can_install() const; + void install(); + EditorAssetLibraryItemDownload(); }; @@ -171,35 +176,37 @@ class EditorAssetLibrary : public PanelContainer { String host; - EditorFileDialog *asset_open; - EditorAssetInstaller *asset_installer; + EditorFileDialog *asset_open = nullptr; + EditorAssetInstaller *asset_installer = nullptr; void _asset_open(); void _asset_file_selected(const String &p_file); void _update_repository_options(); - PanelContainer *library_scroll_bg; - ScrollContainer *library_scroll; - VBoxContainer *library_vb; - Label *library_loading; - Label *library_error; - LineEdit *filter; - Timer *filter_debounce_timer; - OptionButton *categories; - OptionButton *repository; - OptionButton *sort; - HBoxContainer *error_hb; - TextureRect *error_tr; - Label *error_label; - MenuButton *support; - - HBoxContainer *contents; - - HBoxContainer *asset_top_page; - GridContainer *asset_items; - HBoxContainer *asset_bottom_page; - - HTTPRequest *request; + PanelContainer *library_scroll_bg = nullptr; + ScrollContainer *library_scroll = nullptr; + VBoxContainer *library_vb = nullptr; + Label *library_info = nullptr; + VBoxContainer *library_error = nullptr; + Label *library_error_label = nullptr; + Button *library_error_retry = nullptr; + LineEdit *filter = nullptr; + Timer *filter_debounce_timer = nullptr; + OptionButton *categories = nullptr; + OptionButton *repository = nullptr; + OptionButton *sort = nullptr; + HBoxContainer *error_hb = nullptr; + TextureRect *error_tr = nullptr; + Label *error_label = nullptr; + MenuButton *support = nullptr; + + HBoxContainer *contents = nullptr; + + HBoxContainer *asset_top_page = nullptr; + GridContainer *asset_items = nullptr; + HBoxContainer *asset_bottom_page = nullptr; + + HTTPRequest *request = nullptr; bool templates_only; bool initial_loading; @@ -245,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); @@ -255,7 +262,7 @@ class EditorAssetLibrary : public PanelContainer { HBoxContainer *_make_pages(int p_page, int p_page_count, int p_page_len, int p_total_items, int p_current_items); // - EditorAssetLibraryItemDescription *description; + EditorAssetLibraryItemDescription *description = nullptr; // enum RequestType { @@ -268,8 +275,8 @@ class EditorAssetLibrary : public PanelContainer { RequestType requesting; Dictionary category_map; - ScrollContainer *downloads_scroll; - HBoxContainer *downloads_hb; + ScrollContainer *downloads_scroll = nullptr; + HBoxContainer *downloads_hb = nullptr; void _install_asset(); @@ -285,21 +292,24 @@ class EditorAssetLibrary : public PanelContainer { void _search_text_submitted(const String &p_text = ""); 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 _http_download_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data); void _filter_debounce_timer_timeout(); + void _request_current_config(); + EditorAssetLibraryItemDownload *_get_asset_in_progress(int p_asset_id) const; void _repository_changed(int p_repository_id); void _support_toggled(int p_support); void _install_external_asset(String p_zip_path, String p_title); + void _update_asset_items_columns(); + friend class EditorAssetLibraryItemDescription; friend class EditorAssetLibraryItem; protected: static void _bind_methods(); void _notification(int p_what); - virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; public: void disable_community_support(); @@ -310,10 +320,11 @@ public: class AssetLibraryEditorPlugin : public EditorPlugin { GDCLASS(AssetLibraryEditorPlugin, EditorPlugin); - EditorAssetLibrary *addon_library; - EditorNode *editor; + EditorAssetLibrary *addon_library = nullptr; public: + static bool is_available(); + virtual String get_name() const override { return "AssetLib"; } bool has_main_screen() const override { return true; } virtual void edit(Object *p_object) override {} @@ -323,8 +334,8 @@ public: //virtual Dictionary get_state() const; //virtual void set_state(const Dictionary& p_state); - AssetLibraryEditorPlugin(EditorNode *p_node); + AssetLibraryEditorPlugin(); ~AssetLibraryEditorPlugin(); }; -#endif // EDITORASSETLIBRARY_H +#endif // ASSET_LIBRARY_EDITOR_PLUGIN_H diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp deleted file mode 100644 index 482c08f50a..0000000000 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ /dev/null @@ -1,282 +0,0 @@ -/*************************************************************************/ -/* audio_stream_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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_scale.h" -#include "editor/editor_settings.h" - -void AudioStreamEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - AudioStreamPreviewGenerator::get_singleton()->connect("preview_updated", callable_mp(this, &AudioStreamEditor::_preview_changed)); - } - - if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - _play_button->set_icon(get_theme_icon(SNAME("MainPlay"), SNAME("EditorIcons"))); - _stop_button->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons"))); - _preview->set_color(get_theme_color(SNAME("dark_color_2"), SNAME("Editor"))); - set_color(get_theme_color(SNAME("dark_color_1"), SNAME("Editor"))); - - _indicator->update(); - _preview->update(); - } - - if (p_what == NOTIFICATION_PROCESS) { - _current = _player->get_playback_position(); - _indicator->update(); - } - - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible_in_tree()) { - _stop(); - } - } -} - -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() == MOUSE_BUTTON_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_align(Label::ALIGN_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(EditorNode *p_node) { - editor = p_node; - 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 new file mode 100644 index 0000000000..9e551ae0ed --- /dev/null +++ b/editor/plugins/audio_stream_randomizer_editor_plugin.cpp @@ -0,0 +1,121 @@ +/*************************************************************************/ +/* audio_stream_randomizer_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_randomizer_editor_plugin.h" + +#include "editor/editor_node.h" + +void AudioStreamRandomizerEditorPlugin::edit(Object *p_object) { +} + +bool AudioStreamRandomizerEditorPlugin::handles(Object *p_object) const { + return false; +} + +void AudioStreamRandomizerEditorPlugin::make_visible(bool p_visible) { +} + +void AudioStreamRandomizerEditorPlugin::_move_stream_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos) { + UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + + AudioStreamRandomizer *randomizer = Object::cast_to<AudioStreamRandomizer>(p_edited); + if (!randomizer) { + return; + } + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "stream_") { + end = randomizer->get_streams_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for AudioStreamRandomizer."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + if (p_from_index < 0) { + undo_redo->add_undo_method(randomizer, "remove_stream", p_to_pos < 0 ? randomizer->get_streams_count() : p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_undo_method(randomizer, "add_stream", p_from_index); + } + + List<PropertyInfo> properties; + randomizer->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (str[to_char_index] < '0' || str[to_char_index] > '9') { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(randomizer, pi.name); + } + } + } + } +#undef ADD_UNDO + + if (p_from_index < 0) { + undo_redo->add_do_method(randomizer, "add_stream", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(randomizer, "remove_stream", p_from_index); + } else { + undo_redo->add_do_method(randomizer, "move_stream", p_from_index, p_to_pos); + } +} + +AudioStreamRandomizerEditorPlugin::AudioStreamRandomizerEditorPlugin() { + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("AudioStreamRandomizer"), callable_mp(this, &AudioStreamRandomizerEditorPlugin::_move_stream_array_element)); +} + +AudioStreamRandomizerEditorPlugin::~AudioStreamRandomizerEditorPlugin() {} diff --git a/editor/plugins/audio_stream_randomizer_editor_plugin.h b/editor/plugins/audio_stream_randomizer_editor_plugin.h new file mode 100644 index 0000000000..7e509dc670 --- /dev/null +++ b/editor/plugins/audio_stream_randomizer_editor_plugin.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* audio_stream_randomizer_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 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 AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H +#define AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "servers/audio/audio_stream.h" + +class AudioStreamRandomizerEditorPlugin : public EditorPlugin { + GDCLASS(AudioStreamRandomizerEditorPlugin, EditorPlugin); + +private: + void _move_stream_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); + +public: + virtual String get_name() const override { return "AudioStreamRandomizer"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + AudioStreamRandomizerEditorPlugin(); + ~AudioStreamRandomizerEditorPlugin(); +}; + +#endif // AUDIO_STREAM_RANDOMIZER_EDITOR_PLUGIN_H diff --git a/editor/plugins/bit_map_editor_plugin.cpp b/editor/plugins/bit_map_editor_plugin.cpp new file mode 100644 index 0000000000..657c5a36b6 --- /dev/null +++ b/editor/plugins/bit_map_editor_plugin.cpp @@ -0,0 +1,82 @@ +/*************************************************************************/ +/* bit_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 "bit_map_editor_plugin.h" + +#include "editor/editor_scale.h" + +void BitMapEditor::setup(const Ref<BitMap> &p_bitmap) { + texture_rect->set_texture(ImageTexture::create_from_image(p_bitmap->convert_to_image())); + size_label->set_text(vformat(String::utf8("%s×%s"), p_bitmap->get_size().width, p_bitmap->get_size().height)); +} + +BitMapEditor::BitMapEditor() { + texture_rect = memnew(TextureRect); + texture_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + texture_rect->set_texture_filter(TEXTURE_FILTER_NEAREST); + texture_rect->set_custom_minimum_size(Size2(0, 250) * EDSCALE); + add_child(texture_rect); + + size_label = memnew(Label); + size_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); + add_child(size_label); + + // Reduce extra padding on top and bottom of size label. + Ref<StyleBoxEmpty> stylebox; + stylebox.instantiate(); + stylebox->set_default_margin(SIDE_RIGHT, 4 * EDSCALE); + size_label->add_theme_style_override("normal", stylebox); +} + +/////////////////////// + +bool EditorInspectorPluginBitMap::can_handle(Object *p_object) { + return Object::cast_to<BitMap>(p_object) != nullptr; +} + +void EditorInspectorPluginBitMap::parse_begin(Object *p_object) { + BitMap *bitmap = Object::cast_to<BitMap>(p_object); + if (!bitmap) { + return; + } + Ref<BitMap> bm(bitmap); + + BitMapEditor *editor = memnew(BitMapEditor); + editor->setup(bm); + add_custom_control(editor); +} + +/////////////////////// + +BitMapEditorPlugin::BitMapEditorPlugin() { + Ref<EditorInspectorPluginBitMap> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/font_editor_plugin.h b/editor/plugins/bit_map_editor_plugin.h index 3530815872..b045f8c751 100644 --- a/editor/plugins/font_editor_plugin.h +++ b/editor/plugins/bit_map_editor_plugin.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* font_editor_plugin.h */ +/* bit_map_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,51 +28,37 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef FONT_EDITOR_PLUGIN_H -#define FONT_EDITOR_PLUGIN_H +#ifndef BIT_MAP_EDITOR_PLUGIN_H +#define BIT_MAP_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "scene/resources/font.h" -#include "scene/resources/text_line.h" +#include "scene/resources/bit_map.h" -class FontDataPreview : public Control { - GDCLASS(FontDataPreview, Control); +class BitMapEditor : public VBoxContainer { + GDCLASS(BitMapEditor, VBoxContainer); -protected: - void _notification(int p_what); - static void _bind_methods(); - - Ref<TextLine> line; + TextureRect *texture_rect = nullptr; + Label *size_label = nullptr; public: - virtual Size2 get_minimum_size() const override; - - void set_data(const Ref<FontData> &p_data); + void setup(const Ref<BitMap> &p_bitmap); - FontDataPreview(); + BitMapEditor(); }; -/*************************************************************************/ - -class EditorInspectorPluginFont : public EditorInspectorPlugin { - GDCLASS(EditorInspectorPluginFont, EditorInspectorPlugin); +class EditorInspectorPluginBitMap : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginBitMap, 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); +class BitMapEditorPlugin : public EditorPlugin { + GDCLASS(BitMapEditorPlugin, EditorPlugin); public: - FontEditorPlugin(EditorNode *p_node); - - virtual String get_name() const override { return "Font"; } + BitMapEditorPlugin(); }; -#endif // FONT_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..70775c1ee2 --- /dev/null +++ b/editor/plugins/bone_map_editor_plugin.cpp @@ -0,0 +1,499 @@ +/*************************************************************************/ +/* 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); + + 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() { + skeleton_bone_selector = memnew(EditorPropertyTextEnum); + skeleton_bone_selector->setup(skeleton_bone_names, false, true); + skeleton_bone_selector->set_label(profile_bone_name); + skeleton_bone_selector->set_selectable(false); + 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)); + add_child(skeleton_bone_selector); +} + +void BoneMapperItem::_update_property() { + if (skeleton_bone_selector->get_edited_object() && skeleton_bone_selector->get_edited_property()) { + skeleton_bone_selector->update_property(); + } +} + +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() { +} + +BoneMapperItem::BoneMapperItem(Ref<BoneMap> &p_bone_map, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name) { + bone_map = p_bone_map; + skeleton_bone_names = p_skeleton_bone_names; + profile_bone_name = p_profile_bone_name; +} + +BoneMapperItem::~BoneMapperItem() { +} + +void BoneMapper::create_editor() { + profile_group_selector = memnew(EditorPropertyEnum); + profile_group_selector->set_label("Group"); + profile_group_selector->set_selectable(false); + 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)); + add_child(profile_group_selector); + + 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); + + separator = memnew(HSeparator); + add_child(separator); + + 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::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++) { + 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()) { + PackedStringArray skeleton_bone_names; + int len = skeleton->get_bone_count(); + for (int i = 0; i < len; i++) { + skeleton_bone_names.push_back(skeleton->get_bone_name(i)); + } + + 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, skeleton_bone_names, bn))); + 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::_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::_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: { + if (bone_mapper) { + remove_child(bone_mapper); + bone_mapper->queue_delete(); + } + 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..339547ea10 --- /dev/null +++ b/editor/plugins/bone_map_editor_plugin.h @@ -0,0 +1,180 @@ +/*************************************************************************/ +/* 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 "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; + + PackedStringArray skeleton_bone_names; + Ref<BoneMap> bone_map; + + EditorPropertyTextEnum *skeleton_bone_selector; + + void _update_property(); + +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, PackedStringArray p_skeleton_bone_names, const StringName &p_profile_bone_name = StringName()); + ~BoneMapperItem(); +}; + +class BoneMapper : public VBoxContainer { + GDCLASS(BoneMapper, VBoxContainer); + + Skeleton3D *skeleton; + Ref<BoneMap> bone_map; + + Vector<BoneMapperItem *> bone_mapper_items; + + VBoxContainer *mapper_item_vbox; + HSeparator *separator; + + 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(); + +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); + +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.cpp b/editor/plugins/camera_3d_editor_plugin.cpp index 8583e95b25..141837244a 100644 --- a/editor/plugins/camera_3d_editor_plugin.cpp +++ b/editor/plugins/camera_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,7 @@ #include "camera_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "node_3d_editor_plugin.h" void Camera3DEditor::_node_removed(Node *p_node) { @@ -95,10 +96,9 @@ void Camera3DEditorPlugin::make_visible(bool p_visible) { } } -Camera3DEditorPlugin::Camera3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +Camera3DEditorPlugin::Camera3DEditorPlugin() { /* camera_editor = memnew( CameraEditor ); - editor->get_main_control()->add_child(camera_editor); + EditorNode::get_singleton()->get_main_control()->add_child(camera_editor); camera_editor->set_anchor(SIDE_LEFT,Control::ANCHOR_END); camera_editor->set_anchor(SIDE_RIGHT,Control::ANCHOR_END); diff --git a/editor/plugins/camera_3d_editor_plugin.h b/editor/plugins/camera_3d_editor_plugin.h index e087dd22a8..a969b31976 100644 --- a/editor/plugins/camera_3d_editor_plugin.h +++ b/editor/plugins/camera_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,19 +28,18 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CAMERA_EDITOR_PLUGIN_H -#define CAMERA_EDITOR_PLUGIN_H +#ifndef CAMERA_3D_EDITOR_PLUGIN_H +#define CAMERA_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/camera_3d.h" class Camera3DEditor : public Control { GDCLASS(Camera3DEditor, Control); - Panel *panel; - Button *preview; - Node *node; + Panel *panel = nullptr; + Button *preview = nullptr; + Node *node = nullptr; void _pressed(); @@ -57,7 +56,6 @@ class Camera3DEditorPlugin : public EditorPlugin { GDCLASS(Camera3DEditorPlugin, EditorPlugin); //CameraEditor *camera_editor; - EditorNode *editor; public: virtual String get_name() const override { return "Camera3D"; } @@ -66,8 +64,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Camera3DEditorPlugin(EditorNode *p_node); + Camera3DEditorPlugin(); ~Camera3DEditorPlugin(); }; -#endif // CAMERA_EDITOR_PLUGIN_H +#endif // CAMERA_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index d96cc1cd18..fc70ace331 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -39,8 +39,10 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/editor_toaster.h" #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/script_editor_plugin.h" +#include "editor/scene_tree_dock.h" #include "scene/2d/cpu_particles_2d.h" #include "scene/2d/gpu_particles_2d.h" #include "scene/2d/light_2d.h" @@ -48,21 +50,24 @@ #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" #include "scene/main/window.h" #include "scene/resources/packed_scene.h" // Min and Max are power of two in order to play nicely with successive increment. // That way, we can naturally reach a 100% zoom from boundaries. -#define MIN_ZOOM 1. / 128 -#define MAX_ZOOM 128 +constexpr real_t MIN_ZOOM = 1. / 128; +constexpr real_t MAX_ZOOM = 128; #define RULER_WIDTH (15 * EDSCALE) -#define SCALE_HANDLE_DISTANCE 25 -#define MOVE_HANDLE_DISTANCE 25 +constexpr real_t SCALE_HANDLE_DISTANCE = 25; +constexpr real_t MOVE_HANDLE_DISTANCE = 25; class SnapDialog : public ConfirmationDialog { GDCLASS(SnapDialog, ConfirmationDialog); @@ -233,7 +238,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) { @@ -242,7 +247,7 @@ bool CanvasItemEditor::_is_node_movable(const Node *p_node, bool p_popup_warning } if (Object::cast_to<Control>(p_node) && Object::cast_to<Container>(p_node->get_parent())) { if (p_popup_warning) { - _popup_warning_temporarily(warning_child_of_container, 3.0); + EditorToaster::get_singleton()->popup_str("Children of a container get their position and size determined only by their parent.", EditorToaster::SEVERITY_WARNING); } return false; } @@ -333,7 +338,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CTRL); + bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(Key::CTRL); // Smart snap using the canvas position Vector2 output = p_target; @@ -387,7 +392,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig // Self center if ((is_snap_active && snap_node_center && (p_modes & SNAP_NODE_CENTER)) || (p_forced_modes & SNAP_NODE_CENTER)) { if (p_self_canvas_item->_edit_use_rect()) { - Point2 center = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_position() + p_self_canvas_item->_edit_get_rect().get_size() / 2.0); + Point2 center = p_self_canvas_item->get_global_transform_with_canvas().xform(p_self_canvas_item->_edit_get_rect().get_center()); _snap_if_closer_point(p_target, output, snap_target, center, SNAP_TARGET_SELF, rotation); } else { Point2 position = p_self_canvas_item->get_global_transform_with_canvas().xform(Point2()); @@ -417,16 +422,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); } @@ -461,7 +464,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig } real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const { - if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) && snap_rotation_step != 0) { + if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(Key::CTRL)) && snap_rotation_step != 0) { if (snap_relative) { return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step); } else { @@ -472,7 +475,7 @@ real_t CanvasItemEditor::snap_angle(real_t p_target, real_t p_start) const { } } -void CanvasItemEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { +void CanvasItemEditor::shortcut_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; @@ -482,16 +485,16 @@ void CanvasItemEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { } if (k.is_valid()) { - if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT) { + if (k->get_keycode() == Key::CTRL || k->get_keycode() == Key::ALT || k->get_keycode() == Key::SHIFT) { viewport->update(); } - if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo()) { - if ((grid_snap_active || show_grid) && multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) { + if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo() && (grid_snap_active || _is_grid_visible())) { + if (multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->matches_event(p_ev)) { // Multiply the grid size grid_step_multiplier = MIN(grid_step_multiplier + 1, 12); viewport->update(); - } else if ((grid_snap_active || show_grid) && divide_grid_step_shortcut.is_valid() && divide_grid_step_shortcut->matches_event(p_ev)) { + } else if (divide_grid_step_shortcut.is_valid() && divide_grid_step_shortcut->matches_event(p_ev)) { // Divide the grid size Point2 new_grid_step = grid_step * Math::pow(2.0, grid_step_multiplier - 1); if (new_grid_step.x >= 1.0 && new_grid_step.y >= 1.0) { @@ -513,7 +516,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) { } void CanvasItemEditor::_keying_changed() { - if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree()) { + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->is_visible_in_tree()) { animation_hb->show(); } else { animation_hb->hide(); @@ -525,7 +528,7 @@ Rect2 CanvasItemEditor::_get_encompassing_rect_from_list(List<CanvasItem *> p_li // Handles the first element CanvasItem *canvas_item = p_list.front()->get(); - Rect2 rect = Rect2(canvas_item->get_global_transform_with_canvas().xform(canvas_item->_edit_get_rect().position + canvas_item->_edit_get_rect().size / 2), Size2()); + Rect2 rect = Rect2(canvas_item->get_global_transform_with_canvas().xform(canvas_item->_edit_get_rect().get_center()), Size2()); // Expand with the other ones for (CanvasItem *canvas_item2 : p_list) { @@ -561,10 +564,14 @@ 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.position + rect.size / 2), Size2()); + r_rect = Rect2(xform.xform(rect.get_center()), Size2()); r_first = false; } r_rect.expand_to(xform.xform(rect.position)); @@ -607,7 +614,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); @@ -622,7 +633,7 @@ void CanvasItemEditor::_find_canvas_items_at_pos(const Point2 &p_pos, Node *p_no } void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked) { - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); _find_canvas_items_at_pos(p_pos, scene, r_items); @@ -658,7 +669,7 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel //Remove the item if invalid if (!canvas_item || duplicate || (canvas_item != scene && canvas_item->get_owner() != scene && !scene->is_editable_instance(canvas_item->get_owner())) || (!p_allow_locked && _is_node_locked(canvas_item))) { - r_items.remove(i); + r_items.remove_at(i); i--; } else { r_items.write[i].item = canvas_item; @@ -675,10 +686,10 @@ void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_n } CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - Node *scene = editor->get_edited_scene(); + 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) { @@ -697,7 +708,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(); @@ -724,7 +739,7 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po still_selected = false; if (editor_selection->get_selected_node_list().size() == 1) { - editor->push_item(editor_selection->get_selected_node_list()[0]); + EditorNode::get_singleton()->push_item(editor_selection->get_selected_node_list()[0]); } } else { // Add the item to the selection @@ -738,7 +753,7 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po // Reselect if (Engine::get_singleton()->is_editor_hint()) { selected_from_canvas = true; - editor->call("edit_node", item); + EditorNode::get_singleton()->edit_node(item); } } } @@ -746,11 +761,11 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po return still_selected; } -List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_locked, bool remove_canvas_item_if_parent_in_selection) { +List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retrieve_locked, bool remove_canvas_item_if_parent_in_selection) { List<CanvasItem *> selection; - for (Map<Node *, Object *>::Element *E = editor_selection->get_selection().front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); - if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retreive_locked || !_is_node_locked(canvas_item))) { + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); + if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retrieve_locked || !_is_node_locked(canvas_item))) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); if (se) { selection.push_back(canvas_item); @@ -800,9 +815,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()) { @@ -857,7 +880,7 @@ void CanvasItemEditor::_commit_canvas_item_state(List<CanvasItem *> p_canvas_ite } void CanvasItemEditor::_snap_changed() { - ((SnapDialog *)snap_dialog)->get_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); + static_cast<SnapDialog *>(snap_dialog)->get_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); grid_step_multiplier = 0; viewport->update(); } @@ -877,14 +900,43 @@ void CanvasItemEditor::_selection_result_pressed(int p_result) { void CanvasItemEditor::_selection_menu_hide() { selection_results.clear(); selection_menu->clear(); - selection_menu->set_size(Vector2(0, 0)); + selection_menu->reset_size(); } void CanvasItemEditor::_add_node_pressed(int p_result) { - if (p_result == AddNodeOption::ADD_NODE) { - editor->get_scene_tree_dock()->open_add_child_dialog(); - } else if (p_result == AddNodeOption::ADD_INSTANCE) { - editor->get_scene_tree_dock()->open_instance_child_dialog(); + List<Node *> nodes_to_move; + + switch (p_result) { + case ADD_NODE: { + SceneTreeDock::get_singleton()->open_add_child_dialog(); + } break; + case ADD_INSTANCE: { + SceneTreeDock::get_singleton()->open_instance_child_dialog(); + } break; + case ADD_PASTE: { + nodes_to_move = SceneTreeDock::get_singleton()->paste_nodes(); + [[fallthrough]]; + } + case ADD_MOVE: { + if (p_result == ADD_MOVE) { + nodes_to_move = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + } + if (nodes_to_move.is_empty()) { + return; + } + + undo_redo->create_action(TTR("Move Node(s) to Position")); + for (Node *node : nodes_to_move) { + CanvasItem *ci = Object::cast_to<CanvasItem>(node); + if (ci) { + Transform2D xform = ci->get_global_transform_with_canvas().affine_inverse() * ci->get_transform(); + undo_redo->add_do_method(ci, "_edit_set_position", xform.xform(node_create_position)); + undo_redo->add_undo_method(ci, "_edit_set_position", ci->_edit_get_position()); + } + } + undo_redo->commit_action(); + _reset_create_position(); + } break; } } @@ -906,6 +958,60 @@ void CanvasItemEditor::_reset_create_position() { node_create_position = Point2(); } +bool CanvasItemEditor::_is_grid_visible() const { + switch (grid_visibility) { + case GRID_VISIBILITY_SHOW: + return true; + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + return grid_snap_active; + case GRID_VISIBILITY_HIDE: + return false; + } + ERR_FAIL_V_MSG(true, "Unexpected grid_visibility value"); +} + +void CanvasItemEditor::_prepare_grid_menu() { + for (int i = GRID_VISIBILITY_SHOW; i <= GRID_VISIBILITY_HIDE; i++) { + grid_menu->set_item_checked(i, i == grid_visibility); + } +} + +void CanvasItemEditor::_on_grid_menu_id_pressed(int p_id) { + switch (p_id) { + case GRID_VISIBILITY_SHOW: + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + case GRID_VISIBILITY_HIDE: + grid_visibility = (GridVisibility)p_id; + viewport->update(); + view_menu->get_popup()->hide(); + return; + } + + // Toggle grid: go to the least restrictive option possible. + if (grid_snap_active) { + switch (grid_visibility) { + case GRID_VISIBILITY_SHOW: + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + grid_visibility = GRID_VISIBILITY_HIDE; + break; + case GRID_VISIBILITY_HIDE: + grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING; + break; + } + } else { + switch (grid_visibility) { + case GRID_VISIBILITY_SHOW: + grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING; + break; + case GRID_VISIBILITY_SHOW_WHEN_SNAPPING: + case GRID_VISIBILITY_HIDE: + grid_visibility = GRID_VISIBILITY_SHOW; + break; + } + } + viewport->update(); +} + bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> b = p_event; Ref<InputEventMouseMotion> m = p_event; @@ -914,14 +1020,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; @@ -950,7 +1050,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } // Start dragging a guide - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { // Press button if (b->get_position().x < RULER_WIDTH && b->get_position().y < RULER_WIDTH) { // Drag a new double guide @@ -1009,19 +1109,13 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } // Release confirms the guide move - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) { if (show_guides && 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()); Point2 edited = snap_point(xform.affine_inverse().xform(b->get_position()), SNAP_GRID | SNAP_PIXEL | SNAP_OTHER_NODES); if (drag_type == DRAG_V_GUIDE) { @@ -1045,7 +1139,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } } else { if (dragged_guide_index >= 0) { - vguides.remove(dragged_guide_index); + vguides.remove_at(dragged_guide_index); undo_redo->create_action(TTR("Remove Vertical Guide")); if (vguides.is_empty()) { undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_vertical_guides_"); @@ -1078,7 +1172,7 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } } else { if (dragged_guide_index >= 0) { - hguides.remove(dragged_guide_index); + hguides.remove_at(dragged_guide_index); undo_redo->create_action(TTR("Remove Horizontal Guide")); if (hguides.is_empty()) { undo_redo->add_do_method(EditorNode::get_singleton()->get_edited_scene(), "remove_meta", "_edit_horizontal_guides_"); @@ -1107,7 +1201,9 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } } } - drag_type = DRAG_NONE; + snap_target[0] = SNAP_TARGET_NONE; + snap_target[1] = SNAP_TARGET_NONE; + _reset_drag(); viewport->update(); return true; } @@ -1116,142 +1212,42 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve } bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) { - Ref<InputEventMouseButton> b = p_event; - if (b.is_valid() && !p_already_accepted) { - const bool pan_on_scroll = bool(EditorSettings::get_singleton()->get("editors/2d/scroll_to_pan")) && !b->is_ctrl_pressed(); - - if (pan_on_scroll) { - // Perform horizontal scrolling first so we can check for Shift being held. - if (b->is_pressed() && - (b->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || (b->is_shift_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP))) { - // Pan left - view_offset.x -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - return true; - } - - if (b->is_pressed() && - (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->is_shift_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN))) { - // Pan right - view_offset.x += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - return true; - } - } - - if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { - // Scroll or pan down - if (pan_on_scroll) { - view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - } else { - zoom_widget->set_zoom_by_increments(-1, Input::get_singleton()->is_key_pressed(KEY_ALT)); - if (!Math::is_equal_approx(b->get_factor(), 1.0f)) { - // Handle high-precision (analog) scrolling. - zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f)); - } - _zoom_on_position(zoom_widget->get_zoom(), b->get_position()); - } - return true; - } - - if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { - // Scroll or pan up - if (pan_on_scroll) { - view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); - update_viewport(); - } else { - zoom_widget->set_zoom_by_increments(1, Input::get_singleton()->is_key_pressed(KEY_ALT)); - if (!Math::is_equal_approx(b->get_factor(), 1.0f)) { - // Handle high-precision (analog) scrolling. - zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f)); - } - _zoom_on_position(zoom_widget->get_zoom(), b->get_position()); - } - return true; - } - - if (!panning) { - if (b->is_pressed() && - (b->get_button_index() == MOUSE_BUTTON_MIDDLE || - b->get_button_index() == MOUSE_BUTTON_RIGHT || - (b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_PAN) || - (b->get_button_index() == MOUSE_BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { - // Pan the viewport - panning = true; - } - } + panner->set_force_drag(tool == TOOL_PAN); + bool panner_active = panner->gui_input(p_event, warped_panning ? viewport->get_global_rect() : Rect2()); + if (panner->is_panning() != pan_pressed) { + pan_pressed = panner->is_panning(); + _update_cursor(); + } - if (panning) { - if (!b->is_pressed() && (pan_on_scroll || (b->get_button_index() != MOUSE_BUTTON_WHEEL_DOWN && b->get_button_index() != MOUSE_BUTTON_WHEEL_UP))) { - // Stop panning the viewport (for any mouse button press except zooming) - panning = false; - } - } + if (panner_active) { + return true; } Ref<InputEventKey> k = p_event; if (k.is_valid()) { if (k->is_pressed()) { if (ED_GET_SHORTCUT("canvas_item_editor/zoom_3.125_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 32.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 32.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_6.25_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 16.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 16.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_12.5_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 8.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 8.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_25_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 4.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 4.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_50_percent")->matches_event(p_event)) { - _update_zoom((1.0 / 2.0) * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0 / 2.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_100_percent")->matches_event(p_event)) { - _update_zoom(1.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(1.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_200_percent")->matches_event(p_event)) { - _update_zoom(2.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(2.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_400_percent")->matches_event(p_event)) { - _update_zoom(4.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(4.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_800_percent")->matches_event(p_event)) { - _update_zoom(8.0 * MAX(1, EDSCALE)); + _shortcut_zoom_set(8.0); } else if (ED_GET_SHORTCUT("canvas_item_editor/zoom_1600_percent")->matches_event(p_event)) { - _update_zoom(16.0 * MAX(1, EDSCALE)); - } - } - - bool is_pan_key = pan_view_shortcut.is_valid() && pan_view_shortcut->matches_event(p_event); - - if (is_pan_key && (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || drag_type != DRAG_NONE)) { - if (!panning) { - if (k->is_pressed() && !k->is_echo()) { - //Pan the viewport - panning = true; - } - } else { - if (!k->is_pressed()) { - // Stop panning the viewport (for any mouse button press) - panning = false; - } + _shortcut_zoom_set(16.0); } } - - if (is_pan_key) { - pan_pressed = k->is_pressed(); - } - } - - Ref<InputEventMouseMotion> m = p_event; - if (m.is_valid()) { - if (panning) { - // Pan the viewport - Point2i relative; - if (bool(EditorSettings::get_singleton()->get("editors/2d/warped_mouse_panning"))) { - relative = Input::get_singleton()->warp_mouse_motion(m, viewport->get_global_rect()); - } else { - relative = m->get_relative(); - } - view_offset.x -= relative.x / zoom; - view_offset.y -= relative.y / zoom; - update_viewport(); - return true; - } } Ref<InputEventMagnifyGesture> magnify_gesture = p_event; @@ -1277,7 +1273,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } // Pan gesture - const Vector2 delta = (int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom) * pan_gesture->get_delta(); + const Vector2 delta = (pan_speed / zoom) * pan_gesture->get_delta(); view_offset.x += delta.x; view_offset.y += delta.y; update_viewport(); @@ -1287,6 +1283,27 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo return false; } +void CanvasItemEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { + _pan_callback(-p_scroll_vec * pan_speed); +} + +void CanvasItemEditor::_pan_callback(Vector2 p_scroll_vec) { + view_offset.x -= p_scroll_vec.x / zoom; + view_offset.y -= p_scroll_vec.y / zoom; + update_viewport(); +} + +void CanvasItemEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { + 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) * ABS(p_scroll_vec.y) + 1.f)); + } + _zoom_on_position(zoom_widget->get_zoom(), p_origin); +} + bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> m = p_event; Ref<InputEventMouseButton> b = p_event; @@ -1294,8 +1311,8 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { // Drag the pivot (in pivot mode / with V key) if (drag_type == DRAG_NONE) { - if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_EDIT_PIVOT) || - (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == KEY_V)) { + if ((b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) || + (k.is_valid() && k->is_pressed() && !k->is_echo() && k->get_keycode() == Key::V && tool == TOOL_SELECT && k->get_modifiers_mask() == Key::NONE)) { List<CanvasItem *> selection = _get_edited_canvas_items(); // Filters the selection with nodes that allow setting the pivot @@ -1345,8 +1362,8 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { // Confirm the pivot move if (drag_selection.size() >= 1 && - ((b.is_valid() && !b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_EDIT_PIVOT) || - (k.is_valid() && !k->is_pressed() && k->get_keycode() == KEY_V))) { + ((b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT && tool == TOOL_EDIT_PIVOT) || + (k.is_valid() && !k->is_pressed() && k->get_keycode() == Key::V))) { _commit_canvas_item_state( drag_selection, vformat( @@ -1354,14 +1371,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() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + 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; } @@ -1375,7 +1392,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { // Start rotation if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { if ((b->is_command_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) { List<CanvasItem *> selection = _get_edited_canvas_items(); @@ -1418,7 +1435,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { } // Confirms the node rotation - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) { if (drag_selection.size() != 1) { _commit_canvas_item_state( drag_selection, @@ -1437,14 +1454,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() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + 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; } @@ -1456,12 +1473,12 @@ bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEven Ref<InputEventMouseButton> b = p_event; // Open a sub-scene on double-click - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; - if (canvas_item->get_filename() != "" && canvas_item != editor->get_edited_scene()) { - editor->open_request(canvas_item->get_filename()); + if (!canvas_item->get_scene_file_path().is_empty() && canvas_item != EditorNode::get_singleton()->get_edited_scene()) { + EditorNode::get_singleton()->open_request(canvas_item->get_scene_file_path()); return true; } } @@ -1475,7 +1492,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { // Starts anchor dragging if needed if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { Control *control = Object::cast_to<Control>(selection[0]); @@ -1497,7 +1514,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { } } - DragType dragger[] = { + const DragType dragger[] = { DRAG_ANCHOR_TOP_LEFT, DRAG_ANCHOR_TOP_RIGHT, DRAG_ANCHOR_BOTTOM_RIGHT, @@ -1595,18 +1612,18 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { } // Confirms new anchor position - if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { + if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) { _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() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + 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; } @@ -1620,7 +1637,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { // Drag resize handles if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; @@ -1628,14 +1645,14 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { Rect2 rect = canvas_item->_edit_get_rect(); Transform2D xform = transform * canvas_item->get_global_transform_with_canvas(); - Vector2 endpoints[4] = { + const Vector2 endpoints[4] = { xform.xform(rect.position), xform.xform(rect.position + Vector2(rect.size.x, 0)), xform.xform(rect.position + rect.size), xform.xform(rect.position + Vector2(0, rect.size.y)) }; - DragType dragger[] = { + const DragType dragger[] = { DRAG_TOP_LEFT, DRAG_TOP, DRAG_TOP_RIGHT, @@ -1774,7 +1791,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { } // Confirm resize - if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { + if (drag_selection.size() >= 1 && b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) { const Node2D *node2d = Object::cast_to<Node2D>(drag_selection[0]); if (node2d) { // Extends from Node2D. @@ -1805,17 +1822,17 @@ 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; } // Cancel a drag - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { _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; } @@ -1829,7 +1846,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { // Drag resize handles if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_ctrl_pressed()) || tool == TOOL_SCALE)) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_ctrl_pressed()) || tool == TOOL_SCALE)) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; @@ -1876,13 +1893,13 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform; bool uniform = m->is_shift_pressed(); - bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); Point2 drag_from_local = simple_xform.xform(drag_from); Point2 drag_to_local = simple_xform.xform(drag_to); Point2 offset = drag_to_local - drag_from_local; - Size2 scale = canvas_item->call("get_scale"); + Size2 scale = canvas_item->_edit_get_scale(); Size2 original_scale = scale; real_t ratio = scale.y / scale.x; if (drag_type == DRAG_SCALE_BOTH) { @@ -1920,12 +1937,12 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } } - canvas_item->call("set_scale", scale); + canvas_item->_edit_set_scale(scale); return true; } // Confirm resize - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && !b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && !b->is_pressed()) { if (drag_selection.size() != 1) { _commit_canvas_item_state( drag_selection, @@ -1944,15 +1961,15 @@ 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; } // Cancel a drag - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + 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; } @@ -1967,7 +1984,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_NONE) { //Start moving the nodes - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) { if ((b->is_alt_pressed() && !b->is_ctrl_pressed()) || tool == TOOL_MOVE) { List<CanvasItem *> selection = _get_edited_canvas_items(); @@ -2050,7 +2067,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } // Confirm the move (only if it was moved) - if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) { if (transform.affine_inverse().xform(b->get_position()) != drag_from) { if (drag_selection.size() != 1) { _commit_canvas_item_state( @@ -2077,17 +2094,17 @@ 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; } // Cancel a drag - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_pressed()) { + if (b.is_valid() && b->get_button_index() == MouseButton::RIGHT && b->is_pressed()) { _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; } @@ -2095,10 +2112,18 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { // Move the canvas items with the arrow keys if (k.is_valid() && k->is_pressed() && (tool == TOOL_SELECT || tool == TOOL_MOVE) && - (k->get_keycode() == KEY_UP || k->get_keycode() == KEY_DOWN || k->get_keycode() == KEY_LEFT || k->get_keycode() == KEY_RIGHT)) { + (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) { if (!k->is_echo()) { - // Start moving the canvas items with the keyboard - drag_selection = _get_edited_canvas_items(); + // Start moving the canvas items with the keyboard, if they are movable + List<CanvasItem *> selection = _get_edited_canvas_items(); + + drag_selection.clear(); + for (CanvasItem *item : selection) { + if (_is_node_movable(item, true)) { + drag_selection.push_back(item); + } + } + drag_type = DRAG_KEY_MOVE; drag_from = Vector2(); drag_to = Vector2(); @@ -2112,13 +2137,13 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { bool move_local_base_rotated = k->is_ctrl_pressed() || k->is_meta_pressed(); Vector2 dir; - if (k->get_keycode() == KEY_UP) { + if (k->get_keycode() == Key::UP) { dir += Vector2(0, -1); - } else if (k->get_keycode() == KEY_DOWN) { + } else if (k->get_keycode() == Key::DOWN) { dir += Vector2(0, 1); - } else if (k->get_keycode() == KEY_LEFT) { + } else if (k->get_keycode() == Key::LEFT) { dir += Vector2(-1, 0); - } else if (k->get_keycode() == KEY_RIGHT) { + } else if (k->get_keycode() == Key::RIGHT) { dir += Vector2(1, 0); } if (k->is_shift_pressed()) { @@ -2166,12 +2191,12 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } if (k.is_valid() && !k->is_pressed() && drag_type == DRAG_KEY_MOVE && (tool == TOOL_SELECT || tool == TOOL_MOVE) && - (k->get_keycode() == KEY_UP || k->get_keycode() == KEY_DOWN || k->get_keycode() == KEY_LEFT || k->get_keycode() == KEY_RIGHT)) { + (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)) { // Confirm canvas items move by arrow keys - if ((!Input::get_singleton()->is_key_pressed(KEY_UP)) && - (!Input::get_singleton()->is_key_pressed(KEY_DOWN)) && - (!Input::get_singleton()->is_key_pressed(KEY_LEFT)) && - (!Input::get_singleton()->is_key_pressed(KEY_RIGHT))) { + if ((!Input::get_singleton()->is_key_pressed(Key::UP)) && + (!Input::get_singleton()->is_key_pressed(Key::DOWN)) && + (!Input::get_singleton()->is_key_pressed(Key::LEFT)) && + (!Input::get_singleton()->is_key_pressed(Key::RIGHT))) { if (drag_selection.size() > 1) { _commit_canvas_item_state( drag_selection, @@ -2186,13 +2211,13 @@ 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; } - return (k.is_valid() && (k->get_keycode() == KEY_UP || k->get_keycode() == KEY_DOWN || k->get_keycode() == KEY_LEFT || k->get_keycode() == KEY_RIGHT)); // Accept the key event in any case + return (k.is_valid() && (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::LEFT || k->get_keycode() == Key::RIGHT)); // Accept the key event in any case } bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { @@ -2201,9 +2226,9 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; if (drag_type == DRAG_NONE) { - if (b.is_valid() && - ((b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) || - (b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_LIST_SELECT))) { + if (b.is_valid() && b->is_pressed() && + ((b->get_button_index() == MouseButton::RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) || + (b->get_button_index() == MouseButton::LEFT && tool == TOOL_LIST_SELECT))) { // Popup the selection menu list Point2 click = transform.affine_inverse().xform(b->get_position()); @@ -2233,7 +2258,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (_is_node_locked(item)) { locked = 1; } else { - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); Node *node = item; while (node && node != scene->get_parent()) { @@ -2258,25 +2283,42 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } selection_menu_additive_selection = b->is_shift_pressed(); - selection_menu->set_position(get_screen_transform().xform(b->get_position())); + selection_menu->set_position(viewport->get_screen_transform().xform(b->get_position())); + selection_menu->reset_size(); selection_menu->popup(); return true; } } - if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_ctrl_pressed()) { - add_node_menu->set_position(get_global_transform().xform(get_local_mouse_position())); - add_node_menu->set_size(Vector2(1, 1)); + if (b.is_valid() && b->is_pressed() && b->get_button_index() == MouseButton::RIGHT) { + add_node_menu->clear(); + add_node_menu->add_icon_item(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add Node Here"), ADD_NODE); + add_node_menu->add_icon_item(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Instantiate Scene Here"), ADD_INSTANCE); + for (Node *node : SceneTreeDock::get_singleton()->get_node_clipboard()) { + if (Object::cast_to<CanvasItem>(node)) { + add_node_menu->add_icon_item(get_theme_icon(SNAME("ActionPaste"), SNAME("EditorIcons")), TTR("Paste Node(s) Here"), ADD_PASTE); + break; + } + } + for (Node *node : EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list()) { + if (Object::cast_to<CanvasItem>(node)) { + add_node_menu->add_icon_item(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons")), TTR("Move Node(s) Here"), ADD_MOVE); + break; + } + } + + add_node_menu->reset_size(); + add_node_menu->set_position(viewport->get_screen_transform().xform(b->get_position())); add_node_menu->popup(); - node_create_position = transform.affine_inverse().xform((get_local_mouse_position())); + node_create_position = transform.affine_inverse().xform(b->get_position()); return true; } - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && tool == TOOL_SELECT && !panner->is_panning()) { // Single item selection Point2 click = transform.affine_inverse().xform(b->get_position()); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (!scene) { return true; } @@ -2286,7 +2328,6 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { Vector<_SelectResult> selection = Vector<_SelectResult>(); // Retrieve the canvas items - selection = Vector<_SelectResult>(); _get_canvas_items_at_pos(click, selection); if (!selection.is_empty()) { canvas_item = selection[0].item; @@ -2321,7 +2362,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()) { @@ -2339,7 +2380,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (selection2.size() > 0) { drag_type = DRAG_MOVE; - drag_from = click; + drag_from = drag_start_origin; _save_canvas_item_state(drag_selection); } return true; @@ -2348,9 +2389,9 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } if (drag_type == DRAG_BOX_SELECTION) { - if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b.is_valid() && !b->is_pressed() && b->get_button_index() == MouseButton::LEFT) { // Confirms box selection - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (scene) { List<CanvasItem *> selitems; @@ -2365,21 +2406,21 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { _find_canvas_items_in_rect(Rect2(bsfrom, bsto - bsfrom), scene, &selitems); if (selitems.size() == 1 && editor_selection->get_selected_node_list().is_empty()) { - editor->push_item(selitems[0]); + EditorNode::get_singleton()->push_item(selitems[0]); } for (CanvasItem *E : selitems) { editor_selection->add_node(E); } } - drag_type = DRAG_NONE; + _reset_drag(); viewport->update(); return true; } - if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT) { + 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; } @@ -2392,7 +2433,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } } - if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_ESCAPE && drag_type == DRAG_NONE && tool == TOOL_SELECT) { + if (k.is_valid() && k->is_pressed() && k->get_keycode() == Key::ESCAPE && drag_type == DRAG_NONE && tool == TOOL_SELECT) { // Unselect everything editor_selection->clear(); viewport->update(); @@ -2414,7 +2455,7 @@ bool CanvasItemEditor::_gui_input_ruler_tool(const Ref<InputEvent> &p_event) { ruler_tool_origin = snap_point(viewport->get_local_mouse_position() / zoom + view_offset); } - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT) { + if (b.is_valid() && b->get_button_index() == MouseButton::LEFT) { if (b->is_pressed()) { ruler_tool_active = true; } else { @@ -2489,31 +2530,34 @@ bool CanvasItemEditor::_gui_input_hover(const Ref<InputEvent> &p_event) { void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { bool accepted = false; - if (EditorSettings::get_singleton()->get("editors/2d/simple_panning") || !pan_pressed) { + Ref<InputEventMouseButton> mb = p_event; + bool release_lmb = (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT); // Required to properly release some stuff (e.g. selection box) while panning. + + if (EditorSettings::get_singleton()->get("editors/panning/simple_panning") || !pan_pressed || release_lmb) { if ((accepted = _gui_input_rulers_and_guides(p_event))) { - //printf("Rulers and guides\n"); - } else if ((accepted = editor->get_editor_plugins_over()->forward_gui_input(p_event))) { - //printf("Plugin\n"); + // print_line("Rulers and guides"); + } else if ((accepted = EditorNode::get_singleton()->get_editor_plugins_over()->forward_gui_input(p_event))) { + // print_line("Plugin"); } else if ((accepted = _gui_input_open_scene_on_double_click(p_event))) { - //printf("Open scene on double click\n"); + // print_line("Open scene on double click"); } else if ((accepted = _gui_input_scale(p_event))) { - //printf("Set scale\n"); + // print_line("Set scale"); } else if ((accepted = _gui_input_pivot(p_event))) { - //printf("Set pivot\n"); + // print_line("Set pivot"); } else if ((accepted = _gui_input_resize(p_event))) { - //printf("Resize\n"); + // print_line("Resize"); } else if ((accepted = _gui_input_rotate(p_event))) { - //printf("Rotate\n"); + // print_line("Rotate"); } else if ((accepted = _gui_input_move(p_event))) { - //printf("Move\n"); + // print_line("Move"); } else if ((accepted = _gui_input_anchors(p_event))) { - //printf("Anchors\n"); + // print_line("Anchors"); } else if ((accepted = _gui_input_select(p_event))) { - //printf("Selection\n"); + // print_line("Selection"); } else if ((accepted = _gui_input_ruler_tool(p_event))) { - //printf("Measure\n"); + // print_line("Measure"); } else { - //printf("Not accepted\n"); + // print_line("Not accepted"); } } @@ -2530,14 +2574,14 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) { _update_cursor(); // Grab focus - if (!viewport->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) { + if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) { viewport->call_deferred(SNAME("grab_focus")); } } void CanvasItemEditor::_update_cursor() { // Compute an eventual rotation of the cursor - CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE }; + const CursorShape rotation_array[4] = { CURSOR_HSIZE, CURSOR_BDIAGSIZE, CURSOR_VSIZE, CURSOR_FDIAGSIZE }; int rotation_array_index = 0; List<CanvasItem *> selection = _get_edited_canvas_items(); @@ -2615,7 +2659,19 @@ void CanvasItemEditor::_update_cursor() { c = CURSOR_HSIZE; } - viewport->set_default_cursor_shape(c); + if (pan_pressed) { + c = CURSOR_DRAG; + } + + if (c != viewport->get_default_cursor_shape()) { + viewport->set_default_cursor_shape(c); + + // Force refresh cursor if it's over the viewport. + if (viewport->get_global_rect().has_point(get_global_mouse_position())) { + DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); + DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); + } + } } void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Side p_side) { @@ -2623,7 +2679,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); @@ -2638,7 +2694,7 @@ void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string p_position += Vector2(-text_size.x / 2, text_size.y + 5); break; } - viewport->draw_string(font, p_position, p_string, HALIGN_LEFT, -1, font_size, color); + viewport->draw_string(font, p_position, p_string, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, color); } void CanvasItemEditor::_draw_margin_at_position(int p_value, Point2 p_position, Side p_side) { @@ -2666,9 +2722,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; @@ -2676,10 +2732,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; @@ -2689,7 +2743,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; @@ -2697,16 +2751,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, HALIGN_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, HALIGN_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)); } } @@ -2735,13 +2791,13 @@ void CanvasItemEditor::_draw_rulers() { // The rule transform Transform2D ruler_transform = Transform2D(); - if (show_grid || grid_snap_active) { + 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) { @@ -2778,7 +2834,7 @@ void CanvasItemEditor::_draw_rulers() { if (i % (major_subdivision * minor_subdivision) == 0) { viewport->draw_line(Point2(position.x, 0), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE)); real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).x; - viewport->draw_string(font, Point2(position.x + 2, font->get_height(font_size)), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HALIGN_LEFT, -1, font_size, font_color); + viewport->draw_string(font, Point2(position.x + 2, font->get_height(font_size)), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } else { if (i % minor_subdivision == 0) { viewport->draw_line(Point2(position.x, RULER_WIDTH * 0.33), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE)); @@ -2798,7 +2854,7 @@ void CanvasItemEditor::_draw_rulers() { Transform2D text_xform = Transform2D(-Math_PI / 2.0, Point2(font->get_height(font_size), position.y - 2)); viewport->draw_set_transform_matrix(viewport->get_transform() * text_xform); - viewport->draw_string(font, Point2(), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HALIGN_LEFT, -1, font_size, font_color); + viewport->draw_string(font, Point2(), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); viewport->draw_set_transform_matrix(viewport->get_transform()); } else { @@ -2815,7 +2871,7 @@ void CanvasItemEditor::_draw_rulers() { } void CanvasItemEditor::_draw_grid() { - if (show_grid || grid_snap_active) { + if (_is_grid_visible()) { // Draw the grid Vector2 real_grid_offset; const List<CanvasItem *> selection = _get_edited_canvas_items(); @@ -2901,14 +2957,6 @@ void CanvasItemEditor::_draw_ruler_tool() { Point2 corner = Point2(begin.x, end.y); Vector2 length_vector = (begin - end).abs() / zoom; - 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)); - } - 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")); @@ -2924,26 +2972,47 @@ void CanvasItemEditor::_draw_ruler_tool() { Point2 text_pos = (begin + end) / 2 - Vector2(text_width / 2, text_height / 2); 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); - viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HALIGN_LEFT, -1, font_size, font_color, outline_size, outline_color); + 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); + + 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) { - const real_t horizontal_angle_rad = atan2(length_vector.y, length_vector.x); + viewport->draw_line(begin, corner, ruler_secondary_color, Math::round(EDSCALE)); + viewport->draw_line(corner, end, ruler_secondary_color, Math::round(EDSCALE)); + } + + 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)), HALIGN_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)), HALIGN_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)), HALIGN_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); @@ -2960,7 +3029,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)), HALIGN_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); + 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); // Angle arcs int arc_point_count = 8; @@ -2971,18 +3041,16 @@ void CanvasItemEditor::_draw_ruler_tool() { 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_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_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); @@ -2997,17 +3065,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())), HALIGN_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))), HALIGN_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))), HALIGN_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()))), HALIGN_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 { @@ -3097,7 +3169,6 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) { if (dragged_anchor >= 0) { // Draw the 4 lines when dragged - bool anchor_snapped; Color color_snapped = Color(0.64, 0.93, 0.67, 0.5); Vector2 corners_pos[4]; @@ -3111,7 +3182,7 @@ void CanvasItemEditor::_draw_control_helpers(Control *control) { real_t anchor_val = (i >= 2) ? ANCHOR_END - anchors_values[i] : anchors_values[i]; line_starts[i] = corners_pos[i].lerp(corners_pos[(i + 1) % 4], anchor_val); line_ends[i] = corners_pos[(i + 3) % 4].lerp(corners_pos[(i + 2) % 4], anchor_val); - anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0; + bool anchor_snapped = anchors_values[i] == 0.0 || anchors_values[i] == 0.5 || anchors_values[i] == 1.0; viewport->draw_line(line_starts[i], line_ends[i], anchor_snapped ? color_snapped : color_base, (i == dragged_anchor || (i + 3) % 4 == dragged_anchor) ? 2 : 1); } @@ -3275,7 +3346,7 @@ void CanvasItemEditor::_draw_selection() { // Draw the selected items position / surrounding boxes if (canvas_item->_edit_use_rect()) { Rect2 rect = canvas_item->_edit_get_rect(); - Vector2 endpoints[4] = { + const Vector2 endpoints[4] = { xform.xform(rect.position), xform.xform(rect.position + Vector2(rect.size.x, 0)), xform.xform(rect.position + rect.size), @@ -3321,7 +3392,7 @@ void CanvasItemEditor::_draw_selection() { // Draw the resize handles if (tool == TOOL_SELECT && canvas_item->_edit_use_rect() && _is_node_movable(canvas_item)) { Rect2 rect = canvas_item->_edit_get_rect(); - Vector2 endpoints[4] = { + const Vector2 endpoints[4] = { xform.xform(rect.position), xform.xform(rect.position + Vector2(rect.size.x, 0)), xform.xform(rect.position + rect.size), @@ -3344,8 +3415,8 @@ void CanvasItemEditor::_draw_selection() { } // Draw the move handles - bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); - bool is_alt = Input::get_singleton()->is_key_pressed(KEY_ALT); + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); + bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT); if (tool == TOOL_MOVE && show_transformation_gizmos) { if (_is_node_movable(canvas_item)) { Transform2D unscaled_transform = (xform * canvas_item->get_transform().affine_inverse() * canvas_item->_edit_get_transform()).orthonormalized(); @@ -3354,10 +3425,11 @@ void CanvasItemEditor::_draw_selection() { Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE); viewport->draw_set_transform_matrix(simple_xform); - Vector<Point2> points; - points.push_back(Vector2(move_factor.x * EDSCALE, 5 * EDSCALE)); - points.push_back(Vector2(move_factor.x * EDSCALE, -5 * EDSCALE)); - points.push_back(Vector2((move_factor.x + 10) * EDSCALE, 0)); + Vector<Point2> points = { + Vector2(move_factor.x * EDSCALE, 5 * EDSCALE), + Vector2(move_factor.x * EDSCALE, -5 * EDSCALE), + Vector2((move_factor.x + 10) * EDSCALE, 0) + }; viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), SNAME("Editor")), Math::round(EDSCALE)); @@ -3381,7 +3453,7 @@ void CanvasItemEditor::_draw_selection() { Transform2D simple_xform = viewport->get_transform() * unscaled_transform; Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE); - bool uniform = Input::get_singleton()->is_key_pressed(KEY_SHIFT); + bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT); Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom; if (drag_type == DRAG_SCALE_X) { @@ -3489,7 +3561,7 @@ void CanvasItemEditor::_draw_axis() { Color area_axis_color = EditorSettings::get_singleton()->get("editors/2d/viewport_border_color"); - Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + Size2 screen_size = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); Vector2 screen_endpoints[4] = { transform.xform(Vector2(0, 0)), @@ -3507,12 +3579,12 @@ void CanvasItemEditor::_draw_axis() { void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { ERR_FAIL_COND(!p_node); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) { return; } CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - if (canvas_item && !canvas_item->is_visible()) { + if (canvas_item && !canvas_item->is_visible_in_tree()) { return; } @@ -3553,7 +3625,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); + 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); @@ -3570,19 +3642,80 @@ void CanvasItemEditor::_draw_hover() { viewport->draw_texture(node_icon, pos, Color(1.0, 1.0, 1.0, 0.5)); // Draw name - viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, HALIGN_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); + viewport->draw_string(font, pos + Point2(node_icon->get_size().x + 4, item_size.y - 3), node_name, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); + } +} + +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); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); if (p_node != scene && p_node->get_owner() != scene && !scene->is_editable_instance(p_node->get_owner())) { return; } CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - if (canvas_item && !canvas_item->is_visible()) { + if (canvas_item && !canvas_item->is_visible_in_tree()) { return; } @@ -3623,8 +3756,8 @@ void CanvasItemEditor::_draw_viewport() { // Update the transform transform = Transform2D(); transform.scale_basis(Size2(zoom, zoom)); - transform.elements[2] = -view_offset * zoom; - editor->get_scene_root()->set_global_canvas_transform(transform); + transform.columns[2] = -view_offset * zoom; + EditorNode::get_singleton()->get_scene_root()->set_global_canvas_transform(transform); // hide/show buttons depending on the selection bool all_locked = true; @@ -3655,25 +3788,23 @@ void CanvasItemEditor::_draw_viewport() { group_button->set_disabled(selection.is_empty()); ungroup_button->set_visible(all_group); - info_overlay->set_offset(SIDE_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10); - _draw_grid(); _draw_ruler_tool(); _draw_axis(); - if (editor->get_edited_scene()) { - _draw_locks_and_groups(editor->get_edited_scene()); - _draw_invisible_nodes_positions(editor->get_edited_scene()); + if (EditorNode::get_singleton()->get_edited_scene()) { + _draw_locks_and_groups(EditorNode::get_singleton()->get_edited_scene()); + _draw_invisible_nodes_positions(EditorNode::get_singleton()->get_edited_scene()); } _draw_selection(); RID ci = viewport->get_canvas_item(); RenderingServer::get_singleton()->canvas_item_add_set_transform(ci, Transform2D()); - EditorPluginList *over_plugin_list = editor->get_editor_plugins_over(); + EditorPluginList *over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { over_plugin_list->forward_canvas_draw_over_viewport(viewport); } - EditorPluginList *force_over_plugin_list = editor->get_editor_plugins_force_over(); + EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over(); if (!force_over_plugin_list->is_empty()) { force_over_plugin_list->forward_canvas_force_draw_over_viewport(viewport); } @@ -3687,6 +3818,7 @@ void CanvasItemEditor::_draw_viewport() { _draw_smart_snapping(); _draw_focus(); _draw_hover(); + _draw_transform_message(); } void CanvasItemEditor::update_viewport() { @@ -3698,266 +3830,168 @@ void CanvasItemEditor::set_current_tool(Tool p_tool) { _button_tool_select(p_tool); } -void CanvasItemEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_PHYSICS_PROCESS) { - EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels")); +void CanvasItemEditor::_update_editor_settings() { + select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + list_select_button->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); + move_button->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + scale_button->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + rotate_button->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + smart_snap_button->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + grid_snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + snap_config_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + skeleton_menu->set_icon(get_theme_icon(SNAME("Bone"), SNAME("EditorIcons"))); + override_camera_button->set_icon(get_theme_icon(SNAME("Camera2D"), SNAME("EditorIcons"))); + pan_button->set_icon(get_theme_icon(SNAME("ToolPan"), SNAME("EditorIcons"))); + ruler_button->set_icon(get_theme_icon(SNAME("Ruler"), SNAME("EditorIcons"))); + pivot_button->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); + select_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + anchor_handle = get_theme_icon(SNAME("EditorControlAnchor"), SNAME("EditorIcons")); + lock_button->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); + unlock_button->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); + group_button->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + ungroup_button->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); + key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); + key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); + key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); + key_auto_insert_button->set_icon(get_theme_icon(SNAME("AutoKey"), SNAME("EditorIcons"))); + // Use a different color for the active autokey icon to make them easier + // to distinguish from the other key icons at the top. On a light theme, + // the icon will be dark, so we need to lighten it before blending it + // with the red color. + const Color key_auto_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25); + key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55)); + animation_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + + context_menu_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("ContextualToolbar"), SNAME("EditorStyles"))); + + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/2d_editor_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + pan_speed = int(EditorSettings::get_singleton()->get("editors/panning/2d_editor_pan_speed")); + warped_panning = bool(EditorSettings::get_singleton()->get("editors/panning/warped_mouse_panning")); +} - bool has_container_parents = false; - int nb_control = 0; - int nb_having_pivot = 0; +void CanvasItemEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_PHYSICS_PROCESS: { + EditorNode::get_singleton()->get_scene_root()->set_snap_controls_to_pixels(GLOBAL_GET("gui/common/snap_controls_to_pixels")); - // Update the viewport if the canvas_item changes - List<CanvasItem *> selection = _get_edited_canvas_items(true); - for (CanvasItem *canvas_item : selection) { - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); + int nb_having_pivot = 0; - Rect2 rect; - if (canvas_item->_edit_use_rect()) { - rect = canvas_item->_edit_get_rect(); - } else { - rect = Rect2(); - } - Transform2D xform = canvas_item->get_transform(); + // Update the viewport if the canvas_item changes + List<CanvasItem *> selection = _get_edited_canvas_items(true); + for (CanvasItem *canvas_item : selection) { + CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - if (rect != se->prev_rect || xform != se->prev_xform) { - viewport->update(); - se->prev_rect = rect; - se->prev_xform = xform; - } + Rect2 rect; + if (canvas_item->_edit_use_rect()) { + rect = canvas_item->_edit_get_rect(); + } else { + rect = Rect2(); + } + Transform2D xform = canvas_item->get_transform(); - Control *control = Object::cast_to<Control>(canvas_item); - if (control) { - real_t anchors[4]; - Vector2 pivot; - - pivot = control->get_pivot_offset(); - anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT); - anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT); - anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP); - anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM); - - if (pivot != se->prev_pivot || anchors[SIDE_LEFT] != se->prev_anchors[SIDE_LEFT] || anchors[SIDE_RIGHT] != se->prev_anchors[SIDE_RIGHT] || anchors[SIDE_TOP] != se->prev_anchors[SIDE_TOP] || anchors[SIDE_BOTTOM] != se->prev_anchors[SIDE_BOTTOM]) { - se->prev_pivot = pivot; - se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT]; - se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT]; - se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP]; - se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM]; + if (rect != se->prev_rect || xform != se->prev_xform) { viewport->update(); + se->prev_rect = rect; + se->prev_xform = xform; } - nb_control++; - if (Object::cast_to<Container>(control->get_parent())) { - has_container_parents = true; + Control *control = Object::cast_to<Control>(canvas_item); + if (control) { + real_t anchors[4]; + Vector2 pivot; + + pivot = control->get_pivot_offset(); + anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT); + anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT); + anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP); + anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM); + + if (pivot != se->prev_pivot || anchors[SIDE_LEFT] != se->prev_anchors[SIDE_LEFT] || anchors[SIDE_RIGHT] != se->prev_anchors[SIDE_RIGHT] || anchors[SIDE_TOP] != se->prev_anchors[SIDE_TOP] || anchors[SIDE_BOTTOM] != se->prev_anchors[SIDE_BOTTOM]) { + se->prev_pivot = pivot; + se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT]; + se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT]; + se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP]; + se->prev_anchors[SIDE_BOTTOM] = anchors[SIDE_BOTTOM]; + viewport->update(); + } } - } - if (canvas_item->_edit_use_pivot()) { - nb_having_pivot++; + if (canvas_item->_edit_use_pivot()) { + nb_having_pivot++; + } } - } - // Activate / Deactivate the pivot tool - pivot_button->set_disabled(nb_having_pivot == 0); + // Activate / Deactivate the pivot tool + pivot_button->set_disabled(nb_having_pivot == 0); - // Show / Hide the layout and anchors mode buttons - if (nb_control > 0 && nb_control == selection.size()) { - presets_menu->set_visible(true); - anchor_mode_button->set_visible(true); + // Update the viewport if bones changes + for (KeyValue<BoneKey, BoneList> &E : bone_list) { + Object *b = ObjectDB::get_instance(E.key.from); + if (!b) { + viewport->update(); + break; + } - // Disable if the selected node is child of a container - if (has_container_parents) { - presets_menu->set_disabled(true); - presets_menu->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - anchor_mode_button->set_disabled(true); - anchor_mode_button->set_tooltip(TTR("Children of containers have their anchors and margins values overridden by their parent.")); - } else { - presets_menu->set_disabled(false); - presets_menu->set_tooltip(TTR("Presets for the anchors and margins values of a Control node.")); - anchor_mode_button->set_disabled(false); - anchor_mode_button->set_tooltip(TTR("When active, moving Control nodes changes their anchors instead of their margins.")); - } - } else { - presets_menu->set_visible(false); - anchor_mode_button->set_visible(false); - } + Node2D *b2 = Object::cast_to<Node2D>(b); + if (!b2 || !b2->is_inside_tree()) { + continue; + } - // Update the viewport if bones changes - for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { - Object *b = ObjectDB::get_instance(E->key().from); - if (!b) { - viewport->update(); - break; + Transform2D global_xform = b2->get_global_transform(); + + if (global_xform != E.value.xform) { + E.value.xform = global_xform; + viewport->update(); + } + + Bone2D *bone = Object::cast_to<Bone2D>(b); + if (bone && bone->get_length() != E.value.length) { + E.value.length = bone->get_length(); + viewport->update(); + } } + } break; - Node2D *b2 = Object::cast_to<Node2D>(b); - if (!b2 || !b2->is_inside_tree()) { - continue; + case NOTIFICATION_ENTER_TREE: { + select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); + for (int i = 0; i < 4; i++) { + select_sb->set_margin_size(Side(i), 4); + select_sb->set_default_margin(Side(i), 4); } - Transform2D global_xform = b2->get_global_transform(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); + _keying_changed(); + _update_editor_settings(); + } break; - if (global_xform != E->get().xform) { - E->get().xform = global_xform; - viewport->update(); - } + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); + _update_editor_settings(); + } break; - Bone2D *bone = Object::cast_to<Bone2D>(b); - if (bone && bone->get_length() != E->get().length) { - E->get().length = bone->get_length(); - viewport->update(); - } - } - } + case NOTIFICATION_VISIBILITY_CHANGED: { + if (!is_visible() && override_camera_button->is_pressed()) { + EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); - if (p_what == NOTIFICATION_ENTER_TREE) { - select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); - for (int i = 0; i < 4; i++) { - select_sb->set_margin_size(Side(i), 4); - select_sb->set_default_margin(Side(i), 4); - } - - AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); - _keying_changed(); - - } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - select_sb->set_texture(get_theme_icon(SNAME("EditorRect2D"), SNAME("EditorIcons"))); - } - - if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - list_select_button->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); - move_button->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); - scale_button->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); - rotate_button->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); - smart_snap_button->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - grid_snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); - snap_config_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - skeleton_menu->set_icon(get_theme_icon(SNAME("Bone"), SNAME("EditorIcons"))); - override_camera_button->set_icon(get_theme_icon(SNAME("Camera2D"), SNAME("EditorIcons"))); - pan_button->set_icon(get_theme_icon(SNAME("ToolPan"), SNAME("EditorIcons"))); - ruler_button->set_icon(get_theme_icon(SNAME("Ruler"), SNAME("EditorIcons"))); - pivot_button->set_icon(get_theme_icon(SNAME("EditPivot"), SNAME("EditorIcons"))); - select_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); - anchor_handle = get_theme_icon(SNAME("EditorControlAnchor"), SNAME("EditorIcons")); - lock_button->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); - unlock_button->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); - group_button->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); - ungroup_button->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); - key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); - key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); - key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); - key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); - key_auto_insert_button->set_icon(get_theme_icon(SNAME("AutoKey"), SNAME("EditorIcons"))); - // Use a different color for the active autokey icon to make them easier - // to distinguish from the other key icons at the top. On a light theme, - // the icon will be dark, so we need to lighten it before blending it - // with the red color. - const Color key_auto_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25); - key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55)); - animation_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - - _update_context_menu_stylebox(); - - presets_menu->set_icon(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); - - PopupMenu *p = presets_menu->get_popup(); - - p->clear(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_AND_OFFSETS_PRESET_CENTER); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE); - p->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE); - p->add_separator(); - p->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_WIDE); - p->add_icon_item(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons")), TTR("Keep Ratio"), ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO); - p->add_separator(); - p->add_submenu_item(TTR("Anchors only"), "Anchors"); - p->set_item_icon(21, get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - - anchors_popup->clear(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopLeft"), SNAME("EditorIcons")), TTR("Top Left"), ANCHORS_PRESET_TOP_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopRight"), SNAME("EditorIcons")), TTR("Top Right"), ANCHORS_PRESET_TOP_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomRight"), SNAME("EditorIcons")), TTR("Bottom Right"), ANCHORS_PRESET_BOTTOM_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomLeft"), SNAME("EditorIcons")), TTR("Bottom Left"), ANCHORS_PRESET_BOTTOM_LEFT); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftCenter"), SNAME("EditorIcons")), TTR("Center Left"), ANCHORS_PRESET_CENTER_LEFT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopCenter"), SNAME("EditorIcons")), TTR("Center Top"), ANCHORS_PRESET_CENTER_TOP); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightCenter"), SNAME("EditorIcons")), TTR("Center Right"), ANCHORS_PRESET_CENTER_RIGHT); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomCenter"), SNAME("EditorIcons")), TTR("Center Bottom"), ANCHORS_PRESET_CENTER_BOTTOM); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Center"), ANCHORS_PRESET_CENTER); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignLeftWide"), SNAME("EditorIcons")), TTR("Left Wide"), ANCHORS_PRESET_LEFT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignTopWide"), SNAME("EditorIcons")), TTR("Top Wide"), ANCHORS_PRESET_TOP_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignRightWide"), SNAME("EditorIcons")), TTR("Right Wide"), ANCHORS_PRESET_RIGHT_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignBottomWide"), SNAME("EditorIcons")), TTR("Bottom Wide"), ANCHORS_PRESET_BOTTOM_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlVcenterWide"), SNAME("EditorIcons")), TTR("VCenter Wide"), ANCHORS_PRESET_VCENTER_WIDE); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlHcenterWide"), SNAME("EditorIcons")), TTR("HCenter Wide"), ANCHORS_PRESET_HCENTER_WIDE); - anchors_popup->add_separator(); - anchors_popup->add_icon_item(get_theme_icon(SNAME("ControlAlignWide"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_WIDE); - - anchor_mode_button->set_icon(get_theme_icon(SNAME("Anchor"), SNAME("EditorIcons"))); - - info_overlay->get_theme()->set_stylebox("normal", "Label", get_theme_stylebox(SNAME("CanvasItemInfoOverlay"), SNAME("EditorStyles"))); - warning_child_of_container->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - warning_child_of_container->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts"))); - warning_child_of_container->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("main_size"), SNAME("EditorFonts"))); - } - - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - if (!is_visible() && override_camera_button->is_pressed()) { - EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); - - debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE); - override_camera_button->set_pressed(false); - } + debugger->set_camera_override(EditorDebuggerNode::OVERRIDE_NONE); + override_camera_button->set_pressed(false); + } + } break; } } void CanvasItemEditor::_selection_changed() { - // Update the anchors_mode - int nbValidControls = 0; - int nbAnchorsMode = 0; - List<Node *> selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control) { - continue; - } - if (Object::cast_to<Container>(control->get_parent())) { - continue; - } - - nbValidControls++; - if (control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_")) { - nbAnchorsMode++; - } - } - anchors_mode = (nbValidControls == nbAnchorsMode); - anchor_mode_button->set_pressed(anchors_mode); - if (!selected_from_canvas) { - drag_type = DRAG_NONE; + _reset_drag(); } selected_from_canvas = false; } void CanvasItemEditor::edit(CanvasItem *p_canvas_item) { Array selection = editor_selection->get_selected_nodes(); - if (selection.size() != 1 || (Node *)selection[0] != p_canvas_item) { - drag_type = DRAG_NONE; + if (selection.size() != 1 || Object::cast_to<Node>(selection[0]) != p_canvas_item) { + _reset_drag(); // Clear the selection editor_selection->clear(); //_clear_canvas_items(); @@ -3965,18 +3999,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("accent_color", "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; @@ -3989,13 +4011,13 @@ void CanvasItemEditor::_update_scrollbars() { Size2 vmin = v_scroll->get_minimum_size(); // Get the visible frame. - Size2 screen_rect = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + Size2 screen_rect = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height)); // Calculate scrollable area. Rect2 canvas_item_rect = Rect2(Point2(), screen_rect); - if (editor->is_inside_tree() && editor->get_edited_scene()) { - Rect2 content_rect = _get_encompassing_rect(editor->get_edited_scene()); + if (EditorNode::get_singleton()->is_inside_tree() && EditorNode::get_singleton()->get_edited_scene()) { + Rect2 content_rect = _get_encompassing_rect(EditorNode::get_singleton()->get_edited_scene()); canvas_item_rect.expand_to(content_rect.position); canvas_item_rect.expand_to(content_rect.position + content_rect.size); } @@ -4069,34 +4091,6 @@ void CanvasItemEditor::_update_scrollbars() { updating_scroll = false; } -void CanvasItemEditor::_popup_warning_depop(Control *p_control) { - ERR_FAIL_COND(!popup_temporarily_timers.has(p_control)); - - Timer *timer = popup_temporarily_timers[p_control]; - timer->queue_delete(); - p_control->hide(); - popup_temporarily_timers.erase(p_control); - info_overlay->set_offset(SIDE_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10); -} - -void CanvasItemEditor::_popup_warning_temporarily(Control *p_control, const double p_duration) { - Timer *timer; - if (!popup_temporarily_timers.has(p_control)) { - timer = memnew(Timer); - timer->connect("timeout", callable_mp(this, &CanvasItemEditor::_popup_warning_depop), varray(p_control)); - timer->set_one_shot(true); - add_child(timer); - - popup_temporarily_timers[p_control] = timer; - } else { - timer = popup_temporarily_timers[p_control]; - } - - timer->start(p_duration); - p_control->show(); - info_overlay->set_offset(SIDE_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10); -} - void CanvasItemEditor::_update_scroll(real_t) { if (updating_scroll) { return; @@ -4107,90 +4101,6 @@ void CanvasItemEditor::_update_scroll(real_t) { viewport->update(); } -void CanvasItemEditor::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - switch (p_preset) { - case PRESET_TOP_LEFT: - case PRESET_TOP_RIGHT: - case PRESET_BOTTOM_LEFT: - case PRESET_BOTTOM_RIGHT: - case PRESET_CENTER_LEFT: - case PRESET_CENTER_TOP: - case PRESET_CENTER_RIGHT: - case PRESET_CENTER_BOTTOM: - case PRESET_CENTER: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); - break; - case PRESET_LEFT_WIDE: - case PRESET_TOP_WIDE: - case PRESET_RIGHT_WIDE: - case PRESET_BOTTOM_WIDE: - case PRESET_VCENTER_WIDE: - case PRESET_HCENTER_WIDE: - case PRESET_WIDE: - undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); - break; - } - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); - - anchors_mode = false; - anchor_mode_button->set_pressed(anchors_mode); -} - -void CanvasItemEditor::_set_anchors_and_offsets_to_keep_ratio() { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors and Offsets")); - - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - Point2 top_left_anchor = _position_to_anchor(control, Point2()); - Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); - undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); - undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); - undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); - - bool use_anchors = control->has_meta("_edit_use_anchors_") && control->get_meta("_edit_use_anchors_"); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", use_anchors); - - anchors_mode = true; - anchor_mode_button->set_pressed(anchors_mode); - } - } - - undo_redo->commit_action(); -} - -void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) { - List<Node *> selection = editor_selection->get_selected_node_list(); - - undo_redo->create_action(TTR("Change Anchors")); - for (Node *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (control) { - undo_redo->add_do_method(control, "set_anchors_preset", p_preset); - undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); - } - } - - undo_redo->commit_action(); -} - void CanvasItemEditor::_zoom_on_position(real_t p_zoom, Point2 p_position) { p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM); @@ -4224,6 +4134,10 @@ void CanvasItemEditor::_update_zoom(real_t p_zoom) { _zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0); } +void CanvasItemEditor::_shortcut_zoom_set(real_t p_zoom) { + _zoom_on_position(p_zoom * MAX(1, EDSCALE), viewport->get_local_mouse_position()); +} + void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) { smart_snap_active = p_status; viewport->update(); @@ -4254,17 +4168,15 @@ void CanvasItemEditor::_button_tool_select(int p_index) { viewport->update(); _update_cursor(); - - // Request immediate refresh of cursor when using hot-keys to switch between tools - DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); - DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); } void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) { - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + 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()) { continue; } @@ -4277,13 +4189,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::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::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::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()) { @@ -4309,13 +4221,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::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::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::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); } } } @@ -4325,31 +4237,17 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Control *ctrl = Object::cast_to<Control>(canvas_item); if (key_pos) { - AnimationPlayerEditor::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::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::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); + te->insert_node_value_key(ctrl, "size", ctrl->get_size(), p_on_existing); } } } -} - -void CanvasItemEditor::_button_toggle_anchor_mode(bool p_status) { - List<CanvasItem *> selection = _get_edited_canvas_items(false, false); - for (CanvasItem *E : selection) { - Control *control = Object::cast_to<Control>(E); - if (!control || Object::cast_to<Container>(control->get_parent())) { - continue; - } - - control->set_meta("_edit_use_anchors_", p_status); - } - - anchors_mode = p_status; - viewport->update(); + te->commit_insert_queue(); } void CanvasItemEditor::_update_override_camera_button(bool p_game_running) { @@ -4366,12 +4264,6 @@ void CanvasItemEditor::_update_override_camera_button(bool p_game_running) { void CanvasItemEditor::_popup_callback(int p_op) { last_option = MenuOption(p_op); switch (p_op) { - case SHOW_GRID: { - show_grid = !show_grid; - int idx = view_menu->get_popup()->get_item_index(SHOW_GRID); - view_menu->get_popup()->set_item_checked(idx, show_grid); - viewport->update(); - } break; case SHOW_ORIGIN: { show_origin = !show_origin; int idx = view_menu->get_popup()->get_item_index(SHOW_ORIGIN); @@ -4448,7 +4340,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel); } break; case SNAP_CONFIGURE: { - ((SnapDialog *)snap_dialog)->set_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); + static_cast<SnapDialog *>(snap_dialog)->set_fields(grid_offset, grid_step, primary_grid_steps, snap_rotation_offset, snap_rotation_step, snap_scale_step); snap_dialog->popup_centered(Size2(220, 160) * EDSCALE); } break; case SKELETON_SHOW_BONES: { @@ -4503,8 +4395,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "update"); undo_redo->commit_action(); } break; case UNLOCK_SELECTED: { @@ -4525,8 +4417,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->add_do_method(this, "emit_signal", "item_lock_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_lock_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "update"); undo_redo->commit_action(); } break; case GROUP_SELECTED: { @@ -4547,8 +4439,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "update"); undo_redo->commit_action(); } break; case UNGROUP_SELECTED: { @@ -4569,110 +4461,10 @@ void CanvasItemEditor::_popup_callback(int p_op) { undo_redo->add_do_method(this, "emit_signal", "item_group_status_changed"); undo_redo->add_undo_method(this, "emit_signal", "item_group_status_changed"); } - undo_redo->add_do_method(viewport, "update", Variant()); - undo_redo->add_undo_method(viewport, "update", Variant()); + undo_redo->add_do_method(viewport, "update"); + undo_redo->add_undo_method(viewport, "update"); undo_redo->commit_action(); } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT: { - _set_anchors_and_offsets_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT: { - _set_anchors_and_offsets_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP: { - _set_anchors_and_offsets_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM: { - _set_anchors_and_offsets_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_AND_OFFSETS_PRESET_CENTER: { - _set_anchors_and_offsets_preset(PRESET_CENTER); - } break; - case ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE: { - _set_anchors_and_offsets_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE: { - _set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE: { - _set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE: { - _set_anchors_and_offsets_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_WIDE: { - _set_anchors_and_offsets_preset(Control::PRESET_WIDE); - } break; - case ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO: { - _set_anchors_and_offsets_to_keep_ratio(); - } break; - - case ANCHORS_PRESET_TOP_LEFT: { - _set_anchors_preset(PRESET_TOP_LEFT); - } break; - case ANCHORS_PRESET_TOP_RIGHT: { - _set_anchors_preset(PRESET_TOP_RIGHT); - } break; - case ANCHORS_PRESET_BOTTOM_LEFT: { - _set_anchors_preset(PRESET_BOTTOM_LEFT); - } break; - case ANCHORS_PRESET_BOTTOM_RIGHT: { - _set_anchors_preset(PRESET_BOTTOM_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_LEFT: { - _set_anchors_preset(PRESET_CENTER_LEFT); - } break; - case ANCHORS_PRESET_CENTER_RIGHT: { - _set_anchors_preset(PRESET_CENTER_RIGHT); - } break; - case ANCHORS_PRESET_CENTER_TOP: { - _set_anchors_preset(PRESET_CENTER_TOP); - } break; - case ANCHORS_PRESET_CENTER_BOTTOM: { - _set_anchors_preset(PRESET_CENTER_BOTTOM); - } break; - case ANCHORS_PRESET_CENTER: { - _set_anchors_preset(PRESET_CENTER); - } break; - case ANCHORS_PRESET_TOP_WIDE: { - _set_anchors_preset(PRESET_TOP_WIDE); - } break; - case ANCHORS_PRESET_LEFT_WIDE: { - _set_anchors_preset(PRESET_LEFT_WIDE); - } break; - case ANCHORS_PRESET_RIGHT_WIDE: { - _set_anchors_preset(PRESET_RIGHT_WIDE); - } break; - case ANCHORS_PRESET_BOTTOM_WIDE: { - _set_anchors_preset(PRESET_BOTTOM_WIDE); - } break; - case ANCHORS_PRESET_VCENTER_WIDE: { - _set_anchors_preset(PRESET_VCENTER_WIDE); - } break; - case ANCHORS_PRESET_HCENTER_WIDE: { - _set_anchors_preset(PRESET_HCENTER_WIDE); - } break; - case ANCHORS_PRESET_WIDE: { - _set_anchors_preset(Control::PRESET_WIDE); - } break; case ANIM_INSERT_KEY: case ANIM_INSERT_KEY_EXISTING: { @@ -4693,10 +4485,10 @@ void CanvasItemEditor::_popup_callback(int p_op) { case ANIM_COPY_POSE: { pose_clipboard.clear(); - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + 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()) { continue; } @@ -4739,10 +4531,10 @@ 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 (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + 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()) { continue; } @@ -4769,10 +4561,6 @@ void CanvasItemEditor::_popup_callback(int p_op) { if (key_pos) { ctrl->set_position(Point2()); } - /* - if (key_scale) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); - */ } } @@ -4812,12 +4600,12 @@ 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)")); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node2D *n2d = Object::cast_to<Node2D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node2D *n2d = Object::cast_to<Node2D>(E.key); Bone2D *new_bone = memnew(Bone2D); String new_bone_name = n2d->get_name(); @@ -4860,9 +4648,9 @@ void CanvasItemEditor::_focus_selection(int p_op) { Rect2 rect; int count = 0; - Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + 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) { continue; } @@ -4896,10 +4684,9 @@ void CanvasItemEditor::_focus_selection(int p_op) { }; if (p_op == VIEW_CENTER_TO_SELECTION) { - center = rect.position + rect.size / 2; - Vector2 offset = viewport->get_size() / 2 - editor->get_scene_root()->get_global_canvas_transform().xform(center); - view_offset.x -= Math::round(offset.x / zoom); - view_offset.y -= Math::round(offset.y / zoom); + center = rect.get_center(); + Vector2 offset = viewport->get_size() / 2 - EditorNode::get_singleton()->get_scene_root()->get_global_canvas_transform().xform(center); + view_offset -= (offset / zoom).round(); update_viewport(); } else { // VIEW_FRAME_TO_SELECTION @@ -4916,6 +4703,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); @@ -4949,7 +4741,7 @@ Dictionary CanvasItemEditor::get_state() const { state["snap_node_center"] = snap_node_center; state["snap_other_nodes"] = snap_other_nodes; state["snap_guides"] = snap_guides; - state["show_grid"] = show_grid; + state["grid_visibility"] = grid_visibility; state["show_origin"] = show_origin; state["show_viewport"] = show_viewport; state["show_rulers"] = show_rulers; @@ -5051,10 +4843,8 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { smartsnap_config_popup->set_item_checked(idx, snap_guides); } - if (state.has("show_grid")) { - show_grid = state["show_grid"]; - int idx = view_menu->get_popup()->get_item_index(SHOW_GRID); - view_menu->get_popup()->set_item_checked(idx, show_grid); + if (state.has("grid_visibility")) { + grid_visibility = (GridVisibility)(int)(state["grid_visibility"]); } if (state.has("show_origin")) { @@ -5135,31 +4925,32 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { viewport->update(); } -void CanvasItemEditor::add_control_to_info_overlay(Control *p_control) { +void CanvasItemEditor::add_control_to_menu_panel(Control *p_control) { ERR_FAIL_COND(!p_control); - p_control->set_h_size_flags(p_control->get_h_size_flags() & ~Control::SIZE_EXPAND_FILL); - info_overlay->add_child(p_control); - info_overlay->set_offset(SIDE_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10); + context_menu_hbox->add_child(p_control); } -void CanvasItemEditor::remove_control_from_info_overlay(Control *p_control) { - info_overlay->remove_child(p_control); - info_overlay->set_offset(SIDE_LEFT, (show_rulers ? RULER_WIDTH : 0) + 10); +void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) { + context_menu_hbox->remove_child(p_control); } -void CanvasItemEditor::add_control_to_menu_panel(Control *p_control) { - ERR_FAIL_COND(!p_control); +void CanvasItemEditor::add_control_to_left_panel(Control *p_control) { + left_panel_split->add_child(p_control); + left_panel_split->move_child(p_control, 0); +} - hbc_context_menu->add_child(p_control); +void CanvasItemEditor::add_control_to_right_panel(Control *p_control) { + right_panel_split->add_child(p_control); + right_panel_split->move_child(p_control, 1); } -void CanvasItemEditor::remove_control_from_menu_panel(Control *p_control) { - hbc_context_menu->remove_child(p_control); +void CanvasItemEditor::remove_control_from_left_panel(Control *p_control) { + left_panel_split->remove_child(p_control); } -HSplitContainer *CanvasItemEditor::get_palette_split() { - return palette_split; +void CanvasItemEditor::remove_control_from_right_panel(Control *p_control) { + right_panel_split->remove_child(p_control); } VSplitContainer *CanvasItemEditor::get_bottom_split() { @@ -5170,90 +4961,49 @@ void CanvasItemEditor::focus_selection() { _focus_selection(VIEW_CENTER_TO_SELECTION); } -CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { - key_pos = true; - key_rot = true; - key_scale = false; - - show_grid = false; - show_origin = true; - show_viewport = true; - show_helpers = false; - show_rulers = true; - show_guides = true; - show_transformation_gizmos = true; - show_edit_locks = true; +CanvasItemEditor::CanvasItemEditor() { zoom = 1.0 / MAX(1, EDSCALE); view_offset = Point2(-150 - RULER_WIDTH, -95 - RULER_WIDTH); previous_update_view_offset = view_offset; // Moves the view a little bit to the left so that (0,0) is visible. The values a relative to a 16/10 screen - grid_offset = Point2(); - grid_step = Point2(8, 8); // A power-of-two value works better as a default - primary_grid_steps = 8; // A power-of-two value works better as a default - grid_step_multiplier = 0; - snap_rotation_offset = 0; - snap_rotation_step = Math::deg2rad(15.0); - snap_scale_step = 0.1f; - smart_snap_active = false; - grid_snap_active = false; - snap_node_parent = true; - snap_node_anchors = true; - snap_node_sides = true; - snap_node_center = true; - snap_other_nodes = true; - snap_guides = true; - snap_rotation = false; - snap_scale = false; - snap_relative = false; - snap_pixel = false; + snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - selected_from_canvas = false; - anchors_mode = false; - - drag_type = DRAG_NONE; - drag_from = Vector2(); - drag_to = Vector2(); - dragged_guide_pos = Point2(); - dragged_guide_index = -1; - is_hovering_h_guide = false; - is_hovering_v_guide = false; - panning = false; - pan_pressed = false; - - ruler_tool_active = false; - ruler_tool_origin = Point2(); - - bone_last_frame = 0; - - tool = TOOL_SELECT; - undo_redo = p_editor->get_undo_redo(); - editor = p_editor; - editor_selection = p_editor->get_editor_selection(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->add_editor_plugin(this); editor_selection->connect("selection_changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); editor_selection->connect("selection_changed", callable_mp(this, &CanvasItemEditor::_selection_changed)); - editor->get_scene_tree_dock()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created)); - editor->get_scene_tree_dock()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); + SceneTreeDock::get_singleton()->connect("node_created", callable_mp(this, &CanvasItemEditor::_node_created)); + SceneTreeDock::get_singleton()->connect("add_node_used", callable_mp(this, &CanvasItemEditor::_reset_create_position)); - editor->call_deferred(SNAME("connect"), "play_pressed", Callable(this, "_update_override_camera_button"), make_binds(true)); - editor->call_deferred(SNAME("connect"), "stop_pressed", Callable(this, "_update_override_camera_button"), make_binds(false)); + 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); bottom_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); - palette_split = memnew(HSplitContainer); - bottom_split->add_child(palette_split); - palette_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); + left_panel_split = memnew(HSplitContainer); + bottom_split->add_child(left_panel_split); + left_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); + + right_panel_split = memnew(HSplitContainer); + left_panel_split->add_child(right_panel_split); + right_panel_split->set_v_size_flags(Control::SIZE_EXPAND_FILL); viewport_scrollable = memnew(Control); - palette_split->add_child(viewport_scrollable); + right_panel_split->add_child(viewport_scrollable); viewport_scrollable->set_mouse_filter(MOUSE_FILTER_PASS); viewport_scrollable->set_clip_contents(true); viewport_scrollable->set_v_size_flags(Control::SIZE_EXPAND_FILL); @@ -5263,37 +5013,61 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { SubViewportContainer *scene_tree = memnew(SubViewportContainer); viewport_scrollable->add_child(scene_tree); scene_tree->set_stretch(true); - scene_tree->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - scene_tree->add_child(p_editor->get_scene_root()); + scene_tree->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + scene_tree->add_child(EditorNode::get_singleton()->get_scene_root()); controls_vb = memnew(VBoxContainer); controls_vb->set_begin(Point2(5, 5)); - viewport = memnew(CanvasItemEditorViewport(p_editor, this)); + // To ensure that scripts can parse the list of shortcuts correctly, we have to define + // those shortcuts one by one. Define shortcut before using it (by EditorZoomWidget). + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_5), int32_t(KeyModifierMask::SHIFT | Key::KP_5) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_4), int32_t(KeyModifierMask::SHIFT | Key::KP_4) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_3), int32_t(KeyModifierMask::SHIFT | Key::KP_3) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_2), int32_t(KeyModifierMask::SHIFT | Key::KP_2) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), + { int32_t(KeyModifierMask::SHIFT | Key::KEY_1), int32_t(KeyModifierMask::SHIFT | Key::KP_1) }); + + ED_SHORTCUT_ARRAY("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), + { int32_t(Key::KEY_1), int32_t(KeyModifierMask::CMD | 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); + zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom)); + + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &CanvasItemEditor::_scroll_callback), callable_mp(this, &CanvasItemEditor::_pan_callback), callable_mp(this, &CanvasItemEditor::_zoom_callback)); + + viewport = memnew(CanvasItemEditorViewport(this)); viewport_scrollable->add_child(viewport); viewport->set_mouse_filter(MOUSE_FILTER_PASS); - viewport->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + viewport->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); viewport->set_clip_contents(true); viewport->set_focus_mode(FOCUS_ALL); viewport->connect("draw", callable_mp(this, &CanvasItemEditor::_draw_viewport)); viewport->connect("gui_input", callable_mp(this, &CanvasItemEditor::_gui_input_viewport)); - - info_overlay = memnew(VBoxContainer); - info_overlay->set_anchors_and_offsets_preset(Control::PRESET_BOTTOM_LEFT); - info_overlay->set_offset(SIDE_LEFT, 10); - info_overlay->set_offset(SIDE_BOTTOM, -15); - info_overlay->set_v_grow_direction(Control::GROW_DIRECTION_BEGIN); - info_overlay->add_theme_constant_override("separation", 10); - viewport_scrollable->add_child(info_overlay); - - // Make sure all labels inside of the container are styled the same. - Theme *info_overlay_theme = memnew(Theme); - info_overlay->set_theme(info_overlay_theme); - - warning_child_of_container = memnew(Label); - warning_child_of_container->hide(); - warning_child_of_container->set_text(TTR("Warning: Children of a container get their position and size determined only by their parent.")); - add_control_to_info_overlay(warning_child_of_container); + viewport->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); h_scroll = memnew(HScrollBar); viewport->add_child(h_scroll); @@ -5307,116 +5081,109 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { viewport->add_child(controls_vb); - zoom_widget = memnew(EditorZoomWidget); - controls_vb->add_child(zoom_widget); - zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); - zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom)); - - updating_scroll = false; - // Add some margin to the left for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. Control *margin_left = memnew(Control); - hb->add_child(margin_left); + main_menu_hbox->add_child(margin_left); margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE); select_button = memnew(Button); select_button->set_flat(true); - hb->add_child(select_button); + main_menu_hbox->add_child(select_button); select_button->set_toggle_mode(true); - select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_SELECT)); + select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_SELECT)); select_button->set_pressed(true); - select_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/select_mode", TTR("Select Mode"), KEY_Q)); + select_button->set_shortcut(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_MASK_CMD) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+Drag: Move 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_MASK_CMD) + TTR("RMB: Add node at position clicked.")); + 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->set_shortcut(ED_SHORTCUT("canvas_item_editor/move_mode", TTR("Move Mode"), KEY_W)); + 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->set_shortcut(ED_SHORTCUT("canvas_item_editor/rotate_mode", TTR("Rotate Mode"), KEY_E)); + 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->set_shortcut(ED_SHORTCUT("canvas_item_editor/scale_mode", TTR("Scale Mode"), KEY_S)); + 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("Scale Mode")); + 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->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTR("Pan Mode"), KEY_G)); + 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("Pan Mode")); + 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->set_shortcut(ED_SHORTCUT("canvas_item_editor/ruler_mode", TTR("Ruler Mode"), KEY_R)); + 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.")); - smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTR("Use Smart Snap"), KEY_MASK_SHIFT | KEY_S)); + smart_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_smart_snap", TTR("Use Smart Snap"), KeyModifierMask::SHIFT | Key::S)); smart_snap_button->set_shortcut_context(this); grid_snap_button = memnew(Button); grid_snap_button->set_flat(true); - hb->add_child(grid_snap_button); + main_menu_hbox->add_child(grid_snap_button); grid_snap_button->set_toggle_mode(true); grid_snap_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_grid_snap)); grid_snap_button->set_tooltip(TTR("Toggle grid snapping.")); - grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTR("Use Grid Snap"), KEY_MASK_SHIFT | KEY_G)); + grid_snap_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/use_grid_snap", TTR("Use Grid Snap"), KeyModifierMask::SHIFT | Key::G)); grid_snap_button->set_shortcut_context(this); snap_config_menu = memnew(MenuButton); snap_config_menu->set_shortcut_context(this); - hb->add_child(snap_config_menu); + main_menu_hbox->add_child(snap_config_menu); snap_config_menu->set_h_size_flags(SIZE_SHRINK_END); snap_config_menu->set_tooltip(TTR("Snapping Options")); snap_config_menu->set_switch_on_hover(true); @@ -5445,46 +5212,46 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_other_nodes", TTR("Snap to Other Nodes")), SNAP_USE_OTHER_NODES); smartsnap_config_popup->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/snap_guides", TTR("Snap to Guides")), SNAP_USE_GUIDES); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); lock_button = memnew(Button); lock_button->set_flat(true); - hb->add_child(lock_button); + main_menu_hbox->add_child(lock_button); - lock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(LOCK_SELECTED)); + lock_button->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)"), KEY_MASK_CMD | KEY_L)); + 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)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_L)); + 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)"), KEY_MASK_CMD | KEY_G)); + 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)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G)); + 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); @@ -5492,81 +5259,70 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p->set_hide_on_checkable_item_selection(false); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTR("Show Bones")), SKELETON_SHOW_BONES); p->add_separator(); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Bone2D Node(s) from Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B), SKELETON_MAKE_BONES); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Bone2D Node(s) from Node(s)"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::B), SKELETON_MAKE_BONES); p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); override_camera_button = memnew(Button); override_camera_button->set_flat(true); - hb->add_child(override_camera_button); + main_menu_hbox->add_child(override_camera_button); override_camera_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_override_camera)); override_camera_button->set_toggle_mode(true); override_camera_button->set_disabled(true); _update_override_camera_button(false); - hb->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); view_menu = memnew(MenuButton); - view_menu->set_shortcut_context(this); + // TRANSLATORS: Noun, name of the 2D/3D View menus. view_menu->set_text(TTR("View")); - hb->add_child(view_menu); - view_menu->get_popup()->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); view_menu->set_switch_on_hover(true); + view_menu->set_shortcut_context(this); + main_menu_hbox->add_child(view_menu); + view_menu->get_popup()->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); p = view_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_NUMBERSIGN), SHOW_GRID); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS); + + grid_menu = memnew(PopupMenu); + grid_menu->connect("about_to_popup", callable_mp(this, &CanvasItemEditor::_prepare_grid_menu)); + grid_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_on_grid_menu_id_pressed)); + grid_menu->set_name("GridMenu"); + grid_menu->add_radio_check_item(TTR("Show"), GRID_VISIBILITY_SHOW); + grid_menu->add_radio_check_item(TTR("Show When Snapping"), GRID_VISIBILITY_SHOW_WHEN_SNAPPING); + grid_menu->add_radio_check_item(TTR("Hide"), GRID_VISIBILITY_HIDE); + grid_menu->add_separator(); + grid_menu->add_shortcut(ED_SHORTCUT("canvas_item_editor/toggle_grid", TTR("Toggle Grid"), KeyModifierMask::CMD | Key::APOSTROPHE)); + p->add_child(grid_menu); + p->add_submenu_item(TTR("Grid"), "GridMenu"); + + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), Key::H), SHOW_HELPERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), Key::Y), SHOW_GUIDES); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_origin", TTR("Show Origin")), SHOW_ORIGIN); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_viewport", TTR("Show Viewport")), SHOW_VIEWPORT); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_edit_locks", TTR("Show Group And Lock Icons")), SHOW_EDIT_LOCKS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_transformation_gizmos", TTR("Show Transformation Gizmos")), SHOW_TRANSFORMATION_GIZMOS); p->add_separator(); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/center_selection", TTR("Center Selection"), KEY_F), VIEW_CENTER_TO_SELECTION); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/frame_selection", TTR("Frame Selection"), KEY_MASK_SHIFT | KEY_F), VIEW_FRAME_TO_SELECTION); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/center_selection", TTR("Center Selection"), Key::F), VIEW_CENTER_TO_SELECTION); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/frame_selection", TTR("Frame Selection"), KeyModifierMask::SHIFT | Key::F), VIEW_FRAME_TO_SELECTION); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/clear_guides", TTR("Clear Guides")), CLEAR_GUIDES); p->add_separator(); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_P), PREVIEW_CANVAS_SCALE); - - hb->add_child(memnew(VSeparator)); - - context_menu_container = memnew(PanelContainer); - hbc_context_menu = memnew(HBoxContainer); - context_menu_container->add_child(hbc_context_menu); - // Use a custom stylebox to make contextual menu items stand out from the rest. - // This helps with editor usability as contextual menu items change when selecting nodes, - // even though it may not be immediately obvious at first. - hb->add_child(context_menu_container); - _update_context_menu_stylebox(); - - presets_menu = memnew(MenuButton); - presets_menu->set_shortcut_context(this); - presets_menu->set_text(TTR("Layout")); - hbc_context_menu->add_child(presets_menu); - presets_menu->hide(); - presets_menu->set_switch_on_hover(true); - - p = presets_menu->get_popup(); - p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale"), KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::P), PREVIEW_CANVAS_SCALE); - anchors_popup = memnew(PopupMenu); - p->add_child(anchors_popup); - anchors_popup->set_name("Anchors"); - anchors_popup->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); + main_menu_hbox->add_child(memnew(VSeparator)); - anchor_mode_button = memnew(Button); - anchor_mode_button->set_flat(true); - hbc_context_menu->add_child(anchor_mode_button); - anchor_mode_button->set_toggle_mode(true); - anchor_mode_button->hide(); - anchor_mode_button->connect("toggled", callable_mp(this, &CanvasItemEditor::_button_toggle_anchor_mode)); + // Contextual toolbars. + context_menu_panel = memnew(PanelContainer); + context_menu_hbox = memnew(HBoxContainer); + context_menu_panel->add_child(context_menu_hbox); + main_flow->add_child(context_menu_panel); + // Animation controls. animation_hb = memnew(HBoxContainer); - hbc_context_menu->add_child(animation_hb); + context_menu_hbox->add_child(animation_hb); animation_hb->add_child(memnew(VSeparator)); animation_hb->hide(); @@ -5575,7 +5331,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_loc_button->set_toggle_mode(true); key_loc_button->set_pressed(true); key_loc_button->set_focus_mode(FOCUS_NONE); - key_loc_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_POS)); + key_loc_button->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); @@ -5584,7 +5340,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_rot_button->set_toggle_mode(true); key_rot_button->set_pressed(true); key_rot_button->set_focus_mode(FOCUS_NONE); - key_rot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_ROT)); + key_rot_button->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); @@ -5592,16 +5348,16 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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(ED_SHORTCUT("canvas_item_editor/anim_insert_key", TTR("Insert Key"), Key::INSERT)); key_insert_button->set_shortcut_context(this); animation_hb->add_child(key_insert_button); @@ -5624,11 +5380,11 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p = animation_menu->get_popup(); p->add_shortcut(ED_GET_SHORTCUT("canvas_item_editor/anim_insert_key"), ANIM_INSERT_KEY); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key_existing_tracks", TTR("Insert Key (Existing Tracks)"), KEY_MASK_CMD + KEY_INSERT), ANIM_INSERT_KEY_EXISTING); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_insert_key_existing_tracks", TTR("Insert Key (Existing Tracks)"), KeyModifierMask::CMD + Key::INSERT), ANIM_INSERT_KEY_EXISTING); p->add_separator(); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_copy_pose", TTR("Copy Pose")), ANIM_COPY_POSE); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_paste_pose", TTR("Paste Pose")), ANIM_PASTE_POSE); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_clear_pose", TTR("Clear Pose"), KEY_MASK_SHIFT | KEY_K), ANIM_CLEAR_POSE); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/anim_clear_pose", TTR("Clear Pose"), KeyModifierMask::SHIFT | Key::K), ANIM_CLEAR_POSE); snap_dialog = memnew(SnapDialog); snap_dialog->connect("confirmed", callable_mp(this, &CanvasItemEditor::_snap_changed)); @@ -5640,37 +5396,21 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { add_child(selection_menu); selection_menu->set_min_size(Vector2(100, 0)); selection_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_selection_result_pressed)); - selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide)); + selection_menu->connect("popup_hide", callable_mp(this, &CanvasItemEditor::_selection_menu_hide), CONNECT_DEFERRED); add_node_menu = memnew(PopupMenu); add_child(add_node_menu); - add_node_menu->add_icon_item(editor->get_scene_tree_dock()->get_theme_icon(SNAME("Add"), SNAME("EditorIcons")), TTR("Add Node Here")); - add_node_menu->add_icon_item(editor->get_scene_tree_dock()->get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Instance Scene Here")); add_node_menu->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_add_node_pressed)); - multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), KEY_KP_MULTIPLY); - divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), KEY_KP_DIVIDE); - pan_view_shortcut = ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), KEY_SPACE); + multiply_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/multiply_grid_step", TTR("Multiply grid step by 2"), Key::KP_MULTIPLY); + divide_grid_step_shortcut = ED_SHORTCUT("canvas_item_editor/divide_grid_step", TTR("Divide grid step by 2"), Key::KP_DIVIDE); skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true); + + // Store the singleton instance. singleton = this; - // To ensure that scripts can parse the list of shortcuts correctly, we have to define - // those shortcuts one by one. - // Resetting zoom to 100% is a duplicate shortcut of `canvas_item_editor/reset_zoom`, - // but it ensures both 1 and Ctrl + 0 can be used to reset zoom. - ED_SHORTCUT("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), KEY_MASK_SHIFT | KEY_5); - ED_SHORTCUT("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), KEY_MASK_SHIFT | KEY_4); - ED_SHORTCUT("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), KEY_MASK_SHIFT | KEY_3); - ED_SHORTCUT("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), KEY_MASK_SHIFT | KEY_2); - ED_SHORTCUT("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), KEY_MASK_SHIFT | KEY_1); - ED_SHORTCUT("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), KEY_1); - ED_SHORTCUT("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"), KEY_2); - ED_SHORTCUT("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"), KEY_3); - ED_SHORTCUT("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"), KEY_4); - ED_SHORTCUT("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"), KEY_5); - - set_process_unhandled_key_input(true); + set_process_shortcut_input(true); // Update the menus' checkboxes call_deferred(SNAME("set_state"), get_state()); @@ -5691,12 +5431,12 @@ void CanvasItemEditorPlugin::make_visible(bool p_visible) { if (p_visible) { canvas_item_editor->show(); canvas_item_editor->set_physics_process(true); - RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), false); + RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), false); } else { canvas_item_editor->hide(); canvas_item_editor->set_physics_process(false); - RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), true); + RenderingServer::get_singleton()->viewport_set_disable_2d(EditorNode::get_singleton()->get_scene_root()->get_viewport_rid(), true); } } @@ -5708,12 +5448,11 @@ void CanvasItemEditorPlugin::set_state(const Dictionary &p_state) { canvas_item_editor->set_state(p_state); } -CanvasItemEditorPlugin::CanvasItemEditorPlugin(EditorNode *p_node) { - editor = p_node; - canvas_item_editor = memnew(CanvasItemEditor(editor)); +CanvasItemEditorPlugin::CanvasItemEditorPlugin() { + canvas_item_editor = memnew(CanvasItemEditor); canvas_item_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - editor->get_main_control()->add_child(canvas_item_editor); - canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + EditorNode::get_singleton()->get_main_control()->add_child(canvas_item_editor); + canvas_item_editor->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); canvas_item_editor->hide(); } @@ -5752,7 +5491,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)); @@ -5780,7 +5519,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons } if (add_preview) { - editor->get_scene_root()->add_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->add_child(preview_node); } } @@ -5791,7 +5530,7 @@ void CanvasItemEditorViewport::_remove_preview() { node->queue_delete(); preview_node->remove_child(node); } - editor->get_scene_root()->remove_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node); label->hide(); label_desc->hide(); @@ -5799,7 +5538,7 @@ void CanvasItemEditorViewport::_remove_preview() { } bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { - if (p_desired_node->get_filename() == p_target_scene_path) { + if (p_desired_node->get_scene_file_path() == p_target_scene_path) { return true; } @@ -5830,49 +5569,45 @@ 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); - editor_data->get_undo_redo().add_do_method(child, "set_owner", editor->get_edited_scene()); + 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(editor, "set_edited_scene", child); - editor_data->get_undo_redo().add_do_method(child, "set_owner", editor->get_edited_scene()); + editor_data->get_undo_redo().add_do_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(editor, "set_edited_scene", (Object *)nullptr); + 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", editor->get_edited_scene()->get_path_to(parent), child->get_class(), new_name); - editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + 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)); } - String node_class = child->get_class(); - if (node_class == "Polygon2D") { - editor_data->get_undo_redo().add_do_property(child, "texture/texture", texture); - } else if (node_class == "TouchScreenButton") { - editor_data->get_undo_redo().add_do_property(child, "normal", texture); - } else if (node_class == "TextureButton") { - editor_data->get_undo_redo().add_do_property(child, "texture_button", texture); + if (Object::cast_to<TouchScreenButton>(child) || Object::cast_to<TextureButton>(child)) { + editor_data->get_undo_redo().add_do_property(child, "texture_normal", texture); } else { editor_data->get_undo_redo().add_do_property(child, "texture", texture); } // make visible for certain node type - if (ClassDB::is_parent_class(node_class, "Control")) { + if (Object::cast_to<Control>(child)) { Size2 texture_size = texture->get_size(); editor_data->get_undo_redo().add_do_property(child, "rect_size", texture_size); - } else if (node_class == "Polygon2D") { + } else if (Object::cast_to<Polygon2D>(child)) { Size2 texture_size = texture->get_size(); - Vector<Vector2> list; - list.push_back(Vector2(0, 0)); - list.push_back(Vector2(texture_size.width, 0)); - list.push_back(Vector2(texture_size.width, texture_size.height)); - list.push_back(Vector2(0, texture_size.height)); + Vector<Vector2> list = { + Vector2(0, 0), + Vector2(texture_size.width, 0), + Vector2(texture_size.width, texture_size.height), + Vector2(0, texture_size.height) + }; editor_data->get_undo_redo().add_do_property(child, "polygon", list); } @@ -5896,35 +5631,39 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons return false; } - if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + + if (!edited_scene->get_scene_file_path().is_empty()) { // cyclical instancing + if (_cyclical_dependency_exists(edited_scene->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } } - instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); - editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); + editor_data->get_undo_redo().add_do_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", editor->get_edited_scene()->get_path_to(parent), path, new_name); - editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + 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 *parent_ci = Object::cast_to<CanvasItem>(parent); - if (parent_ci) { + CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); + if (instance_ci) { Vector2 target_pos = canvas_item_editor->get_canvas_transform().affine_inverse().xform(p_point); target_pos = canvas_item_editor->snap_point(target_pos); - target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos); - // Preserve instance position of the original scene. - CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); - if (instance_ci) { - target_pos += instance_ci->_edit_get_position(); + + CanvasItem *parent_ci = Object::cast_to<CanvasItem>(parent); + if (parent_ci) { + target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos); } + // 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); } @@ -5947,7 +5686,7 @@ void CanvasItemEditorViewport::_perform_drop_data() { 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; } @@ -5993,29 +5732,30 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian if (String(d["type"]) == "files") { Vector<String> files = d["files"]; bool can_instantiate = false; - for (int i = 0; i < files.size(); i++) { // check if dragged files contain resource or scene can be created at least once - RES res = ResourceLoader::load(files[i]); - if (res.is_null()) { - continue; - } - String type = res->get_class(); - if (type == "PackedScene") { - Ref<PackedScene> sdata = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instantiated_scene) { + + List<String> scene_extensions; + ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions); + List<String> texture_extensions; + ResourceLoader::get_recognized_extensions_for_type("Texture2D", &texture_extensions); + + for (int i = 0; i < files.size(); i++) { + // 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())) { + Ref<Resource> res = ResourceLoader::load(files[i]); + if (res.is_null()) { continue; } - memdelete(instantiated_scene); - } else if (ClassDB::is_parent_class(type, "Texture2D")) { - Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(*res)); - if (!texture.is_valid()) { - continue; + Ref<PackedScene> scn = res; + if (scn.is_valid()) { + Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!instantiated_scene) { + continue; + } + memdelete(instantiated_scene); } - } else { - continue; + can_instantiate = true; + break; } - can_instantiate = true; - break; } if (can_instantiate) { if (!preview_node->get_parent()) { // create preview only once @@ -6056,9 +5796,9 @@ bool CanvasItemEditorViewport::_only_packed_scenes_selected() const { } void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p_data) { - bool is_shift = Input::get_singleton()->is_key_pressed(KEY_SHIFT); - bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); - bool is_alt = Input::get_singleton()->is_key_pressed(KEY_ALT); + bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT); + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); + bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT); selected_files.clear(); Dictionary d = p_data; @@ -6069,8 +5809,8 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p return; } - List<Node *> selected_nodes = editor->get_editor_selection()->get_selected_node_list(); - Node *root_node = editor->get_edited_scene(); + List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + Node *root_node = EditorNode::get_singleton()->get_edited_scene(); if (selected_nodes.size() > 0) { Node *selected_node = selected_nodes[0]; target_node = root_node; @@ -6083,7 +5823,6 @@ void CanvasItemEditorViewport::drop_data(const Point2 &p_point, const Variant &p if (root_node) { target_node = root_node; } else { - drop_pos = p_point; target_node = nullptr; } } @@ -6121,25 +5860,39 @@ Node *CanvasItemEditorViewport::_make_texture_node_type(String texture_node_type return node; } +void CanvasItemEditorViewport::_update_theme() { + List<BaseButton *> btn_list; + button_group->get_buttons(&btn_list); + + for (int i = 0; i < btn_list.size(); i++) { + CheckBox *check = Object::cast_to<CheckBox>(btn_list[i]); + check->set_icon(get_theme_icon(check->get_text(), SNAME("EditorIcons"))); + } + + label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); +} + void CanvasItemEditorViewport::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + _update_theme(); + } break; + case NOTIFICATION_ENTER_TREE: { + _update_theme(); connect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit)); - label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor"))); } break; + case NOTIFICATION_EXIT_TREE: { disconnect("mouse_exited", callable_mp(this, &CanvasItemEditorViewport::_on_mouse_exit)); } break; - - default: - break; } } void CanvasItemEditorViewport::_bind_methods() { } -CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasItemEditor *p_canvas_item_editor) { +CanvasItemEditorViewport::CanvasItemEditorViewport(CanvasItemEditor *p_canvas_item_editor) { default_texture_node_type = "Sprite2D"; // Node2D texture_node_types.push_back("Sprite2D"); @@ -6154,16 +5907,15 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte texture_node_types.push_back("NinePatchRect"); target_node = nullptr; - editor = p_node; - editor_data = editor->get_scene_tree_dock()->get_editor_data(); + editor_data = SceneTreeDock::get_singleton()->get_editor_data(); canvas_item_editor = p_canvas_item_editor; preview_node = memnew(Control); accept = memnew(AcceptDialog); - editor->get_gui_base()->add_child(accept); + EditorNode::get_singleton()->get_gui_base()->add_child(accept); selector = memnew(AcceptDialog); - editor->get_gui_base()->add_child(selector); + EditorNode::get_singleton()->get_gui_base()->add_child(selector); selector->set_title(TTR("Change Default Type")); selector->connect("confirmed", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_confirmed)); selector->connect("cancelled", callable_mp(this, &CanvasItemEditorViewport::_on_change_type_closed)); @@ -6176,27 +5928,27 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte btn_group = memnew(VBoxContainer); vbc->add_child(btn_group); - btn_group->set_h_size_flags(0); + btn_group->set_h_size_flags(SIZE_EXPAND_FILL); button_group.instantiate(); for (int i = 0; i < texture_node_types.size(); i++) { CheckBox *check = memnew(CheckBox); btn_group->add_child(check); check->set_text(texture_node_types[i]); - check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type), varray(check)); + check->connect("button_down", callable_mp(this, &CanvasItemEditorViewport::_on_select_type).bind(check)); check->set_button_group(button_group); } label = memnew(Label); label->add_theme_color_override("font_shadow_color", Color(0, 0, 0, 1)); - label->add_theme_constant_override("shadow_as_outline", 1 * EDSCALE); + label->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE); label->hide(); canvas_item_editor->get_controls_container()->add_child(label); label_desc = memnew(Label); label_desc->add_theme_color_override("font_color", Color(0.6f, 0.6f, 0.6f, 1)); label_desc->add_theme_color_override("font_shadow_color", Color(0.2f, 0.2f, 0.2f, 1)); - label_desc->add_theme_constant_override("shadow_as_outline", 1 * EDSCALE); + label_desc->add_theme_constant_override("shadow_outline_size", 1 * EDSCALE); label_desc->add_theme_constant_override("line_spacing", 0); label_desc->hide(); canvas_item_editor->get_controls_container()->add_child(label_desc); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 1965efbf30..04fd819dec 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,10 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef CONTROL_EDITOR_PLUGIN_H -#define CONTROL_EDITOR_PLUGIN_H +#ifndef CANVAS_ITEM_EDITOR_PLUGIN_H +#define CANVAS_ITEM_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_zoom_widget.h" #include "scene/gui/box_container.h" @@ -39,9 +38,13 @@ #include "scene/gui/label.h" #include "scene/gui/panel_container.h" #include "scene/gui/spin_box.h" +#include "scene/gui/split_container.h" +#include "scene/gui/texture_rect.h" #include "scene/main/canvas_item.h" +class EditorData; class CanvasItemEditorViewport; +class ViewPanner; class CanvasItemEditorSelectedItem : public Object { GDCLASS(CanvasItemEditorSelectedItem, Object); @@ -83,11 +86,11 @@ public: enum AddNodeOption { ADD_NODE, ADD_INSTANCE, + ADD_PASTE, + ADD_MOVE, }; private: - EditorNode *editor; - enum SnapTarget { SNAP_TARGET_NONE = 0, SNAP_TARGET_PARENT, @@ -113,7 +116,6 @@ private: SNAP_RELATIVE, SNAP_CONFIGURE, SNAP_USE_PIXEL, - SHOW_GRID, SHOW_HELPERS, SHOW_RULERS, SHOW_GUIDES, @@ -125,55 +127,6 @@ private: UNLOCK_SELECTED, GROUP_SELECTED, UNGROUP_SELECTED, - ANCHORS_AND_OFFSETS_PRESET_TOP_LEFT, - ANCHORS_AND_OFFSETS_PRESET_TOP_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_LEFT, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_LEFT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_RIGHT, - ANCHORS_AND_OFFSETS_PRESET_CENTER_TOP, - ANCHORS_AND_OFFSETS_PRESET_CENTER_BOTTOM, - ANCHORS_AND_OFFSETS_PRESET_CENTER, - ANCHORS_AND_OFFSETS_PRESET_TOP_WIDE, - ANCHORS_AND_OFFSETS_PRESET_LEFT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_RIGHT_WIDE, - ANCHORS_AND_OFFSETS_PRESET_BOTTOM_WIDE, - ANCHORS_AND_OFFSETS_PRESET_VCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_HCENTER_WIDE, - ANCHORS_AND_OFFSETS_PRESET_WIDE, - ANCHORS_AND_OFFSETS_PRESET_KEEP_RATIO, - ANCHORS_PRESET_TOP_LEFT, - ANCHORS_PRESET_TOP_RIGHT, - ANCHORS_PRESET_BOTTOM_LEFT, - ANCHORS_PRESET_BOTTOM_RIGHT, - ANCHORS_PRESET_CENTER_LEFT, - ANCHORS_PRESET_CENTER_RIGHT, - ANCHORS_PRESET_CENTER_TOP, - ANCHORS_PRESET_CENTER_BOTTOM, - ANCHORS_PRESET_CENTER, - ANCHORS_PRESET_TOP_WIDE, - ANCHORS_PRESET_LEFT_WIDE, - ANCHORS_PRESET_RIGHT_WIDE, - ANCHORS_PRESET_BOTTOM_WIDE, - ANCHORS_PRESET_VCENTER_WIDE, - ANCHORS_PRESET_HCENTER_WIDE, - ANCHORS_PRESET_WIDE, - OFFSETS_PRESET_TOP_LEFT, - OFFSETS_PRESET_TOP_RIGHT, - OFFSETS_PRESET_BOTTOM_LEFT, - OFFSETS_PRESET_BOTTOM_RIGHT, - OFFSETS_PRESET_CENTER_LEFT, - OFFSETS_PRESET_CENTER_RIGHT, - OFFSETS_PRESET_CENTER_TOP, - OFFSETS_PRESET_CENTER_BOTTOM, - OFFSETS_PRESET_CENTER, - OFFSETS_PRESET_TOP_WIDE, - OFFSETS_PRESET_LEFT_WIDE, - OFFSETS_PRESET_RIGHT_WIDE, - OFFSETS_PRESET_BOTTOM_WIDE, - OFFSETS_PRESET_VCENTER_WIDE, - OFFSETS_PRESET_HCENTER_WIDE, - OFFSETS_PRESET_WIDE, ANIM_INSERT_KEY, ANIM_INSERT_KEY_EXISTING, ANIM_INSERT_POS, @@ -221,70 +174,72 @@ private: DRAG_KEY_MOVE }; - bool selection_menu_additive_selection; + enum GridVisibility { + GRID_VISIBILITY_SHOW, + GRID_VISIBILITY_SHOW_WHEN_SNAPPING, + GRID_VISIBILITY_HIDE, + }; + + bool selection_menu_additive_selection = false; - Tool tool; - Control *viewport; - Control *viewport_scrollable; + Tool tool = TOOL_SELECT; + Control *viewport = nullptr; + Control *viewport_scrollable = nullptr; - HScrollBar *h_scroll; - VScrollBar *v_scroll; - HBoxContainer *hb; + HScrollBar *h_scroll = nullptr; + VScrollBar *v_scroll = nullptr; // Used for secondary menu items which are displayed depending on the currently selected node // (such as MeshInstance's "Mesh" menu). - PanelContainer *context_menu_container; - HBoxContainer *hbc_context_menu; - - Map<Control *, Timer *> popup_temporarily_timers; - - Label *warning_child_of_container; - VBoxContainer *info_overlay; + PanelContainer *context_menu_panel = nullptr; + HBoxContainer *context_menu_hbox = nullptr; Transform2D transform; - bool show_grid; - bool show_rulers; - bool show_guides; - bool show_origin; - bool show_viewport; - bool show_helpers; - bool show_edit_locks; - bool show_transformation_gizmos; - - real_t zoom; + GridVisibility grid_visibility = GRID_VISIBILITY_SHOW_WHEN_SNAPPING; + bool show_rulers = true; + bool show_guides = true; + bool show_origin = true; + bool show_viewport = true; + bool show_helpers = false; + bool show_edit_locks = true; + bool show_transformation_gizmos = true; + + real_t zoom = 1.0; Point2 view_offset; Point2 previous_update_view_offset; - bool selected_from_canvas; - bool anchors_mode; + bool selected_from_canvas = false; Point2 grid_offset; - Point2 grid_step; - int primary_grid_steps; - int grid_step_multiplier; - - real_t snap_rotation_step; - real_t snap_rotation_offset; - real_t snap_scale_step; - bool smart_snap_active; - bool grid_snap_active; - - bool snap_node_parent; - bool snap_node_anchors; - bool snap_node_sides; - bool snap_node_center; - bool snap_other_nodes; - bool snap_guides; - bool snap_rotation; - bool snap_scale; - bool snap_relative; - bool snap_pixel; - bool key_pos; - bool key_rot; - bool key_scale; - bool panning; - bool pan_pressed; - - bool ruler_tool_active; + Point2 grid_step = Point2(8, 8); // A power-of-two value works better as a default. + int primary_grid_steps = 8; + int grid_step_multiplier = 0; + + real_t snap_rotation_step = Math::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; + + bool snap_node_parent = true; + bool snap_node_anchors = true; + bool snap_node_sides = true; + bool snap_node_center = true; + bool snap_other_nodes = true; + bool snap_guides = true; + bool snap_rotation = false; + bool snap_scale = false; + bool snap_relative = false; + // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. + // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. + bool snap_pixel = true; + + bool key_pos = true; + bool key_rot = true; + bool key_scale = false; + + bool pan_pressed = false; + + bool ruler_tool_active = false; Point2 ruler_tool_origin; Point2 node_create_position; @@ -313,7 +268,7 @@ private: uint64_t last_pass = 0; }; - uint64_t bone_last_frame; + uint64_t bone_last_frame = 0; struct BoneKey { ObjectID from; @@ -327,7 +282,7 @@ private: } }; - Map<BoneKey, BoneList> bone_list; + HashMap<BoneKey, BoneList> bone_list; struct PoseClipboard { Vector2 pos; @@ -337,65 +292,61 @@ private: }; List<PoseClipboard> pose_clipboard; - Button *select_button; + Button *select_button = nullptr; - Button *move_button; - Button *scale_button; - Button *rotate_button; + Button *move_button = nullptr; + Button *scale_button = nullptr; + Button *rotate_button = nullptr; - Button *list_select_button; - Button *pivot_button; - Button *pan_button; + Button *list_select_button = nullptr; + Button *pivot_button = nullptr; + Button *pan_button = nullptr; - Button *ruler_button; + Button *ruler_button = nullptr; - Button *smart_snap_button; - Button *grid_snap_button; - MenuButton *snap_config_menu; - PopupMenu *smartsnap_config_popup; + Button *smart_snap_button = nullptr; + Button *grid_snap_button = nullptr; + MenuButton *snap_config_menu = nullptr; + PopupMenu *smartsnap_config_popup = nullptr; - Button *lock_button; - Button *unlock_button; + Button *lock_button = nullptr; + Button *unlock_button = nullptr; - Button *group_button; - Button *ungroup_button; + Button *group_button = nullptr; + Button *ungroup_button = nullptr; - MenuButton *skeleton_menu; - Button *override_camera_button; - MenuButton *view_menu; - HBoxContainer *animation_hb; - MenuButton *animation_menu; + MenuButton *skeleton_menu = nullptr; + Button *override_camera_button = nullptr; + MenuButton *view_menu = nullptr; + PopupMenu *grid_menu = nullptr; + HBoxContainer *animation_hb = nullptr; + MenuButton *animation_menu = nullptr; - MenuButton *presets_menu; - PopupMenu *anchors_and_margins_popup; - PopupMenu *anchors_popup; + Button *key_loc_button = nullptr; + Button *key_rot_button = nullptr; + Button *key_scale_button = nullptr; + Button *key_insert_button = nullptr; + Button *key_auto_insert_button = nullptr; - Button *anchor_mode_button; + PopupMenu *selection_menu = nullptr; + PopupMenu *add_node_menu = nullptr; - Button *key_loc_button; - Button *key_rot_button; - Button *key_scale_button; - Button *key_insert_button; - Button *key_auto_insert_button; - - PopupMenu *selection_menu; - PopupMenu *add_node_menu; - - Control *top_ruler; - Control *left_ruler; + Control *top_ruler = nullptr; + Control *left_ruler = nullptr; Point2 drag_start_origin; - DragType drag_type; - Point2 drag_from; - Point2 drag_to; + DragType drag_type = DRAG_NONE; + Point2 drag_from = Vector2(); + Point2 drag_to = Vector2(); Point2 drag_rotation_center; List<CanvasItem *> drag_selection; - int dragged_guide_index; - Point2 dragged_guide_pos; - bool is_hovering_h_guide; - bool is_hovering_v_guide; + int dragged_guide_index = -1; + Point2 dragged_guide_pos = Point2(); + 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; @@ -407,7 +358,13 @@ private: Ref<Shortcut> set_pivot_shortcut; Ref<Shortcut> multiply_grid_step_shortcut; Ref<Shortcut> divide_grid_step_shortcut; - Ref<Shortcut> pan_view_shortcut; + + Ref<ViewPanner> panner; + bool warped_panning = true; + int pan_speed = 20; + void _scroll_callback(Vector2 p_scroll_vec, bool p_alt); + void _pan_callback(Vector2 p_scroll_vec); + void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); bool _is_node_locked(const Node *p_node); bool _is_node_movable(const Node *p_node, bool p_popup_warning = false); @@ -417,11 +374,9 @@ private: void _find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); bool _select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append); - ConfirmationDialog *snap_dialog; - - CanvasItem *ref_item; + ConfirmationDialog *snap_dialog = nullptr; - void _add_canvas_item(CanvasItem *p_canvas_item); + CanvasItem *ref_item = nullptr; void _save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones = false); void _restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones = false); @@ -431,20 +386,23 @@ private: Vector2 _position_to_anchor(const Control *p_control, Vector2 position); void _popup_callback(int p_op); - bool updating_scroll; + bool updating_scroll = false; void _update_scroll(real_t); void _update_scrollbars(); - void _append_canvas_item(CanvasItem *p_item); void _snap_changed(); void _selection_result_pressed(int); void _selection_menu_hide(); void _add_node_pressed(int p_result); void _node_created(Node *p_node); void _reset_create_position(); + void _update_editor_settings(); + bool _is_grid_visible() const; + void _prepare_grid_menu(); + void _on_grid_menu_id_pressed(int p_id); - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; - List<CanvasItem *> _get_edited_canvas_items(bool retreive_locked = false, bool remove_canvas_item_if_parent_in_selection = true); + List<CanvasItem *> _get_edited_canvas_items(bool retrieve_locked = false, bool remove_canvas_item_if_parent_in_selection = true); Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); void _expand_encompassing_rect_using_children(Rect2 &r_rect, const Node *p_node, bool &r_first, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D(), bool include_locked_nodes = true); Rect2 _get_encompassing_rect(const Node *p_node); @@ -455,7 +413,7 @@ private: void _keying_changed(); - virtual void unhandled_key_input(const Ref<InputEvent> &p_ev) override; + virtual void shortcut_input(const Ref<InputEvent> &p_ev) override; void _draw_text_at_position(Point2 p_position, String p_string, Side p_side); void _draw_margin_at_position(int p_value, Point2 p_position, Side p_side); @@ -475,6 +433,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(); @@ -495,8 +454,8 @@ private: void _update_cursor(); void _selection_changed(); - void _focus_selection(int p_op); + void _reset_drag(); SnapTarget snap_target[2]; Transform2D snap_transform; @@ -518,16 +477,10 @@ private: const SnapTarget p_snap_target, List<const CanvasItem *> p_exceptions, const Node *p_current); - void _set_anchors_preset(Control::LayoutPreset p_preset); - void _set_offsets_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_preset(Control::LayoutPreset p_preset); - void _set_anchors_and_offsets_to_keep_ratio(); - - void _button_toggle_anchor_mode(bool p_status); - - VBoxContainer *controls_vb; - EditorZoomWidget *zoom_widget; + VBoxContainer *controls_vb = nullptr; + EditorZoomWidget *zoom_widget = nullptr; void _update_zoom(real_t p_zoom); + void _shortcut_zoom_set(real_t p_zoom); void _zoom_on_position(real_t p_zoom, Point2 p_position = Point2()); void _button_toggle_smart_snap(bool p_status); void _button_toggle_grid_snap(bool p_status); @@ -536,12 +489,9 @@ private: void _update_override_camera_button(bool p_game_running); - HSplitContainer *palette_split; - VSplitContainer *bottom_split; - - void _update_context_menu_stylebox(); - void _popup_warning_temporarily(Control *p_control, const double p_duration); - void _popup_warning_depop(Control *p_control); + HSplitContainer *left_panel_split = nullptr; + HSplitContainer *right_panel_split = nullptr; + VSplitContainer *bottom_split = nullptr; void _set_owner_for_node_and_children(Node *p_node, Node *p_owner); @@ -551,14 +501,6 @@ protected: void _notification(int p_what); static void _bind_methods(); - void end_drag(); - void box_selection_start(Point2 &click); - bool box_selection_end(); - - HBoxContainer *get_panel_hb() { return hb; } - - template <class P, class C> - void space_selected_items(); static CanvasItemEditor *singleton; @@ -588,10 +530,12 @@ public: void add_control_to_menu_panel(Control *p_control); void remove_control_from_menu_panel(Control *p_control); - void add_control_to_info_overlay(Control *p_control); - void remove_control_from_info_overlay(Control *p_control); + void add_control_to_left_panel(Control *p_control); + void remove_control_from_left_panel(Control *p_control); + + void add_control_to_right_panel(Control *p_control); + void remove_control_from_right_panel(Control *p_control); - HSplitContainer *get_palette_split(); VSplitContainer *get_bottom_split(); Control *get_viewport_control() { return viewport; } @@ -608,18 +552,15 @@ public: void focus_selection(); - bool is_anchors_mode_enabled() { return anchors_mode; }; - - EditorSelection *editor_selection; + EditorSelection *editor_selection = nullptr; - CanvasItemEditor(EditorNode *p_editor); + CanvasItemEditor(); }; class CanvasItemEditorPlugin : public EditorPlugin { GDCLASS(CanvasItemEditorPlugin, EditorPlugin); - CanvasItemEditor *canvas_item_editor; - EditorNode *editor; + CanvasItemEditor *canvas_item_editor = nullptr; public: virtual String get_name() const override { return "2D"; } @@ -632,7 +573,7 @@ public: CanvasItemEditor *get_canvas_item_editor() { return canvas_item_editor; } - CanvasItemEditorPlugin(EditorNode *p_node); + CanvasItemEditorPlugin(); ~CanvasItemEditorPlugin(); }; @@ -645,19 +586,18 @@ class CanvasItemEditorViewport : public Control { Vector<String> texture_node_types; Vector<String> selected_files; - Node *target_node; + Node *target_node = nullptr; Point2 drop_pos; - EditorNode *editor; - EditorData *editor_data; - CanvasItemEditor *canvas_item_editor; - Control *preview_node; - AcceptDialog *accept; - AcceptDialog *selector; - Label *selector_label; - Label *label; - Label *label_desc; - VBoxContainer *btn_group; + EditorData *editor_data = nullptr; + CanvasItemEditor *canvas_item_editor = nullptr; + Control *preview_node = nullptr; + AcceptDialog *accept = nullptr; + AcceptDialog *selector = nullptr; + Label *selector_label = nullptr; + Label *label = nullptr; + Label *label_desc = nullptr; + VBoxContainer *btn_group = nullptr; Ref<ButtonGroup> button_group; void _on_mouse_exit(); @@ -675,6 +615,7 @@ class CanvasItemEditorViewport : public Control { bool _create_instance(Node *parent, String &path, const Point2 &p_point); void _perform_drop_data(); void _show_resource_type_selector(); + void _update_theme(); static void _bind_methods(); @@ -685,8 +626,8 @@ public: virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override; virtual void drop_data(const Point2 &p_point, const Variant &p_data) override; - CanvasItemEditorViewport(EditorNode *p_node, CanvasItemEditor *p_canvas_item_editor); + CanvasItemEditorViewport(CanvasItemEditor *p_canvas_item_editor); ~CanvasItemEditorViewport(); }; -#endif +#endif // CANVAS_ITEM_EDITOR_PLUGIN_H diff --git a/editor/plugins/cast_2d_editor_plugin.cpp b/editor/plugins/cast_2d_editor_plugin.cpp new file mode 100644 index 0000000000..18c38e7ab8 --- /dev/null +++ b/editor/plugins/cast_2d_editor_plugin.cpp @@ -0,0 +1,155 @@ +/*************************************************************************/ +/* cast_2d_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "cast_2d_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" +#include "scene/2d/ray_cast_2d.h" +#include "scene/2d/shape_cast_2d.h" + +void Cast2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed)); + } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &Cast2DEditor::_node_removed)); + } break; + } +} + +void Cast2DEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + } +} + +bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + if (!node || !node->is_visible_in_tree()) { + return false; + } + + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + Vector2 target_position = node->get("target_position"); + + if (mb->is_pressed()) { + if (xform.xform(target_position).distance_to(mb->get_position()) < 8) { + pressed = true; + original_target_position = target_position; + + return true; + } else { + pressed = false; + + return false; + } + } else if (pressed) { + undo_redo->create_action(TTR("Set target_position")); + undo_redo->add_do_property(node, "target_position", target_position); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_property(node, "target_position", original_target_position); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + pressed = false; + + return true; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && pressed) { + Vector2 point = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())); + point = node->get_global_transform().affine_inverse().xform(point); + + node->set("target_position", point); + canvas_item_editor->update_viewport(); + node->notify_property_list_changed(); + + return true; + } + + return false; +} + +void Cast2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { + if (!node || !node->is_visible_in_tree()) { + return; + } + + Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + p_overlay->draw_texture(handle, gt.xform((Vector2)node->get("target_position")) - handle->get_size() / 2); +} + +void Cast2DEditor::edit(Node2D *p_node) { + if (!canvas_item_editor) { + canvas_item_editor = CanvasItemEditor::get_singleton(); + } + + if (Object::cast_to<RayCast2D>(p_node) || Object::cast_to<ShapeCast2D>(p_node)) { + node = p_node; + } else { + node = nullptr; + } + + canvas_item_editor->update_viewport(); +} + +Cast2DEditor::Cast2DEditor() { + undo_redo = EditorNode::get_singleton()->get_undo_redo(); +} + +/////////////////////// + +void Cast2DEditorPlugin::edit(Object *p_object) { + cast_2d_editor->edit(Object::cast_to<Node2D>(p_object)); +} + +bool Cast2DEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<RayCast2D>(p_object) != nullptr || Object::cast_to<ShapeCast2D>(p_object) != nullptr; +} + +void Cast2DEditorPlugin::make_visible(bool p_visible) { + if (!p_visible) { + edit(nullptr); + } +} + +Cast2DEditorPlugin::Cast2DEditorPlugin() { + cast_2d_editor = memnew(Cast2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(cast_2d_editor); +} diff --git a/editor/plugins/audio_stream_editor_plugin.h b/editor/plugins/cast_2d_editor_plugin.h index 14e829d025..d9c0cc4a06 100644 --- a/editor/plugins/audio_stream_editor_plugin.h +++ b/editor/plugins/cast_2d_editor_plugin.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* audio_stream_editor_plugin.h */ +/* cast_2d_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,66 +28,52 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef AUDIO_STREAM_EDITOR_PLUGIN_H -#define AUDIO_STREAM_EDITOR_PLUGIN_H +#ifndef CAST_2D_EDITOR_PLUGIN_H +#define CAST_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "scene/audio/audio_stream_player.h" -#include "scene/gui/color_rect.h" -#include "scene/resources/texture.h" +#include "scene/2d/node_2d.h" -class AudioStreamEditor : public ColorRect { - GDCLASS(AudioStreamEditor, ColorRect); +class CanvasItemEditor; - Ref<AudioStream> stream; - AudioStreamPlayer *_player = nullptr; - ColorRect *_preview = nullptr; - Control *_indicator = nullptr; - Label *_current_label = nullptr; - Label *_duration_label = nullptr; +class Cast2DEditor : public Control { + GDCLASS(Cast2DEditor, Control); - Button *_play_button = nullptr; - Button *_stop_button = nullptr; + UndoRedo *undo_redo = nullptr; + CanvasItemEditor *canvas_item_editor = nullptr; + Node2D *node; - float _current = 0; - bool _dragging = false; - bool _pausing = false; - - void _audio_changed(); + bool pressed = false; + Point2 original_target_position; 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(); + void _node_removed(Node *p_node); public: - void edit(Ref<AudioStream> p_stream); - AudioStreamEditor(); + bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); + void forward_canvas_draw_over_viewport(Control *p_overlay); + void edit(Node2D *p_node); + + Cast2DEditor(); }; -class AudioStreamEditorPlugin : public EditorPlugin { - GDCLASS(AudioStreamEditorPlugin, EditorPlugin); +class Cast2DEditorPlugin : public EditorPlugin { + GDCLASS(Cast2DEditorPlugin, EditorPlugin); - AudioStreamEditor *audio_editor; - EditorNode *editor; + Cast2DEditor *cast_2d_editor = nullptr; public: - virtual String get_name() const override { return "Audio"; } + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return cast_2d_editor->forward_canvas_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { cast_2d_editor->forward_canvas_draw_over_viewport(p_overlay); } + + virtual String get_name() const override { return "Cast2D"; } bool has_main_screen() const override { return false; } virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; + virtual void make_visible(bool visible) override; - AudioStreamEditorPlugin(EditorNode *p_node); - ~AudioStreamEditorPlugin(); + Cast2DEditorPlugin(); }; -#endif // AUDIO_STREAM_EDITOR_PLUGIN_H +#endif // CAST_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.cpp b/editor/plugins/collision_polygon_2d_editor_plugin.cpp index 8e340b28ef..f018376e4b 100644 --- a/editor/plugins/collision_polygon_2d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,11 +38,8 @@ void CollisionPolygon2DEditor::_set_node(Node *p_polygon) { node = Object::cast_to<CollisionPolygon2D>(p_polygon); } -CollisionPolygon2DEditor::CollisionPolygon2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +CollisionPolygon2DEditor::CollisionPolygon2DEditor() {} -CollisionPolygon2DEditorPlugin::CollisionPolygon2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(CollisionPolygon2DEditor(p_node)), "CollisionPolygon2D") { +CollisionPolygon2DEditorPlugin::CollisionPolygon2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(CollisionPolygon2DEditor), "CollisionPolygon2D") { } diff --git a/editor/plugins/collision_polygon_2d_editor_plugin.h b/editor/plugins/collision_polygon_2d_editor_plugin.h index e78c486a39..0225d5d620 100644 --- a/editor/plugins/collision_polygon_2d_editor_plugin.h +++ b/editor/plugins/collision_polygon_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,21 +37,21 @@ class CollisionPolygon2DEditor : public AbstractPolygon2DEditor { GDCLASS(CollisionPolygon2DEditor, AbstractPolygon2DEditor); - CollisionPolygon2D *node; + CollisionPolygon2D *node = nullptr; protected: virtual Node2D *_get_node() const override; virtual void _set_node(Node *p_polygon) override; public: - CollisionPolygon2DEditor(EditorNode *p_editor); + CollisionPolygon2DEditor(); }; class CollisionPolygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(CollisionPolygon2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - CollisionPolygon2DEditorPlugin(EditorNode *p_node); + CollisionPolygon2DEditorPlugin(); }; #endif // COLLISION_POLYGON_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index bfcc293625..af20064a8d 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,6 +32,7 @@ #include "canvas_item_editor_plugin.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "scene/resources/capsule_shape_2d.h" #include "scene/resources/circle_shape_2d.h" #include "scene/resources/concave_polygon_shape_2d.h" @@ -39,7 +40,7 @@ #include "scene/resources/rectangle_shape_2d.h" #include "scene/resources/segment_shape_2d.h" #include "scene/resources/separation_ray_shape_2d.h" -#include "scene/resources/world_margin_shape_2d.h" +#include "scene/resources/world_boundary_shape_2d.h" void CollisionShape2DEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -70,13 +71,13 @@ Variant CollisionShape2DEditor::get_handle_value(int idx) const { case CONVEX_POLYGON_SHAPE: { } break; - case WORLD_MARGIN_SHAPE: { - Ref<WorldMarginShape2D> line = node->get_shape(); + case WORLD_BOUNDARY_SHAPE: { + Ref<WorldBoundaryShape2D> world_boundary = node->get_shape(); if (idx == 0) { - return line->get_distance(); + return world_boundary->get_distance(); } else { - return line->get_normal(); + return world_boundary->get_normal(); } } break; @@ -147,14 +148,14 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) { case CONVEX_POLYGON_SHAPE: { } break; - case WORLD_MARGIN_SHAPE: { + case WORLD_BOUNDARY_SHAPE: { if (idx < 2) { - Ref<WorldMarginShape2D> line = node->get_shape(); + Ref<WorldBoundaryShape2D> world_boundary = node->get_shape(); if (idx == 0) { - line->set_distance(p_point.length()); + world_boundary->set_distance(p_point.length()); } else { - line->set_normal(p_point.normalized()); + world_boundary->set_normal(p_point.normalized()); } canvas_item_editor->update_viewport(); @@ -183,7 +184,7 @@ void CollisionShape2DEditor::set_handle(int idx, Point2 &p_point) { size.y = p_point.y * RECT_HANDLES[idx].y * 2; } - if (Input::get_singleton()->is_key_pressed(KEY_ALT)) { + if (Input::get_singleton()->is_key_pressed(Key::ALT)) { rect->set_size(size.abs()); node->set_global_position(original_transform.get_origin()); } else { @@ -255,18 +256,18 @@ void CollisionShape2DEditor::commit_handle(int idx, Variant &p_org) { // Cannot be edited directly, use CollisionPolygon2D instead. } break; - case WORLD_MARGIN_SHAPE: { - Ref<WorldMarginShape2D> line = node->get_shape(); + case WORLD_BOUNDARY_SHAPE: { + Ref<WorldBoundaryShape2D> world_boundary = node->get_shape(); if (idx == 0) { - undo_redo->add_do_method(line.ptr(), "set_distance", line->get_distance()); + undo_redo->add_do_method(world_boundary.ptr(), "set_distance", world_boundary->get_distance()); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); - undo_redo->add_undo_method(line.ptr(), "set_distance", p_org); + undo_redo->add_undo_method(world_boundary.ptr(), "set_distance", p_org); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); } else { - undo_redo->add_do_method(line.ptr(), "set_normal", line->get_normal()); + undo_redo->add_do_method(world_boundary.ptr(), "set_normal", world_boundary->get_normal()); undo_redo->add_do_method(canvas_item_editor, "update_viewport"); - undo_redo->add_undo_method(line.ptr(), "set_normal", p_org); + undo_redo->add_undo_method(world_boundary.ptr(), "set_normal", p_org); undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); } @@ -323,6 +324,10 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e return false; } + if (!node->is_visible_in_tree()) { + return false; + } + if (shape_type == -1) { return false; } @@ -333,7 +338,7 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e if (mb.is_valid()) { Vector2 gpoint = mb->get_position(); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { for (int i = 0; i < handles.size(); i++) { if (xform.xform(handles[i]).distance_to(gpoint) < 8) { @@ -394,7 +399,7 @@ bool CollisionShape2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_e return false; } - if (shape_type == RECTANGLE_SHAPE && k->get_keycode() == KEY_ALT) { + if (shape_type == RECTANGLE_SHAPE && k->get_keycode() == Key::ALT) { set_handle(edit_handle, last_point); // Update handle when Alt key is toggled. } } @@ -421,8 +426,8 @@ void CollisionShape2DEditor::_get_current_shape_type() { shape_type = CONCAVE_POLYGON_SHAPE; } else if (Object::cast_to<ConvexPolygonShape2D>(*s)) { shape_type = CONVEX_POLYGON_SHAPE; - } else if (Object::cast_to<WorldMarginShape2D>(*s)) { - shape_type = WORLD_MARGIN_SHAPE; + } else if (Object::cast_to<WorldBoundaryShape2D>(*s)) { + shape_type = WORLD_BOUNDARY_SHAPE; } else if (Object::cast_to<SeparationRayShape2D>(*s)) { shape_type = SEPARATION_RAY_SHAPE; } else if (Object::cast_to<RectangleShape2D>(*s)) { @@ -445,6 +450,10 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla return; } + if (!node->is_visible_in_tree()) { + return; + } + _get_current_shape_type(); if (shape_type == -1) { @@ -490,8 +499,8 @@ void CollisionShape2DEditor::forward_canvas_draw_over_viewport(Control *p_overla case CONVEX_POLYGON_SHAPE: { } break; - case WORLD_MARGIN_SHAPE: { - Ref<WorldMarginShape2D> shape = node->get_shape(); + case WORLD_BOUNDARY_SHAPE: { + Ref<WorldBoundaryShape2D> shape = node->get_shape(); handles.resize(2); handles.write[0] = shape->get_normal() * shape->get_distance(); @@ -574,12 +583,11 @@ void CollisionShape2DEditor::_bind_methods() { ClassDB::bind_method("_get_current_shape_type", &CollisionShape2DEditor::_get_current_shape_type); } -CollisionShape2DEditor::CollisionShape2DEditor(EditorNode *p_editor) { +CollisionShape2DEditor::CollisionShape2DEditor() { node = nullptr; canvas_item_editor = nullptr; - editor = p_editor; - undo_redo = p_editor->get_undo_redo(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); edit_handle = -1; pressed = false; @@ -601,11 +609,9 @@ void CollisionShape2DEditorPlugin::make_visible(bool visible) { } } -CollisionShape2DEditorPlugin::CollisionShape2DEditorPlugin(EditorNode *p_editor) { - editor = p_editor; - - collision_shape_2d_editor = memnew(CollisionShape2DEditor(p_editor)); - p_editor->get_gui_base()->add_child(collision_shape_2d_editor); +CollisionShape2DEditorPlugin::CollisionShape2DEditorPlugin() { + collision_shape_2d_editor = memnew(CollisionShape2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(collision_shape_2d_editor); } CollisionShape2DEditorPlugin::~CollisionShape2DEditorPlugin() { diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index 421e674df8..f7de05ddd1 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,7 @@ #ifndef COLLISION_SHAPE_2D_EDITOR_PLUGIN_H #define COLLISION_SHAPE_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" - #include "scene/2d/collision_shape_2d.h" class CanvasItemEditor; @@ -46,7 +44,7 @@ class CollisionShape2DEditor : public Control { CIRCLE_SHAPE, CONCAVE_POLYGON_SHAPE, CONVEX_POLYGON_SHAPE, - WORLD_MARGIN_SHAPE, + WORLD_BOUNDARY_SHAPE, SEPARATION_RAY_SHAPE, RECTANGLE_SHAPE, SEGMENT_SHAPE @@ -63,10 +61,9 @@ class CollisionShape2DEditor : public Control { Point2(1, -1), }; - EditorNode *editor; - UndoRedo *undo_redo; - CanvasItemEditor *canvas_item_editor; - CollisionShape2D *node; + UndoRedo *undo_redo = nullptr; + CanvasItemEditor *canvas_item_editor = nullptr; + CollisionShape2D *node = nullptr; Vector<Point2> handles; @@ -93,14 +90,13 @@ public: void forward_canvas_draw_over_viewport(Control *p_overlay); void edit(Node *p_node); - CollisionShape2DEditor(EditorNode *p_editor); + CollisionShape2DEditor(); }; class CollisionShape2DEditorPlugin : public EditorPlugin { GDCLASS(CollisionShape2DEditorPlugin, EditorPlugin); - CollisionShape2DEditor *collision_shape_2d_editor; - EditorNode *editor; + CollisionShape2DEditor *collision_shape_2d_editor = nullptr; public: virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return collision_shape_2d_editor->forward_canvas_gui_input(p_event); } @@ -112,8 +108,8 @@ public: virtual bool handles(Object *p_obj) const override; virtual void make_visible(bool visible) override; - CollisionShape2DEditorPlugin(EditorNode *p_editor); + CollisionShape2DEditorPlugin(); ~CollisionShape2DEditorPlugin(); }; -#endif //COLLISION_SHAPE_2D_EDITOR_PLUGIN_H +#endif // COLLISION_SHAPE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/control_editor_plugin.cpp b/editor/plugins/control_editor_plugin.cpp new file mode 100644 index 0000000000..2756e45cf4 --- /dev/null +++ b/editor/plugins/control_editor_plugin.cpp @@ -0,0 +1,1009 @@ +/*************************************************************************/ +/* 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 "control_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/plugins/canvas_item_editor_plugin.h" +#include "scene/gui/separator.h" + +void ControlPositioningWarning::_update_warning() { + if (!control_node) { + title_icon->set_texture(nullptr); + title_label->set_text(""); + hint_label->set_text(""); + return; + } + + Node *parent_node = control_node->get_parent_control(); + if (!parent_node) { + title_icon->set_texture(get_theme_icon(SNAME("SubViewport"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node doesn't have a control parent.")); + hint_label->set_text(TTR("Use the appropriate layout properties depending on where you are going to put it.")); + } else if (Object::cast_to<Container>(parent_node)) { + title_icon->set_texture(get_theme_icon(SNAME("Container"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a container.")); + hint_label->set_text(TTR("Use container properties for positioning.")); + } else { + title_icon->set_texture(get_theme_icon(SNAME("ControlLayout"), SNAME("EditorIcons"))); + title_label->set_text(TTR("This node is a child of a regular control.")); + hint_label->set_text(TTR("Use anchors and the rectangle for positioning.")); + } + + bg_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg_group_note"), SNAME("EditorProperty"))); +} + +void ControlPositioningWarning::_update_toggler() { + Ref<Texture2D> arrow; + if (hint_label->is_visible()) { + arrow = get_theme_icon(SNAME("arrow"), SNAME("Tree")); + set_tooltip(TTR("Collapse positioning hint.")); + } else { + if (is_layout_rtl()) { + arrow = get_theme_icon(SNAME("arrow_collapsed"), SNAME("Tree")); + } else { + arrow = get_theme_icon(SNAME("arrow_collapsed_mirrored"), SNAME("Tree")); + } + set_tooltip(TTR("Expand positioning hint.")); + } + + hint_icon->set_texture(arrow); +} + +void ControlPositioningWarning::set_control(Control *p_node) { + control_node = p_node; + _update_warning(); +} + +void ControlPositioningWarning::gui_input(const Ref<InputEvent> &p_event) { + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + bool state = !hint_label->is_visible(); + + hint_filler_left->set_visible(state); + hint_label->set_visible(state); + hint_filler_right->set_visible(state); + + _update_toggler(); + } +} + +void ControlPositioningWarning::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + _update_warning(); + _update_toggler(); + break; + } +} + +ControlPositioningWarning::ControlPositioningWarning() { + set_mouse_filter(MOUSE_FILTER_STOP); + + bg_panel = memnew(PanelContainer); + bg_panel->set_mouse_filter(MOUSE_FILTER_IGNORE); + add_child(bg_panel); + + grid = memnew(GridContainer); + grid->set_columns(3); + bg_panel->add_child(grid); + + title_icon = memnew(TextureRect); + title_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(title_icon); + + title_label = memnew(Label); + title_label->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD); + title_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + title_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + grid->add_child(title_label); + + hint_icon = memnew(TextureRect); + hint_icon->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED); + grid->add_child(hint_icon); + + // Filler. + hint_filler_left = memnew(Control); + hint_filler_left->hide(); + grid->add_child(hint_filler_left); + + hint_label = memnew(Label); + hint_label->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD); + hint_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hint_label->set_vertical_alignment(VerticalAlignment::VERTICAL_ALIGNMENT_CENTER); + hint_label->hide(); + grid->add_child(hint_label); + + // Filler. + hint_filler_right = memnew(Control); + hint_filler_right->hide(); + grid->add_child(hint_filler_right); +} + +void EditorPropertyAnchorsPreset::_set_read_only(bool p_read_only) { + options->set_disabled(p_read_only); +}; + +void EditorPropertyAnchorsPreset::_option_selected(int p_which) { + int64_t val = options->get_item_metadata(p_which); + emit_changed(get_edited_property(), val); +} + +void EditorPropertyAnchorsPreset::update_property() { + int64_t which = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < options->get_item_count(); i++) { + Variant val = options->get_item_metadata(i); + if (val != Variant() && which == (int64_t)val) { + options->select(i); + return; + } + } +} + +void EditorPropertyAnchorsPreset::setup(const Vector<String> &p_options) { + options->clear(); + + Vector<String> split_after; + split_after.append("Custom"); + split_after.append("PresetFullRect"); + split_after.append("PresetBottomLeft"); + split_after.append("PresetCenter"); + + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + + String option_name = text_split[0]; + if (option_name.begins_with("Preset")) { + String preset_name = option_name.trim_prefix("Preset"); + String humanized_name = preset_name.capitalize(); + String icon_name = "ControlAlign" + preset_name; + options->add_icon_item(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(icon_name, "EditorIcons"), humanized_name); + } else { + options->add_item(option_name); + } + + options->set_item_metadata(j, current_val); + if (split_after.has(option_name)) { + options->add_separator(); + j++; + } + } +} + +EditorPropertyAnchorsPreset::EditorPropertyAnchorsPreset() { + options = memnew(OptionButton); + options->set_clip_text(true); + options->set_flat(true); + add_child(options); + add_focusable(options); + options->connect("item_selected", callable_mp(this, &EditorPropertyAnchorsPreset::_option_selected)); +} + +void EditorPropertySizeFlags::_set_read_only(bool p_read_only) { + for (CheckBox *check : flag_checks) { + check->set_disabled(p_read_only); + } + flag_presets->set_disabled(p_read_only); +}; + +void EditorPropertySizeFlags::_preset_selected(int p_which) { + int preset = flag_presets->get_item_id(p_which); + if (preset == SIZE_FLAGS_PRESET_CUSTOM) { + flag_options->set_visible(true); + return; + } + flag_options->set_visible(false); + + uint32_t value = 0; + switch (preset) { + case SIZE_FLAGS_PRESET_FILL: + value = Control::SIZE_FILL; + break; + case SIZE_FLAGS_PRESET_SHRINK_BEGIN: + value = Control::SIZE_SHRINK_BEGIN; + break; + case SIZE_FLAGS_PRESET_SHRINK_CENTER: + value = Control::SIZE_SHRINK_CENTER; + break; + case SIZE_FLAGS_PRESET_SHRINK_END: + value = Control::SIZE_SHRINK_END; + break; + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_expand_toggled() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + if (flag_expand->is_visible() && flag_expand->is_pressed()) { + value |= Control::SIZE_EXPAND; + } else { + value ^= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::_flag_toggled() { + uint32_t value = 0; + for (int i = 0; i < flag_checks.size(); i++) { + if (flag_checks[i]->is_pressed()) { + int flag_value = flag_checks[i]->get_meta("_value"); + value |= flag_value; + } + } + + bool is_expand = flag_expand->is_visible() && flag_expand->is_pressed(); + if (is_expand) { + value |= Control::SIZE_EXPAND; + } + + // Keep the custom preset selected as we toggle individual flags. + keep_selected_preset = true; + emit_changed(get_edited_property(), value); +} + +void EditorPropertySizeFlags::update_property() { + uint32_t value = get_edited_object()->get(get_edited_property()); + + for (int i = 0; i < flag_checks.size(); i++) { + int flag_value = flag_checks[i]->get_meta("_value"); + if (value & flag_value) { + flag_checks[i]->set_pressed(true); + } else { + flag_checks[i]->set_pressed(false); + } + } + + bool is_expand = value & Control::SIZE_EXPAND; + flag_expand->set_pressed(is_expand); + + if (keep_selected_preset) { + keep_selected_preset = false; + return; + } + + FlagPreset preset = SIZE_FLAGS_PRESET_CUSTOM; + if (value == Control::SIZE_FILL || value == (Control::SIZE_FILL | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_FILL; + } else if (value == Control::SIZE_SHRINK_BEGIN || value == (Control::SIZE_SHRINK_BEGIN | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_BEGIN; + } else if (value == Control::SIZE_SHRINK_CENTER || value == (Control::SIZE_SHRINK_CENTER | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_CENTER; + } else if (value == Control::SIZE_SHRINK_END || value == (Control::SIZE_SHRINK_END | Control::SIZE_EXPAND)) { + preset = SIZE_FLAGS_PRESET_SHRINK_END; + } + + int preset_idx = flag_presets->get_item_index(preset); + if (preset_idx >= 0) { + flag_presets->select(preset_idx); + } + flag_options->set_visible(preset == SIZE_FLAGS_PRESET_CUSTOM); +} + +void EditorPropertySizeFlags::setup(const Vector<String> &p_options, bool p_vertical) { + vertical = p_vertical; + + if (p_options.size() == 0) { + flag_presets->clear(); + flag_presets->add_item(TTR("Container Default")); + flag_presets->set_disabled(true); + flag_expand->set_visible(false); + return; + } + + HashMap<int, String> flags; + for (int i = 0, j = 0; i < p_options.size(); i++, j++) { + Vector<String> text_split = p_options[i].split(":"); + int64_t current_val = text_split[1].to_int(); + flags[current_val] = text_split[0]; + + if (current_val == SIZE_EXPAND) { + continue; + } + + CheckBox *cb = memnew(CheckBox); + cb->set_text(text_split[0]); + cb->set_clip_text(true); + cb->set_meta("_value", current_val); + cb->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_flag_toggled)); + add_focusable(cb); + + flag_options->add_child(cb); + flag_checks.append(cb); + } + + Control *gui_base = EditorNode::get_singleton()->get_gui_base(); + String wide_preset_icon = SNAME("ControlAlignHCenterWide"); + String begin_preset_icon = SNAME("ControlAlignCenterLeft"); + String end_preset_icon = SNAME("ControlAlignCenterRight"); + if (vertical) { + wide_preset_icon = SNAME("ControlAlignVCenterWide"); + begin_preset_icon = SNAME("ControlAlignCenterTop"); + end_preset_icon = SNAME("ControlAlignCenterBottom"); + } + + flag_presets->clear(); + if (flags.has(SIZE_FILL)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(wide_preset_icon, SNAME("EditorIcons")), TTR("Fill"), SIZE_FLAGS_PRESET_FILL); + } + // Shrink Begin is the same as no flags at all, as such it cannot be disabled. + flag_presets->add_icon_item(gui_base->get_theme_icon(begin_preset_icon, SNAME("EditorIcons")), TTR("Shrink Begin"), SIZE_FLAGS_PRESET_SHRINK_BEGIN); + if (flags.has(SIZE_SHRINK_CENTER)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(SNAME("ControlAlignCenter"), SNAME("EditorIcons")), TTR("Shrink Center"), SIZE_FLAGS_PRESET_SHRINK_CENTER); + } + if (flags.has(SIZE_SHRINK_END)) { + flag_presets->add_icon_item(gui_base->get_theme_icon(end_preset_icon, SNAME("EditorIcons")), TTR("Shrink End"), SIZE_FLAGS_PRESET_SHRINK_END); + } + flag_presets->add_separator(); + flag_presets->add_item(TTR("Custom"), SIZE_FLAGS_PRESET_CUSTOM); + + flag_expand->set_visible(flags.has(SIZE_EXPAND)); +} + +EditorPropertySizeFlags::EditorPropertySizeFlags() { + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + + flag_presets = memnew(OptionButton); + flag_presets->set_clip_text(true); + flag_presets->set_flat(true); + vb->add_child(flag_presets); + add_focusable(flag_presets); + set_label_reference(flag_presets); + flag_presets->connect("item_selected", callable_mp(this, &EditorPropertySizeFlags::_preset_selected)); + + flag_options = memnew(VBoxContainer); + flag_options->hide(); + vb->add_child(flag_options); + + flag_expand = memnew(CheckBox); + flag_expand->set_text(TTR("Expand")); + vb->add_child(flag_expand); + add_focusable(flag_expand); + flag_expand->connect("pressed", callable_mp(this, &EditorPropertySizeFlags::_expand_toggled)); +} + +bool EditorInspectorPluginControl::can_handle(Object *p_object) { + return Object::cast_to<Control>(p_object) != nullptr; +} + +void EditorInspectorPluginControl::parse_group(Object *p_object, const String &p_group) { + Control *control = Object::cast_to<Control>(p_object); + if (!control || p_group != "Layout") { + return; + } + + ControlPositioningWarning *pos_warning = memnew(ControlPositioningWarning); + pos_warning->set_control(control); + add_custom_control(pos_warning); +} + +bool EditorInspectorPluginControl::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { + Control *control = Object::cast_to<Control>(p_object); + if (!control) { + return false; + } + + if (p_path == "anchors_preset") { + EditorPropertyAnchorsPreset *prop_editor = memnew(EditorPropertyAnchorsPreset); + Vector<String> options = p_hint_text.split(","); + prop_editor->setup(options); + add_property_editor(p_path, prop_editor); + + return true; + } + + if (p_path == "size_flags_horizontal" || p_path == "size_flags_vertical") { + EditorPropertySizeFlags *prop_editor = memnew(EditorPropertySizeFlags); + Vector<String> options; + if (!p_hint_text.is_empty()) { + options = p_hint_text.split(","); + } + prop_editor->setup(options, p_path == "size_flags_vertical"); + add_property_editor(p_path, prop_editor); + + return true; + } + + return false; +} + +void ControlEditorToolbar::_set_anchors_and_offsets_preset(Control::LayoutPreset p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors and Offsets")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_anchors_preset", p_preset); + switch (p_preset) { + case PRESET_TOP_LEFT: + case PRESET_TOP_RIGHT: + case PRESET_BOTTOM_LEFT: + case PRESET_BOTTOM_RIGHT: + case PRESET_CENTER_LEFT: + case PRESET_CENTER_TOP: + case PRESET_CENTER_RIGHT: + case PRESET_CENTER_BOTTOM: + case PRESET_CENTER: + undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_KEEP_SIZE); + break; + case PRESET_LEFT_WIDE: + case PRESET_TOP_WIDE: + case PRESET_RIGHT_WIDE: + case PRESET_BOTTOM_WIDE: + case PRESET_VCENTER_WIDE: + case PRESET_HCENTER_WIDE: + case PRESET_FULL_RECT: + undo_redo->add_do_method(control, "set_offsets_preset", p_preset, Control::PRESET_MODE_MINSIZE); + break; + } + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); + + anchors_mode = false; + anchor_mode_button->set_pressed(anchors_mode); +} + +void ControlEditorToolbar::_set_anchors_and_offsets_to_keep_ratio() { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors and Offsets")); + + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + Point2 top_left_anchor = _position_to_anchor(control, Point2()); + Point2 bottom_right_anchor = _position_to_anchor(control, control->get_size()); + undo_redo->add_do_method(control, "set_anchor", SIDE_LEFT, top_left_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_RIGHT, bottom_right_anchor.x, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_TOP, top_left_anchor.y, false, true); + undo_redo->add_do_method(control, "set_anchor", SIDE_BOTTOM, bottom_right_anchor.y, false, true); + undo_redo->add_do_method(control, "set_meta", "_edit_use_anchors_", true); + + const bool use_anchors = control->get_meta("_edit_use_anchors_", false); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + if (use_anchors) { + undo_redo->add_undo_method(control, "set_meta", "_edit_use_anchors_", true); + } else { + undo_redo->add_undo_method(control, "remove_meta", "_edit_use_anchors_"); + } + + anchors_mode = true; + anchor_mode_button->set_pressed(anchors_mode); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_anchors_preset(Control::LayoutPreset p_preset) { + List<Node *> selection = editor_selection->get_selected_node_list(); + + undo_redo->create_action(TTR("Change Anchors")); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (control) { + undo_redo->add_do_method(control, "set_anchors_preset", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +void ControlEditorToolbar::_set_container_h_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_h_size_flags", p_preset); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + 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); + undo_redo->add_undo_method(control, "_edit_set_state", control->_edit_get_state()); + } + } + + undo_redo->commit_action(); +} + +Vector2 ControlEditorToolbar::_anchor_to_position(const Control *p_control, Vector2 anchor) { + ERR_FAIL_COND_V(!p_control, Vector2()); + + Transform2D parent_transform = p_control->get_transform().affine_inverse(); + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + if (p_control->is_layout_rtl()) { + return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x - parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y)); + } else { + return parent_transform.xform(parent_rect.position + Vector2(parent_rect.size.x * anchor.x, parent_rect.size.y * anchor.y)); + } +} + +Vector2 ControlEditorToolbar::_position_to_anchor(const Control *p_control, Vector2 position) { + ERR_FAIL_COND_V(!p_control, Vector2()); + + Rect2 parent_rect = p_control->get_parent_anchorable_rect(); + + Vector2 output = Vector2(); + if (p_control->is_layout_rtl()) { + output.x = (parent_rect.size.x == 0) ? 0.0 : (parent_rect.size.x - p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } else { + output.x = (parent_rect.size.x == 0) ? 0.0 : (p_control->get_transform().xform(position).x - parent_rect.position.x) / parent_rect.size.x; + } + output.y = (parent_rect.size.y == 0) ? 0.0 : (p_control->get_transform().xform(position).y - parent_rect.position.y) / parent_rect.size.y; + return output; +} + +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->get_meta("_edit_lock_", false); +} + +List<Control *> ControlEditorToolbar::_get_edited_controls(bool retrieve_locked, bool remove_controls_if_parent_in_selection) { + 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))) { + 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_FULL_RECT: { + _set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); + } 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_FULL_RECT: { + _set_anchors_preset(Control::PRESET_FULL_RECT); + } 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; + + List<Node *> selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Control *control = Object::cast_to<Control>(E); + if (!control) { + continue; + } + + nb_controls++; + 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); + + if (nb_controls > 0) { + set_physics_process(true); + } else { + set_physics_process(false); + 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("ControlAlignFullRect"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_AND_OFFSETS_PRESET_FULL_RECT); + 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("ControlAlignFullRect"), SNAME("EditorIcons")), TTR("Full Rect"), ANCHORS_PRESET_FULL_RECT); + + 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); + } + } 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); + + PopupMenu *p = anchor_presets_menu->get_popup(); + p->connect("id_pressed", callable_mp(this, &ControlEditorToolbar::_popup_callback)); + + 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)); + + 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)); + + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + editor_selection = EditorNode::get_singleton()->get_editor_selection(); + editor_selection->add_editor_plugin(this); + editor_selection->connect("selection_changed", callable_mp(this, &ControlEditorToolbar::_selection_changed)); + + singleton = this; +} + +ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr; + +ControlEditorPlugin::ControlEditorPlugin() { + toolbar = memnew(ControlEditorToolbar); + toolbar->hide(); + add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); + + Ref<EditorInspectorPluginControl> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/control_editor_plugin.h b/editor/plugins/control_editor_plugin.h new file mode 100644 index 0000000000..11389bc095 --- /dev/null +++ b/editor/plugins/control_editor_plugin.h @@ -0,0 +1,250 @@ +/*************************************************************************/ +/* 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 CONTROL_EDITOR_PLUGIN_H +#define CONTROL_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "scene/gui/box_container.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/texture_rect.h" + +class ControlPositioningWarning : public MarginContainer { + GDCLASS(ControlPositioningWarning, MarginContainer); + + Control *control_node = nullptr; + + PanelContainer *bg_panel = nullptr; + GridContainer *grid = nullptr; + TextureRect *title_icon = nullptr; + TextureRect *hint_icon = nullptr; + Label *title_label = nullptr; + Label *hint_label = nullptr; + Control *hint_filler_left = nullptr; + Control *hint_filler_right = nullptr; + + void _update_warning(); + void _update_toggler(); + virtual void gui_input(const Ref<InputEvent> &p_event) override; + +protected: + void _notification(int p_notification); + +public: + void set_control(Control *p_node); + + ControlPositioningWarning(); +}; + +class EditorPropertyAnchorsPreset : public EditorProperty { + GDCLASS(EditorPropertyAnchorsPreset, EditorProperty); + OptionButton *options = nullptr; + + void _option_selected(int p_which); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options); + virtual void update_property() override; + EditorPropertyAnchorsPreset(); +}; + +class EditorPropertySizeFlags : public EditorProperty { + GDCLASS(EditorPropertySizeFlags, EditorProperty); + + enum FlagPreset { + SIZE_FLAGS_PRESET_FILL, + SIZE_FLAGS_PRESET_SHRINK_BEGIN, + SIZE_FLAGS_PRESET_SHRINK_CENTER, + SIZE_FLAGS_PRESET_SHRINK_END, + SIZE_FLAGS_PRESET_CUSTOM, + }; + + OptionButton *flag_presets = nullptr; + CheckBox *flag_expand = nullptr; + VBoxContainer *flag_options = nullptr; + Vector<CheckBox *> flag_checks; + + bool vertical = false; + + bool keep_selected_preset = false; + + void _preset_selected(int p_which); + void _expand_toggled(); + void _flag_toggled(); + +protected: + virtual void _set_read_only(bool p_read_only) override; + +public: + void setup(const Vector<String> &p_options, bool p_vertical); + virtual void update_property() override; + EditorPropertySizeFlags(); +}; + +class EditorInspectorPluginControl : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginControl, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_group(Object *p_object, const String &p_group) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; +}; + +class ControlEditorToolbar : public HBoxContainer { + GDCLASS(ControlEditorToolbar, HBoxContainer); + + UndoRedo *undo_redo = nullptr; + 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_FULL_RECT, + + 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_FULL_RECT, + + // 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_FULL_RECT, + + 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; + + Button *anchor_mode_button = 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); + + 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); + void _selection_changed(); + +protected: + void _notification(int p_notification); + + static ControlEditorToolbar *singleton; + +public: + bool is_anchors_mode_enabled() { return anchors_mode; }; + + static ControlEditorToolbar *get_singleton() { return singleton; } + + ControlEditorToolbar(); +}; + +class ControlEditorPlugin : public EditorPlugin { + GDCLASS(ControlEditorPlugin, EditorPlugin); + + ControlEditorToolbar *toolbar = nullptr; + +public: + virtual String get_name() const override { return "Control"; } + + ControlEditorPlugin(); +}; + +#endif // CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp index 6f246c1661..a7c3c32120 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,6 +32,8 @@ #include "canvas_item_editor_plugin.h" #include "core/io/image_loader.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "scene/2d/cpu_particles_2d.h" #include "scene/gui/separator.h" #include "scene/resources/particles_material.h" @@ -83,7 +85,7 @@ void CPUParticles2DEditorPlugin::_generate_emission_mask() { } img->convert(Image::FORMAT_RGBA8); ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8); - Size2i s = Size2(img->get_width(), img->get_height()); + Size2i s = img->get_size(); ERR_FAIL_COND(s.width == 0 || s.height == 0); Vector<Point2> valid_positions; @@ -222,20 +224,21 @@ void CPUParticles2DEditorPlugin::_generate_emission_mask() { } void CPUParticles2DEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - menu->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(epoints->get_theme_icon(SNAME("CPUParticles2D"), SNAME("EditorIcons"))); - file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + menu->get_popup()->connect("id_pressed", callable_mp(this, &CPUParticles2DEditorPlugin::_menu_callback)); + menu->set_icon(epoints->get_theme_icon(SNAME("CPUParticles2D"), SNAME("EditorIcons"))); + file->connect("file_selected", callable_mp(this, &CPUParticles2DEditorPlugin::_file_selected)); + } break; } } void CPUParticles2DEditorPlugin::_bind_methods() { } -CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin(EditorNode *p_node) { +CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin() { particles = nullptr; - editor = p_node; - undo_redo = editor->get_undo_redo(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); toolbar = memnew(HBoxContainer); add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); @@ -254,7 +257,7 @@ CPUParticles2DEditorPlugin::CPUParticles2DEditorPlugin(EditorNode *p_node) { List<String> ext; ImageLoader::get_recognized_extensions(&ext); for (const String &E : ext) { - file->add_filter("*." + E + "; " + E.to_upper()); + file->add_filter("*." + E, E.to_upper()); } file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); toolbar->add_child(file); diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.h b/editor/plugins/cpu_particles_2d_editor_plugin.h index b188df8e96..cc59bc924f 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.h +++ b/editor/plugins/cpu_particles_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,12 +31,14 @@ #ifndef CPU_PARTICLES_2D_EDITOR_PLUGIN_H #define CPU_PARTICLES_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/cpu_particles_2d.h" #include "scene/gui/box_container.h" -#include "scene/gui/file_dialog.h" + +class EditorPlugin; +class SpinBox; +class EditorFileDialog; class CPUParticles2DEditorPlugin : public EditorPlugin { GDCLASS(CPUParticles2DEditorPlugin, EditorPlugin); @@ -53,23 +55,22 @@ class CPUParticles2DEditorPlugin : public EditorPlugin { EMISSION_MODE_BORDER_DIRECTED }; - CPUParticles2D *particles; + CPUParticles2D *particles = nullptr; - EditorFileDialog *file; - EditorNode *editor; + EditorFileDialog *file = nullptr; - HBoxContainer *toolbar; - MenuButton *menu; + HBoxContainer *toolbar = nullptr; + MenuButton *menu = nullptr; - SpinBox *epoints; + SpinBox *epoints = nullptr; - ConfirmationDialog *emission_mask; - OptionButton *emission_mask_mode; - CheckBox *emission_colors; + ConfirmationDialog *emission_mask = nullptr; + OptionButton *emission_mask_mode = nullptr; + CheckBox *emission_colors = nullptr; String source_emission_file; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; void _file_selected(const String &p_file); void _menu_callback(int p_idx); void _generate_emission_mask(); @@ -85,7 +86,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - CPUParticles2DEditorPlugin(EditorNode *p_node); + CPUParticles2DEditorPlugin(); ~CPUParticles2DEditorPlugin(); }; diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.cpp b/editor/plugins/cpu_particles_3d_editor_plugin.cpp index fc52cd0f99..775c2dbb2a 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,7 +30,10 @@ #include "cpu_particles_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/plugins/node_3d_editor_plugin.h" +#include "editor/scene_tree_editor.h" +#include "scene/gui/menu_button.h" void CPUParticles3DEditor::_node_removed(Node *p_node) { if (p_node == node) { @@ -40,8 +43,10 @@ void CPUParticles3DEditor::_node_removed(Node *p_node) { } void CPUParticles3DEditor::_notification(int p_notification) { - if (p_notification == NOTIFICATION_ENTER_TREE) { - options->set_icon(get_theme_icon(SNAME("CPUParticles3D"), SNAME("EditorIcons"))); + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: { + options->set_icon(get_theme_icon(SNAME("CPUParticles3D"), SNAME("EditorIcons"))); + } break; } } @@ -119,10 +124,9 @@ void CPUParticles3DEditorPlugin::make_visible(bool p_visible) { } } -CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +CPUParticles3DEditorPlugin::CPUParticles3DEditorPlugin() { particles_editor = memnew(CPUParticles3DEditor); - editor->get_main_control()->add_child(particles_editor); + EditorNode::get_singleton()->get_main_control()->add_child(particles_editor); particles_editor->hide(); } diff --git a/editor/plugins/cpu_particles_3d_editor_plugin.h b/editor/plugins/cpu_particles_3d_editor_plugin.h index 9dced3ea86..f38349985c 100644 --- a/editor/plugins/cpu_particles_3d_editor_plugin.h +++ b/editor/plugins/cpu_particles_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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" @@ -44,7 +44,7 @@ class CPUParticles3DEditor : public GPUParticles3DEditorBase { }; - CPUParticles3D *node; + CPUParticles3D *node = nullptr; void _menu_option(int); @@ -65,8 +65,7 @@ public: class CPUParticles3DEditorPlugin : public EditorPlugin { GDCLASS(CPUParticles3DEditorPlugin, EditorPlugin); - CPUParticles3DEditor *particles_editor; - EditorNode *editor; + CPUParticles3DEditor *particles_editor = nullptr; public: virtual String get_name() const override { return "CPUParticles3D"; } @@ -75,8 +74,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - CPUParticles3DEditorPlugin(EditorNode *p_node); + CPUParticles3DEditorPlugin(); ~CPUParticles3DEditorPlugin(); }; -#endif // CPU_PARTICLES_EDITOR_PLUGIN_H +#endif // CPU_PARTICLES_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 4a22dc5b62..8aeab684e3 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,9 @@ #include "core/core_string_names.h" #include "core/input/input.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" CurveEditor::CurveEditor() { _selected_point = -1; @@ -96,8 +98,10 @@ Size2 CurveEditor::get_minimum_size() const { } void CurveEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - _draw(); + switch (p_what) { + case NOTIFICATION_DRAW: { + _draw(); + } break; } } @@ -115,16 +119,16 @@ void CurveEditor::gui_input(const Ref<InputEvent> &p_event) { } switch (mb.get_button_index()) { - case MOUSE_BUTTON_RIGHT: + case MouseButton::RIGHT: _context_click_pos = mpos; - open_context_menu(get_global_transform().xform(mpos)); + open_context_menu(get_screen_position() + mpos); break; - case MOUSE_BUTTON_MIDDLE: + case MouseButton::MIDDLE: remove_point(_hover_point); break; - case MOUSE_BUTTON_LEFT: + case MouseButton::LEFT: _dragging = true; break; default: @@ -132,7 +136,7 @@ void CurveEditor::gui_input(const Ref<InputEvent> &p_event) { } } - if (!mb.is_pressed() && _dragging && mb.get_button_index() == MOUSE_BUTTON_LEFT) { + 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(); @@ -210,7 +214,7 @@ void CurveEditor::gui_input(const Ref<InputEvent> &p_event) { tangent = 9999 * (dir.y >= 0 ? 1 : -1); } - bool link = !Input::get_singleton()->is_key_pressed(KEY_SHIFT); + bool link = !Input::get_singleton()->is_key_pressed(Key::SHIFT); if (_selected_tangent == TANGENT_LEFT) { curve.set_point_left_tangent(_selected_point, tangent); @@ -240,7 +244,7 @@ void CurveEditor::gui_input(const Ref<InputEvent> &p_event) { const InputEventKey &key = **key_ref; if (key.is_pressed() && _selected_point != -1) { - if (key.get_keycode() == KEY_DELETE) { + if (key.get_keycode() == Key::KEY_DELETE) { remove_point(_selected_point); } } @@ -354,9 +358,9 @@ void CurveEditor::open_context_menu(Vector2 pos) { _context_menu->add_check_item(TTR("Linear"), CONTEXT_LINEAR); - bool is_linear = _selected_tangent == TANGENT_LEFT ? - _curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR : - _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR; + bool is_linear = _selected_tangent == TANGENT_LEFT + ? _curve_ref->get_point_left_mode(_selected_point) == Curve::TANGENT_LINEAR + : _curve_ref->get_point_right_mode(_selected_point) == Curve::TANGENT_LINEAR; _context_menu->set_item_checked(_context_menu->get_item_index(CONTEXT_LINEAR), is_linear); @@ -383,7 +387,7 @@ void CurveEditor::open_context_menu(Vector2 pos) { _context_menu->add_submenu_item(TTR("Load Preset"), _presets_menu->get_name()); - _context_menu->set_size(Size2(0, 0)); + _context_menu->reset_size(); _context_menu->popup(); } @@ -460,7 +464,7 @@ void CurveEditor::remove_point(int index) { 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.pos, p.left_tangent, p.right_tangent, p.left_mode, p.right_mode); + 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); @@ -539,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; } @@ -675,11 +679,11 @@ void CurveEditor::_draw() { // X axis float y = curve.get_min_value(); Vector2 off(0, font_height - 1); - draw_string(font, get_view_pos(Vector2(0, y)) + off, "0.0", HALIGN_LEFT, -1, font_size, text_color); - draw_string(font, get_view_pos(Vector2(0.25, y)) + off, "0.25", HALIGN_LEFT, -1, font_size, text_color); - draw_string(font, get_view_pos(Vector2(0.5, y)) + off, "0.5", HALIGN_LEFT, -1, font_size, text_color); - draw_string(font, get_view_pos(Vector2(0.75, y)) + off, "0.75", HALIGN_LEFT, -1, font_size, text_color); - draw_string(font, get_view_pos(Vector2(1, y)) + off, "1.0", HALIGN_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(0, y)) + off, "0.0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(0.25, y)) + off, "0.25", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(0.5, y)) + off, "0.5", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(0.75, y)) + off, "0.75", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(1, y)) + off, "1.0", HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); } { @@ -688,9 +692,9 @@ void CurveEditor::_draw() { float m1 = 0.5 * (curve.get_min_value() + curve.get_max_value()); float m2 = curve.get_max_value(); Vector2 off(1, -1); - draw_string(font, get_view_pos(Vector2(0, m0)) + off, String::num(m0, 2), HALIGN_LEFT, -1, font_size, text_color); - draw_string(font, get_view_pos(Vector2(0, m1)) + off, String::num(m1, 2), HALIGN_LEFT, -1, font_size, text_color); - draw_string(font, get_view_pos(Vector2(0, m2)) + off, String::num(m2, 3), HALIGN_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(0, m0)) + off, String::num(m0, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(0, m1)) + off, String::num(m1, 2), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); + draw_string(font, get_view_pos(Vector2(0, m2)) + off, String::num(m2, 3), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); } // Draw tangents for current point @@ -748,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"), HALIGN_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"), HALIGN_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); } } @@ -773,7 +778,7 @@ void EditorInspectorPluginCurve::parse_begin(Object *p_object) { add_custom_control(editor); } -CurveEditorPlugin::CurveEditorPlugin(EditorNode *p_node) { +CurveEditorPlugin::CurveEditorPlugin() { Ref<EditorInspectorPluginCurve> curve_plugin; curve_plugin.instantiate(); EditorInspector::add_inspector_plugin(curve_plugin); @@ -803,11 +808,8 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons im.create(thumbnail_size, thumbnail_size / 2, false, Image::FORMAT_RGBA8); Color bg_color(0.1, 0.1, 0.1, 1.0); - for (int i = 0; i < thumbnail_size; i++) { - for (int j = 0; j < thumbnail_size / 2; j++) { - im.set_pixel(i, j, bg_color); - } - } + + im.fill(bg_color); Color line_color(0.8, 0.8, 0.8, 1.0); float range_y = curve.get_max_value() - curve.get_min_value(); @@ -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/curve_editor_plugin.h b/editor/plugins/curve_editor_plugin.h index c351f6ebe9..5cf3b16a06 100644 --- a/editor/plugins/curve_editor_plugin.h +++ b/editor/plugins/curve_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef CURVE_EDITOR_PLUGIN_H #define CURVE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_resource_preview.h" #include "scene/resources/curve.h" @@ -100,8 +99,8 @@ private: Transform2D _world_to_view; Ref<Curve> _curve_ref; - PopupMenu *_context_menu; - PopupMenu *_presets_menu; + PopupMenu *_context_menu = nullptr; + PopupMenu *_presets_menu = nullptr; Array _undo_data; bool _has_undo_data; @@ -129,7 +128,7 @@ class CurveEditorPlugin : public EditorPlugin { GDCLASS(CurveEditorPlugin, EditorPlugin); public: - CurveEditorPlugin(EditorNode *p_node); + CurveEditorPlugin(); virtual String get_name() const override { return "Curve"; } }; diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 1512e1817a..c572b5b7fe 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,16 +34,19 @@ #include "editor/debugger/editor_debugger_node.h" #include "editor/debugger/editor_debugger_server.h" #include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "editor/fileserver/editor_file_server.h" +#include "editor/plugins/script_editor_plugin.h" #include "scene/gui/menu_button.h" -DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor, MenuButton *p_debug_menu) { +DebuggerEditorPlugin::DebuggerEditorPlugin(MenuButton *p_debug_menu) { EditorDebuggerServer::initialize(); - ED_SHORTCUT("debugger/step_into", TTR("Step Into"), KEY_F11); - ED_SHORTCUT("debugger/step_over", TTR("Step Over"), KEY_F10); + ED_SHORTCUT("debugger/step_into", TTR("Step Into"), Key::F11); + ED_SHORTCUT("debugger/step_over", TTR("Step Over"), Key::F10); ED_SHORTCUT("debugger/break", TTR("Break")); - ED_SHORTCUT("debugger/continue", TTR("Continue"), KEY_F12); + ED_SHORTCUT("debugger/continue", TTR("Continue"), Key::F12); ED_SHORTCUT("debugger/keep_debugger_open", TTR("Keep Debugger Open")); ED_SHORTCUT("debugger/debug_with_external_editor", TTR("Debug with External Editor")); @@ -52,6 +55,8 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor, MenuButton *p_d EditorDebuggerNode *debugger = memnew(EditorDebuggerNode); Button *db = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Debugger"), debugger); + // Add separation for the warning/error icon that is displayed later. + db->add_theme_constant_override("h_separation", 6 * EDSCALE); debugger->set_tool_button(db); // Main editor debug menu. @@ -59,30 +64,27 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor, MenuButton *p_d PopupMenu *p = debug_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); p->add_check_shortcut(ED_SHORTCUT("editor/deploy_with_remote_debug", TTR("Deploy with Remote Debug")), RUN_DEPLOY_REMOTE_DEBUG); - p->set_item_tooltip( - p->get_item_count() - 1, + p->set_item_tooltip(-1, TTR("When this option is enabled, using one-click deploy will make the executable attempt to connect to this computer's IP so the running project can be debugged.\nThis option is intended to be used for remote debugging (typically with a mobile device).\nYou don't need to enable it to use the GDScript debugger locally.")); p->add_check_shortcut(ED_SHORTCUT("editor/small_deploy_with_network_fs", TTR("Small Deploy with Network Filesystem")), RUN_FILE_SERVER); - p->set_item_tooltip( - p->get_item_count() - 1, + p->set_item_tooltip(-1, TTR("When this option is enabled, using one-click deploy for Android will only export an executable without the project data.\nThe filesystem will be provided from the project by the editor over the network.\nOn Android, deploying will use the USB cable for faster performance. This option speeds up testing for projects with large assets.")); p->add_separator(); p->add_check_shortcut(ED_SHORTCUT("editor/visible_collision_shapes", TTR("Visible Collision Shapes")), RUN_DEBUG_COLLISONS); - p->set_item_tooltip( - p->get_item_count() - 1, + p->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_paths", TTR("Visible Paths")), RUN_DEBUG_PATHS); + p->set_item_tooltip(-1, + TTR("When this option is enabled, curve resources used by path nodes will be visible in the running project.")); p->add_check_shortcut(ED_SHORTCUT("editor/visible_navigation", TTR("Visible Navigation")), RUN_DEBUG_NAVIGATION); - p->set_item_tooltip( - p->get_item_count() - 1, + p->set_item_tooltip(-1, TTR("When this option is enabled, navigation meshes and polygons will be visible in the running project.")); p->add_separator(); p->add_check_shortcut(ED_SHORTCUT("editor/sync_scene_changes", TTR("Synchronize Scene Changes")), RUN_LIVE_DEBUG); - p->set_item_tooltip( - p->get_item_count() - 1, + p->set_item_tooltip(-1, TTR("When this option is enabled, any changes made to the scene in the editor will be replicated in the running project.\nWhen used remotely on a device, this is more efficient when the network filesystem option is enabled.")); p->add_check_shortcut(ED_SHORTCUT("editor/sync_script_changes", TTR("Synchronize Script Changes")), RUN_RELOAD_SCRIPTS); - p->set_item_tooltip( - p->get_item_count() - 1, + p->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 @@ -155,6 +157,12 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { EditorSettings::get_singleton()->set_project_metadata("debug_options", "run_debug_collisons", !ischecked); } break; + case RUN_DEBUG_PATHS: { + bool ischecked = debug_menu->get_popup()->is_item_checked(debug_menu->get_popup()->get_item_index(RUN_DEBUG_PATHS)); + debug_menu->get_popup()->set_item_checked(debug_menu->get_popup()->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); @@ -173,8 +181,10 @@ void DebuggerEditorPlugin::_menu_option(int p_option) { } void DebuggerEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - _update_debug_options(); + switch (p_what) { + case NOTIFICATION_READY: { + _update_debug_options(); + } break; } } @@ -182,6 +192,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 +207,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 a6fab01c29..fb963385cd 100644 --- a/editor/plugins/debugger_editor_plugin.h +++ b/editor/plugins/debugger_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,7 +33,6 @@ #include "editor/editor_plugin.h" -class EditorNode; class EditorFileServer; class MenuButton; class PopupMenu; @@ -42,14 +41,15 @@ class DebuggerEditorPlugin : public EditorPlugin { GDCLASS(DebuggerEditorPlugin, EditorPlugin); private: - MenuButton *debug_menu; - EditorFileServer *file_server; - PopupMenu *instances_menu; + MenuButton *debug_menu = nullptr; + EditorFileServer *file_server = nullptr; + PopupMenu *instances_menu = nullptr; enum MenuOptions { RUN_FILE_SERVER, RUN_LIVE_DEBUG, RUN_DEBUG_COLLISONS, + RUN_DEBUG_PATHS, RUN_DEBUG_NAVIGATION, RUN_DEPLOY_REMOTE_DEBUG, RUN_RELOAD_SCRIPTS, @@ -64,7 +64,7 @@ public: virtual String get_name() const override { return "Debugger"; } bool has_main_screen() const override { return false; } - DebuggerEditorPlugin(EditorNode *p_node, MenuButton *p_menu); + DebuggerEditorPlugin(MenuButton *p_menu); ~DebuggerEditorPlugin(); }; diff --git a/editor/plugins/editor_debugger_plugin.cpp b/editor/plugins/editor_debugger_plugin.cpp index 5f3b11ac42..4ce3d7cfd5 100644 --- a/editor/plugins/editor_debugger_plugin.cpp +++ b/editor/plugins/editor_debugger_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/editor/plugins/editor_debugger_plugin.h b/editor/plugins/editor_debugger_plugin.h index 5995d790c5..b602c36912 100644 --- a/editor/plugins/editor_debugger_plugin.h +++ b/editor/plugins/editor_debugger_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 415832ab3b..0196214ceb 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,10 +30,11 @@ #include "editor_preview_plugins.h" +#include "core/config/project_settings.h" #include "core/io/file_access_memory.h" #include "core/io/resource_loader.h" #include "core/os/os.h" -#include "editor/editor_node.h" +#include "editor/editor_paths.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "scene/resources/bit_map.h" @@ -78,7 +79,7 @@ bool EditorTexturePreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorTexturePreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorTexturePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Image> img; Ref<AtlasTexture> atex = p_from; if (atex.is_valid()) { @@ -126,13 +127,9 @@ Ref<Texture2D> EditorTexturePreviewPlugin::generate(const RES &p_from, const Siz } Vector2i new_size_i(MAX(1, (int)new_size.x), MAX(1, (int)new_size.y)); img->resize(new_size_i.x, new_size_i.y, Image::INTERPOLATE_CUBIC); - post_process_preview(img); - Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); - - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } EditorTexturePreviewPlugin::EditorTexturePreviewPlugin() { @@ -144,7 +141,7 @@ bool EditorImagePreviewPlugin::handles(const String &p_type) const { return p_type == "Image"; } -Ref<Texture2D> EditorImagePreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorImagePreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Image> img = p_from; if (img.is_null() || img->is_empty()) { @@ -170,14 +167,9 @@ Ref<Texture2D> EditorImagePreviewPlugin::generate(const RES &p_from, const Size2 new_size = Vector2(new_size.x * p_size.y / new_size.y, p_size.y); } img->resize(new_size.x, new_size.y, Image::INTERPOLATE_CUBIC); - post_process_preview(img); - Ref<ImageTexture> ptex; - ptex.instantiate(); - - ptex->create_from_image(img); - return ptex; + return ImageTexture::create_from_image(img); } EditorImagePreviewPlugin::EditorImagePreviewPlugin() { @@ -188,12 +180,12 @@ bool EditorImagePreviewPlugin::generate_small_preview_automatically() const { } //////////////////////////////////////////////////////////////////////////// -///////////////////////////////////////////////// + bool EditorBitmapPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "BitMap"); } -Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<BitMap> bm = p_from; if (bm->get_size() == Size2()) { @@ -238,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 { @@ -260,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); } @@ -281,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>(); @@ -297,37 +282,34 @@ EditorPackedScenePreviewPlugin::EditorPackedScenePreviewPlugin() { ////////////////////////////////////////////////////////////////// -void EditorMaterialPreviewPlugin::_preview_done(const Variant &p_udata) { - preview_done.set(); +void EditorMaterialPreviewPlugin::_generate_frame_started() { + RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture + + RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<EditorMaterialPreviewPlugin *>(this), &EditorMaterialPreviewPlugin::_preview_done)); } -void EditorMaterialPreviewPlugin::_bind_methods() { - ClassDB::bind_method("_preview_done", &EditorMaterialPreviewPlugin::_preview_done); +void EditorMaterialPreviewPlugin::_preview_done() { + preview_done.post(); } bool EditorMaterialPreviewPlugin::handles(const String &p_type) const { - return ClassDB::is_parent_class(p_type, "Material"); //any material + return ClassDB::is_parent_class(p_type, "Material"); // Any material. } bool EditorMaterialPreviewPlugin::generate_small_preview_automatically() const { return true; } -Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Material> material = p_from; ERR_FAIL_COND_V(material.is_null(), Ref<Texture2D>()); if (material->get_shader_mode() == Shader::MODE_SPATIAL) { RS::get_singleton()->mesh_surface_set_material(sphere, 0, material->get_rid()); - RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture - - preview_done.clear(); - RS::get_singleton()->request_frame_drawn_callback(const_cast<EditorMaterialPreviewPlugin *>(this), "_preview_done", Variant()); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMaterialPreviewPlugin *>(this), &EditorMaterialPreviewPlugin::_generate_frame_started), Object::CONNECT_ONESHOT); - while (!preview_done.is_set()) { - OS::get_singleton()->delay_usec(10); - } + preview_done.wait(); Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture); RS::get_singleton()->mesh_surface_set_material(sphere, 0, RID()); @@ -338,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>(); @@ -465,30 +445,26 @@ EditorMaterialPreviewPlugin::~EditorMaterialPreviewPlugin() { /////////////////////////////////////////////////////////////////////////// -static bool _is_text_char(char32_t c) { - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_'; -} - bool EditorScriptPreviewPlugin::handles(const String &p_type) const { return ClassDB::is_parent_class(p_type, "Script"); } -Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorScriptPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Script> scr = p_from; if (scr.is_null()) { return Ref<Texture2D>(); } String code = scr->get_source_code().strip_edges(); - if (code == "") { + if (code.is_empty()) { return Ref<Texture2D>(); } 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)) { @@ -517,11 +493,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size } bg_color.a = MAX(bg_color.a, 0.2); // some background - for (int i = 0; i < thumbnail_size; i++) { - for (int j = 0; j < thumbnail_size; j++) { - img->set_pixel(i, j, bg_color); - } - } + img->fill(bg_color); const int x0 = thumbnail_size / 8; const int y0 = thumbnail_size / 8; @@ -545,15 +517,15 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size if (in_comment) { color = comment_color; } else { - if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) { + if (is_symbol(c)) { //make symbol a little visible color = symbol_color; in_control_flow_keyword = false; in_keyword = false; - } else if (!prev_is_text && _is_text_char(c)) { + } else if (!prev_is_text && is_ascii_identifier_char(c)) { int pos = i; - while (_is_text_char(code[pos])) { + while (is_ascii_identifier_char(code[pos])) { pos++; } String word = code.substr(i, pos - i); @@ -563,7 +535,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size in_keyword = true; } - } else if (!_is_text_char(c)) { + } else if (!is_ascii_identifier_char(c)) { in_keyword = false; } @@ -578,7 +550,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size img->set_pixel(col, y0 + line * 2, bg_color.blend(ul)); img->set_pixel(col, y0 + line * 2 + 1, color); - prev_is_text = _is_text_char(c); + prev_is_text = is_ascii_identifier_char(c); } col++; } else { @@ -601,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() { @@ -619,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>()); @@ -632,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(); @@ -686,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() { @@ -699,26 +664,28 @@ EditorAudioStreamPreviewPlugin::EditorAudioStreamPreviewPlugin() { /////////////////////////////////////////////////////////////////////////// -void EditorMeshPreviewPlugin::_preview_done(const Variant &p_udata) { - preview_done.set(); +void EditorMeshPreviewPlugin::_generate_frame_started() { + RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture + + RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<EditorMeshPreviewPlugin *>(this), &EditorMeshPreviewPlugin::_preview_done)); } -void EditorMeshPreviewPlugin::_bind_methods() { - ClassDB::bind_method("_preview_done", &EditorMeshPreviewPlugin::_preview_done); +void EditorMeshPreviewPlugin::_preview_done() { + preview_done.post(); } bool EditorMeshPreviewPlugin::handles(const String &p_type) const { - return ClassDB::is_parent_class(p_type, "Mesh"); //any Mesh + return ClassDB::is_parent_class(p_type, "Mesh"); // Any mesh. } -Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2 &p_size) const { +Ref<Texture2D> EditorMeshPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { Ref<Mesh> mesh = p_from; ERR_FAIL_COND_V(mesh.is_null(), Ref<Texture2D>()); RS::get_singleton()->instance_set_base(mesh_instance, mesh->get_rid()); AABB aabb = mesh->get_aabb(); - Vector3 ofs = aabb.position + aabb.size * 0.5; + Vector3 ofs = aabb.get_center(); aabb.position -= ofs; Transform3D xform; xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math_PI * 0.125); @@ -735,14 +702,9 @@ 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()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture - - preview_done.clear(); - RS::get_singleton()->request_frame_drawn_callback(const_cast<EditorMeshPreviewPlugin *>(this), "_preview_done", Variant()); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMeshPreviewPlugin *>(this), &EditorMeshPreviewPlugin::_generate_frame_started), Object::CONNECT_ONESHOT); - while (!preview_done.is_set()) { - OS::get_singleton()->delay_usec(10); - } + preview_done.wait(); Ref<Image> img = RS::get_singleton()->texture_2d_get(viewport_texture); ERR_FAIL_COND_V(img.is_null(), Ref<ImageTexture>()); @@ -759,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() { @@ -814,27 +773,23 @@ EditorMeshPreviewPlugin::~EditorMeshPreviewPlugin() { /////////////////////////////////////////////////////////////////////////// -void EditorFontPreviewPlugin::_preview_done(const Variant &p_udata) { - preview_done.set(); +void EditorFontPreviewPlugin::_generate_frame_started() { + RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture + + RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<EditorFontPreviewPlugin *>(this), &EditorFontPreviewPlugin::_preview_done)); } -void EditorFontPreviewPlugin::_bind_methods() { - ClassDB::bind_method("_preview_done", &EditorFontPreviewPlugin::_preview_done); +void EditorFontPreviewPlugin::_preview_done() { + preview_done.post(); } 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); - 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😀"; @@ -846,24 +801,20 @@ 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; - - font->draw_string(canvas_item, pos, sample, HALIGN_LEFT, -1.f, 50, Color(1, 1, 1)); + const Color c = GLOBAL_GET("rendering/environment/defaults/default_clear_color"); + const float fg = c.get_luminance() < 0.5 ? 1.0 : 0.0; + sampled_font->draw_string(canvas_item, pos, sample, HORIZONTAL_ALIGNMENT_LEFT, -1.f, 50, Color(fg, fg, fg)); - preview_done.clear(); - RS::get_singleton()->viewport_set_update_mode(viewport, RS::VIEWPORT_UPDATE_ONCE); //once used for capture - RS::get_singleton()->request_frame_drawn_callback(const_cast<EditorFontPreviewPlugin *>(this), "_preview_done", Variant()); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorFontPreviewPlugin *>(this), &EditorFontPreviewPlugin::_generate_frame_started), Object::CONNECT_ONESHOT); - while (!preview_done.is_set()) { - OS::get_singleton()->delay_usec(10); - } + preview_done.wait(); RS::get_singleton()->canvas_item_clear(canvas_item); @@ -880,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>(); @@ -916,3 +863,30 @@ EditorFontPreviewPlugin::~EditorFontPreviewPlugin() { RS::get_singleton()->free(canvas); RS::get_singleton()->free(viewport); } + +//////////////////////////////////////////////////////////////////////////// + +static const real_t GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR = 4.0; + +bool EditorGradientPreviewPlugin::handles(const String &p_type) const { + return ClassDB::is_parent_class(p_type, "Gradient"); +} + +bool EditorGradientPreviewPlugin::generate_small_preview_automatically() const { + return true; +} + +Ref<Texture2D> EditorGradientPreviewPlugin::generate(const Ref<Resource> &p_from, const Size2 &p_size) const { + Ref<Gradient> gradient = p_from; + if (gradient.is_valid()) { + Ref<GradientTexture1D> ptex; + ptex.instantiate(); + ptex->set_width(p_size.width * GRADIENT_PREVIEW_TEXTURE_SCALE_FACTOR * EDSCALE); + ptex->set_gradient(gradient); + return ImageTexture::create_from_image(ptex->get_image()); + } + return Ref<Texture2D>(); +} + +EditorGradientPreviewPlugin::EditorGradientPreviewPlugin() { +} diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h index 6e8b9a34cf..163cfe79f9 100644 --- a/editor/plugins/editor_preview_plugins.h +++ b/editor/plugins/editor_preview_plugins.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,12 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef EDITORPREVIEWPLUGINS_H -#define EDITORPREVIEWPLUGINS_H - -#include "editor/editor_resource_preview.h" +#ifndef EDITOR_PREVIEW_PLUGINS_H +#define EDITOR_PREVIEW_PLUGINS_H #include "core/templates/safe_refcount.h" +#include "editor/editor_resource_preview.h" void post_process_preview(Ref<Image> p_image); @@ -43,7 +42,7 @@ class EditorTexturePreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorTexturePreviewPlugin(); }; @@ -54,7 +53,7 @@ class EditorImagePreviewPlugin : public EditorResourcePreviewGenerator { public: virtual bool handles(const String &p_type) const override; virtual bool generate_small_preview_automatically() const override; - virtual Ref<Texture2D> generate(const RES &p_from, const Size2 &p_size) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; EditorImagePreviewPlugin(); }; @@ -65,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(); }; @@ -73,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(); @@ -92,17 +91,15 @@ class EditorMaterialPreviewPlugin : public EditorResourcePreviewGenerator { RID light2; RID light_instance2; RID camera; - mutable SafeFlag preview_done; + Semaphore preview_done; - void _preview_done(const Variant &p_udata); - -protected: - static void _bind_methods(); + void _generate_frame_started(); + void _preview_done(); 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(); @@ -111,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(); }; @@ -119,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(); }; @@ -136,16 +133,14 @@ class EditorMeshPreviewPlugin : public EditorResourcePreviewGenerator { RID light2; RID light_instance2; RID camera; - mutable SafeFlag preview_done; - - void _preview_done(const Variant &p_udata); + Semaphore preview_done; -protected: - static void _bind_methods(); + void _generate_frame_started(); + void _preview_done(); 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(); @@ -158,19 +153,45 @@ class EditorFontPreviewPlugin : public EditorResourcePreviewGenerator { RID viewport_texture; RID canvas; RID canvas_item; - mutable SafeFlag preview_done; - - void _preview_done(const Variant &p_udata); + Semaphore preview_done; -protected: - static void _bind_methods(); + void _generate_frame_started(); + void _preview_done(); 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(); ~EditorFontPreviewPlugin(); }; -#endif // EDITORPREVIEWPLUGINS_H + +class EditorTileMapPatternPreviewPlugin : public EditorResourcePreviewGenerator { + GDCLASS(EditorTileMapPatternPreviewPlugin, EditorResourcePreviewGenerator); + + Semaphore preview_done; + + void _generate_frame_started(); + void _preview_done(); + +public: + virtual bool handles(const String &p_type) const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + + EditorTileMapPatternPreviewPlugin(); + ~EditorTileMapPatternPreviewPlugin(); +}; + +class EditorGradientPreviewPlugin : public EditorResourcePreviewGenerator { + GDCLASS(EditorGradientPreviewPlugin, EditorResourcePreviewGenerator); + +public: + virtual bool handles(const String &p_type) const override; + virtual bool generate_small_preview_automatically() const override; + virtual Ref<Texture2D> generate(const Ref<Resource> &p_from, const Size2 &p_size) const override; + + EditorGradientPreviewPlugin(); +}; + +#endif // EDITOR_PREVIEW_PLUGINS_H diff --git a/editor/plugins/editor_resource_conversion_plugin.cpp b/editor/plugins/editor_resource_conversion_plugin.cpp new file mode 100644 index 0000000000..91394dbac7 --- /dev/null +++ b/editor/plugins/editor_resource_conversion_plugin.cpp @@ -0,0 +1,64 @@ +/*************************************************************************/ +/* editor_resource_conversion_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 "editor_resource_conversion_plugin.h" + +void EditorResourceConversionPlugin::_bind_methods() { + GDVIRTUAL_BIND(_converts_to); + GDVIRTUAL_BIND(_handles, "resource"); + GDVIRTUAL_BIND(_convert, "resource"); +} + +String EditorResourceConversionPlugin::converts_to() const { + String ret; + if (GDVIRTUAL_CALL(_converts_to, ret)) { + return ret; + } + + return ""; +} + +bool EditorResourceConversionPlugin::handles(const Ref<Resource> &p_resource) const { + bool ret; + if (GDVIRTUAL_CALL(_handles, p_resource, ret)) { + return ret; + } + + return false; +} + +Ref<Resource> EditorResourceConversionPlugin::convert(const Ref<Resource> &p_resource) const { + Ref<Resource> ret; + if (GDVIRTUAL_CALL(_convert, p_resource, ret)) { + return ret; + } + + return Ref<Resource>(); +} diff --git a/editor/plugins/editor_resource_conversion_plugin.h b/editor/plugins/editor_resource_conversion_plugin.h new file mode 100644 index 0000000000..34b0837383 --- /dev/null +++ b/editor/plugins/editor_resource_conversion_plugin.h @@ -0,0 +1,54 @@ +/*************************************************************************/ +/* editor_resource_conversion_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 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 EDITOR_RESOURCE_CONVERSION_PLUGIN_H +#define EDITOR_RESOURCE_CONVERSION_PLUGIN_H + +#include "core/io/resource.h" +#include "core/object/gdvirtual.gen.inc" +#include "core/object/script_language.h" + +class EditorResourceConversionPlugin : public RefCounted { + GDCLASS(EditorResourceConversionPlugin, RefCounted); + +protected: + static void _bind_methods(); + + GDVIRTUAL0RC(String, _converts_to) + GDVIRTUAL1RC(bool, _handles, Ref<Resource>) + GDVIRTUAL1RC(Ref<Resource>, _convert, Ref<Resource>) + +public: + virtual String converts_to() const; + virtual bool handles(const Ref<Resource> &p_resource) const; + virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const; +}; + +#endif // EDITOR_RESOURCE_CONVERSION_PLUGIN_H diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp new file mode 100644 index 0000000000..cadb974345 --- /dev/null +++ b/editor/plugins/font_config_plugin.cpp @@ -0,0 +1,1057 @@ +/*************************************************************************/ +/* 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::_bind_methods() { + ClassDB::bind_method(D_METHOD("property_can_revert", "name"), &EditorPropertyFontOTObject::property_can_revert); + ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &EditorPropertyFontOTObject::property_get_revert); +} + +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 String &p_name) { + 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; +} + +Variant EditorPropertyFontOTObject::property_get_revert(const String &p_name) { + 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]; + return range.z; + } + } + + return Variant(); +} + +/*************************************************************************/ +/* 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..3eaa2fdc17 --- /dev/null +++ b/editor/plugins/font_config_plugin.h @@ -0,0 +1,288 @@ +/*************************************************************************/ +/* 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; + static void _bind_methods(); + +public: + void set_dict(const Dictionary &p_dict); + Dictionary get_dict(); + + void set_defaults(const Dictionary &p_dict); + Dictionary get_defaults(); + + bool property_can_revert(const String &p_name); + Variant property_get_revert(const String &p_name); + + 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 52fb5b69ea..0000000000 --- a/editor/plugins/font_editor_plugin.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/*************************************************************************/ -/* font_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "font_editor_plugin.h" - -#include "editor/editor_scale.h" - -void FontDataPreview::_notification(int p_what) { - if (p_what == NOTIFICATION_DRAW) { - Color text_color = get_theme_color(SNAME("font_color"), SNAME("Label")); - Color line_color = text_color; - line_color.a *= 0.6; - Vector2 pos = (get_size() - line->get_size()) / 2; - line->draw(get_canvas_item(), pos, text_color); - draw_line(Vector2(0, pos.y + line->get_line_ascent()), Vector2(pos.x - 5, pos.y + line->get_line_ascent()), line_color); - draw_line(Vector2(pos.x + line->get_size().x + 5, pos.y + line->get_line_ascent()), Vector2(get_size().x, pos.y + line->get_line_ascent()), line_color); - } -} - -void FontDataPreview::_bind_methods() {} - -Size2 FontDataPreview::get_minimum_size() const { - return Vector2(64, 64) * EDSCALE; -} - -void FontDataPreview::set_data(const Ref<FontData> &p_data) { - Ref<Font> f = memnew(Font); - f->add_data(p_data); - - line->clear(); - if (p_data.is_valid()) { - String sample; - static const String sample_base = U"12漢字ԱբΑαАбΑαאבابܐܒހށआআਆઆଆஆఆಆആආกิກິༀကႠა한글ሀᎣᐁᚁᚠᜀᜠᝀᝠកᠠᤁᥐAb😀"; - for (int i = 0; i < sample_base.length(); i++) { - if (p_data->has_char(sample_base[i])) { - sample += sample_base[i]; - } - } - if (sample.is_empty()) { - sample = p_data->get_supported_chars().substr(0, 6); - } - line->add_string(sample, f, 72); - } - - update(); -} - -FontDataPreview::FontDataPreview() { - line.instantiate(); -} - -/*************************************************************************/ - -bool EditorInspectorPluginFont::can_handle(Object *p_object) { - return Object::cast_to<FontData>(p_object) != nullptr; -} - -void EditorInspectorPluginFont::parse_begin(Object *p_object) { - FontData *fd = Object::cast_to<FontData>(p_object); - ERR_FAIL_COND(!fd); - - FontDataPreview *editor = memnew(FontDataPreview); - editor->set_data(fd); - add_custom_control(editor); -} - -bool EditorInspectorPluginFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { - return false; -} - -/*************************************************************************/ - -FontEditorPlugin::FontEditorPlugin(EditorNode *p_node) { - Ref<EditorInspectorPluginFont> fd_plugin; - fd_plugin.instantiate(); - EditorInspector::add_inspector_plugin(fd_plugin); -} diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h new file mode 100644 index 0000000000..b5eca46ad3 --- /dev/null +++ b/editor/plugins/gdextension_export_plugin.h @@ -0,0 +1,144 @@ +/*************************************************************************/ +/* gdextension_export_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 GDEXTENSION_EXPORT_PLUGIN_H +#define GDEXTENSION_EXPORT_PLUGIN_H + +#include "editor/export/editor_export.h" + +class GDExtensionExportPlugin : public EditorExportPlugin { +protected: + virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); +}; + +void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { + if (p_type != "NativeExtension") { + return; + } + + Ref<ConfigFile> config; + config.instantiate(); + + Error err = config->load(p_path); + ERR_FAIL_COND_MSG(err, "Failed to load GDExtension file: " + p_path); + + ERR_FAIL_COND_MSG(!config->has_section_key("configuration", "entry_symbol"), "Failed to export GDExtension file, missing entry symbol: " + p_path); + + String entry_symbol = config->get_value("configuration", "entry_symbol"); + + List<String> libraries; + + 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; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_features.has(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met) { + String library_path = config->get_value("libraries", E); + if (!library_path.begins_with("res://")) { + print_line("Skipping export of out-of-project library " + library_path); + continue; + } + add_shared_object(library_path, tags); + + if (p_features.has("iOS") && (library_path.ends_with(".a") || library_path.ends_with(".xcframework"))) { + String additional_code = "extern void register_dynamic_symbol(char *name, void *address);\n" + "extern void add_ios_init_callback(void (*cb)());\n" + "\n" + "extern \"C\" void $ENTRY();\n" + "void $ENTRY_init() {\n" + " if (&$ENTRY) register_dynamic_symbol((char *)\"$ENTRY\", (void *)$ENTRY);\n" + "}\n" + "struct $ENTRY_struct {\n" + " $ENTRY_struct() {\n" + " add_ios_init_callback($ENTRY_init);\n" + " }\n" + "};\n" + "$ENTRY_struct $ENTRY_struct_instance;\n\n"; + additional_code = additional_code.replace("$ENTRY", entry_symbol); + add_ios_cpp_code(additional_code); + + String linker_flags = "-Wl,-U,_" + entry_symbol; + add_ios_linker_flags(linker_flags); + } + 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); + } + + for (const String &E : libraries) { + Vector<String> tags = E.split("."); + bool all_tags_met = true; + for (int i = 0; i < tags.size(); i++) { + String tag = tags[i].strip_edges(); + if (!p_features.has(tag)) { + all_tags_met = false; + break; + } + } + + if (all_tags_met) { + Dictionary dependency = config->get_value("dependencies", E); + for (const Variant *key = dependency.next(nullptr); key; key = dependency.next(key)) { + String library_path = *key; + String target_path = dependency[*key]; + if (!library_path.begins_with("res://")) { + print_line("Skipping export of out-of-project library " + library_path); + continue; + } + add_shared_object(library_path, tags, target_path); + } + break; + } + } +} + +#endif // GDEXTENSION_EXPORT_PLUGIN_H diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index dd91df747a..8e6687c836 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,6 +32,9 @@ #include "canvas_item_editor_plugin.h" #include "core/io/image_loader.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" +#include "editor/scene_tree_dock.h" #include "scene/2d/cpu_particles_2d.h" #include "scene/gui/separator.h" #include "scene/resources/particles_material.h" @@ -57,6 +60,27 @@ void GPUParticles2DEditorPlugin::_file_selected(const String &p_file) { emission_mask->popup_centered(); } +void GPUParticles2DEditorPlugin::_selection_changed() { + List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + + if (selected_particles.is_empty() && selected_nodes.is_empty()) { + return; + } + + for (GPUParticles2D *SP : selected_particles) { + SP->set_show_visibility_rect(false); + } + selected_particles.clear(); + + for (Node *P : selected_nodes) { + GPUParticles2D *selected_particle = Object::cast_to<GPUParticles2D>(P); + if (selected_particle != nullptr) { + selected_particle->set_show_visibility_rect(true); + selected_particles.push_back(selected_particle); + } + } +} + void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) { switch (p_idx) { case MENU_GENERATE_VISIBILITY_RECT: { @@ -89,9 +113,9 @@ void GPUParticles2DEditorPlugin::_menu_callback(int p_idx) { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Convert to CPUParticles2D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", particles, cpu_particles, true, false); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", particles, cpu_particles, true, false); ur->add_do_reference(cpu_particles); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", cpu_particles, particles, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", cpu_particles, particles, false, false); ur->add_undo_reference(particles); ur->commit_action(); @@ -158,7 +182,7 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } img->convert(Image::FORMAT_RGBA8); ERR_FAIL_COND(img->get_format() != Image::FORMAT_RGBA8); - Size2i s = Size2(img->get_width(), img->get_height()); + Size2i s = img->get_size(); ERR_FAIL_COND(s.width == 0 || s.height == 0); Vector<Point2> valid_positions; @@ -266,7 +290,7 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { { uint8_t *tw = texdata.ptrw(); - float *twf = (float *)tw; + float *twf = reinterpret_cast<float *>(tw); for (int i = 0; i < vpc; i++) { twf[i * 2 + 0] = valid_positions[i].x; twf[i * 2 + 1] = valid_positions[i].y; @@ -275,12 +299,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) { @@ -296,10 +315,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()) { @@ -310,7 +326,7 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { { uint8_t *tw = normdata.ptrw(); - float *twf = (float *)tw; + float *twf = reinterpret_cast<float *>(tw); for (int i = 0; i < vpc; i++) { twf[i * 2 + 0] = valid_normals[i].x; twf[i * 2 + 1] = valid_normals[i].y; @@ -319,10 +335,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); @@ -330,20 +343,22 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } void GPUParticles2DEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - menu->get_popup()->connect("id_pressed", callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback)); - menu->set_icon(menu->get_theme_icon(SNAME("GPUParticles2D"), SNAME("EditorIcons"))); - file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + menu->get_popup()->connect("id_pressed", callable_mp(this, &GPUParticles2DEditorPlugin::_menu_callback)); + menu->set_icon(menu->get_theme_icon(SNAME("GPUParticles2D"), SNAME("EditorIcons"))); + file->connect("file_selected", callable_mp(this, &GPUParticles2DEditorPlugin::_file_selected)); + EditorNode::get_singleton()->get_editor_selection()->connect("selection_changed", callable_mp(this, &GPUParticles2DEditorPlugin::_selection_changed)); + } break; } } void GPUParticles2DEditorPlugin::_bind_methods() { } -GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin(EditorNode *p_node) { +GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin() { particles = nullptr; - editor = p_node; - undo_redo = editor->get_undo_redo(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); toolbar = memnew(HBoxContainer); add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar); @@ -365,7 +380,7 @@ GPUParticles2DEditorPlugin::GPUParticles2DEditorPlugin(EditorNode *p_node) { List<String> ext; ImageLoader::get_recognized_extensions(&ext); for (const String &E : ext) { - file->add_filter("*." + E + "; " + E.to_upper()); + file->add_filter("*." + E, E.to_upper()); } file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); toolbar->add_child(file); diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.h b/editor/plugins/gpu_particles_2d_editor_plugin.h index 0b2028b745..bf49a82166 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.h +++ b/editor/plugins/gpu_particles_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,15 +28,16 @@ /* 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_node.h" #include "editor/editor_plugin.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/gpu_particles_2d.h" #include "scene/gui/box_container.h" -#include "scene/gui/file_dialog.h" +#include "scene/gui/spin_box.h" + +class EditorFileDialog; class GPUParticles2DEditorPlugin : public EditorPlugin { GDCLASS(GPUParticles2DEditorPlugin, EditorPlugin); @@ -55,30 +56,31 @@ class GPUParticles2DEditorPlugin : public EditorPlugin { EMISSION_MODE_BORDER_DIRECTED }; - GPUParticles2D *particles; + GPUParticles2D *particles = nullptr; + List<GPUParticles2D *> selected_particles; - EditorFileDialog *file; - EditorNode *editor; + EditorFileDialog *file = nullptr; - HBoxContainer *toolbar; - MenuButton *menu; + HBoxContainer *toolbar = nullptr; + MenuButton *menu = nullptr; - SpinBox *epoints; + SpinBox *epoints = nullptr; - ConfirmationDialog *generate_visibility_rect; - SpinBox *generate_seconds; + ConfirmationDialog *generate_visibility_rect = nullptr; + SpinBox *generate_seconds = nullptr; - ConfirmationDialog *emission_mask; - OptionButton *emission_mask_mode; - CheckBox *emission_colors; + ConfirmationDialog *emission_mask = nullptr; + OptionButton *emission_mask_mode = nullptr; + CheckBox *emission_colors = nullptr; String source_emission_file; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; void _file_selected(const String &p_file); void _menu_callback(int p_idx); void _generate_visibility_rect(); void _generate_emission_mask(); + void _selection_changed(); protected: void _notification(int p_what); @@ -91,8 +93,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - GPUParticles2DEditorPlugin(EditorNode *p_node); + GPUParticles2DEditorPlugin(); ~GPUParticles2DEditorPlugin(); }; -#endif // PARTICLES_2D_EDITOR_PLUGIN_H +#endif // GPU_PARTICLES_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 903a3689b0..6750f1aa9c 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,9 @@ #include "gpu_particles_3d_editor_plugin.h" #include "core/io/resource_loader.h" +#include "editor/editor_node.h" #include "editor/plugins/node_3d_editor_plugin.h" +#include "editor/scene_tree_dock.h" #include "scene/3d/cpu_particles_3d.h" #include "scene/resources/particles_material.h" @@ -40,7 +42,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(); @@ -61,9 +63,9 @@ bool GPUParticles3DEditorBase::_generate(Vector<Vector3> &points, Vector<Vector3 for (int i = 0; i < emissor_count; i++) { float areapos = Math::random(0.0f, area_accum); - Map<float, int>::Element *E = triangle_area_map.find_closest(areapos); + RBMap<float, int>::Iterator E = triangle_area_map.find_closest(areapos); ERR_FAIL_COND_V(!E, false); - int index = E->get(); + int index = E->value; ERR_FAIL_INDEX_V(index, geometry.size(), false); // ok FINALLY get face @@ -164,20 +166,20 @@ void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) { return; } - VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sel); - if (!vi) { + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(sel); + if (!mi || mi->get_mesh().is_null()) { EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain geometry."), sel->get_name())); return; } - geometry = vi->get_faces(VisualInstance3D::FACES_SOLID); + geometry = mi->get_mesh()->get_faces(); if (geometry.size() == 0) { EditorNode::get_singleton()->show_warning(vformat(TTR("\"%s\" doesn't contain face geometry."), sel->get_name())); return; } - Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform(); + Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * mi->get_global_transform(); int gc = geometry.size(); Face3 *w = geometry.ptrw(); @@ -211,9 +213,9 @@ GPUParticles3DEditorBase::GPUParticles3DEditorBase() { emission_fill->add_item(TTR("Surface Points")); emission_fill->add_item(TTR("Surface Points+Normal (Directed)")); emission_fill->add_item(TTR("Volume")); - emd_vb->add_margin_child(TTR("Emission Source: "), emission_fill); + emd_vb->add_margin_child(TTR("Emission Source:"), emission_fill); - emission_dialog->get_ok_button()->set_text(TTR("Create")); + emission_dialog->set_ok_button_text(TTR("Create")); emission_dialog->connect("confirmed", callable_mp(this, &GPUParticles3DEditorBase::_generate_emission_points)); emission_tree_dialog = memnew(SceneTreeDialog); @@ -229,9 +231,11 @@ void GPUParticles3DEditor::_node_removed(Node *p_node) { } void GPUParticles3DEditor::_notification(int p_notification) { - if (p_notification == NOTIFICATION_ENTER_TREE) { - options->set_icon(options->get_popup()->get_theme_icon(SNAME("GPUParticles3D"), SNAME("EditorIcons"))); - get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed)); + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: { + options->set_icon(options->get_popup()->get_theme_icon(SNAME("GPUParticles3D"), SNAME("EditorIcons"))); + get_tree()->connect("node_removed", callable_mp(this, &GPUParticles3DEditor::_node_removed)); + } break; } } @@ -269,9 +273,9 @@ void GPUParticles3DEditor::_menu_option(int p_option) { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Convert to CPUParticles3D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", node, cpu_particles, true, false); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, cpu_particles, true, false); ur->add_do_reference(cpu_particles); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", cpu_particles, node, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", cpu_particles, node, false, false); ur->add_undo_reference(node); ur->commit_action(); @@ -350,7 +354,7 @@ void GPUParticles3DEditor::_generate_emission_points() { uint8_t *iw = point_img.ptrw(); memset(iw, 0, w * h * 3 * sizeof(float)); const Vector3 *r = points.ptr(); - float *wf = (float *)iw; + float *wf = reinterpret_cast<float *>(iw); for (int i = 0; i < point_count; i++) { wf[i * 3 + 0] = r[i].x; wf[i * 3 + 1] = r[i].y; @@ -359,9 +363,7 @@ void GPUParticles3DEditor::_generate_emission_points() { } Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); - - Ref<ImageTexture> tex; - tex.instantiate(); + Ref<ImageTexture> tex = ImageTexture::create_from_image(image); Ref<ParticlesMaterial> material = node->get_process_material(); ERR_FAIL_COND(material.is_null()); @@ -378,7 +380,7 @@ void GPUParticles3DEditor::_generate_emission_points() { uint8_t *iw = point_img2.ptrw(); memset(iw, 0, w * h * 3 * sizeof(float)); const Vector3 *r = normals.ptr(); - float *wf = (float *)iw; + float *wf = reinterpret_cast<float *>(iw); for (int i = 0; i < point_count; i++) { wf[i * 3 + 0] = r[i].x; wf[i * 3 + 1] = r[i].y; @@ -387,11 +389,7 @@ void GPUParticles3DEditor::_generate_emission_points() { } Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2)); - - Ref<ImageTexture> tex2; - tex2.instantiate(); - - 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); @@ -453,10 +451,9 @@ void GPUParticles3DEditorPlugin::make_visible(bool p_visible) { } } -GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +GPUParticles3DEditorPlugin::GPUParticles3DEditorPlugin() { particles_editor = memnew(GPUParticles3DEditor); - editor->get_main_control()->add_child(particles_editor); + EditorNode::get_singleton()->get_main_control()->add_child(particles_editor); particles_editor->hide(); } diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.h b/editor/plugins/gpu_particles_3d_editor_plugin.h index bd10895459..17bdfa6e3f 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.h +++ b/editor/plugins/gpu_particles_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,33 +28,34 @@ /* 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_node.h" #include "editor/editor_plugin.h" #include "scene/3d/gpu_particles_3d.h" #include "scene/gui/spin_box.h" +class SceneTreeDialog; + class GPUParticles3DEditorBase : public Control { GDCLASS(GPUParticles3DEditorBase, Control); protected: - Node3D *base_node; - Panel *panel; - MenuButton *options; - HBoxContainer *particles_editor_hb; + Node3D *base_node = nullptr; + Panel *panel = nullptr; + MenuButton *options = nullptr; + HBoxContainer *particles_editor_hb = nullptr; - SceneTreeDialog *emission_tree_dialog; + SceneTreeDialog *emission_tree_dialog = nullptr; - ConfirmationDialog *emission_dialog; - SpinBox *emission_amount; - OptionButton *emission_fill; + ConfirmationDialog *emission_dialog = nullptr; + SpinBox *emission_amount = nullptr; + OptionButton *emission_fill = nullptr; Vector<Face3> geometry; bool _generate(Vector<Vector3> &points, Vector<Vector3> &normals); - virtual void _generate_emission_points() = 0; + virtual void _generate_emission_points(){}; void _node_selected(const NodePath &p_path); static void _bind_methods(); @@ -66,9 +67,9 @@ public: class GPUParticles3DEditor : public GPUParticles3DEditorBase { GDCLASS(GPUParticles3DEditor, GPUParticles3DEditorBase); - ConfirmationDialog *generate_aabb; - SpinBox *generate_seconds; - GPUParticles3D *node; + ConfirmationDialog *generate_aabb = nullptr; + SpinBox *generate_seconds = nullptr; + GPUParticles3D *node = nullptr; enum Menu { MENU_OPTION_GENERATE_AABB, @@ -100,8 +101,7 @@ public: class GPUParticles3DEditorPlugin : public EditorPlugin { GDCLASS(GPUParticles3DEditorPlugin, EditorPlugin); - GPUParticles3DEditor *particles_editor; - EditorNode *editor; + GPUParticles3DEditor *particles_editor = nullptr; public: virtual String get_name() const override { return "GPUParticles3D"; } @@ -110,8 +110,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - GPUParticles3DEditorPlugin(EditorNode *p_node); + GPUParticles3DEditorPlugin(); ~GPUParticles3DEditorPlugin(); }; -#endif // PARTICLES_EDITOR_PLUGIN_H +#endif // GPU_PARTICLES_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp index a4436525fb..b54cb515e4 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,11 +30,14 @@ #include "gpu_particles_collision_sdf_editor_plugin.h" -void GPUParticlesCollisionSDFEditorPlugin::_bake() { +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + +void GPUParticlesCollisionSDF3DEditorPlugin::_bake() { if (col_sdf) { if (col_sdf->get_texture().is_null() || !col_sdf->get_texture()->get_path().is_resource_file()) { - String path = get_tree()->get_edited_scene_root()->get_filename(); - if (path == String()) { + String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); + if (path.is_empty()) { path = "res://" + col_sdf->get_name() + "_data.exr"; } else { String ext = path.get_extension(); @@ -49,8 +52,8 @@ void GPUParticlesCollisionSDFEditorPlugin::_bake() { } } -void GPUParticlesCollisionSDFEditorPlugin::edit(Object *p_object) { - GPUParticlesCollisionSDF *s = Object::cast_to<GPUParticlesCollisionSDF>(p_object); +void GPUParticlesCollisionSDF3DEditorPlugin::edit(Object *p_object) { + GPUParticlesCollisionSDF3D *s = Object::cast_to<GPUParticlesCollisionSDF3D>(p_object); if (!s) { return; } @@ -58,46 +61,52 @@ void GPUParticlesCollisionSDFEditorPlugin::edit(Object *p_object) { col_sdf = s; } -bool GPUParticlesCollisionSDFEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("GPUParticlesCollisionSDF"); +bool GPUParticlesCollisionSDF3DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("GPUParticlesCollisionSDF3D"); } -void GPUParticlesCollisionSDFEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { - if (!col_sdf) { - return; - } +void GPUParticlesCollisionSDF3DEditorPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_PROCESS: { + if (!col_sdf) { + return; + } - const Vector3i size = col_sdf->get_estimated_cell_size(); - String text = vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z); - int data_size = 2; + // Set information tooltip on the Bake button. This information is useful + // to optimize performance (video RAM size) and reduce collision tunneling (individual cell size). - const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); - text += " - " + vformat(TTR("VRAM Size: %s MB"), String::num(size_mb, 2)); + const Vector3i size = col_sdf->get_estimated_cell_size(); - if (bake_info->get_text() == text) { - return; - } + const Vector3 extents = col_sdf->get_extents(); - // Color the label depending on the estimated performance level. - Color color; - if (size_mb <= 16.0 + CMP_EPSILON) { - // Fast. - color = bake_info->get_theme_color(SNAME("success_color"), SNAME("Editor")); - } else if (size_mb <= 64.0 + CMP_EPSILON) { - // Medium. - color = bake_info->get_theme_color(SNAME("warning_color"), SNAME("Editor")); - } else { - // Slow. - color = bake_info->get_theme_color(SNAME("error_color"), SNAME("Editor")); - } - bake_info->add_theme_color_override("font_color", color); + int data_size = 2; + const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); + // Add a qualitative measurement to help the user assess whether a GPUParticlesCollisionSDF3D node is using a lot of VRAM. + String size_quality; + if (size_mb < 8.0) { + size_quality = TTR("Low"); + } else if (size_mb < 32.0) { + size_quality = TTR("Moderate"); + } else { + size_quality = TTR("High"); + } + + String text; + text += vformat(TTR("Subdivisions: %s"), vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z)) + "\n"; + text += vformat(TTR("Cell size: %s"), vformat(String::utf8("%.3f × %.3f × %.3f"), extents.x / size.x, extents.y / size.y, extents.z / size.z)) + "\n"; + text += vformat(TTR("Video RAM size: %s MB (%s)"), String::num(size_mb, 2), size_quality); + + // Only update the tooltip when needed to avoid constant redrawing. + if (bake->get_tooltip(Point2()) == text) { + return; + } - bake_info->set_text(text); + bake->set_tooltip(text); + } break; } } -void GPUParticlesCollisionSDFEditorPlugin::make_visible(bool p_visible) { +void GPUParticlesCollisionSDF3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { bake_hb->show(); set_process(true); @@ -107,31 +116,31 @@ void GPUParticlesCollisionSDFEditorPlugin::make_visible(bool p_visible) { } } -EditorProgress *GPUParticlesCollisionSDFEditorPlugin::tmp_progress = nullptr; +EditorProgress *GPUParticlesCollisionSDF3DEditorPlugin::tmp_progress = nullptr; -void GPUParticlesCollisionSDFEditorPlugin::bake_func_begin(int p_steps) { +void GPUParticlesCollisionSDF3DEditorPlugin::bake_func_begin(int p_steps) { ERR_FAIL_COND(tmp_progress != nullptr); tmp_progress = memnew(EditorProgress("bake_sdf", TTR("Bake SDF"), p_steps)); } -void GPUParticlesCollisionSDFEditorPlugin::bake_func_step(int p_step, const String &p_description) { +void GPUParticlesCollisionSDF3DEditorPlugin::bake_func_step(int p_step, const String &p_description) { ERR_FAIL_COND(tmp_progress == nullptr); tmp_progress->step(p_description, p_step, false); } -void GPUParticlesCollisionSDFEditorPlugin::bake_func_end() { +void GPUParticlesCollisionSDF3DEditorPlugin::bake_func_end() { ERR_FAIL_COND(tmp_progress == nullptr); memdelete(tmp_progress); tmp_progress = nullptr; } -void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String &p_path) { +void GPUParticlesCollisionSDF3DEditorPlugin::_sdf_save_path_and_bake(const String &p_path) { probe_file->hide(); 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; } @@ -143,7 +152,7 @@ void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String } config->set_value("remap", "importer", "3d_texture"); - config->set_value("remap", "type", "StreamTexture3D"); + config->set_value("remap", "type", "CompressedTexture3D"); if (!config->has_section_key("params", "compress/mode")) { config->set_value("params", "compress/mode", 3); //user may want another compression, so leave it be } @@ -164,38 +173,33 @@ void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String } } -void GPUParticlesCollisionSDFEditorPlugin::_bind_methods() { +void GPUParticlesCollisionSDF3DEditorPlugin::_bind_methods() { } -GPUParticlesCollisionSDFEditorPlugin::GPUParticlesCollisionSDFEditorPlugin(EditorNode *p_node) { - editor = p_node; +GPUParticlesCollisionSDF3DEditorPlugin::GPUParticlesCollisionSDF3DEditorPlugin() { bake_hb = memnew(HBoxContainer); bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); bake_hb->hide(); bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake SDF")); - bake->connect("pressed", callable_mp(this, &GPUParticlesCollisionSDFEditorPlugin::_bake)); + bake->connect("pressed", callable_mp(this, &GPUParticlesCollisionSDF3DEditorPlugin::_bake)); bake_hb->add_child(bake); - bake_info = memnew(Label); - bake_info->set_h_size_flags(Control::SIZE_EXPAND_FILL); - bake_info->set_clip_text(true); - bake_hb->add_child(bake_info); add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake_hb); col_sdf = nullptr; probe_file = memnew(EditorFileDialog); probe_file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); probe_file->add_filter("*.exr"); - probe_file->connect("file_selected", callable_mp(this, &GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake)); + probe_file->connect("file_selected", callable_mp(this, &GPUParticlesCollisionSDF3DEditorPlugin::_sdf_save_path_and_bake)); get_editor_interface()->get_base_control()->add_child(probe_file); probe_file->set_title(TTR("Select path for SDF Texture")); - GPUParticlesCollisionSDF::bake_begin_function = bake_func_begin; - GPUParticlesCollisionSDF::bake_step_function = bake_func_step; - GPUParticlesCollisionSDF::bake_end_function = bake_func_end; + GPUParticlesCollisionSDF3D::bake_begin_function = bake_func_begin; + GPUParticlesCollisionSDF3D::bake_step_function = bake_func_step; + GPUParticlesCollisionSDF3D::bake_end_function = bake_func_end; } -GPUParticlesCollisionSDFEditorPlugin::~GPUParticlesCollisionSDFEditorPlugin() { +GPUParticlesCollisionSDF3DEditorPlugin::~GPUParticlesCollisionSDF3DEditorPlugin() { } diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h index 5a71fc44ef..684279039a 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,22 +31,22 @@ #ifndef GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H #define GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/gpu_particles_collision_3d.h" #include "scene/resources/material.h" -class GPUParticlesCollisionSDFEditorPlugin : public EditorPlugin { - GDCLASS(GPUParticlesCollisionSDFEditorPlugin, EditorPlugin); +struct EditorProgress; +class EditorFileDialog; - GPUParticlesCollisionSDF *col_sdf; +class GPUParticlesCollisionSDF3DEditorPlugin : public EditorPlugin { + GDCLASS(GPUParticlesCollisionSDF3DEditorPlugin, EditorPlugin); - HBoxContainer *bake_hb; - Label *bake_info; - Button *bake; - EditorNode *editor; + GPUParticlesCollisionSDF3D *col_sdf = nullptr; - EditorFileDialog *probe_file; + HBoxContainer *bake_hb = nullptr; + Button *bake = nullptr; + + EditorFileDialog *probe_file = nullptr; static EditorProgress *tmp_progress; static void bake_func_begin(int p_steps); @@ -61,14 +61,14 @@ protected: void _notification(int p_what); public: - virtual String get_name() const override { return "GPUParticlesCollisionSDF"; } + virtual String get_name() const override { return "GPUParticlesCollisionSDF3D"; } 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; - GPUParticlesCollisionSDFEditorPlugin(EditorNode *p_node); - ~GPUParticlesCollisionSDFEditorPlugin(); + GPUParticlesCollisionSDF3DEditorPlugin(); + ~GPUParticlesCollisionSDF3DEditorPlugin(); }; #endif // GPU_PARTICLES_COLLISION_SDF_EDITOR_PLUGIN_H diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index 355bdb69d8..542aee879b 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,9 @@ #include "gradient_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "node_3d_editor_plugin.h" Size2 GradientEditor::get_minimum_size() const { @@ -46,17 +48,21 @@ void GradientEditor::_gradient_changed() { editing = true; Vector<Gradient::Point> points = gradient->get_points(); set_points(points); + set_interpolation_mode(gradient->get_interpolation_mode()); + update(); editing = false; } void GradientEditor::_ramp_changed() { editing = true; UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); - undo_redo->create_action(TTR("Gradient Edited")); + undo_redo->create_action(TTR("Gradient Edited"), UndoRedo::MERGE_ENDS); undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets()); undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors()); + undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode()); undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets()); undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors()); + undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode()); undo_redo->commit_action(); editing = false; } @@ -69,14 +75,42 @@ void GradientEditor::set_gradient(const Ref<Gradient> &p_gradient) { connect("ramp_changed", callable_mp(this, &GradientEditor::_ramp_changed)); gradient->connect("changed", callable_mp(this, &GradientEditor::_gradient_changed)); set_points(gradient->get_points()); + set_interpolation_mode(gradient->get_interpolation_mode()); +} + +void GradientEditor::reverse_gradient() { + gradient->reverse(); + set_points(gradient->get_points()); + emit_signal(SNAME("ramp_changed")); + update(); } GradientEditor::GradientEditor() { + GradientEdit::get_popup()->connect("about_to_popup", callable_mp(EditorNode::get_singleton(), &EditorNode::setup_color_picker).bind(GradientEdit::get_picker())); editing = false; } /////////////////////// +void GradientReverseButton::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_DRAW: { + Ref<Texture2D> icon = get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons")); + if (is_pressed()) { + draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height()), false, get_theme_color(SNAME("icon_pressed_color"), SNAME("Button"))); + } else { + draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height())); + } + } break; + } +} + +Size2 GradientReverseButton::get_minimum_size() const { + return (get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons"))->get_size() + Size2(margin * 2, margin * 2)); +} + +/////////////////////// + bool EditorInspectorPluginGradient::can_handle(Object *p_object) { return Object::cast_to<Gradient>(p_object) != nullptr; } @@ -85,12 +119,29 @@ void EditorInspectorPluginGradient::parse_begin(Object *p_object) { Gradient *gradient = Object::cast_to<Gradient>(p_object); Ref<Gradient> g(gradient); - GradientEditor *editor = memnew(GradientEditor); + editor = memnew(GradientEditor); editor->set_gradient(g); add_custom_control(editor); + + int picker_shape = EDITOR_GET("interface/inspector/default_color_picker_shape"); + editor->get_picker()->set_picker_shape((ColorPicker::PickerShapeType)picker_shape); + + reverse_btn = memnew(GradientReverseButton); + + gradient_tools_hbox = memnew(HBoxContainer); + gradient_tools_hbox->add_child(reverse_btn); + + add_custom_control(gradient_tools_hbox); + + reverse_btn->connect("pressed", callable_mp(this, &EditorInspectorPluginGradient::_reverse_button_pressed)); + reverse_btn->set_tooltip(TTR("Reverse/mirror gradient.")); +} + +void EditorInspectorPluginGradient::_reverse_button_pressed() { + editor->reverse_gradient(); } -GradientEditorPlugin::GradientEditorPlugin(EditorNode *p_node) { +GradientEditorPlugin::GradientEditorPlugin() { Ref<EditorInspectorPluginGradient> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/gradient_editor_plugin.h b/editor/plugins/gradient_editor_plugin.h index bcbb86e422..26bf76fecd 100644 --- a/editor/plugins/gradient_editor_plugin.h +++ b/editor/plugins/gradient_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef GRADIENT_EDITOR_PLUGIN_H #define GRADIENT_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/gui/gradient_edit.h" @@ -50,12 +49,28 @@ protected: public: virtual Size2 get_minimum_size() const override; void set_gradient(const Ref<Gradient> &p_gradient); + void reverse_gradient(); GradientEditor(); }; +class GradientReverseButton : public BaseButton { + GDCLASS(GradientReverseButton, BaseButton); + + int margin = 2; + + void _notification(int p_what); + virtual Size2 get_minimum_size() const override; +}; + class EditorInspectorPluginGradient : public EditorInspectorPlugin { GDCLASS(EditorInspectorPluginGradient, EditorInspectorPlugin); + GradientEditor *editor = nullptr; + HBoxContainer *gradient_tools_hbox = nullptr; + GradientReverseButton *reverse_btn = nullptr; + + void _reverse_button_pressed(); + public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; @@ -67,7 +82,7 @@ class GradientEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Gradient"; } - GradientEditorPlugin(EditorNode *p_node); + GradientEditorPlugin(); }; #endif // GRADIENT_EDITOR_PLUGIN_H diff --git a/editor/plugins/gradient_texture_2d_editor_plugin.cpp b/editor/plugins/gradient_texture_2d_editor_plugin.cpp new file mode 100644 index 0000000000..df45d6c290 --- /dev/null +++ b/editor/plugins/gradient_texture_2d_editor_plugin.cpp @@ -0,0 +1,284 @@ +/*************************************************************************/ +/* gradient_texture_2d_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "gradient_texture_2d_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "scene/gui/box_container.h" +#include "scene/gui/flow_container.h" +#include "scene/gui/separator.h" + +Point2 GradientTexture2DEditorRect::_get_handle_position(const Handle p_handle) { + // Get the handle's mouse position in pixels relative to offset. + return (p_handle == HANDLE_FILL_FROM ? texture->get_fill_from() : texture->get_fill_to()).clamp(Vector2(), Vector2(1, 1)) * size; +} + +void GradientTexture2DEditorRect::_update_fill_position() { + if (handle == HANDLE_NONE) { + return; + } + + // Update the texture's fill_from/fill_to property based on mouse input. + Vector2 percent = ((get_local_mouse_position() - offset) / size).clamp(Vector2(), Vector2(1, 1)); + if (snap_enabled) { + percent = (percent - Vector2(0.5, 0.5)).snapped(Vector2(snap_size, snap_size)) + Vector2(0.5, 0.5); + } + + String property_name = handle == HANDLE_FILL_FROM ? "fill_from" : "fill_to"; + + undo_redo->create_action(vformat(TTR("Set %s"), property_name), UndoRedo::MERGE_ENDS); + undo_redo->add_do_property(texture.ptr(), property_name, percent); + undo_redo->add_undo_property(texture.ptr(), property_name, handle == HANDLE_FILL_FROM ? texture->get_fill_from() : texture->get_fill_to()); + undo_redo->commit_action(); +} + +void GradientTexture2DEditorRect::gui_input(const Ref<InputEvent> &p_event) { + // Grab/release handle. + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + Point2 mouse_position = mb->get_position() - offset; + if (Rect2(_get_handle_position(HANDLE_FILL_FROM).round() - handle_size / 2, handle_size).has_point(mouse_position)) { + handle = HANDLE_FILL_FROM; + } else if (Rect2(_get_handle_position(HANDLE_FILL_TO).round() - handle_size / 2, handle_size).has_point(mouse_position)) { + handle = HANDLE_FILL_TO; + } else { + handle = HANDLE_NONE; + } + } else { + _update_fill_position(); + handle = HANDLE_NONE; + } + } + + // Move handle. + const Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + _update_fill_position(); + } +} + +void GradientTexture2DEditorRect::set_texture(Ref<GradientTexture2D> &p_texture) { + texture = p_texture; + texture->connect("changed", callable_mp((CanvasItem *)this, &CanvasItem::update)); +} + +void GradientTexture2DEditorRect::set_snap_enabled(bool p_snap_enabled) { + snap_enabled = p_snap_enabled; + update(); +} + +void GradientTexture2DEditorRect::set_snap_size(float p_snap_size) { + snap_size = p_snap_size; + update(); +} + +void GradientTexture2DEditorRect::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + checkerboard->set_texture(get_theme_icon(SNAME("GuiMiniCheckerboard"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_DRAW: { + if (texture.is_null()) { + return; + } + + const Ref<Texture2D> fill_from_icon = get_theme_icon(SNAME("EditorPathSmoothHandle"), SNAME("EditorIcons")); + const Ref<Texture2D> fill_to_icon = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); + handle_size = fill_from_icon->get_size(); + + Size2 rect_size = get_size(); + + // Get the size and position to draw the texture and handles at. + 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() * size.width / texture->get_width(); + } + offset = ((rect_size - size + handle_size) / 2).round(); + size -= handle_size; + checkerboard->set_rect(Rect2(offset, size)); + + draw_set_transform(offset); + draw_texture_rect(texture, Rect2(Point2(), size)); + + // Draw grid snap lines. + if (snap_enabled) { + const Color primary_line_color = Color(0.5, 0.5, 0.5, 0.9); + const Color line_color = Color(0.5, 0.5, 0.5, 0.5); + + // Draw border and centered axis lines. + draw_rect(Rect2(Point2(), size), primary_line_color, false); + draw_line(Point2(size.width / 2, 0), Point2(size.width / 2, size.height), primary_line_color); + draw_line(Point2(0, size.height / 2), Point2(size.width, size.height / 2), primary_line_color); + + // Draw vertical lines. + int prev_idx = 0; + for (int x = 0; x < size.width; x++) { + int idx = int((x / size.width - 0.5) / snap_size); + + if (x > 0 && prev_idx != idx) { + draw_line(Point2(x, 0), Point2(x, size.height), line_color); + } + + prev_idx = idx; + } + + // Draw horizontal lines. + prev_idx = 0; + for (int y = 0; y < size.height; y++) { + int idx = int((y / size.height - 0.5) / snap_size); + + if (y > 0 && prev_idx != idx) { + draw_line(Point2(0, y), Point2(size.width, y), line_color); + } + + prev_idx = idx; + } + } + + // Draw handles. + draw_texture(fill_from_icon, (_get_handle_position(HANDLE_FILL_FROM) - handle_size / 2).round()); + draw_texture(fill_to_icon, (_get_handle_position(HANDLE_FILL_TO) - handle_size / 2).round()); + } break; + } +} + +GradientTexture2DEditorRect::GradientTexture2DEditorRect() { + undo_redo = EditorNode::get_singleton()->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)); +} + +/////////////////////// + +void GradientTexture2DEditor::_reverse_button_pressed() { + undo_redo->create_action(TTR("Swap GradientTexture2D Fill Points")); + undo_redo->add_do_property(texture.ptr(), "fill_from", texture->get_fill_to()); + undo_redo->add_do_property(texture.ptr(), "fill_to", texture->get_fill_from()); + undo_redo->add_undo_property(texture.ptr(), "fill_from", texture->get_fill_from()); + undo_redo->add_undo_property(texture.ptr(), "fill_to", texture->get_fill_to()); + undo_redo->commit_action(); +} + +void GradientTexture2DEditor::_set_snap_enabled(bool p_enabled) { + texture_editor_rect->set_snap_enabled(p_enabled); + + snap_size_edit->set_visible(p_enabled); +} + +void GradientTexture2DEditor::_set_snap_size(float p_snap_size) { + texture_editor_rect->set_snap_size(MAX(p_snap_size, 0.01)); +} + +void GradientTexture2DEditor::set_texture(Ref<GradientTexture2D> &p_texture) { + texture = p_texture; + texture_editor_rect->set_texture(p_texture); +} + +void GradientTexture2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + reverse_button->set_icon(get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons"))); + snap_button->set_icon(get_theme_icon(SNAME("SnapGrid"), SNAME("EditorIcons"))); + } break; + } +} + +GradientTexture2DEditor::GradientTexture2DEditor() { + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + HFlowContainer *toolbar = memnew(HFlowContainer); + add_child(toolbar); + + reverse_button = memnew(Button); + reverse_button->set_tooltip(TTR("Swap Gradient Fill Points")); + toolbar->add_child(reverse_button); + reverse_button->connect("pressed", callable_mp(this, &GradientTexture2DEditor::_reverse_button_pressed)); + + toolbar->add_child(memnew(VSeparator)); + + snap_button = memnew(Button); + snap_button->set_tooltip(TTR("Toggle Grid Snap")); + snap_button->set_toggle_mode(true); + toolbar->add_child(snap_button); + snap_button->connect("toggled", callable_mp(this, &GradientTexture2DEditor::_set_snap_enabled)); + + snap_size_edit = memnew(EditorSpinSlider); + snap_size_edit->set_min(0.01); + snap_size_edit->set_max(0.5); + snap_size_edit->set_step(0.01); + snap_size_edit->set_value(0.1); + snap_size_edit->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); + toolbar->add_child(snap_size_edit); + snap_size_edit->connect("value_changed", callable_mp(this, &GradientTexture2DEditor::_set_snap_size)); + + texture_editor_rect = memnew(GradientTexture2DEditorRect); + add_child(texture_editor_rect); + + set_mouse_filter(MOUSE_FILTER_STOP); + _set_snap_enabled(snap_button->is_pressed()); + _set_snap_size(snap_size_edit->get_value()); +} + +/////////////////////// + +bool EditorInspectorPluginGradientTexture2D::can_handle(Object *p_object) { + return Object::cast_to<GradientTexture2D>(p_object) != nullptr; +} + +void EditorInspectorPluginGradientTexture2D::parse_begin(Object *p_object) { + GradientTexture2D *texture = Object::cast_to<GradientTexture2D>(p_object); + if (!texture) { + return; + } + Ref<GradientTexture2D> t(texture); + + GradientTexture2DEditor *editor = memnew(GradientTexture2DEditor); + editor->set_texture(t); + add_custom_control(editor); +} + +/////////////////////// + +GradientTexture2DEditorPlugin::GradientTexture2DEditorPlugin() { + Ref<EditorInspectorPluginGradientTexture2D> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/ot_features_plugin.h b/editor/plugins/gradient_texture_2d_editor_plugin.h index dbafa3bbf6..93c49b1e6f 100644 --- a/editor/plugins/ot_features_plugin.h +++ b/editor/plugins/gradient_texture_2d_editor_plugin.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* ot_features_plugin.h */ +/* gradient_texture_2d_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,78 +28,85 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef OT_FEATURES_PLUGIN_H -#define OT_FEATURES_PLUGIN_H +#ifndef GRADIENT_TEXTURE_2D_EDITOR_PLUGIN_H +#define GRADIENT_TEXTURE_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "editor/editor_properties.h" +#include "editor/editor_spin_slider.h" -/*************************************************************************/ +class GradientTexture2DEditorRect : public Control { + GDCLASS(GradientTexture2DEditorRect, Control); + + enum Handle { + HANDLE_NONE, + HANDLE_FILL_FROM, + HANDLE_FILL_TO + }; + + Ref<GradientTexture2D> texture; + UndoRedo *undo_redo = nullptr; + bool snap_enabled = false; + float snap_size = 0; -class OpenTypeFeaturesEditor : public EditorProperty { - GDCLASS(OpenTypeFeaturesEditor, EditorProperty); - EditorSpinSlider *spin; - bool setting = true; - void _value_changed(double p_val); - Button *button = nullptr; + TextureRect *checkerboard = nullptr; - void _remove_feature(); + Handle handle = HANDLE_NONE; + Size2 handle_size; + Point2 offset; + Size2 size; + + Point2 _get_handle_position(const Handle p_handle); + void _update_fill_position(); + virtual void gui_input(const Ref<InputEvent> &p_event) override; protected: void _notification(int p_what); - static void _bind_methods(); public: - virtual void update_property() override; - OpenTypeFeaturesEditor(); + void set_texture(Ref<GradientTexture2D> &p_texture); + void set_snap_enabled(bool p_snap_enabled); + void set_snap_size(float p_snap_size); + + GradientTexture2DEditorRect(); }; -/*************************************************************************/ +class GradientTexture2DEditor : public VBoxContainer { + GDCLASS(GradientTexture2DEditor, VBoxContainer); -class OpenTypeFeaturesAdd : public EditorProperty { - GDCLASS(OpenTypeFeaturesAdd, EditorProperty); + Ref<GradientTexture2D> texture; + UndoRedo *undo_redo = nullptr; - Button *button = nullptr; - PopupMenu *menu = nullptr; - PopupMenu *menu_ss = nullptr; - PopupMenu *menu_cv = nullptr; - PopupMenu *menu_cu = nullptr; + Button *reverse_button = nullptr; + Button *snap_button = nullptr; + EditorSpinSlider *snap_size_edit = nullptr; + GradientTexture2DEditorRect *texture_editor_rect = nullptr; - void _add_feature(int p_option); - void _features_menu(); + void _reverse_button_pressed(); + void _set_snap_enabled(bool p_enabled); + void _set_snap_size(float p_snap_size); protected: void _notification(int p_what); - static void _bind_methods(); public: - virtual void update_property() override; + void set_texture(Ref<GradientTexture2D> &p_texture); - OpenTypeFeaturesAdd(); + GradientTexture2DEditor(); }; -/*************************************************************************/ - -class EditorInspectorPluginOpenTypeFeatures : public EditorInspectorPlugin { - GDCLASS(EditorInspectorPluginOpenTypeFeatures, EditorInspectorPlugin); +class EditorInspectorPluginGradientTexture2D : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginGradientTexture2D, EditorInspectorPlugin); public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; - virtual void parse_category(Object *p_object, const String &p_parse_category) 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); +class GradientTexture2DEditorPlugin : public EditorPlugin { + GDCLASS(GradientTexture2DEditorPlugin, EditorPlugin); public: - OpenTypeFeaturesEditorPlugin(EditorNode *p_node); - - virtual String get_name() const override { return "OpenTypeFeatures"; } + GradientTexture2DEditorPlugin(); }; -#endif // OT_FEATURES_PLUGIN_H +#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 d3d2de92f5..153eab32d2 100644 --- a/editor/plugins/input_event_editor_plugin.cpp +++ b/editor/plugins/input_event_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,6 +33,15 @@ void InputEventConfigContainer::_bind_methods() { } +void InputEventConfigContainer::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + open_config_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + } break; + } +} + void InputEventConfigContainer::_configure_pressed() { config_dialog->popup_and_configure(input_event); } @@ -47,12 +56,6 @@ void InputEventConfigContainer::_config_dialog_confirmed() { _event_changed(); } -Size2 InputEventConfigContainer::get_minimum_size() const { - // Don't bother with a minimum x size for the control - we don't want the inspector - // to jump in size if a long text is placed in the label (e.g. Joypad Axis description) - return Size2(0, HBoxContainer::get_minimum_size().y); -} - void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { Ref<InputEventKey> k = p_event; Ref<InputEventMouseButton> m = p_event; @@ -75,29 +78,25 @@ void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { } InputEventConfigContainer::InputEventConfigContainer() { - MarginContainer *mc = memnew(MarginContainer); - mc->add_theme_constant_override("margin_left", 10); - mc->add_theme_constant_override("margin_right", 10); - mc->add_theme_constant_override("margin_top", 10); - mc->add_theme_constant_override("margin_bottom", 10); - add_child(mc); - - HBoxContainer *hb = memnew(HBoxContainer); - mc->add_child(hb); - - open_config_button = memnew(Button); - open_config_button->set_text(TTR("Configure")); + input_event_text = memnew(Label); + input_event_text->set_h_size_flags(SIZE_EXPAND_FILL); + input_event_text->set_autowrap_mode(TextServer::AutowrapMode::AUTOWRAP_WORD_SMART); + input_event_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + add_child(input_event_text); + + open_config_button = EditorInspector::create_inspector_action_button(TTR("Configure")); open_config_button->connect("pressed", callable_mp(this, &InputEventConfigContainer::_configure_pressed)); - hb->add_child(open_config_button); + add_child(open_config_button); - input_event_text = memnew(Label); - hb->add_child(input_event_text); + add_child(memnew(Control)); config_dialog = memnew(InputEventConfigurationDialog); config_dialog->connect("confirmed", callable_mp(this, &InputEventConfigContainer::_config_dialog_confirmed)); add_child(config_dialog); } +/////////////////////// + bool EditorInspectorPluginInputEvent::can_handle(Object *p_object) { Ref<InputEventKey> k = Ref<InputEventKey>(p_object); Ref<InputEventMouseButton> m = Ref<InputEventMouseButton>(p_object); @@ -115,7 +114,9 @@ void EditorInspectorPluginInputEvent::parse_begin(Object *p_object) { add_custom_control(picker_controls); } -InputEventEditorPlugin::InputEventEditorPlugin(EditorNode *p_node) { +/////////////////////// + +InputEventEditorPlugin::InputEventEditorPlugin() { Ref<EditorInspectorPluginInputEvent> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/input_event_editor_plugin.h b/editor/plugins/input_event_editor_plugin.h index bc8293c9e5..344f176e78 100644 --- a/editor/plugins/input_event_editor_plugin.h +++ b/editor/plugins/input_event_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,16 +33,16 @@ #include "editor/action_map_editor.h" #include "editor/editor_inspector.h" -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" -class InputEventConfigContainer : public HBoxContainer { - GDCLASS(InputEventConfigContainer, HBoxContainer); +class InputEventConfigContainer : public VBoxContainer { + GDCLASS(InputEventConfigContainer, VBoxContainer); - Label *input_event_text; - Button *open_config_button; + Label *input_event_text = nullptr; + Button *open_config_button = nullptr; Ref<InputEvent> input_event; - InputEventConfigurationDialog *config_dialog; + InputEventConfigurationDialog *config_dialog = nullptr; void _config_dialog_confirmed(); void _configure_pressed(); @@ -50,10 +50,10 @@ class InputEventConfigContainer : public HBoxContainer { void _event_changed(); protected: + void _notification(int p_what); static void _bind_methods(); public: - virtual Size2 get_minimum_size() const override; void set_event(const Ref<InputEvent> &p_event); InputEventConfigContainer(); @@ -73,7 +73,7 @@ class InputEventEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "InputEvent"; } - InputEventEditorPlugin(EditorNode *p_node); + InputEventEditorPlugin(); }; #endif // INPUT_EVENT_EDITOR_PLUGIN_H diff --git a/editor/plugins/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp deleted file mode 100644 index 3207a989bd..0000000000 --- a/editor/plugins/item_list_editor_plugin.cpp +++ /dev/null @@ -1,398 +0,0 @@ -/*************************************************************************/ -/* item_list_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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 "item_list_editor_plugin.h" - -#include "core/io/resource_loader.h" -#include "editor/editor_scale.h" - -bool ItemListPlugin::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name; - int idx = name.get_slice("/", 0).to_int(); - String what = name.get_slice("/", 1); - - if (what == "text") { - set_item_text(idx, p_value); - } else if (what == "icon") { - set_item_icon(idx, p_value); - } else if (what == "checkable") { - // This keeps compatibility to/from versions where this property was a boolean, before radio buttons - switch ((int)p_value) { - case 0: - case 1: - set_item_checkable(idx, p_value); - break; - case 2: - set_item_radio_checkable(idx, true); - break; - } - } else if (what == "checked") { - set_item_checked(idx, p_value); - } else if (what == "id") { - set_item_id(idx, p_value); - } else if (what == "enabled") { - set_item_enabled(idx, p_value); - } else if (what == "separator") { - set_item_separator(idx, p_value); - } else { - return false; - } - - return true; -} - -bool ItemListPlugin::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name; - int idx = name.get_slice("/", 0).to_int(); - String what = name.get_slice("/", 1); - - if (what == "text") { - r_ret = get_item_text(idx); - } else if (what == "icon") { - r_ret = get_item_icon(idx); - } else if (what == "checkable") { - // This keeps compatibility to/from versions where this property was a boolean, before radio buttons - if (!is_item_checkable(idx)) { - r_ret = 0; - } else { - r_ret = is_item_radio_checkable(idx) ? 2 : 1; - } - } else if (what == "checked") { - r_ret = is_item_checked(idx); - } else if (what == "id") { - r_ret = get_item_id(idx); - } else if (what == "enabled") { - r_ret = is_item_enabled(idx); - } else if (what == "separator") { - r_ret = is_item_separator(idx); - } else { - return false; - } - - return true; -} - -void ItemListPlugin::_get_property_list(List<PropertyInfo> *p_list) const { - for (int i = 0; i < get_item_count(); i++) { - String base = itos(i) + "/"; - - p_list->push_back(PropertyInfo(Variant::STRING, base + "text")); - p_list->push_back(PropertyInfo(Variant::OBJECT, base + "icon", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D")); - - int flags = get_flags(); - - if (flags & FLAG_CHECKABLE) { - p_list->push_back(PropertyInfo(Variant::INT, base + "checkable", PROPERTY_HINT_ENUM, "No,As checkbox,As radio button")); - p_list->push_back(PropertyInfo(Variant::BOOL, base + "checked")); - } - - if (flags & FLAG_ID) { - p_list->push_back(PropertyInfo(Variant::INT, base + "id", PROPERTY_HINT_RANGE, "-1,4096")); - } - - if (flags & FLAG_ENABLE) { - p_list->push_back(PropertyInfo(Variant::BOOL, base + "enabled")); - } - - if (flags & FLAG_SEPARATOR) { - p_list->push_back(PropertyInfo(Variant::BOOL, base + "separator")); - } - } -} - -/////////////////////////////////////////////////////////////// -///////////////////////// PLUGINS ///////////////////////////// -/////////////////////////////////////////////////////////////// - -void ItemListOptionButtonPlugin::set_object(Object *p_object) { - ob = Object::cast_to<OptionButton>(p_object); -} - -bool ItemListOptionButtonPlugin::handles(Object *p_object) const { - return p_object->is_class("OptionButton"); -} - -int ItemListOptionButtonPlugin::get_flags() const { - return FLAG_ICON | FLAG_ID | FLAG_ENABLE; -} - -void ItemListOptionButtonPlugin::add_item() { - ob->add_item(vformat(TTR("Item %d"), ob->get_item_count())); - notify_property_list_changed(); -} - -int ItemListOptionButtonPlugin::get_item_count() const { - return ob->get_item_count(); -} - -void ItemListOptionButtonPlugin::erase(int p_idx) { - ob->remove_item(p_idx); - notify_property_list_changed(); -} - -ItemListOptionButtonPlugin::ItemListOptionButtonPlugin() { - ob = nullptr; -} - -/////////////////////////////////////////////////////////////// - -void ItemListPopupMenuPlugin::set_object(Object *p_object) { - if (p_object->is_class("MenuButton")) { - pp = Object::cast_to<MenuButton>(p_object)->get_popup(); - } else { - pp = Object::cast_to<PopupMenu>(p_object); - } -} - -bool ItemListPopupMenuPlugin::handles(Object *p_object) const { - return p_object->is_class("PopupMenu") || p_object->is_class("MenuButton"); -} - -int ItemListPopupMenuPlugin::get_flags() const { - return FLAG_ICON | FLAG_CHECKABLE | FLAG_ID | FLAG_ENABLE | FLAG_SEPARATOR; -} - -void ItemListPopupMenuPlugin::add_item() { - pp->add_item(vformat(TTR("Item %d"), pp->get_item_count())); - notify_property_list_changed(); -} - -int ItemListPopupMenuPlugin::get_item_count() const { - return pp->get_item_count(); -} - -void ItemListPopupMenuPlugin::erase(int p_idx) { - pp->remove_item(p_idx); - notify_property_list_changed(); -} - -ItemListPopupMenuPlugin::ItemListPopupMenuPlugin() { - pp = nullptr; -} - -/////////////////////////////////////////////////////////////// - -void ItemListItemListPlugin::set_object(Object *p_object) { - pp = Object::cast_to<ItemList>(p_object); -} - -bool ItemListItemListPlugin::handles(Object *p_object) const { - return p_object->is_class("ItemList"); -} - -int ItemListItemListPlugin::get_flags() const { - return FLAG_ICON | FLAG_ENABLE; -} - -void ItemListItemListPlugin::add_item() { - pp->add_item(vformat(TTR("Item %d"), pp->get_item_count())); - notify_property_list_changed(); -} - -int ItemListItemListPlugin::get_item_count() const { - return pp->get_item_count(); -} - -void ItemListItemListPlugin::erase(int p_idx) { - pp->remove_item(p_idx); - notify_property_list_changed(); -} - -ItemListItemListPlugin::ItemListItemListPlugin() { - pp = nullptr; -} - -/////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////// - -void ItemListEditor::_node_removed(Node *p_node) { - if (p_node == item_list) { - item_list = nullptr; - hide(); - dialog->hide(); - } -} - -void ItemListEditor::_notification(int p_notification) { - if (p_notification == NOTIFICATION_ENTER_TREE || p_notification == NOTIFICATION_THEME_CHANGED) { - add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - del_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - } else if (p_notification == NOTIFICATION_READY) { - get_tree()->connect("node_removed", callable_mp(this, &ItemListEditor::_node_removed)); - } -} - -void ItemListEditor::_add_pressed() { - if (selected_idx == -1) { - return; - } - - item_plugins[selected_idx]->add_item(); -} - -void ItemListEditor::_delete_pressed() { - if (selected_idx == -1) { - return; - } - - String current_selected = (String)property_editor->get_selected_path(); - - if (current_selected == "") { - return; - } - - // FIXME: Currently relying on selecting a *property* to derive what item to delete - // e.g. you select "1/enabled" to delete item 1. - // This should be fixed so that you can delete by selecting the item section header, - // or a delete button on that header. - - int idx = current_selected.get_slice("/", 0).to_int(); - - item_plugins[selected_idx]->erase(idx); -} - -void ItemListEditor::_edit_items() { - dialog->popup_centered_clamped(Vector2(425, 1200) * EDSCALE, 0.8); -} - -void ItemListEditor::edit(Node *p_item_list) { - item_list = p_item_list; - - if (!item_list) { - selected_idx = -1; - property_editor->edit(nullptr); - return; - } - - for (int i = 0; i < item_plugins.size(); i++) { - if (item_plugins[i]->handles(p_item_list)) { - item_plugins[i]->set_object(p_item_list); - property_editor->edit(item_plugins[i]); - - toolbar_button->set_icon(EditorNode::get_singleton()->get_object_icon(item_list, "")); - - selected_idx = i; - return; - } - } - - selected_idx = -1; - property_editor->edit(nullptr); -} - -bool ItemListEditor::handles(Object *p_object) const { - for (int i = 0; i < item_plugins.size(); i++) { - if (item_plugins[i]->handles(p_object)) { - return true; - } - } - - return false; -} - -void ItemListEditor::_bind_methods() { -} - -ItemListEditor::ItemListEditor() { - selected_idx = -1; - item_list = nullptr; - - toolbar_button = memnew(Button); - toolbar_button->set_flat(true); - toolbar_button->set_text(TTR("Items")); - add_child(toolbar_button); - toolbar_button->connect("pressed", callable_mp(this, &ItemListEditor::_edit_items)); - - dialog = memnew(AcceptDialog); - dialog->set_title(TTR("Item List Editor")); - add_child(dialog); - - VBoxContainer *vbc = memnew(VBoxContainer); - dialog->add_child(vbc); - //dialog->set_child_rect(vbc); - - HBoxContainer *hbc = memnew(HBoxContainer); - hbc->set_h_size_flags(SIZE_EXPAND_FILL); - vbc->add_child(hbc); - - add_button = memnew(Button); - add_button->set_text(TTR("Add")); - hbc->add_child(add_button); - add_button->connect("pressed", callable_mp(this, &ItemListEditor::_add_pressed)); - - hbc->add_spacer(); - - del_button = memnew(Button); - del_button->set_text(TTR("Delete")); - hbc->add_child(del_button); - del_button->connect("pressed", callable_mp(this, &ItemListEditor::_delete_pressed)); - - property_editor = memnew(EditorInspector); - vbc->add_child(property_editor); - property_editor->set_v_size_flags(SIZE_EXPAND_FILL); -} - -ItemListEditor::~ItemListEditor() { - for (int i = 0; i < item_plugins.size(); i++) { - memdelete(item_plugins[i]); - } -} - -void ItemListEditorPlugin::edit(Object *p_object) { - item_list_editor->edit(Object::cast_to<Node>(p_object)); -} - -bool ItemListEditorPlugin::handles(Object *p_object) const { - return item_list_editor->handles(p_object); -} - -void ItemListEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - item_list_editor->show(); - } else { - item_list_editor->hide(); - item_list_editor->edit(nullptr); - } -} - -ItemListEditorPlugin::ItemListEditorPlugin(EditorNode *p_node) { - editor = p_node; - item_list_editor = memnew(ItemListEditor); - CanvasItemEditor::get_singleton()->add_control_to_menu_panel(item_list_editor); - - item_list_editor->hide(); - item_list_editor->add_plugin(memnew(ItemListOptionButtonPlugin)); - item_list_editor->add_plugin(memnew(ItemListPopupMenuPlugin)); - item_list_editor->add_plugin(memnew(ItemListItemListPlugin)); -} - -ItemListEditorPlugin::~ItemListEditorPlugin() { -} diff --git a/editor/plugins/item_list_editor_plugin.h b/editor/plugins/item_list_editor_plugin.h deleted file mode 100644 index 8c77f3d952..0000000000 --- a/editor/plugins/item_list_editor_plugin.h +++ /dev/null @@ -1,248 +0,0 @@ -/*************************************************************************/ -/* item_list_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 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 ITEM_LIST_EDITOR_PLUGIN_H -#define ITEM_LIST_EDITOR_PLUGIN_H - -#include "canvas_item_editor_plugin.h" -#include "editor/editor_inspector.h" -#include "editor/editor_node.h" -#include "editor/editor_plugin.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/option_button.h" -#include "scene/gui/popup_menu.h" - -class ItemListPlugin : public Object { - GDCLASS(ItemListPlugin, Object); - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - -public: - enum Flags { - FLAG_ICON = 1, - FLAG_CHECKABLE = 2, - FLAG_ID = 4, - FLAG_ENABLE = 8, - FLAG_SEPARATOR = 16 - }; - - virtual void set_object(Object *p_object) = 0; - virtual bool handles(Object *p_object) const = 0; - - virtual int get_flags() const = 0; - - virtual void set_item_text(int p_idx, const String &p_text) {} - virtual String get_item_text(int p_idx) const { return ""; }; - - virtual void set_item_icon(int p_idx, const Ref<Texture2D> &p_tex) {} - virtual Ref<Texture2D> get_item_icon(int p_idx) const { return Ref<Texture2D>(); }; - - virtual void set_item_checkable(int p_idx, bool p_check) {} - virtual void set_item_radio_checkable(int p_idx, bool p_check) {} - virtual bool is_item_checkable(int p_idx) const { return false; }; - virtual bool is_item_radio_checkable(int p_idx) const { return false; }; - - virtual void set_item_checked(int p_idx, bool p_checked) {} - virtual bool is_item_checked(int p_idx) const { return false; }; - - virtual void set_item_enabled(int p_idx, int p_enabled) {} - virtual bool is_item_enabled(int p_idx) const { return false; }; - - virtual void set_item_id(int p_idx, int p_id) {} - virtual int get_item_id(int p_idx) const { return -1; }; - - virtual void set_item_separator(int p_idx, bool p_separator) {} - virtual bool is_item_separator(int p_idx) const { return false; }; - - virtual void add_item() = 0; - virtual int get_item_count() const = 0; - virtual void erase(int p_idx) = 0; - - ItemListPlugin() {} -}; - -/////////////////////////////////////////////////////////////// - -class ItemListOptionButtonPlugin : public ItemListPlugin { - GDCLASS(ItemListOptionButtonPlugin, ItemListPlugin); - - OptionButton *ob; - -public: - virtual void set_object(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual int get_flags() const override; - - virtual void set_item_text(int p_idx, const String &p_text) override { ob->set_item_text(p_idx, p_text); } - virtual String get_item_text(int p_idx) const override { return ob->get_item_text(p_idx); } - - virtual void set_item_icon(int p_idx, const Ref<Texture2D> &p_tex) override { ob->set_item_icon(p_idx, p_tex); } - virtual Ref<Texture2D> get_item_icon(int p_idx) const override { return ob->get_item_icon(p_idx); } - - virtual void set_item_enabled(int p_idx, int p_enabled) override { ob->set_item_disabled(p_idx, !p_enabled); } - virtual bool is_item_enabled(int p_idx) const override { return !ob->is_item_disabled(p_idx); } - - virtual void set_item_id(int p_idx, int p_id) override { ob->set_item_id(p_idx, p_id); } - virtual int get_item_id(int p_idx) const override { return ob->get_item_id(p_idx); } - - virtual void add_item() override; - virtual int get_item_count() const override; - virtual void erase(int p_idx) override; - - ItemListOptionButtonPlugin(); -}; - -class ItemListPopupMenuPlugin : public ItemListPlugin { - GDCLASS(ItemListPopupMenuPlugin, ItemListPlugin); - - PopupMenu *pp; - -public: - virtual void set_object(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual int get_flags() const override; - - virtual void set_item_text(int p_idx, const String &p_text) override { pp->set_item_text(p_idx, p_text); } - virtual String get_item_text(int p_idx) const override { return pp->get_item_text(p_idx); } - - virtual void set_item_icon(int p_idx, const Ref<Texture2D> &p_tex) override { pp->set_item_icon(p_idx, p_tex); } - virtual Ref<Texture2D> get_item_icon(int p_idx) const override { return pp->get_item_icon(p_idx); } - - virtual void set_item_checkable(int p_idx, bool p_check) override { pp->set_item_as_checkable(p_idx, p_check); } - virtual void set_item_radio_checkable(int p_idx, bool p_check) override { pp->set_item_as_radio_checkable(p_idx, p_check); } - virtual bool is_item_checkable(int p_idx) const override { return pp->is_item_checkable(p_idx); } - virtual bool is_item_radio_checkable(int p_idx) const override { return pp->is_item_radio_checkable(p_idx); } - - virtual void set_item_checked(int p_idx, bool p_checked) override { pp->set_item_checked(p_idx, p_checked); } - virtual bool is_item_checked(int p_idx) const override { return pp->is_item_checked(p_idx); } - - virtual void set_item_enabled(int p_idx, int p_enabled) override { pp->set_item_disabled(p_idx, !p_enabled); } - virtual bool is_item_enabled(int p_idx) const override { return !pp->is_item_disabled(p_idx); } - - virtual void set_item_id(int p_idx, int p_id) override { pp->set_item_id(p_idx, p_id); } - virtual int get_item_id(int p_idx) const override { return pp->get_item_id(p_idx); } - - virtual void set_item_separator(int p_idx, bool p_separator) override { pp->set_item_as_separator(p_idx, p_separator); } - virtual bool is_item_separator(int p_idx) const override { return pp->is_item_separator(p_idx); } - - virtual void add_item() override; - virtual int get_item_count() const override; - virtual void erase(int p_idx) override; - - ItemListPopupMenuPlugin(); -}; - -/////////////////////////////////////////////////////////////// - -class ItemListItemListPlugin : public ItemListPlugin { - GDCLASS(ItemListItemListPlugin, ItemListPlugin); - - ItemList *pp; - -public: - virtual void set_object(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual int get_flags() const override; - - virtual void set_item_text(int p_idx, const String &p_text) override { pp->set_item_text(p_idx, p_text); } - virtual String get_item_text(int p_idx) const override { return pp->get_item_text(p_idx); } - - virtual void set_item_icon(int p_idx, const Ref<Texture2D> &p_tex) override { pp->set_item_icon(p_idx, p_tex); } - virtual Ref<Texture2D> get_item_icon(int p_idx) const override { return pp->get_item_icon(p_idx); } - - virtual void set_item_enabled(int p_idx, int p_enabled) override { pp->set_item_disabled(p_idx, !p_enabled); } - virtual bool is_item_enabled(int p_idx) const override { return !pp->is_item_disabled(p_idx); } - - virtual void add_item() override; - virtual int get_item_count() const override; - virtual void erase(int p_idx) override; - - ItemListItemListPlugin(); -}; - -/////////////////////////////////////////////////////////////// - -class ItemListEditor : public HBoxContainer { - GDCLASS(ItemListEditor, HBoxContainer); - - Node *item_list; - - Button *toolbar_button; - - AcceptDialog *dialog; - EditorInspector *property_editor; - Tree *tree; - Button *add_button; - Button *del_button; - - int selected_idx; - - Vector<ItemListPlugin *> item_plugins; - - void _edit_items(); - - void _add_pressed(); - void _delete_pressed(); - - void _node_removed(Node *p_node); - -protected: - void _notification(int p_notification); - static void _bind_methods(); - -public: - void edit(Node *p_item_list); - bool handles(Object *p_object) const; - void add_plugin(ItemListPlugin *p_plugin) { item_plugins.push_back(p_plugin); } - ItemListEditor(); - ~ItemListEditor(); -}; - -class ItemListEditorPlugin : public EditorPlugin { - GDCLASS(ItemListEditorPlugin, EditorPlugin); - - ItemListEditor *item_list_editor; - EditorNode *editor; - -public: - virtual String get_name() const override { return "ItemList"; } - 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; - - ItemListEditorPlugin(EditorNode *p_node); - ~ItemListEditorPlugin(); -}; - -#endif // ITEM_LIST_EDITOR_PLUGIN_H diff --git a/editor/plugins/light_occluder_2d_editor_plugin.cpp b/editor/plugins/light_occluder_2d_editor_plugin.cpp index 3d555d7eba..e7ef65c32b 100644 --- a/editor/plugins/light_occluder_2d_editor_plugin.cpp +++ b/editor/plugins/light_occluder_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -96,17 +96,14 @@ 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); } -LightOccluder2DEditor::LightOccluder2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +LightOccluder2DEditor::LightOccluder2DEditor() {} -LightOccluder2DEditorPlugin::LightOccluder2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(LightOccluder2DEditor(p_node)), "LightOccluder2D") { +LightOccluder2DEditorPlugin::LightOccluder2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(LightOccluder2DEditor), "LightOccluder2D") { } diff --git a/editor/plugins/light_occluder_2d_editor_plugin.h b/editor/plugins/light_occluder_2d_editor_plugin.h index eb1ce04788..aeee12b5b6 100644 --- a/editor/plugins/light_occluder_2d_editor_plugin.h +++ b/editor/plugins/light_occluder_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,7 +37,7 @@ class LightOccluder2DEditor : public AbstractPolygon2DEditor { GDCLASS(LightOccluder2DEditor, AbstractPolygon2DEditor); - LightOccluder2D *node; + LightOccluder2D *node = nullptr; Ref<OccluderPolygon2D> _ensure_occluder() const; @@ -56,14 +56,14 @@ protected: virtual void _create_resource() override; public: - LightOccluder2DEditor(EditorNode *p_editor); + LightOccluder2DEditor(); }; class LightOccluder2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(LightOccluder2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - LightOccluder2DEditorPlugin(EditorNode *p_node); + LightOccluder2DEditorPlugin(); }; #endif // LIGHT_OCCLUDER_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index b4a70cd31d..8413c5e875 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,24 +30,28 @@ #include "lightmap_gi_editor_plugin.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { if (lightmap) { LightmapGI::BakeError err; + const uint64_t time_started = OS::get_singleton()->get_ticks_msec(); if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) { err = lightmap->bake(lightmap, p_file, bake_func_step); } else { err = lightmap->bake(lightmap->get_parent(), p_file, bake_func_step); } - bake_func_end(); + bake_func_end(time_started); switch (err) { case LightmapGI::BAKE_ERROR_NO_SAVE_PATH: { - String scene_path = lightmap->get_filename(); - if (scene_path == String()) { - scene_path = lightmap->get_owner()->get_filename(); + String scene_path = lightmap->get_scene_file_path(); + if (scene_path.is_empty()) { + scene_path = lightmap->get_owner()->get_scene_file_path(); } - if (scene_path == String()) { + if (scene_path.is_empty()) { EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene and try again.")); break; } @@ -104,22 +108,28 @@ bool LightmapGIEditorPlugin::bake_func_step(float p_progress, const String &p_de return tmp_progress->step(p_description, p_progress * 1000, p_refresh); } -void LightmapGIEditorPlugin::bake_func_end() { +void LightmapGIEditorPlugin::bake_func_end(uint64_t p_time_started) { if (tmp_progress != nullptr) { memdelete(tmp_progress); tmp_progress = nullptr; } + + const int time_taken = (OS::get_singleton()->get_ticks_msec() - p_time_started) * 0.001; + print_line(vformat("Done baking lightmaps in %02d:%02d:%02d.", time_taken / 3600, (time_taken % 3600) / 60, time_taken % 60)); + // Request attention in case the user was doing something else. + // Baking lightmaps is likely the editor task that can take the most time, + // so only request the attention for baking lightmaps. + DisplayServer::get_singleton()->window_request_attention(); } void LightmapGIEditorPlugin::_bind_methods() { ClassDB::bind_method("_bake", &LightmapGIEditorPlugin::_bake); } -LightmapGIEditorPlugin::LightmapGIEditorPlugin(EditorNode *p_node) { - editor = p_node; +LightmapGIEditorPlugin::LightmapGIEditorPlugin() { bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake Lightmaps")); bake->hide(); bake->connect("pressed", Callable(this, "_bake")); @@ -128,7 +138,7 @@ LightmapGIEditorPlugin::LightmapGIEditorPlugin(EditorNode *p_node) { file_dialog = memnew(EditorFileDialog); file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - file_dialog->add_filter("*.lmbake ; LightMap Bake"); + file_dialog->add_filter("*.lmbake", TTR("LightMap Bake")); file_dialog->set_title(TTR("Select lightmap bake file:")); file_dialog->connect("file_selected", callable_mp(this, &LightmapGIEditorPlugin::_bake_select_file)); bake->add_child(file_dialog); diff --git a/editor/plugins/lightmap_gi_editor_plugin.h b/editor/plugins/lightmap_gi_editor_plugin.h index 12d080d6be..a06f97fc94 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.h +++ b/editor/plugins/lightmap_gi_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,26 +28,27 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef BAKED_LIGHTMAP_EDITOR_PLUGIN_H -#define BAKED_LIGHTMAP_EDITOR_PLUGIN_H +#ifndef LIGHTMAP_GI_EDITOR_PLUGIN_H +#define LIGHTMAP_GI_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/lightmap_gi.h" #include "scene/resources/material.h" +struct EditorProgress; +class EditorFileDialog; + class LightmapGIEditorPlugin : public EditorPlugin { GDCLASS(LightmapGIEditorPlugin, EditorPlugin); - LightmapGI *lightmap; + LightmapGI *lightmap = nullptr; - Button *bake; - EditorNode *editor; + Button *bake = nullptr; - EditorFileDialog *file_dialog; + EditorFileDialog *file_dialog = nullptr; static EditorProgress *tmp_progress; static bool bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh); - static void bake_func_end(); + static void bake_func_end(uint64_t p_time_started); void _bake_select_file(const String &p_file); void _bake(); @@ -62,8 +63,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - LightmapGIEditorPlugin(EditorNode *p_node); + LightmapGIEditorPlugin(); ~LightmapGIEditorPlugin(); }; -#endif +#endif // LIGHTMAP_GI_EDITOR_PLUGIN_H diff --git a/editor/plugins/line_2d_editor_plugin.cpp b/editor/plugins/line_2d_editor_plugin.cpp index 08c5ef02a4..31053f90b8 100644 --- a/editor/plugins/line_2d_editor_plugin.cpp +++ b/editor/plugins/line_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -56,11 +56,8 @@ void Line2DEditor::_action_set_polygon(int p_idx, const Variant &p_previous, con undo_redo->add_undo_method(node, "set_points", p_previous); } -Line2DEditor::Line2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +Line2DEditor::Line2DEditor() {} -Line2DEditorPlugin::Line2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(Line2DEditor(p_node)), "Line2D") { +Line2DEditorPlugin::Line2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(Line2DEditor), "Line2D") { } diff --git a/editor/plugins/line_2d_editor_plugin.h b/editor/plugins/line_2d_editor_plugin.h index 769109583a..0d407b3150 100644 --- a/editor/plugins/line_2d_editor_plugin.h +++ b/editor/plugins/line_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,7 +37,7 @@ class Line2DEditor : public AbstractPolygon2DEditor { GDCLASS(Line2DEditor, AbstractPolygon2DEditor); - Line2D *node; + Line2D *node = nullptr; protected: virtual Node2D *_get_node() const override; @@ -49,14 +49,14 @@ protected: virtual void _action_set_polygon(int p_idx, const Variant &p_previous, const Variant &p_polygon) override; public: - Line2DEditor(EditorNode *p_editor); + Line2DEditor(); }; class Line2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(Line2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - Line2DEditorPlugin(EditorNode *p_node); + Line2DEditorPlugin(); }; #endif // LINE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp index 94966d4fe6..1b4d98fc3f 100644 --- a/editor/plugins/material_editor_plugin.cpp +++ b/editor/plugins/material_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,37 +30,42 @@ #include "material_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "scene/gui/subviewport_container.h" +#include "scene/resources/fog_material.h" #include "scene/resources/particles_material.h" #include "scene/resources/sky_material.h" void MaterialEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); + switch (p_what) { + case NOTIFICATION_READY: { + //get_scene()->connect("node_removed",this,"_node_removed"); - if (first_enter) { - //it's in propertyeditor so.. could be moved around + if (first_enter) { + //it's in propertyeditor so.. could be moved around - light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); - light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); - light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); - light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); + light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); + light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); + light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); + light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); - sphere_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewSphereOff"), SNAME("EditorIcons"))); - sphere_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewSphere"), SNAME("EditorIcons"))); - box_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewCubeOff"), SNAME("EditorIcons"))); - box_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewCube"), SNAME("EditorIcons"))); + sphere_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewSphereOff"), SNAME("EditorIcons"))); + sphere_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewSphere"), SNAME("EditorIcons"))); + box_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewCubeOff"), SNAME("EditorIcons"))); + box_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewCube"), SNAME("EditorIcons"))); - first_enter = false; - } - } + first_enter = false; + } + } break; - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - Size2 size = get_size(); + case NOTIFICATION_DRAW: { + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); + Size2 size = get_size(); - draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + } break; } } @@ -68,8 +73,24 @@ void MaterialEditor::edit(Ref<Material> p_material, const Ref<Environment> &p_en material = p_material; camera->set_environment(p_env); if (!material.is_null()) { - sphere_instance->set_material_override(material); - box_instance->set_material_override(material); + Shader::Mode mode = p_material->get_shader_mode(); + switch (mode) { + case Shader::MODE_CANVAS_ITEM: + layout_3d->hide(); + layout_2d->show(); + vc->hide(); + rect_instance->set_material(material); + break; + case Shader::MODE_SPATIAL: + layout_2d->hide(); + layout_3d->show(); + vc->show(); + sphere_instance->set_material_override(material); + box_instance->set_material_override(material); + break; + default: + break; + } } else { hide(); } @@ -105,10 +126,25 @@ void MaterialEditor::_bind_methods() { } MaterialEditor::MaterialEditor() { + // canvas item + + layout_2d = memnew(HBoxContainer); + layout_2d->set_alignment(BoxContainer::ALIGNMENT_CENTER); + add_child(layout_2d); + layout_2d->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + + rect_instance = memnew(ColorRect); + layout_2d->add_child(rect_instance); + rect_instance->set_custom_minimum_size(Size2(150, 150) * EDSCALE); + + layout_2d->set_visible(false); + + // spatial + 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(); @@ -120,7 +156,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); @@ -142,8 +180,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(); @@ -153,39 +191,39 @@ MaterialEditor::MaterialEditor() { set_custom_minimum_size(Size2(1, 150) * EDSCALE); - HBoxContainer *hb = memnew(HBoxContainer); - add_child(hb); - hb->set_anchors_and_offsets_preset(Control::PRESET_WIDE, Control::PRESET_MODE_MINSIZE, 2); + layout_3d = memnew(HBoxContainer); + add_child(layout_3d); + layout_3d->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT, Control::PRESET_MODE_MINSIZE, 2); VBoxContainer *vb_shape = memnew(VBoxContainer); - hb->add_child(vb_shape); + layout_3d->add_child(vb_shape); sphere_switch = memnew(TextureButton); 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)); - hb->add_spacer(); + layout_3d->add_spacer(); VBoxContainer *vb_light = memnew(VBoxContainer); - hb->add_child(vb_light); + layout_3d->add_child(vb_light); 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; @@ -206,8 +244,8 @@ bool EditorInspectorPluginMaterial::can_handle(Object *p_object) { if (!material) { return false; } - - return material->get_shader_mode() == Shader::MODE_SPATIAL; + Shader::Mode mode = material->get_shader_mode(); + return mode == Shader::MODE_SPATIAL || mode == Shader::MODE_CANVAS_ITEM; } void EditorInspectorPluginMaterial::parse_begin(Object *p_object) { @@ -222,6 +260,43 @@ void EditorInspectorPluginMaterial::parse_begin(Object *p_object) { add_custom_control(editor); } +void EditorInspectorPluginMaterial::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { + UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); + if (!undo_redo) { + return; + } + + // For BaseMaterial3D, if a roughness or metallic textures is being assigned to an empty slot, + // set the respective metallic or roughness factor to 1.0 as a convenience feature + BaseMaterial3D *base_material = Object::cast_to<StandardMaterial3D>(p_edited); + if (base_material) { + Texture2D *texture = Object::cast_to<Texture2D>(p_new_value); + if (texture) { + if (p_property == "roughness_texture") { + if (base_material->get_texture(StandardMaterial3D::TEXTURE_ROUGHNESS).is_null()) { + undo_redo->add_do_property(p_edited, "roughness", 1.0); + + bool valid = false; + Variant value = p_edited->get("roughness", &valid); + if (valid) { + undo_redo->add_undo_property(p_edited, "roughness", value); + } + } + } else if (p_property == "metallic_texture") { + if (base_material->get_texture(StandardMaterial3D::TEXTURE_METALLIC).is_null()) { + undo_redo->add_do_property(p_edited, "metallic", 1.0); + + bool valid = false; + Variant value = p_edited->get("metallic", &valid); + if (valid) { + undo_redo->add_undo_property(p_edited, "metallic", value); + } + } + } + } + } +} + EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() { env.instantiate(); Ref<Sky> sky = memnew(Sky()); @@ -229,9 +304,11 @@ EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() { env->set_background(Environment::BG_COLOR); env->set_ambient_source(Environment::AMBIENT_SOURCE_SKY); env->set_reflection_source(Environment::REFLECTION_SOURCE_SKY); + + EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &EditorInspectorPluginMaterial::_undo_redo_inspector_callback)); } -MaterialEditorPlugin::MaterialEditorPlugin(EditorNode *p_node) { +MaterialEditorPlugin::MaterialEditorPlugin() { Ref<EditorInspectorPluginMaterial> plugin; plugin.instantiate(); add_inspector_plugin(plugin); @@ -263,21 +340,69 @@ 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_uniform(E.name, value); + } + } + + smat->set_render_priority(mat->get_render_priority()); + smat->set_local_to_scene(mat->is_local_to_scene()); + smat->set_name(mat->get_name()); + return smat; +} + +String ORMMaterial3DConversionPlugin::converts_to() const { + return "ShaderMaterial"; +} + +bool ORMMaterial3DConversionPlugin::handles(const Ref<Resource> &p_resource) const { + Ref<ORMMaterial3D> mat = p_resource; + return mat.is_valid(); +} + +Ref<Resource> ORMMaterial3DConversionPlugin::convert(const Ref<Resource> &p_resource) const { + Ref<ORMMaterial3D> mat = p_resource; + ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); + + Ref<ShaderMaterial> smat; + smat.instantiate(); + + Ref<Shader> shader; + shader.instantiate(); + + String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); + + shader->set_code(code); + + smat->set_shader(shader); + + List<PropertyInfo> params; + 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_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); } } smat->set_render_priority(mat->get_render_priority()); + smat->set_local_to_scene(mat->is_local_to_scene()); + smat->set_name(mat->get_name()); return smat; } @@ -307,14 +432,16 @@ 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()); + smat->set_local_to_scene(mat->is_local_to_scene()); + smat->set_name(mat->get_name()); return smat; } @@ -344,14 +471,16 @@ 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()); + smat->set_local_to_scene(mat->is_local_to_scene()); + smat->set_name(mat->get_name()); return smat; } @@ -381,14 +510,16 @@ 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()); + smat->set_local_to_scene(mat->is_local_to_scene()); + smat->set_name(mat->get_name()); return smat; } @@ -418,14 +549,16 @@ 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()); + smat->set_local_to_scene(mat->is_local_to_scene()); + smat->set_name(mat->get_name()); return smat; } @@ -455,11 +588,50 @@ 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_uniform(E.name, value); + } + + smat->set_render_priority(mat->get_render_priority()); + smat->set_local_to_scene(mat->is_local_to_scene()); + smat->set_name(mat->get_name()); + return smat; +} + +String FogMaterialConversionPlugin::converts_to() const { + return "ShaderMaterial"; +} + +bool FogMaterialConversionPlugin::handles(const Ref<Resource> &p_resource) const { + Ref<FogMaterial> mat = p_resource; + return mat.is_valid(); +} + +Ref<Resource> FogMaterialConversionPlugin::convert(const Ref<Resource> &p_resource) const { + Ref<FogMaterial> mat = p_resource; + ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); + + Ref<ShaderMaterial> smat; + smat.instantiate(); + + Ref<Shader> shader; + shader.instantiate(); + + String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); + + shader->set_code(code); + + smat->set_shader(shader); + + List<PropertyInfo> params; + 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 a4532b58b3..fc3da5fd9f 100644 --- a/editor/plugins/material_editor_plugin.h +++ b/editor/plugins/material_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,37 +31,41 @@ #ifndef MATERIAL_EDITOR_PLUGIN_H #define MATERIAL_EDITOR_PLUGIN_H -#include "editor/property_editor.h" -#include "scene/resources/primitive_meshes.h" - -#include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/plugins/editor_resource_conversion_plugin.h" #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/gui/color_rect.h" #include "scene/resources/material.h" +#include "scene/resources/primitive_meshes.h" class SubViewportContainer; class MaterialEditor : public Control { GDCLASS(MaterialEditor, Control); - SubViewportContainer *vc; - SubViewport *viewport; - MeshInstance3D *sphere_instance; - MeshInstance3D *box_instance; - DirectionalLight3D *light1; - DirectionalLight3D *light2; - Camera3D *camera; + HBoxContainer *layout_2d = nullptr; + ColorRect *rect_instance = nullptr; + + SubViewportContainer *vc = nullptr; + SubViewport *viewport = nullptr; + MeshInstance3D *sphere_instance = nullptr; + MeshInstance3D *box_instance = nullptr; + DirectionalLight3D *light1 = nullptr; + DirectionalLight3D *light2 = nullptr; + Camera3D *camera = nullptr; Ref<SphereMesh> sphere_mesh; Ref<BoxMesh> box_mesh; - TextureButton *sphere_switch; - TextureButton *box_switch; + HBoxContainer *layout_3d = nullptr; + + TextureButton *sphere_switch = nullptr; + TextureButton *box_switch = nullptr; - TextureButton *light_1_switch; - TextureButton *light_2_switch; + TextureButton *light_1_switch = nullptr; + TextureButton *light_2_switch = nullptr; Ref<Material> material; @@ -86,6 +90,8 @@ public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; + void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); + EditorInspectorPluginMaterial(); }; @@ -95,7 +101,7 @@ class MaterialEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Material"; } - MaterialEditorPlugin(EditorNode *p_node); + MaterialEditorPlugin(); }; class StandardMaterial3DConversionPlugin : public EditorResourceConversionPlugin { @@ -107,6 +113,15 @@ public: virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const override; }; +class ORMMaterial3DConversionPlugin : public EditorResourceConversionPlugin { + GDCLASS(ORMMaterial3DConversionPlugin, EditorResourceConversionPlugin); + +public: + virtual String converts_to() const override; + virtual bool handles(const Ref<Resource> &p_resource) const override; + virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const override; +}; + class ParticlesMaterialConversionPlugin : public EditorResourceConversionPlugin { GDCLASS(ParticlesMaterialConversionPlugin, EditorResourceConversionPlugin); @@ -152,4 +167,13 @@ public: virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const override; }; +class FogMaterialConversionPlugin : public EditorResourceConversionPlugin { + GDCLASS(FogMaterialConversionPlugin, EditorResourceConversionPlugin); + +public: + virtual String converts_to() const override; + virtual bool handles(const Ref<Resource> &p_resource) const override; + virtual Ref<Resource> convert(const Ref<Resource> &p_resource) const override; +}; + #endif // MATERIAL_EDITOR_PLUGIN_H diff --git a/editor/plugins/mesh_editor_plugin.cpp b/editor/plugins/mesh_editor_plugin.cpp index 768f29e15a..31c9f1e387 100644 --- a/editor/plugins/mesh_editor_plugin.cpp +++ b/editor/plugins/mesh_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,7 +36,7 @@ void MeshEditor::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { rot_x -= mm->get_relative().y * 0.01; rot_y -= mm->get_relative().x * 0.01; if (rot_x < -Math_PI / 2) { @@ -49,18 +49,20 @@ void MeshEditor::gui_input(const Ref<InputEvent> &p_event) { } void MeshEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); - - if (first_enter) { - //it's in propertyeditor so. could be moved around - - light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); - light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); - light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); - light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); - first_enter = false; - } + switch (p_what) { + case NOTIFICATION_READY: { + //get_scene()->connect("node_removed",this,"_node_removed"); + + if (first_enter) { + //it's in propertyeditor so. could be moved around + + light_1_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight1"), SNAME("EditorIcons"))); + light_1_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight1Off"), SNAME("EditorIcons"))); + light_2_switch->set_normal_texture(get_theme_icon(SNAME("MaterialPreviewLight2"), SNAME("EditorIcons"))); + light_2_switch->set_pressed_texture(get_theme_icon(SNAME("MaterialPreviewLight2Off"), SNAME("EditorIcons"))); + first_enter = false; + } + } break; } } @@ -80,7 +82,7 @@ void MeshEditor::edit(Ref<Mesh> p_mesh) { _update_rotation(); AABB aabb = mesh->get_aabb(); - Vector3 ofs = aabb.position + aabb.size * 0.5; + Vector3 ofs = aabb.get_center(); float m = aabb.get_longest_axis_size(); if (m != 0) { m = 1.0 / m; @@ -110,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))); @@ -135,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(); @@ -145,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; @@ -176,7 +178,7 @@ void EditorInspectorPluginMesh::parse_begin(Object *p_object) { add_custom_control(editor); } -MeshEditorPlugin::MeshEditorPlugin(EditorNode *p_node) { +MeshEditorPlugin::MeshEditorPlugin() { Ref<EditorInspectorPluginMesh> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/mesh_editor_plugin.h b/editor/plugins/mesh_editor_plugin.h index 1e88b70202..fb61f03485 100644 --- a/editor/plugins/mesh_editor_plugin.h +++ b/editor/plugins/mesh_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef MESH_EDITOR_PLUGIN_H #define MESH_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" @@ -45,17 +44,17 @@ class MeshEditor : public SubViewportContainer { float rot_x; float rot_y; - SubViewport *viewport; - MeshInstance3D *mesh_instance; - Node3D *rotation; - DirectionalLight3D *light1; - DirectionalLight3D *light2; - Camera3D *camera; + SubViewport *viewport = nullptr; + MeshInstance3D *mesh_instance = nullptr; + Node3D *rotation = nullptr; + DirectionalLight3D *light1 = nullptr; + DirectionalLight3D *light2 = nullptr; + Camera3D *camera = nullptr; Ref<Mesh> mesh; - TextureButton *light_1_switch; - TextureButton *light_2_switch; + TextureButton *light_1_switch = nullptr; + TextureButton *light_2_switch = nullptr; void _button_pressed(Node *p_button); bool first_enter; @@ -85,7 +84,7 @@ class MeshEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Mesh"; } - MeshEditorPlugin(EditorNode *p_node); + MeshEditorPlugin(); }; -#endif +#endif // MESH_EDITOR_PLUGIN_H diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 9a2b222f21..5fb885ad1f 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,7 @@ #include "mesh_instance_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" @@ -74,14 +75,16 @@ void MeshInstance3DEditor::_menu_option(int p_option) { CollisionShape3D *cshape = memnew(CollisionShape3D); cshape->set_shape(shape); StaticBody3D *body = memnew(StaticBody3D); - body->add_child(cshape); + body->add_child(cshape, true); - Node *owner = node == get_tree()->get_edited_scene_root() ? node : node->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); ur->create_action(TTR("Create Static Trimesh Body")); - ur->add_do_method(node, "add_child", body); + 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(); @@ -109,13 +112,15 @@ void MeshInstance3DEditor::_menu_option(int p_option) { CollisionShape3D *cshape = memnew(CollisionShape3D); cshape->set_shape(shape); StaticBody3D *body = memnew(StaticBody3D); - body->add_child(cshape); + body->add_child(cshape, true); - Node *owner = instance == get_tree()->get_edited_scene_root() ? instance : instance->get_owner(); + Node *owner = get_tree()->get_edited_scene_root(); - ur->add_do_method(instance, "add_child", body); + 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); } @@ -140,15 +145,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(); ur->create_action(TTR("Create Trimesh Static Shape")); - ur->add_do_method(node->get_parent(), "add_child", cshape); + 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(); @@ -183,11 +189,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); + 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); @@ -202,7 +209,8 @@ void MeshInstance3DEditor::_menu_option(int p_option) { return; } - Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(); + Mesh::ConvexDecompositionSettings settings; + Vector<Ref<Shape3D>> shapes = mesh->convex_decompose(settings); if (!shapes.size()) { err_dialog->set_text(TTR("Couldn't create any collision shapes.")); @@ -215,14 +223,17 @@ void MeshInstance3DEditor::_menu_option(int p_option) { 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); } @@ -241,13 +252,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(); ur->create_action(TTR("Create Navigation Mesh")); - ur->add_do_method(node, "add_child", nmi); + 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); @@ -265,13 +277,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; } + UndoRedo *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(); @@ -298,12 +349,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() {} @@ -322,7 +374,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) { @@ -397,7 +449,7 @@ void MeshInstance3DEditor::_create_outline_mesh() { } if (mesh->get_surface_count() == 0) { - err_dialog->set_text(TTR("Mesh has not surface to create outlines from.")); + err_dialog->set_text(TTR("Mesh has no surface to create outlines from.")); err_dialog->popup_centered(); return; } else if (mesh->get_surface_count() == 1 && mesh->surface_get_primitive_type(0) != Mesh::PRIMITIVE_TRIANGLES) { @@ -416,17 +468,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(); ur->create_action(TTR("Create Outline")); - ur->add_do_method(node, "add_child", mi); + 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); @@ -445,16 +495,16 @@ MeshInstance3DEditor::MeshInstance3DEditor() { options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MeshInstance3D"), SNAME("EditorIcons"))); options->get_popup()->add_item(TTR("Create Trimesh Static Body"), MENU_OPTION_CREATE_STATIC_TRIMESH_BODY); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a StaticBody3D and assigns a polygon-based collision shape to it automatically.\nThis is the most accurate (but slowest) option for collision detection.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a StaticBody3D and assigns a polygon-based collision shape to it automatically.\nThis is the most accurate (but slowest) option for collision detection.")); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Create Trimesh Collision Sibling"), MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection.")); options->get_popup()->add_item(TTR("Create Single Convex Collision Sibling"), MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection.")); options->get_popup()->add_item(TTR("Create Simplified Convex Collision Sibling"), MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy.")); options->get_popup()->add_item(TTR("Create Multiple Convex Collision Siblings"), MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision.")); + options->get_popup()->set_item_tooltip(-1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision.")); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Create Navigation Mesh"), MENU_OPTION_CREATE_NAVMESH); options->get_popup()->add_separator(); @@ -469,7 +519,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); @@ -514,10 +564,9 @@ void MeshInstance3DEditorPlugin::make_visible(bool p_visible) { } } -MeshInstance3DEditorPlugin::MeshInstance3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +MeshInstance3DEditorPlugin::MeshInstance3DEditorPlugin() { mesh_editor = memnew(MeshInstance3DEditor); - editor->get_main_control()->add_child(mesh_editor); + EditorNode::get_singleton()->get_main_control()->add_child(mesh_editor); mesh_editor->options->hide(); } diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h index 98b667c978..7968176744 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.h +++ b/editor/plugins/mesh_instance_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,10 +28,9 @@ /* 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_node.h" #include "editor/editor_plugin.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/spin_box.h" @@ -52,17 +51,17 @@ class MeshInstance3DEditor : public Control { MENU_OPTION_DEBUG_UV2, }; - MeshInstance3D *node; + MeshInstance3D *node = nullptr; - MenuButton *options; + MenuButton *options = nullptr; - ConfirmationDialog *outline_dialog; - SpinBox *outline_size; + ConfirmationDialog *outline_dialog = nullptr; + SpinBox *outline_size = nullptr; - AcceptDialog *err_dialog; + AcceptDialog *err_dialog = nullptr; - AcceptDialog *debug_uv_dialog; - Control *debug_uv; + AcceptDialog *debug_uv_dialog = nullptr; + Control *debug_uv = nullptr; Vector<Vector2> uv_lines; void _menu_option(int p_option); @@ -85,8 +84,7 @@ public: class MeshInstance3DEditorPlugin : public EditorPlugin { GDCLASS(MeshInstance3DEditorPlugin, EditorPlugin); - MeshInstance3DEditor *mesh_editor; - EditorNode *editor; + MeshInstance3DEditor *mesh_editor = nullptr; public: virtual String get_name() const override { return "MeshInstance3D"; } @@ -95,8 +93,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - MeshInstance3DEditorPlugin(EditorNode *p_node); + MeshInstance3DEditorPlugin(); ~MeshInstance3DEditorPlugin(); }; -#endif // MESH_EDITOR_PLUGIN_H +#endif // MESH_INSTANCE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index b3f92c9d95..319f6ee9de 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,7 @@ #include "mesh_library_editor_plugin.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "main/main.h" @@ -47,28 +48,30 @@ void MeshLibraryEditor::edit(const Ref<MeshLibrary> &p_mesh_library) { } } -void MeshLibraryEditor::_menu_confirm() { +void MeshLibraryEditor::_menu_remove_confirm() { switch (option) { case MENU_OPTION_REMOVE_ITEM: { mesh_library->remove_item(to_erase); } break; - case MENU_OPTION_UPDATE_FROM_SCENE: { - String existing = mesh_library->get_meta("_editor_source_scene"); - ERR_FAIL_COND(existing == ""); - _import_scene_cbk(existing); - - } break; default: { }; } } -void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge) { +void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) { + cd_update->hide(); + apply_xforms = p_apply_xforms; + String existing = mesh_library->get_meta("_editor_source_scene"); + ERR_FAIL_COND(existing.is_empty()); + _import_scene_cbk(existing); +} + +void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms) { if (!p_merge) { 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); @@ -108,6 +111,13 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, } p_library->set_item_mesh(id, mesh); + + if (p_apply_xforms) { + p_library->set_item_mesh_transform(id, mi->get_transform()); + } else { + p_library->set_item_mesh_transform(id, Transform3D()); + } + mesh_instances[id] = mi; Vector<MeshLibrary::ShapeData> collisions; @@ -127,9 +137,11 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, continue; } - //Transform3D shape_transform = sb->shape_owner_get_transform(E); - - //shape_transform.set_origin(shape_transform.get_origin() - phys_offset); + Transform3D shape_transform; + if (p_apply_xforms) { + shape_transform = mi->get_transform(); + } + shape_transform *= sb->get_transform() * sb->shape_owner_get_transform(E); for (int k = 0; k < sb->shape_owner_get_shape_count(E); k++) { Ref<Shape3D> collision = sb->shape_owner_get_shape(E, k); @@ -138,7 +150,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, } MeshLibrary::ShapeData shape_data; shape_data.shape = collision; - shape_data.local_transform = sb->get_transform() * sb->shape_owner_get_transform(E); + shape_data.local_transform = shape_transform; collisions.push_back(shape_data); } } @@ -197,15 +209,16 @@ void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'."); - _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE); + _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE, apply_xforms); memdelete(scene); mesh_library->set_meta("_editor_source_scene", p_str); + menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false); } -Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge) { - _import_scene(p_base_scene, ml, p_merge); +Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge, bool p_apply_xforms) { + _import_scene(p_base_scene, ml, p_merge, p_apply_xforms); return OK; } @@ -216,19 +229,24 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { mesh_library->create_item(mesh_library->get_last_unused_item_id()); } break; case MENU_OPTION_REMOVE_ITEM: { - String p = editor->get_inspector()->get_selected_path(); + String p = InspectorDock::get_inspector_singleton()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); - cd->set_text(vformat(TTR("Remove item %d?"), to_erase)); - cd->popup_centered(Size2(300, 60)); + cd_remove->set_text(vformat(TTR("Remove item %d?"), to_erase)); + cd_remove->popup_centered(Size2(300, 60)); } } break; case MENU_OPTION_IMPORT_FROM_SCENE: { + apply_xforms = false; + file->popup_file_dialog(); + } break; + case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: { + apply_xforms = true; file->popup_file_dialog(); } break; case MENU_OPTION_UPDATE_FROM_SCENE: { - cd->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); - cd->popup_centered(Size2(500, 60)); + cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); + cd_update->popup_centered(Size2(500, 60)); } break; } } @@ -236,7 +254,7 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { void MeshLibraryEditor::_bind_methods() { } -MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { +MeshLibraryEditor::MeshLibraryEditor() { file = memnew(EditorFileDialog); file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); //not for now? @@ -245,7 +263,7 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { file->clear_filters(); file->set_title(TTR("Import Scene")); for (int i = 0; i < extensions.size(); i++) { - file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); + file->add_filter("*." + extensions[i], extensions[i].to_upper()); } add_child(file); file->connect("file_selected", callable_mp(this, &MeshLibraryEditor::_import_scene_cbk)); @@ -253,21 +271,26 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { menu = memnew(MenuButton); Node3DEditor::get_singleton()->add_control_to_menu_panel(menu); menu->set_position(Point2(1, 1)); - menu->set_text(TTR("Mesh Library")); + menu->set_text(TTR("MeshLibrary")); menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("MeshLibrary"), SNAME("EditorIcons"))); menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM); menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM); menu->get_popup()->add_separator(); - menu->get_popup()->add_item(TTR("Import from Scene"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS); menu->get_popup()->add_item(TTR("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE); menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), true); menu->get_popup()->connect("id_pressed", callable_mp(this, &MeshLibraryEditor::_menu_cbk)); menu->hide(); - editor = p_editor; - cd = memnew(ConfirmationDialog); - add_child(cd); - cd->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_confirm)); + cd_remove = memnew(ConfirmationDialog); + add_child(cd_remove); + cd_remove->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_remove_confirm)); + cd_update = memnew(ConfirmationDialog); + add_child(cd_update); + cd_update->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) { @@ -293,14 +316,11 @@ void MeshLibraryEditorPlugin::make_visible(bool p_visible) { } } -MeshLibraryEditorPlugin::MeshLibraryEditorPlugin(EditorNode *p_node) { - EDITOR_DEF("editors/grid_map/preview_size", 64); - mesh_library_editor = memnew(MeshLibraryEditor(p_node)); +MeshLibraryEditorPlugin::MeshLibraryEditorPlugin() { + mesh_library_editor = memnew(MeshLibraryEditor); - p_node->get_main_control()->add_child(mesh_library_editor); + EditorNode::get_singleton()->get_main_control()->add_child(mesh_library_editor); mesh_library_editor->set_anchors_and_offsets_preset(Control::PRESET_TOP_WIDE); mesh_library_editor->set_end(Point2(0, 22)); mesh_library_editor->hide(); - - editor = nullptr; } diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 6c33c8bb9e..f4b4288a5f 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,33 +31,40 @@ #ifndef MESH_LIBRARY_EDITOR_PLUGIN_H #define MESH_LIBRARY_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" #include "scene/resources/mesh_library.h" +class EditorFileDialog; +class ConfirmationDialog; +class MenuButton; + class MeshLibraryEditor : public Control { GDCLASS(MeshLibraryEditor, Control); Ref<MeshLibrary> mesh_library; - EditorNode *editor; - MenuButton *menu; - ConfirmationDialog *cd; - EditorFileDialog *file; - int to_erase; + MenuButton *menu = nullptr; + ConfirmationDialog *cd_remove = nullptr; + ConfirmationDialog *cd_update = nullptr; + EditorFileDialog *file = nullptr; + bool apply_xforms = false; + int to_erase = 0; enum { MENU_OPTION_ADD_ITEM, MENU_OPTION_REMOVE_ITEM, MENU_OPTION_UPDATE_FROM_SCENE, - MENU_OPTION_IMPORT_FROM_SCENE + MENU_OPTION_IMPORT_FROM_SCENE, + 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_confirm(); + void _menu_remove_confirm(); + void _menu_update_confirm(bool p_apply_xforms); - static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge); + static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms); protected: static void _bind_methods(); @@ -66,16 +73,15 @@ public: MenuButton *get_menu_button() const { return menu; } void edit(const Ref<MeshLibrary> &p_mesh_library); - static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true); + static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true, bool p_apply_xforms = false); - MeshLibraryEditor(EditorNode *p_editor); + MeshLibraryEditor(); }; class MeshLibraryEditorPlugin : public EditorPlugin { GDCLASS(MeshLibraryEditorPlugin, EditorPlugin); - MeshLibraryEditor *mesh_library_editor; - EditorNode *editor; + MeshLibraryEditor *mesh_library_editor = nullptr; public: virtual String get_name() const override { return "MeshLibrary"; } @@ -84,7 +90,7 @@ public: virtual bool handles(Object *p_node) const override; virtual void make_visible(bool p_visible) override; - MeshLibraryEditorPlugin(EditorNode *p_node); + MeshLibraryEditorPlugin(); }; #endif // MESH_LIBRARY_EDITOR_PLUGIN_H diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp index 5514bccabb..fc4dc5bc2f 100644 --- a/editor/plugins/multimesh_editor_plugin.cpp +++ b/editor/plugins/multimesh_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,8 @@ #include "multimesh_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/scene_tree_editor.h" #include "node_3d_editor_plugin.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/gui/box_container.h" @@ -48,7 +50,7 @@ void MultiMeshEditor::_populate() { Ref<Mesh> mesh; - if (mesh_source->get_text() == "") { + if (mesh_source->get_text().is_empty()) { Ref<MultiMesh> multimesh; multimesh = node->get_multimesh(); if (multimesh.is_null()) { @@ -89,7 +91,7 @@ void MultiMeshEditor::_populate() { } } - if (surface_source->get_text() == "") { + if (surface_source->get_text().is_empty()) { err_dialog->set_text(TTR("No surface source specified.")); err_dialog->popup_centered(); return; @@ -103,9 +105,9 @@ void MultiMeshEditor::_populate() { return; } - GeometryInstance3D *ss_instance = Object::cast_to<MeshInstance3D>(ss_node); + MeshInstance3D *ss_instance = Object::cast_to<MeshInstance3D>(ss_node); - if (!ss_instance) { + if (!ss_instance || !ss_instance->get_mesh().is_valid()) { err_dialog->set_text(TTR("Surface source is invalid (no geometry).")); err_dialog->popup_centered(); return; @@ -113,7 +115,7 @@ void MultiMeshEditor::_populate() { Transform3D geom_xform = node->get_global_transform().affine_inverse() * ss_instance->get_global_transform(); - Vector<Face3> geometry = ss_instance->get_faces(VisualInstance3D::FACES_SOLID); + Vector<Face3> geometry = ss_instance->get_mesh()->get_faces(); if (geometry.size() == 0) { err_dialog->set_text(TTR("Surface source is invalid (no faces).")); @@ -139,7 +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) { @@ -178,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 @@ -198,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(); @@ -289,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); @@ -301,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")); @@ -345,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); @@ -375,10 +377,9 @@ void MultiMeshEditorPlugin::make_visible(bool p_visible) { } } -MultiMeshEditorPlugin::MultiMeshEditorPlugin(EditorNode *p_node) { - editor = p_node; +MultiMeshEditorPlugin::MultiMeshEditorPlugin() { multimesh_editor = memnew(MultiMeshEditor); - editor->get_main_control()->add_child(multimesh_editor); + EditorNode::get_singleton()->get_main_control()->add_child(multimesh_editor); multimesh_editor->options->hide(); } diff --git a/editor/plugins/multimesh_editor_plugin.h b/editor/plugins/multimesh_editor_plugin.h index 2cdd7cf504..5773989d0d 100644 --- a/editor/plugins/multimesh_editor_plugin.h +++ b/editor/plugins/multimesh_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,36 +31,38 @@ #ifndef MULTIMESH_EDITOR_PLUGIN_H #define MULTIMESH_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/multimesh_instance_3d.h" +#include "scene/gui/slider.h" #include "scene/gui/spin_box.h" +class SceneTreeDialog; + class MultiMeshEditor : public Control { GDCLASS(MultiMeshEditor, Control); friend class MultiMeshEditorPlugin; - AcceptDialog *err_dialog; - MenuButton *options; - MultiMeshInstance3D *_last_pp_node; - bool browsing_source; + AcceptDialog *err_dialog = nullptr; + MenuButton *options = nullptr; + MultiMeshInstance3D *_last_pp_node = nullptr; + bool browsing_source = false; - Panel *panel; - MultiMeshInstance3D *node; + Panel *panel = nullptr; + MultiMeshInstance3D *node = nullptr; - LineEdit *surface_source; - LineEdit *mesh_source; + LineEdit *surface_source = nullptr; + LineEdit *mesh_source = nullptr; - SceneTreeDialog *std; + SceneTreeDialog *std = nullptr; - ConfirmationDialog *populate_dialog; - OptionButton *populate_axis; - HSlider *populate_rotate_random; - HSlider *populate_tilt_random; - SpinBox *populate_scale_random; - SpinBox *populate_scale; - SpinBox *populate_amount; + ConfirmationDialog *populate_dialog = nullptr; + OptionButton *populate_axis = nullptr; + HSlider *populate_rotate_random = nullptr; + HSlider *populate_tilt_random = nullptr; + SpinBox *populate_scale_random = nullptr; + SpinBox *populate_scale = nullptr; + SpinBox *populate_amount = nullptr; enum Menu { MENU_OPTION_POPULATE @@ -83,8 +85,7 @@ public: class MultiMeshEditorPlugin : public EditorPlugin { GDCLASS(MultiMeshEditorPlugin, EditorPlugin); - MultiMeshEditor *multimesh_editor; - EditorNode *editor; + MultiMeshEditor *multimesh_editor = nullptr; public: virtual String get_name() const override { return "MultiMesh"; } @@ -93,7 +94,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - MultiMeshEditorPlugin(EditorNode *p_node); + MultiMeshEditorPlugin(); ~MultiMeshEditorPlugin(); }; diff --git a/editor/plugins/navigation_polygon_editor_plugin.cpp b/editor/plugins/navigation_polygon_editor_plugin.cpp index 9971d3111d..8f3553b8cf 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.cpp +++ b/editor/plugins/navigation_polygon_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -106,17 +106,14 @@ 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); } -NavigationPolygonEditor::NavigationPolygonEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; -} +NavigationPolygonEditor::NavigationPolygonEditor() {} -NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(NavigationPolygonEditor(p_node)), "NavigationRegion2D") { +NavigationPolygonEditorPlugin::NavigationPolygonEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(NavigationPolygonEditor), "NavigationRegion2D") { } diff --git a/editor/plugins/navigation_polygon_editor_plugin.h b/editor/plugins/navigation_polygon_editor_plugin.h index 0f5928d416..239da88ba2 100644 --- a/editor/plugins/navigation_polygon_editor_plugin.h +++ b/editor/plugins/navigation_polygon_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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" @@ -37,7 +37,7 @@ class NavigationPolygonEditor : public AbstractPolygon2DEditor { GDCLASS(NavigationPolygonEditor, AbstractPolygon2DEditor); - NavigationRegion2D *node; + NavigationRegion2D *node = nullptr; Ref<NavigationPolygon> _ensure_navpoly() const; @@ -57,14 +57,14 @@ protected: virtual void _create_resource() override; public: - NavigationPolygonEditor(EditorNode *p_editor); + NavigationPolygonEditor(); }; class NavigationPolygonEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(NavigationPolygonEditorPlugin, AbstractPolygon2DEditorPlugin); public: - NavigationPolygonEditorPlugin(EditorNode *p_node); + NavigationPolygonEditorPlugin(); }; -#endif // NAVIGATIONPOLYGONEDITORPLUGIN_H +#endif // NAVIGATION_POLYGON_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index d04e88e915..e8f143a637 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,27 +33,32 @@ #include "core/math/convex_hull.h" #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "editor/plugins/node_3d_editor_plugin.h" +#include "scene/3d/audio_listener_3d.h" #include "scene/3d/audio_stream_player_3d.h" #include "scene/3d/camera_3d.h" #include "scene/3d/collision_polygon_3d.h" #include "scene/3d/collision_shape_3d.h" #include "scene/3d/cpu_particles_3d.h" #include "scene/3d/decal.h" +#include "scene/3d/fog_volume.h" #include "scene/3d/gpu_particles_3d.h" #include "scene/3d/gpu_particles_collision_3d.h" +#include "scene/3d/joint_3d.h" +#include "scene/3d/label_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_gi.h" #include "scene/3d/lightmap_probe.h" -#include "scene/3d/listener_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/physics_joint_3d.h" #include "scene/3d/position_3d.h" #include "scene/3d/ray_cast_3d.h" #include "scene/3d/reflection_probe.h" -#include "scene/3d/soft_body_3d.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" #include "scene/3d/vehicle_body_3d.h" @@ -69,7 +74,8 @@ #include "scene/resources/separation_ray_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" #include "scene/resources/surface_tool.h" -#include "scene/resources/world_margin_shape_3d.h" +#include "scene/resources/world_boundary_shape_3d.h" +#include "servers/navigation_server_3d.h" #define HANDLE_HALF_SIZE 9.5 @@ -116,52 +122,52 @@ void EditorNode3DGizmo::redraw() { } } -String EditorNode3DGizmo::get_handle_name(int p_id) const { +String EditorNode3DGizmo::get_handle_name(int p_id, bool p_secondary) const { String ret; - if (GDVIRTUAL_CALL(_get_handle_name, p_id, ret)) { + if (GDVIRTUAL_CALL(_get_handle_name, p_id, p_secondary, ret)) { return ret; } ERR_FAIL_COND_V(!gizmo_plugin, ""); - return gizmo_plugin->get_handle_name(this, p_id); + return gizmo_plugin->get_handle_name(this, p_id, p_secondary); } -bool EditorNode3DGizmo::is_handle_highlighted(int p_id) const { +bool EditorNode3DGizmo::is_handle_highlighted(int p_id, bool p_secondary) const { bool success; - if (GDVIRTUAL_CALL(_is_handle_highlighted, p_id, success)) { + if (GDVIRTUAL_CALL(_is_handle_highlighted, p_id, p_secondary, success)) { return success; } ERR_FAIL_COND_V(!gizmo_plugin, false); - return gizmo_plugin->is_handle_highlighted(this, p_id); + return gizmo_plugin->is_handle_highlighted(this, p_id, p_secondary); } -Variant EditorNode3DGizmo::get_handle_value(int p_id) const { +Variant EditorNode3DGizmo::get_handle_value(int p_id, bool p_secondary) const { Variant value; - if (GDVIRTUAL_CALL(_get_handle_value, p_id, value)) { + if (GDVIRTUAL_CALL(_get_handle_value, p_id, p_secondary, value)) { return value; } ERR_FAIL_COND_V(!gizmo_plugin, Variant()); - return gizmo_plugin->get_handle_value(this, p_id); + return gizmo_plugin->get_handle_value(this, p_id, p_secondary); } -void EditorNode3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) { - if (GDVIRTUAL_CALL(_set_handle, p_id, p_camera, p_point)) { +void EditorNode3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + if (GDVIRTUAL_CALL(_set_handle, p_id, p_secondary, p_camera, p_point)) { return; } ERR_FAIL_COND(!gizmo_plugin); - gizmo_plugin->set_handle(this, p_id, p_camera, p_point); + gizmo_plugin->set_handle(this, p_id, p_secondary, p_camera, p_point); } -void EditorNode3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) { - if (GDVIRTUAL_CALL(_commit_handle, p_id, p_restore, p_cancel)) { +void EditorNode3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + if (GDVIRTUAL_CALL(_commit_handle, p_id, p_secondary, p_restore, p_cancel)) { return; } ERR_FAIL_COND(!gizmo_plugin); - gizmo_plugin->commit_handle(this, p_id, p_restore, p_cancel); + gizmo_plugin->commit_handle(this, p_id, p_secondary, p_restore, p_cancel); } int EditorNode3DGizmo::subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const { @@ -241,10 +247,13 @@ 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<ArrayMesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) { +void EditorNode3DGizmo::add_mesh(const Ref<Mesh> &p_mesh, const Ref<Material> &p_material, const Transform3D &p_xform, const Ref<SkinReference> &p_skin_reference) { ERR_FAIL_COND(!spatial_node); + ERR_FAIL_COND_MSG(!p_mesh.is_valid(), "EditorNode3DGizmo.add_mesh() requires a valid Mesh resource."); + Instance ins; ins.mesh = p_mesh; @@ -321,37 +330,34 @@ void EditorNode3DGizmo::add_unscaled_billboard(const Ref<Material> &p_material, ERR_FAIL_COND(!spatial_node); Instance ins; - Vector<Vector3> vs; - Vector<Vector2> uv; - Vector<Color> colors; - - vs.push_back(Vector3(-p_scale, p_scale, 0)); - vs.push_back(Vector3(p_scale, p_scale, 0)); - vs.push_back(Vector3(p_scale, -p_scale, 0)); - vs.push_back(Vector3(-p_scale, -p_scale, 0)); - - uv.push_back(Vector2(0, 0)); - uv.push_back(Vector2(1, 0)); - uv.push_back(Vector2(1, 1)); - uv.push_back(Vector2(0, 1)); - - colors.push_back(p_modulate); - colors.push_back(p_modulate); - colors.push_back(p_modulate); - colors.push_back(p_modulate); + Vector<Vector3> vs = { + Vector3(-p_scale, p_scale, 0), + Vector3(p_scale, p_scale, 0), + Vector3(p_scale, -p_scale, 0), + Vector3(-p_scale, -p_scale, 0) + }; + + Vector<Vector2> uv = { + Vector2(0, 0), + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1) + }; + + Vector<Color> colors = { + p_modulate, + p_modulate, + p_modulate, + p_modulate + }; + + Vector<int> indices = { 0, 1, 2, 0, 2, 3 }; Ref<ArrayMesh> mesh = memnew(ArrayMesh); Array a; a.resize(Mesh::ARRAY_MAX); a[Mesh::ARRAY_VERTEX] = vs; a[Mesh::ARRAY_TEX_UV] = uv; - Vector<int> indices; - indices.push_back(0); - indices.push_back(1); - indices.push_back(2); - indices.push_back(0); - indices.push_back(2); - indices.push_back(3); a[Mesh::ARRAY_INDEX] = indices; a[Mesh::ARRAY_COLOR] = colors; mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, a); @@ -407,7 +413,8 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< } bool is_current_hover_gizmo = Node3DEditor::get_singleton()->get_current_hover_gizmo() == this; - int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle(); + bool current_hover_handle_secondary; + int current_hover_handle = Node3DEditor::get_singleton()->get_current_hover_gizmo_handle(current_hover_handle_secondary); Instance ins; Ref<ArrayMesh> mesh = memnew(ArrayMesh); @@ -421,12 +428,12 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< Color *w = colors.ptrw(); for (int i = 0; i < p_handles.size(); i++) { Color col(1, 1, 1, 1); - if (is_handle_highlighted(i)) { + if (is_handle_highlighted(i, p_secondary)) { col = Color(0, 0, 1, 0.9); } int id = p_ids.is_empty() ? i : p_ids[i]; - if (!is_current_hover_gizmo || current_hover_handle != id) { + if (!is_current_hover_gizmo || current_hover_handle != id || p_secondary != current_hover_handle_secondary) { col.a = 0.8; } @@ -472,7 +479,7 @@ void EditorNode3DGizmo::add_handles(const Vector<Vector3> &p_handles, const Ref< } } -void EditorNode3DGizmo::add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) { +void EditorNode3DGizmo::add_solid_box(const Ref<Material> &p_material, Vector3 p_size, Vector3 p_position, const Transform3D &p_xform) { ERR_FAIL_COND(!spatial_node); BoxMesh box_mesh; @@ -571,8 +578,9 @@ bool EditorNode3DGizmo::intersect_frustum(const Camera3D *p_camera, const Vector return false; } -void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id) { +void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id, bool &r_secondary) { r_id = -1; + r_secondary = false; ERR_FAIL_COND(!spatial_node); ERR_FAIL_COND(!valid); @@ -584,7 +592,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; @@ -602,6 +610,7 @@ void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 } else { r_id = secondary_handle_ids[i]; } + r_secondary = true; } } } @@ -625,6 +634,7 @@ void EditorNode3DGizmo::handles_intersect_ray(Camera3D *p_camera, const Vector2 } else { r_id = handle_ids[i]; } + r_secondary = false; } } } @@ -658,7 +668,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); } @@ -682,13 +692,13 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, } if (collision_segments.size()) { - Plane camp(p_camera->get_transform().origin, (-p_camera->get_transform().basis.get_axis(2)).normalized()); + 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; @@ -735,7 +745,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(); @@ -832,16 +842,16 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin); ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear); ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden); - ClassDB::bind_method(D_METHOD("is_subgizmo_selected"), &EditorNode3DGizmo::is_subgizmo_selected); + ClassDB::bind_method(D_METHOD("is_subgizmo_selected", "id"), &EditorNode3DGizmo::is_subgizmo_selected); ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection); GDVIRTUAL_BIND(_redraw); - GDVIRTUAL_BIND(_get_handle_name, "id"); - GDVIRTUAL_BIND(_is_handle_highlighted, "id"); + GDVIRTUAL_BIND(_get_handle_name, "id", "secondary"); + GDVIRTUAL_BIND(_is_handle_highlighted, "id", "secondary"); - GDVIRTUAL_BIND(_get_handle_value, "id"); - GDVIRTUAL_BIND(_set_handle, "id", "camera", "point"); - GDVIRTUAL_BIND(_commit_handle, "id", "restore", "cancel"); + GDVIRTUAL_BIND(_get_handle_value, "id", "secondary"); + GDVIRTUAL_BIND(_set_handle, "id", "secondary", "camera", "point"); + GDVIRTUAL_BIND(_commit_handle, "id", "secondary", "restore", "cancel"); GDVIRTUAL_BIND(_subgizmos_intersect_ray, "camera", "point"); GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "camera", "frustum"); @@ -870,7 +880,7 @@ EditorNode3DGizmo::~EditorNode3DGizmo() { ///// void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) { - Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6)); + Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated"); Vector<Ref<StandardMaterial3D>> mats; @@ -912,7 +922,7 @@ void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color } void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) { - Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6)); + Color instantiated_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/instantiated"); Vector<Ref<StandardMaterial3D>> icons; @@ -1003,7 +1013,9 @@ String EditorNode3DGizmoPlugin::get_gizmo_name() const { if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) { return get_script_instance()->call("_get_gizmo_name"); } - return TTR("Nameless gizmo"); + + WARN_PRINT_ONCE("A 3D editor gizmo has no name defined (it will appear as \"Unnamed Gizmo\" in the \"View > Gizmos\" menu). To resolve this, override the `_get_gizmo_name()` function to return a String in the script that extends EditorNode3DGizmoPlugin."); + return TTR("Unnamed Gizmo"); } int EditorNode3DGizmoPlugin::get_priority() const { @@ -1049,19 +1061,18 @@ void EditorNode3DGizmoPlugin::_bind_methods() { GDVIRTUAL_BIND(_is_selectable_when_hidden); GDVIRTUAL_BIND(_redraw, "gizmo"); - GDVIRTUAL_BIND(_get_handle_name, "gizmo", "handle_id"); - GDVIRTUAL_BIND(_is_handle_highlighted, "gizmo", "handle_id"); - GDVIRTUAL_BIND(_get_handle_value, "gizmo", "handle_id"); + GDVIRTUAL_BIND(_get_handle_name, "gizmo", "handle_id", "secondary"); + GDVIRTUAL_BIND(_is_handle_highlighted, "gizmo", "handle_id", "secondary"); + GDVIRTUAL_BIND(_get_handle_value, "gizmo", "handle_id", "secondary"); - GDVIRTUAL_BIND(_set_handle, "gizmo", "handle_id", "camera", "screen_pos"); - GDVIRTUAL_BIND(_commit_handle, "gizmo", "handle_id", "restore", "cancel"); + GDVIRTUAL_BIND(_set_handle, "gizmo", "handle_id", "secondary", "camera", "screen_pos"); + GDVIRTUAL_BIND(_commit_handle, "gizmo", "handle_id", "secondary", "restore", "cancel"); GDVIRTUAL_BIND(_subgizmos_intersect_ray, "gizmo", "camera", "screen_pos"); GDVIRTUAL_BIND(_subgizmos_intersect_frustum, "gizmo", "camera", "frustum_planes"); GDVIRTUAL_BIND(_get_subgizmo_transform, "gizmo", "subgizmo_id"); GDVIRTUAL_BIND(_set_subgizmo_transform, "gizmo", "subgizmo_id", "transform"); GDVIRTUAL_BIND(_commit_subgizmos, "gizmo", "ids", "restores", "cancel"); - ; } bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -1105,36 +1116,36 @@ void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { GDVIRTUAL_CALL(_redraw, p_gizmo); } -bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const { +bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { bool ret; - if (GDVIRTUAL_CALL(_is_handle_highlighted, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { + if (GDVIRTUAL_CALL(_is_handle_highlighted, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret)) { return ret; } return false; } -String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { String ret; - if (GDVIRTUAL_CALL(_get_handle_name, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { + if (GDVIRTUAL_CALL(_get_handle_name, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret)) { return ret; } return ""; } -Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant EditorNode3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Variant ret; - if (GDVIRTUAL_CALL(_get_handle_value, Ref<EditorNode3DGizmo>(p_gizmo), p_id, ret)) { + if (GDVIRTUAL_CALL(_get_handle_value, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, ret)) { return ret; } return Variant(); } -void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { - GDVIRTUAL_CALL(_set_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_camera, p_point); +void EditorNode3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + GDVIRTUAL_CALL(_set_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, p_camera, p_point); } -void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - GDVIRTUAL_CALL(_commit_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_restore, p_cancel); +void EditorNode3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + GDVIRTUAL_CALL(_commit_handle, Ref<EditorNode3DGizmo>(p_gizmo), p_id, p_secondary, p_restore, p_cancel); } int EditorNode3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { @@ -1239,7 +1250,7 @@ int Light3DGizmoPlugin::get_priority() const { return -1; } -String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { if (p_id == 0) { return "Radius"; } else { @@ -1247,7 +1258,7 @@ String Light3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int } } -Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant Light3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); if (p_id == 0) { return light->get_param(Light3D::PARAM_RANGE); @@ -1286,7 +1297,7 @@ static float _find_closest_angle_to_half_pi_arc(const Vector3 &p_from, const Vec return Math::rad2deg(a); } -void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); Transform3D gt = light->get_global_transform(); Transform3D gi = gt.affine_inverse(); @@ -1311,7 +1322,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(gt.origin, p_camera->get_transform().basis.get_axis(2)); + Plane cp = Plane(p_camera->get_transform().basis.get_column(2), gt.origin); Vector3 inters; if (cp.intersects_ray(ray_from, ray_dir, &inters)) { @@ -1330,7 +1341,7 @@ void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } } -void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void Light3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { Light3D *light = Object::cast_to<Light3D>(p_gizmo->get_spatial_node()); if (p_cancel) { light->set_param(p_id == 0 ? Light3D::PARAM_RANGE : Light3D::PARAM_SPOT_ANGLE, p_restore); @@ -1472,17 +1483,16 @@ void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(points_primary, material_primary, false, color); p_gizmo->add_lines(points_secondary, material_secondary, false, color); - Vector<Vector3> handles; - handles.push_back(Vector3(0, 0, -r)); - handles.push_back(Vector3(w, 0, -d)); + Vector<Vector3> handles = { + Vector3(0, 0, -r), + Vector3(w, 0, -d) + }; p_gizmo->add_handles(handles, get_material("handles")); p_gizmo->add_unscaled_billboard(icon, 0.05, color); } } -////// - //// player gizmo AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() { Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/stream_player_3d", Color(0.4, 0.8, 1)); @@ -1490,6 +1500,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"); } @@ -1505,16 +1518,16 @@ int AudioStreamPlayer3DGizmoPlugin::get_priority() const { return -1; } -String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String AudioStreamPlayer3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { return "Emission Radius"; } -Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant AudioStreamPlayer3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); return player->get_emission_angle(); } -void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); Transform3D gt = player->get_global_transform(); @@ -1551,7 +1564,7 @@ void AudioStreamPlayer3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo } } -void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void AudioStreamPlayer3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { AudioStreamPlayer3D *player = Object::cast_to<AudioStreamPlayer3D>(p_gizmo->get_spatial_node()); if (p_cancel) { @@ -1573,6 +1586,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)); @@ -1621,6 +1716,29 @@ void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ////// +AudioListener3DGizmoPlugin::AudioListener3DGizmoPlugin() { + create_icon_material("audio_listener_3d_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoAudioListener3D"), SNAME("EditorIcons"))); +} + +bool AudioListener3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<AudioListener3D>(p_spatial) != nullptr; +} + +String AudioListener3DGizmoPlugin::get_gizmo_name() const { + return "AudioListener3D"; +} + +int AudioListener3DGizmoPlugin::get_priority() const { + return -1; +} + +void AudioListener3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + const Ref<Material> icon = get_material("audio_listener_3d_icon", p_gizmo); + p_gizmo->add_unscaled_billboard(icon, 0.05); +} + +////// + Camera3DGizmoPlugin::Camera3DGizmoPlugin() { Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/camera", Color(0.8, 0.4, 0.8)); @@ -1640,7 +1758,7 @@ int Camera3DGizmoPlugin::get_priority() const { return -1; } -String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { @@ -1650,7 +1768,7 @@ String Camera3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, in } } -Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { @@ -1660,7 +1778,7 @@ Variant Camera3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, } } -void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); Transform3D gt = camera->get_global_transform(); @@ -1689,7 +1807,7 @@ void Camera3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, } } -void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void Camera3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { Camera3D *camera = Object::cast_to<Camera3D>(p_gizmo->get_spatial_node()); if (camera->get_projection() == Camera3D::PROJECTION_PERSPECTIVE) { @@ -1817,47 +1935,6 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_handles(handles, get_material("handles")); - - ClippedCamera3D *clipcam = Object::cast_to<ClippedCamera3D>(camera); - if (clipcam) { - Node3D *parent = Object::cast_to<Node3D>(camera->get_parent()); - if (!parent) { - return; - } - Vector3 cam_normal = -camera->get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized(); - Vector3 cam_x = camera->get_global_transform().basis.get_axis(Vector3::AXIS_X).normalized(); - Vector3 cam_y = camera->get_global_transform().basis.get_axis(Vector3::AXIS_Y).normalized(); - Vector3 cam_pos = camera->get_global_transform().origin; - Vector3 parent_pos = parent->get_global_transform().origin; - - Plane parent_plane(parent_pos, cam_normal); - Vector3 ray_from = parent_plane.project(cam_pos); - - lines.clear(); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5); - - lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5); - lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5); - - lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5); - lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5); - - lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5); - - if (parent_plane.distance_to(cam_pos) < 0) { - lines.push_back(ray_from); - lines.push_back(cam_pos); - } - - Transform3D local = camera->get_global_transform().affine_inverse(); - for (int i = 0; i < lines.size(); i++) { - lines.write[i] = local.xform(lines[i]); - } - - p_gizmo->add_lines(lines, material); - } } ////// @@ -1866,7 +1943,7 @@ MeshInstance3DGizmoPlugin::MeshInstance3DGizmoPlugin() { } bool MeshInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftBody3D>(p_spatial) == nullptr; + return Object::cast_to<MeshInstance3D>(p_spatial) != nullptr && Object::cast_to<SoftDynamicBody3D>(p_spatial) == nullptr; } String MeshInstance3DGizmoPlugin::get_gizmo_name() const { @@ -1902,6 +1979,7 @@ void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { OccluderInstance3DGizmoPlugin::OccluderInstance3DGizmoPlugin() { create_material("line_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/occluder", Color(0.8, 0.5, 1))); + create_handle_material("handles"); } bool OccluderInstance3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -1916,6 +1994,189 @@ int OccluderInstance3DGizmoPlugin::get_priority() const { return -1; } +String OccluderInstance3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + const OccluderInstance3D *cs = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + + Ref<Occluder3D> o = cs->get_occluder(); + if (o.is_null()) { + return ""; + } + + if (Object::cast_to<SphereOccluder3D>(*o)) { + return "Radius"; + } + + if (Object::cast_to<BoxOccluder3D>(*o) || Object::cast_to<QuadOccluder3D>(*o)) { + return "Size"; + } + + return ""; +} + +Variant OccluderInstance3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + + Ref<Occluder3D> o = oi->get_occluder(); + if (o.is_null()) { + return Variant(); + } + + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + return so->get_radius(); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Ref<BoxOccluder3D> bo = o; + return bo->get_size(); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + return qo->get_size(); + } + + return Variant(); +} + +void OccluderInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + + Ref<Occluder3D> o = oi->get_occluder(); + if (o.is_null()) { + return; + } + + Transform3D gt = oi->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; + + bool snap_enabled = Node3DEditor::get_singleton()->is_snap_enabled(); + float snap = Node3DEditor::get_singleton()->get_translate_snap(); + + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); + float d = ra.x; + if (snap_enabled) { + d = Math::snapped(d, snap); + } + + if (d < 0.001) { + d = 0.001; + } + + so->set_radius(d); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Vector3 axis; + axis[p_id] = 1.0; + Ref<BoxOccluder3D> bo = o; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = ra[p_id]; + if (snap_enabled) { + d = Math::snapped(d, snap); + } + + if (d < 0.001) { + d = 0.001; + } + + Vector3 he = bo->get_size(); + he[p_id] = d * 2; + bo->set_size(he); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + Plane p = Plane(Vector3(0.0f, 0.0f, 1.0f), 0.0f); + Vector3 intersection; + if (!p.intersects_segment(sg[0], sg[1], &intersection)) { + return; + } + + if (p_id == 2) { + Vector2 s = Vector2(intersection.x, intersection.y) * 2.0f; + if (snap_enabled) { + s = s.snapped(Vector2(snap, snap)); + } + s = s.max(Vector2(0.001, 0.001)); + qo->set_size(s); + } else { + float d = intersection[p_id]; + if (snap_enabled) { + d = Math::snapped(d, snap); + } + + if (d < 0.001) { + d = 0.001; + } + + Vector2 he = qo->get_size(); + he[p_id] = d * 2.0f; + qo->set_size(he); + } + } +} + +void OccluderInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + OccluderInstance3D *oi = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); + + Ref<Occluder3D> o = oi->get_occluder(); + if (o.is_null()) { + return; + } + + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + if (p_cancel) { + so->set_radius(p_restore); + return; + } + + UndoRedo *ur = Node3DEditor::get_singleton()->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); + ur->commit_action(); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Ref<BoxOccluder3D> bo = o; + if (p_cancel) { + bo->set_size(p_restore); + return; + } + + UndoRedo *ur = Node3DEditor::get_singleton()->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); + ur->commit_action(); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + if (p_cancel) { + qo->set_size(p_restore); + return; + } + + UndoRedo *ur = Node3DEditor::get_singleton()->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); + ur->commit_action(); + } +} + void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { OccluderInstance3D *occluder_instance = Object::cast_to<OccluderInstance3D>(p_gizmo->get_spatial_node()); @@ -1933,6 +2194,35 @@ void OccluderInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_collision_segments(lines); } + + Ref<Material> handles_material = get_material("handles"); + if (Object::cast_to<SphereOccluder3D>(*o)) { + Ref<SphereOccluder3D> so = o; + float r = so->get_radius(); + Vector<Vector3> handles = { Vector3(r, 0, 0) }; + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to<BoxOccluder3D>(*o)) { + Ref<BoxOccluder3D> bo = o; + + Vector<Vector3> handles; + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = bo->get_size()[i] / 2; + handles.push_back(ax); + } + + p_gizmo->add_handles(handles, handles_material); + } + + if (Object::cast_to<QuadOccluder3D>(*o)) { + Ref<QuadOccluder3D> qo = o; + Vector2 size = qo->get_size(); + Vector3 s = Vector3(size.x, size.y, 0.0f) / 2.0f; + Vector<Vector3> handles = { Vector3(s.x, 0.0f, 0.0f), Vector3(0.0f, s.y, 0.0f), Vector3(s.x, s.y, 0.0f) }; + p_gizmo->add_handles(handles, handles_material); + } } ///// @@ -1969,6 +2259,38 @@ void Sprite3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { /// +Label3DGizmoPlugin::Label3DGizmoPlugin() { +} + +bool Label3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Label3D>(p_spatial) != nullptr; +} + +String Label3DGizmoPlugin::get_gizmo_name() const { + return "Label3D"; +} + +int Label3DGizmoPlugin::get_priority() const { + return -1; +} + +bool Label3DGizmoPlugin::can_be_hidden() const { + return false; +} + +void Label3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Label3D *label = Object::cast_to<Label3D>(p_gizmo->get_spatial_node()); + + p_gizmo->clear(); + + Ref<TriangleMesh> tm = label->generate_triangle_mesh(); + if (tm.is_valid()) { + p_gizmo->add_collision_triangles(tm); + } +} + +/// + Position3DGizmoPlugin::Position3DGizmoPlugin() { pos3d_mesh = Ref<ArrayMesh>(memnew(ArrayMesh)); cursor_points = Vector<Vector3>(); @@ -2047,175 +2369,10 @@ void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(cursor_points); } -///// - -Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); - create_material("skeleton_material", gizmo_color); -} - -bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; -} - -String Skeleton3DGizmoPlugin::get_gizmo_name() const { - return "Skeleton3D"; -} - -int Skeleton3DGizmoPlugin::get_priority() const { - return -1; -} - -void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); - - p_gizmo->clear(); - - Ref<Material> material = get_material("skeleton_material", p_gizmo); - - Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); - - surface_tool->begin(Mesh::PRIMITIVE_LINES); - surface_tool->set_material(material); - LocalVector<Transform3D> grests; - grests.resize(skel->get_bone_count()); - - LocalVector<int> bones; - LocalVector<float> weights; - bones.resize(4); - weights.resize(4); - - for (int i = 0; i < 4; i++) { - bones[i] = 0; - weights[i] = 0; - } - - weights[0] = 1; - - AABB aabb; - - Color bonecolor = Color(1.0, 0.4, 0.4, 0.3); - Color rootcolor = Color(0.4, 1.0, 0.4, 0.1); - - //LocalVector<int> bones_to_process = skel->get_parentless_bones(); - LocalVector<int> bones_to_process; - bones_to_process = skel->get_parentless_bones(); - - while (bones_to_process.size() > 0) { - int current_bone_idx = bones_to_process[0]; - bones_to_process.erase(current_bone_idx); - - LocalVector<int> child_bones_vector; - child_bones_vector = skel->get_bone_children(current_bone_idx); - int child_bones_size = child_bones_vector.size(); - - // You have children but no parent, then you must be a root/parentless bone. - if (child_bones_size >= 0 && skel->get_bone_parent(current_bone_idx) <= 0) { - grests[current_bone_idx] = skel->global_pose_to_local_pose(current_bone_idx, skel->get_bone_global_pose(current_bone_idx)); - } - - for (int i = 0; i < child_bones_size; i++) { - int child_bone_idx = child_bones_vector[i]; - - grests[child_bone_idx] = skel->global_pose_to_local_pose(child_bone_idx, skel->get_bone_global_pose(child_bone_idx)); - Vector3 v0 = grests[current_bone_idx].origin; - Vector3 v1 = grests[child_bone_idx].origin; - Vector3 d = skel->get_bone_rest(child_bone_idx).origin.normalized(); - real_t dist = skel->get_bone_rest(child_bone_idx).origin.length(); - - // Find closest axis. - int closest = -1; - real_t closest_d = 0.0; - for (int j = 0; j < 3; j++) { - real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); - if (j == 0 || dp > closest_d) { - closest = j; - } - } - - // Find closest other. - Vector3 first; - Vector3 points[4]; - int point_idx = 0; - for (int j = 0; j < 3; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 - grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 + grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - - if (j == closest) { - continue; - } - - Vector3 axis; - if (first == Vector3()) { - axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); - first = axis; - } else { - axis = d.cross(first).normalized(); - } - - for (int k = 0; k < 2; k++) { - if (k == 1) { - axis = -axis; - } - Vector3 point = v0 + d * dist * 0.2; - point += axis * dist * 0.1; - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v0); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - bones[0] = child_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v1); - points[point_idx++] = point; - } - } - SWAP(points[1], points[2]); - for (int j = 0; j < 4; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[j]); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[(j + 1) % 4]); - } - - // Add the bone's children to the list of bones to be processed. - bones_to_process.push_back(child_bones_vector[i]); - } - } - - Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>())); -} - //// PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() { - create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1))); + create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint")); } bool PhysicalBone3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { @@ -2348,7 +2505,7 @@ void PhysicalBone3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ///// RayCast3DGizmoPlugin::RayCast3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -2385,15 +2542,53 @@ 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()); p_gizmo->clear(); - Vector<Vector3> lines; - - lines.push_back(Vector3()); - lines.push_back(Vector3(0, 0, 1.0) * spring_arm->get_length()); + Vector<Vector3> lines = { + Vector3(), + Vector3(0, 0, 1.0) * spring_arm->get_length() + }; Ref<StandardMaterial3D> material = get_material("shape_material", p_gizmo); @@ -2402,7 +2597,7 @@ void SpringArm3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } SpringArm3DGizmoPlugin::SpringArm3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); } @@ -2421,7 +2616,7 @@ int SpringArm3DGizmoPlugin::get_priority() const { ///// VehicleWheel3DGizmoPlugin::VehicleWheel3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); } @@ -2491,30 +2686,30 @@ void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { /////////// -SoftBody3DGizmoPlugin::SoftBody3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); +SoftDynamicBody3DGizmoPlugin::SoftDynamicBody3DGizmoPlugin() { + Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); create_handle_material("handles"); } -bool SoftBody3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<SoftBody3D>(p_spatial) != nullptr; +bool SoftDynamicBody3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<SoftDynamicBody3D>(p_spatial) != nullptr; } -String SoftBody3DGizmoPlugin::get_gizmo_name() const { - return "SoftBody3D"; +String SoftDynamicBody3DGizmoPlugin::get_gizmo_name() const { + return "SoftDynamicBody3D"; } -int SoftBody3DGizmoPlugin::get_priority() const { +int SoftDynamicBody3DGizmoPlugin::get_priority() const { return -1; } -bool SoftBody3DGizmoPlugin::is_selectable_when_hidden() const { +bool SoftDynamicBody3DGizmoPlugin::is_selectable_when_hidden() const { return true; } -void SoftBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); +void SoftDynamicBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); p_gizmo->clear(); @@ -2550,22 +2745,22 @@ void SoftBody3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_triangles(tm); } -String SoftBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return "SoftBody3D pin point"; +String SoftDynamicBody3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return "SoftDynamicBody3D pin point"; } -Variant SoftBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); +Variant SoftDynamicBody3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); return Variant(soft_body->is_point_pinned(p_id)); } -void SoftBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); +void SoftDynamicBody3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); soft_body->pin_point_toggle(p_id); } -bool SoftBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const { - SoftBody3D *soft_body = Object::cast_to<SoftBody3D>(p_gizmo->get_spatial_node()); +bool SoftDynamicBody3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); return soft_body->is_point_pinned(p_id); } @@ -2591,7 +2786,7 @@ int VisibleOnScreenNotifier3DGizmoPlugin::get_priority() const { return -1; } -String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: return "Size X"; @@ -2610,12 +2805,12 @@ String VisibleOnScreenNotifier3DGizmoPlugin::get_handle_name(const EditorNode3DG return ""; } -Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant VisibleOnScreenNotifier3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); return notifier->get_aabb(); } -void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); Transform3D gt = notifier->get_global_transform(); @@ -2631,7 +2826,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; - Vector3 ofs = aabb.position + aabb.size * 0.5; + Vector3 ofs = aabb.get_center(); Vector3 axis; axis[p_id] = 1.0; @@ -2667,7 +2862,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p } } -void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void VisibleOnScreenNotifier3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { VisibleOnScreenNotifier3D *notifier = Object::cast_to<VisibleOnScreenNotifier3D>(p_gizmo->get_spatial_node()); if (p_cancel) { @@ -2707,7 +2902,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { handles.push_back(ax); } - Vector3 center = aabb.position + aabb.size * 0.5; + Vector3 center = aabb.get_center(); for (int i = 0; i < 3; i++) { Vector3 ax; ax[i] = 1.0; @@ -2723,7 +2918,7 @@ void VisibleOnScreenNotifier3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (p_gizmo->is_selected()) { Ref<Material> solid_material = get_material("visibility_notifier_solid_material", p_gizmo); - p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0); + p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_center()); } p_gizmo->add_handles(handles, get_material("handles")); @@ -2761,7 +2956,7 @@ void CPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { GPUParticles3DGizmoPlugin::GPUParticles3DGizmoPlugin() { Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/particles", Color(0.8, 0.7, 0.4)); create_material("particles_material", gizmo_color); - gizmo_color.a = 0.1; + gizmo_color.a = MAX((gizmo_color.a - 0.2) * 0.02, 0.0); create_material("particles_solid_material", gizmo_color); create_icon_material("particles_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoGPUParticles3D"), SNAME("EditorIcons"))); create_handle_material("handles"); @@ -2783,7 +2978,7 @@ bool GPUParticles3DGizmoPlugin::is_selectable_when_hidden() const { return true; } -String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: return "Size X"; @@ -2802,12 +2997,12 @@ String GPUParticles3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_giz return ""; } -Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant GPUParticles3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); return particles->get_visibility_aabb(); } -void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); Transform3D gt = particles->get_global_transform(); @@ -2822,7 +3017,7 @@ void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; - Vector3 ofs = aabb.position + aabb.size * 0.5; + Vector3 ofs = aabb.get_center(); Vector3 axis; axis[p_id] = 1.0; @@ -2858,7 +3053,7 @@ void GPUParticles3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int } } -void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void GPUParticles3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { GPUParticles3D *particles = Object::cast_to<GPUParticles3D>(p_gizmo->get_spatial_node()); if (p_cancel) { @@ -2898,7 +3093,7 @@ void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { handles.push_back(ax); } - Vector3 center = aabb.position + aabb.size * 0.5; + Vector3 center = aabb.get_center(); for (int i = 0; i < 3; i++) { Vector3 ax; ax[i] = 1.0; @@ -2914,7 +3109,7 @@ void GPUParticles3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (p_gizmo->is_selected()) { Ref<Material> solid_material = get_material("particles_solid_material", p_gizmo); - p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_position() + aabb.get_size() / 2.0); + p_gizmo->add_solid_box(solid_material, aabb.get_size(), aabb.get_center()); } p_gizmo->add_handles(handles, get_material("handles")); @@ -2924,10 +3119,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"); } @@ -2944,35 +3144,35 @@ int GPUParticlesCollision3DGizmoPlugin::get_priority() const { return -1; } -String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { const Node3D *cs = p_gizmo->get_spatial_node(); - if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) { + if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { return "Radius"; } - if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) { + if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) { return "Extents"; } return ""; } -Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { const Node3D *cs = p_gizmo->get_spatial_node(); - if (Object::cast_to<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) { + if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { return p_gizmo->get_spatial_node()->call("get_radius"); } - if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) { + if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) { return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); } return Variant(); } -void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { Node3D *sn = p_gizmo->get_spatial_node(); Transform3D gt = sn->get_global_transform(); @@ -2983,7 +3183,7 @@ void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_g Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; - if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) { + if (Object::cast_to<GPUParticlesCollisionSphere3D>(sn) || Object::cast_to<GPUParticlesAttractorSphere3D>(sn)) { Vector3 ra, rb; Geometry3D::get_closest_points_between_segments(Vector3(), Vector3(4096, 0, 0), sg[0], sg[1], ra, rb); float d = ra.x; @@ -2998,7 +3198,7 @@ void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_g sn->call("set_radius", d); } - if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) { + if (Object::cast_to<GPUParticlesCollisionBox3D>(sn) || Object::cast_to<GPUParticlesAttractorBox3D>(sn) || Object::cast_to<GPUParticlesAttractorVectorField3D>(sn) || Object::cast_to<GPUParticlesCollisionSDF3D>(sn) || Object::cast_to<GPUParticlesCollisionHeightField3D>(sn)) { Vector3 axis; axis[p_id] = 1.0; Vector3 ra, rb; @@ -3018,10 +3218,10 @@ void GPUParticlesCollision3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_g } } -void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { Node3D *sn = p_gizmo->get_spatial_node(); - if (Object::cast_to<GPUParticlesCollisionSphere>(sn) || Object::cast_to<GPUParticlesAttractorSphere>(sn)) { + if (Object::cast_to<GPUParticlesCollisionSphere3D>(sn) || Object::cast_to<GPUParticlesAttractorSphere3D>(sn)) { if (p_cancel) { sn->call("set_radius", p_restore); return; @@ -3034,7 +3234,7 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo * ur->commit_action(); } - if (Object::cast_to<GPUParticlesCollisionBox>(sn) || Object::cast_to<GPUParticlesAttractorBox>(sn) || Object::cast_to<GPUParticlesAttractorVectorField>(sn) || Object::cast_to<GPUParticlesCollisionSDF>(sn) || Object::cast_to<GPUParticlesCollisionHeightField>(sn)) { + if (Object::cast_to<GPUParticlesCollisionBox3D>(sn) || Object::cast_to<GPUParticlesAttractorBox3D>(sn) || Object::cast_to<GPUParticlesAttractorVectorField3D>(sn) || Object::cast_to<GPUParticlesCollisionSDF3D>(sn) || Object::cast_to<GPUParticlesCollisionHeightField3D>(sn)) { if (p_cancel) { sn->call("set_extents", p_restore); return; @@ -3051,17 +3251,21 @@ void GPUParticlesCollision3DGizmoPlugin::commit_handle(const EditorNode3DGizmo * void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Node3D *cs = p_gizmo->get_spatial_node(); - print_line("redraw request " + itos(cs != nullptr)); 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<GPUParticlesCollisionSphere>(cs) || Object::cast_to<GPUParticlesAttractorSphere>(cs)) { + if (Object::cast_to<GPUParticlesCollisionSphere3D>(cs) || Object::cast_to<GPUParticlesAttractorSphere3D>(cs)) { float r = cs->call("get_radius"); Vector<Vector3> points; @@ -3103,7 +3307,7 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_handles(handles, handles_material); } - if (Object::cast_to<GPUParticlesCollisionBox>(cs) || Object::cast_to<GPUParticlesAttractorBox>(cs) || Object::cast_to<GPUParticlesAttractorVectorField>(cs) || Object::cast_to<GPUParticlesCollisionSDF>(cs) || Object::cast_to<GPUParticlesCollisionHeightField>(cs)) { + if (Object::cast_to<GPUParticlesCollisionBox3D>(cs) || Object::cast_to<GPUParticlesAttractorBox3D>(cs) || Object::cast_to<GPUParticlesAttractorVectorField3D>(cs) || Object::cast_to<GPUParticlesCollisionSDF3D>(cs) || Object::cast_to<GPUParticlesCollisionHeightField3D>(cs)) { Vector<Vector3> lines; AABB aabb; aabb.position = -cs->call("get_extents").operator Vector3(); @@ -3128,9 +3332,9 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(lines); p_gizmo->add_handles(handles, handles_material); - GPUParticlesCollisionSDF *col_sdf = Object::cast_to<GPUParticlesCollisionSDF>(cs); + GPUParticlesCollisionSDF3D *col_sdf = Object::cast_to<GPUParticlesCollisionSDF3D>(cs); if (col_sdf) { - static const int subdivs[GPUParticlesCollisionSDF::RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 }; + static const int subdivs[GPUParticlesCollisionSDF3D::RESOLUTION_MAX] = { 16, 32, 64, 128, 256, 512 }; int subdiv = subdivs[col_sdf->get_resolution()]; float cell_size = aabb.get_longest_axis_size() / subdiv; @@ -3142,13 +3346,8 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { continue; } - Vector2 dir; - dir[j] = 1.0; - Vector2 ta, tb; int j_n1 = (j + 1) % 3; int j_n2 = (j + 2) % 3; - ta[j_n1] = 1.0; - tb[j_n2] = 1.0; for (int k = 0; k < 4; k++) { Vector3 from = aabb.position, to = aabb.position; @@ -3208,7 +3407,7 @@ int ReflectionProbeGizmoPlugin::get_priority() const { return -1; } -String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: return "Extents X"; @@ -3227,12 +3426,12 @@ String ReflectionProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gi return ""; } -Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant ReflectionProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); return AABB(probe->get_extents(), probe->get_origin_offset()); } -void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); Transform3D gt = probe->get_global_transform(); @@ -3289,7 +3488,7 @@ void ReflectionProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, in } } -void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void ReflectionProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { ReflectionProbe *probe = Object::cast_to<ReflectionProbe>(p_gizmo->get_spatial_node()); AABB restore = p_restore; @@ -3393,7 +3592,7 @@ int DecalGizmoPlugin::get_priority() const { return -1; } -String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: return "Extents X"; @@ -3406,12 +3605,12 @@ String DecalGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p return ""; } -Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant DecalGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); return decal->get_extents(); } -void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); Transform3D gt = decal->get_global_transform(); @@ -3442,7 +3641,7 @@ void DecalGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Ca decal->set_extents(extents); } -void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void DecalGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { Decal *decal = Object::cast_to<Decal>(p_gizmo->get_spatial_node()); Vector3 restore = p_restore; @@ -3533,7 +3732,7 @@ int VoxelGIGizmoPlugin::get_priority() const { return -1; } -String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { switch (p_id) { case 0: return "Extents X"; @@ -3546,12 +3745,12 @@ String VoxelGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int return ""; } -Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant VoxelGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); return probe->get_extents(); } -void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); Transform3D gt = probe->get_global_transform(); @@ -3582,7 +3781,7 @@ void VoxelGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, probe->set_extents(extents); } -void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void VoxelGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { VoxelGI *probe = Object::cast_to<VoxelGI>(p_gizmo->get_spatial_node()); Vector3 restore = p_restore; @@ -3634,13 +3833,8 @@ void VoxelGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { continue; } - Vector2 dir; - dir[j] = 1.0; - Vector2 ta, tb; int j_n1 = (j + 1) % 3; int j_n2 = (j + 2) % 3; - ta[j_n1] = 1.0; - tb[j_n2] = 1.0; for (int k = 0; k < 4; k++) { Vector3 from = aabb.position, to = aabb.position; @@ -3702,20 +3896,6 @@ LightmapGIGizmoPlugin::LightmapGIGizmoPlugin() { create_icon_material("baked_indirect_light_icon", Node3DEditor::get_singleton()->get_theme_icon(SNAME("GizmoLightmapGI"), SNAME("EditorIcons"))); } -String LightmapGIGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return ""; -} - -Variant LightmapGIGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return Variant(); -} - -void LightmapGIGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { -} - -void LightmapGIGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { -} - bool LightmapGIGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<LightmapGI>(p_spatial) != nullptr; } @@ -3745,7 +3925,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) { @@ -3824,15 +4004,15 @@ void LightmapGIGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { const float c4 = 0.886227; const float c5 = 0.247708; Vector3 light = (c1 * sh_col[8] * (n.x * n.x - n.y * n.y) + - c3 * sh_col[6] * n.z * n.z + - c4 * sh_col[0] - - c5 * sh_col[6] + - 2.0 * c1 * sh_col[4] * n.x * n.y + - 2.0 * c1 * sh_col[7] * n.x * n.z + - 2.0 * c1 * sh_col[5] * n.y * n.z + - 2.0 * c2 * sh_col[3] * n.x + - 2.0 * c2 * sh_col[1] * n.y + - 2.0 * c2 * sh_col[2] * n.z); + c3 * sh_col[6] * n.z * n.z + + c4 * sh_col[0] - + c5 * sh_col[6] + + 2.0 * c1 * sh_col[4] * n.x * n.y + + 2.0 * c1 * sh_col[7] * n.x * n.z + + 2.0 * c1 * sh_col[5] * n.y * n.z + + 2.0 * c2 * sh_col[3] * n.x + + 2.0 * c2 * sh_col[1] * n.y + + 2.0 * c2 * sh_col[2] * n.z); colors.push_back(Color(light.x, light.y, light.z, 1)); } @@ -3884,20 +4064,6 @@ LightmapProbeGizmoPlugin::LightmapProbeGizmoPlugin() { create_material("lightprobe_lines", gizmo_color); } -String LightmapProbeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return ""; -} - -Variant LightmapProbeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { - return Variant(); -} - -void LightmapProbeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { -} - -void LightmapProbeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { -} - bool LightmapProbeGizmoPlugin::has_gizmo(Node3D *p_spatial) { return Object::cast_to<LightmapProbe>(p_spatial) != nullptr; } @@ -3974,7 +4140,7 @@ void LightmapProbeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { //// CollisionObject3DGizmoPlugin::CollisionObject3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -4024,7 +4190,7 @@ void CollisionObject3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { //// CollisionShape3DGizmoPlugin::CollisionShape3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -4044,7 +4210,7 @@ int CollisionShape3DGizmoPlugin::get_priority() const { return -1; } -String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const { +String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { const CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); Ref<Shape3D> s = cs->get_shape(); @@ -4075,7 +4241,7 @@ String CollisionShape3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_g return ""; } -Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { +Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); Ref<Shape3D> s = cs->get_shape(); @@ -4111,7 +4277,7 @@ Variant CollisionShape3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p return Variant(); } -void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { +void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); Ref<Shape3D> s = cs->get_shape(); @@ -4225,7 +4391,7 @@ void CollisionShape3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, i } } -void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel) { +void CollisionShape3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { CollisionShape3D *cs = Object::cast_to<CollisionShape3D>(p_gizmo->get_spatial_node()); Ref<Shape3D> s = cs->get_shape(); @@ -4477,9 +4643,10 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(collision_segments); - Vector<Vector3> handles; - handles.push_back(Vector3(cs2->get_radius(), 0, 0)); - handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0)); + Vector<Vector3> handles = { + Vector3(cs2->get_radius(), 0, 0), + Vector3(0, cs2->get_height() * 0.5, 0) + }; p_gizmo->add_handles(handles, handles_material); } @@ -4533,16 +4700,16 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(collision_segments); - Vector<Vector3> handles; - handles.push_back(Vector3(cs2->get_radius(), 0, 0)); - handles.push_back(Vector3(0, cs2->get_height() * 0.5, 0)); + Vector<Vector3> handles = { + Vector3(cs2->get_radius(), 0, 0), + Vector3(0, cs2->get_height() * 0.5, 0) + }; p_gizmo->add_handles(handles, handles_material); } - if (Object::cast_to<WorldMarginShape3D>(*s)) { - Ref<WorldMarginShape3D> ps = s; - Plane p = ps->get_plane(); - Vector<Vector3> points; + if (Object::cast_to<WorldBoundaryShape3D>(*s)) { + Ref<WorldBoundaryShape3D> wbs = s; + const Plane &p = wbs->get_plane(); Vector3 n1 = p.get_any_perpendicular_normal(); Vector3 n2 = p.normal.cross(n1).normalized(); @@ -4554,16 +4721,18 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p.normal * p.d + n1 * -10.0 + n2 * 10.0, }; - points.push_back(pface[0]); - points.push_back(pface[1]); - points.push_back(pface[1]); - points.push_back(pface[2]); - points.push_back(pface[2]); - points.push_back(pface[3]); - points.push_back(pface[3]); - points.push_back(pface[0]); - points.push_back(p.normal * p.d); - points.push_back(p.normal * p.d + p.normal * 3); + Vector<Vector3> points = { + pface[0], + pface[1], + pface[1], + pface[2], + pface[2], + pface[3], + pface[3], + pface[0], + p.normal * p.d, + p.normal * p.d + p.normal * 3 + }; p_gizmo->add_lines(points, material); p_gizmo->add_collision_segments(points); @@ -4600,9 +4769,10 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (Object::cast_to<SeparationRayShape3D>(*s)) { Ref<SeparationRayShape3D> rs = s; - Vector<Vector3> points; - points.push_back(Vector3()); - points.push_back(Vector3(0, 0, rs->get_length())); + Vector<Vector3> points = { + Vector3(), + Vector3(0, 0, rs->get_length()) + }; p_gizmo->add_lines(points, material); p_gizmo->add_collision_segments(points); Vector<Vector3> handles; @@ -4621,7 +4791,7 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ///// CollisionPolygon3DGizmoPlugin::CollisionPolygon3DGizmoPlugin() { - const Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); + const Color gizmo_color = EDITOR_GET("editors/3d_gizmos/gizmo_colors/shape"); create_material("shape_material", gizmo_color); const float gizmo_value = gizmo_color.get_v(); const Color gizmo_color_disabled = Color(gizmo_value, gizmo_value, gizmo_value, 0.65); @@ -4669,10 +4839,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) { @@ -4688,24 +4858,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; @@ -4721,7 +4886,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); @@ -4739,10 +4904,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; @@ -4752,28 +4917,84 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } Vector<Vector3> lines; - for (Map<_EdgeKey, bool>::Element *E = edge_map.front(); E; E = E->next()) { - if (E->get()) { - lines.push_back(E->key().from); - lines.push_back(E->key().to); + for (const KeyValue<_EdgeKey, bool> &E : edge_map) { + if (E.value) { + lines.push_back(E.key.from); + lines.push_back(E.key.to); } } 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)); + } } ////// @@ -4798,7 +5019,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; @@ -4823,7 +5044,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; @@ -4842,7 +5063,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; @@ -4854,7 +5075,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; @@ -4873,7 +5094,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; @@ -4885,7 +5106,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; @@ -4904,7 +5125,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; @@ -4914,8 +5135,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) { @@ -4957,20 +5178,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); } } @@ -4986,17 +5207,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); @@ -5010,15 +5231,15 @@ 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); } } //// Joint3DGizmoPlugin::Joint3DGizmoPlugin() { - create_material("joint_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint", Color(0.5, 0.8, 1))); + create_material("joint_material", EDITOR_GET("editors/3d_gizmos/gizmo_colors/joint")); create_material("joint_body_a_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_a", Color(0.6, 0.8, 1))); create_material("joint_body_b_material", EDITOR_DEF("editors/3d_gizmos/gizmo_colors/joint_body_b", Color(0.6, 0.9, 1))); @@ -5195,17 +5416,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; @@ -5238,34 +5459,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) { @@ -5388,13 +5609,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) { @@ -5455,3 +5676,119 @@ void Joint3DGizmoPlugin::CreateGeneric6DOFJointGizmo( #undef ADD_VTX } + +//// + +FogVolumeGizmoPlugin::FogVolumeGizmoPlugin() { + Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/fog_volume", Color(0.5, 0.7, 1)); + create_material("shape_material", gizmo_color); + gizmo_color.a = 0.15; + create_material("shape_material_internal", gizmo_color); + + create_handle_material("handles"); +} + +bool FogVolumeGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return (Object::cast_to<FogVolume>(p_spatial) != nullptr); +} + +String FogVolumeGizmoPlugin::get_gizmo_name() const { + return "FogVolume"; +} + +int FogVolumeGizmoPlugin::get_priority() const { + return -1; +} + +String FogVolumeGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return "Extents"; +} + +Variant FogVolumeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); +} + +void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + Node3D *sn = p_gizmo->get_spatial_node(); + + Transform3D gt = sn->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 sg[2] = { gi.xform(ray_from), gi.xform(ray_from + ray_dir * 4096) }; + + Vector3 axis; + axis[p_id] = 1.0; + Vector3 ra, rb; + Geometry3D::get_closest_points_between_segments(Vector3(), axis * 4096, sg[0], sg[1], ra, rb); + float d = ra[p_id]; + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); + } + + if (d < 0.001) { + d = 0.001; + } + + Vector3 he = sn->call("get_extents"); + he[p_id] = d; + sn->call("set_extents", he); +} + +void FogVolumeGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + Node3D *sn = p_gizmo->get_spatial_node(); + + if (p_cancel) { + sn->call("set_extents", p_restore); + return; + } + + UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Change Fog Volume Extents")); + ur->add_do_method(sn, "set_extents", sn->call("get_extents")); + ur->add_undo_method(sn, "set_extents", p_restore); + ur->commit_action(); +} + +void FogVolumeGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Node3D *cs = p_gizmo->get_spatial_node(); + + p_gizmo->clear(); + + if (RS::FogVolumeShape(int(p_gizmo->get_spatial_node()->call("get_shape"))) != RS::FOG_VOLUME_SHAPE_WORLD) { + const Ref<Material> material = + get_material("shape_material", p_gizmo); + const Ref<Material> material_internal = + get_material("shape_material_internal", p_gizmo); + + Ref<Material> handles_material = get_material("handles"); + + Vector<Vector3> lines; + AABB aabb; + aabb.position = -cs->call("get_extents").operator Vector3(); + aabb.size = aabb.position * -2; + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + lines.push_back(a); + lines.push_back(b); + } + + Vector<Vector3> handles; + + for (int i = 0; i < 3; i++) { + Vector3 ax; + ax[i] = cs->call("get_extents").operator Vector3()[i]; + handles.push_back(ax); + } + + p_gizmo->add_lines(lines, material); + p_gizmo->add_collision_segments(lines); + p_gizmo->add_handles(handles, handles_material); + } +} + +///// diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 415ed5da5c..739bf1b929 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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" @@ -45,7 +45,7 @@ class EditorNode3DGizmo : public Node3DGizmo { struct Instance { RID instance; - Ref<ArrayMesh> mesh; + Ref<Mesh> mesh; Ref<Material> material; Ref<SkinReference> skin_reference; bool extra_margin = false; @@ -70,22 +70,21 @@ class EditorNode3DGizmo : public Node3DGizmo { bool valid; bool hidden; Vector<Instance> instances; - Node3D *spatial_node; + Node3D *spatial_node = nullptr; void _set_spatial_node(Node *p_node) { set_spatial_node(Object::cast_to<Node3D>(p_node)); } protected: static void _bind_methods(); - EditorNode3DGizmoPlugin *gizmo_plugin; + EditorNode3DGizmoPlugin *gizmo_plugin = nullptr; GDVIRTUAL0(_redraw) - GDVIRTUAL1RC(String, _get_handle_name, int) - GDVIRTUAL1RC(bool, _is_handle_highlighted, int) - - GDVIRTUAL1RC(Variant, _get_handle_value, int) - GDVIRTUAL3(_set_handle, int, const Camera3D *, Vector2) - GDVIRTUAL3(_commit_handle, int, Variant, bool) + GDVIRTUAL2RC(String, _get_handle_name, int, bool) + GDVIRTUAL2RC(bool, _is_handle_highlighted, int, bool) + GDVIRTUAL2RC(Variant, _get_handle_value, int, bool) + GDVIRTUAL4(_set_handle, int, bool, const Camera3D *, Vector2) + GDVIRTUAL4(_commit_handle, int, bool, Variant, bool) GDVIRTUAL2RC(int, _subgizmos_intersect_ray, const Camera3D *, Vector2) GDVIRTUAL2RC(Vector<int>, _subgizmos_intersect_frustum, const Camera3D *, TypedArray<Plane>) @@ -95,18 +94,18 @@ protected: public: void add_lines(const Vector<Vector3> &p_lines, const Ref<Material> &p_material, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1)); void add_vertices(const Vector<Vector3> &p_vertices, const Ref<Material> &p_material, Mesh::PrimitiveType p_primitive_type, bool p_billboard = false, const Color &p_modulate = Color(1, 1, 1)); - void add_mesh(const Ref<ArrayMesh> &p_mesh, const Ref<Material> &p_material = Ref<Material>(), const Transform3D &p_xform = Transform3D(), const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>()); + void add_mesh(const Ref<Mesh> &p_mesh, const Ref<Material> &p_material = Ref<Material>(), const Transform3D &p_xform = Transform3D(), const Ref<SkinReference> &p_skin_reference = Ref<SkinReference>()); void add_collision_segments(const Vector<Vector3> &p_lines); void add_collision_triangles(const Ref<TriangleMesh> &p_tmesh); void add_unscaled_billboard(const Ref<Material> &p_material, real_t p_scale = 1, const Color &p_modulate = Color(1, 1, 1)); void add_handles(const Vector<Vector3> &p_handles, const Ref<Material> &p_material, const Vector<int> &p_ids = Vector<int>(), bool p_billboard = false, bool p_secondary = false); - void add_solid_box(Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3(), const Transform3D &p_xform = Transform3D()); + void add_solid_box(const Ref<Material> &p_material, Vector3 p_size, Vector3 p_position = Vector3(), const Transform3D &p_xform = Transform3D()); - virtual bool is_handle_highlighted(int p_id) const; - virtual String get_handle_name(int p_id) const; - virtual Variant get_handle_value(int p_id) const; - virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point); - virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false); + virtual bool is_handle_highlighted(int p_id, bool p_secondary) const; + virtual String get_handle_name(int p_id, bool p_secondary) const; + virtual Variant get_handle_value(int p_id, bool p_secondary) const; + virtual void set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point); + virtual void commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false); virtual int subgizmos_intersect_ray(Camera3D *p_camera, const Vector2 &p_point) const; virtual Vector<int> subgizmos_intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum) const; @@ -121,7 +120,7 @@ public: Node3D *get_spatial_node() const { return spatial_node; } Ref<EditorNode3DGizmoPlugin> get_plugin() const { return gizmo_plugin; } bool intersect_frustum(const Camera3D *p_camera, const Vector<Plane> &p_frustum); - void handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id); + void handles_intersect_ray(Camera3D *p_camera, const Vector2 &p_point, bool p_shift_pressed, int &r_id, bool &r_secondary); bool intersect_ray(Camera3D *p_camera, const Point2 &p_point, Vector3 &r_pos, Vector3 &r_normal); bool is_subgizmo_selected(int p_id) const; Vector<int> get_subgizmo_selection() const; @@ -167,12 +166,12 @@ protected: GDVIRTUAL0RC(bool, _is_selectable_when_hidden) GDVIRTUAL1(_redraw, Ref<EditorNode3DGizmo>) - GDVIRTUAL2RC(String, _get_handle_name, Ref<EditorNode3DGizmo>, int) - GDVIRTUAL2RC(bool, _is_handle_highlighted, Ref<EditorNode3DGizmo>, int) - GDVIRTUAL2RC(Variant, _get_handle_value, Ref<EditorNode3DGizmo>, int) + GDVIRTUAL3RC(String, _get_handle_name, Ref<EditorNode3DGizmo>, int, bool) + GDVIRTUAL3RC(bool, _is_handle_highlighted, Ref<EditorNode3DGizmo>, int, bool) + GDVIRTUAL3RC(Variant, _get_handle_value, Ref<EditorNode3DGizmo>, int, bool) - GDVIRTUAL4(_set_handle, Ref<EditorNode3DGizmo>, int, const Camera3D *, Vector2) - GDVIRTUAL4(_commit_handle, Ref<EditorNode3DGizmo>, int, Variant, bool) + GDVIRTUAL5(_set_handle, Ref<EditorNode3DGizmo>, int, bool, const Camera3D *, Vector2) + GDVIRTUAL5(_commit_handle, Ref<EditorNode3DGizmo>, int, bool, Variant, bool) GDVIRTUAL3RC(int, _subgizmos_intersect_ray, Ref<EditorNode3DGizmo>, const Camera3D *, Vector2) GDVIRTUAL3RC(Vector<int>, _subgizmos_intersect_frustum, Ref<EditorNode3DGizmo>, const Camera3D *, TypedArray<Plane>) @@ -194,11 +193,11 @@ public: virtual bool is_selectable_when_hidden() const; virtual void redraw(EditorNode3DGizmo *p_gizmo); - virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const; - virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const; - virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const; - virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point); - virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false); + virtual bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const; + virtual String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const; + virtual Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const; + virtual void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point); + virtual void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false); virtual int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const; virtual Vector<int> subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const; @@ -223,10 +222,10 @@ public: String get_gizmo_name() const override; int get_priority() const override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; void redraw(EditorNode3DGizmo *p_gizmo) override; Light3DGizmoPlugin(); @@ -240,15 +239,28 @@ public: String get_gizmo_name() const override; int get_priority() const override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; void redraw(EditorNode3DGizmo *p_gizmo) override; AudioStreamPlayer3DGizmoPlugin(); }; +class AudioListener3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(AudioListener3DGizmoPlugin, 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; + + AudioListener3DGizmoPlugin(); +}; + class Camera3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Camera3DGizmoPlugin, EditorNode3DGizmoPlugin); @@ -257,10 +269,10 @@ public: String get_gizmo_name() const override; int get_priority() const override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; void redraw(EditorNode3DGizmo *p_gizmo) override; Camera3DGizmoPlugin(); @@ -288,6 +300,11 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + OccluderInstance3DGizmoPlugin(); }; @@ -304,23 +321,24 @@ public: Sprite3DGizmoPlugin(); }; -class Position3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Position3DGizmoPlugin, EditorNode3DGizmoPlugin); - - Ref<ArrayMesh> pos3d_mesh; - Vector<Vector3> cursor_points; +class Label3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Label3DGizmoPlugin, EditorNode3DGizmoPlugin); public: bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; int get_priority() const override; + bool can_be_hidden() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - Position3DGizmoPlugin(); + Label3DGizmoPlugin(); }; -class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); +class Position3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Position3DGizmoPlugin, EditorNode3DGizmoPlugin); + + Ref<ArrayMesh> pos3d_mesh; + Vector<Vector3> cursor_points; public: bool has_gizmo(Node3D *p_spatial) override; @@ -328,7 +346,7 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - Skeleton3DGizmoPlugin(); + Position3DGizmoPlugin(); }; class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin { @@ -355,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); @@ -379,8 +409,8 @@ public: VehicleWheel3DGizmoPlugin(); }; -class SoftBody3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(SoftBody3DGizmoPlugin, EditorNode3DGizmoPlugin); +class SoftDynamicBody3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(SoftDynamicBody3DGizmoPlugin, EditorNode3DGizmoPlugin); public: bool has_gizmo(Node3D *p_spatial) override; @@ -389,12 +419,12 @@ public: bool is_selectable_when_hidden() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; - bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id) const override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + bool is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; - SoftBody3DGizmoPlugin(); + SoftDynamicBody3DGizmoPlugin(); }; class VisibleOnScreenNotifier3DGizmoPlugin : public EditorNode3DGizmoPlugin { @@ -406,10 +436,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; VisibleOnScreenNotifier3DGizmoPlugin(); }; @@ -436,10 +466,10 @@ public: bool is_selectable_when_hidden() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; GPUParticles3DGizmoPlugin(); }; @@ -453,10 +483,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; GPUParticlesCollision3DGizmoPlugin(); }; @@ -470,10 +500,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; ReflectionProbeGizmoPlugin(); }; @@ -487,10 +517,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; DecalGizmoPlugin(); }; @@ -504,10 +534,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; VoxelGIGizmoPlugin(); }; @@ -521,11 +551,6 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; - LightmapGIGizmoPlugin(); }; @@ -538,11 +563,6 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; - LightmapProbeGizmoPlugin(); }; @@ -567,10 +587,10 @@ public: int get_priority() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; - String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const override; - void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) override; - void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, const Variant &p_restore, bool p_cancel = false) override; + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; CollisionShape3DGizmoPlugin(); }; @@ -593,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: @@ -623,7 +649,7 @@ public: class Joint3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(Joint3DGizmoPlugin, EditorNode3DGizmoPlugin); - Timer *update_timer; + Timer *update_timer = nullptr; uint64_t update_idx = 0; void incremental_update_gizmos(); @@ -668,4 +694,21 @@ public: Joint3DGizmoPlugin(); }; +class FogVolumeGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(FogVolumeGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + + FogVolumeGizmoPlugin(); +}; + #endif // NODE_3D_EDITOR_GIZMOS_H diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 291cafab2b..6afc6798d0 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,8 +32,9 @@ #include "core/config/project_settings.h" #include "core/input/input.h" -#include "core/math/camera_matrix.h" +#include "core/input/input_map.h" #include "core/math/math_funcs.h" +#include "core/math/projection.h" #include "core/os/keyboard.h" #include "core/templates/sort_array.h" #include "editor/debugger/editor_debugger_node.h" @@ -42,6 +43,7 @@ #include "editor/plugins/animation_player_editor_plugin.h" #include "editor/plugins/node_3d_editor_gizmos.h" #include "editor/plugins/script_editor_plugin.h" +#include "editor/scene_tree_dock.h" #include "scene/3d/camera_3d.h" #include "scene/3d/collision_shape_3d.h" #include "scene/3d/light_3d.h" @@ -50,59 +52,64 @@ #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" -#define DISTANCE_DEFAULT 4 +constexpr real_t DISTANCE_DEFAULT = 4; -#define GIZMO_ARROW_SIZE 0.35 -#define GIZMO_RING_HALF_WIDTH 0.1 -#define GIZMO_PLANE_SIZE 0.2 -#define GIZMO_PLANE_DST 0.3 -#define GIZMO_CIRCLE_SIZE 1.1 -#define GIZMO_SCALE_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) -#define GIZMO_ARROW_OFFSET (GIZMO_CIRCLE_SIZE + 0.3) +constexpr real_t GIZMO_ARROW_SIZE = 0.35; +constexpr real_t GIZMO_RING_HALF_WIDTH = 0.1; +constexpr real_t GIZMO_PLANE_SIZE = 0.2; +constexpr real_t GIZMO_PLANE_DST = 0.3; +constexpr real_t GIZMO_CIRCLE_SIZE = 1.1; +constexpr real_t GIZMO_SCALE_OFFSET = GIZMO_CIRCLE_SIZE + 0.3; +constexpr real_t GIZMO_ARROW_OFFSET = GIZMO_CIRCLE_SIZE + 0.3; -#define ZOOM_FREELOOK_MIN 0.01 -#define ZOOM_FREELOOK_MULTIPLIER 1.08 -#define ZOOM_FREELOOK_INDICATOR_DELAY_S 1.5 +constexpr real_t ZOOM_FREELOOK_MIN = 0.01; +constexpr real_t ZOOM_FREELOOK_MULTIPLIER = 1.08; +constexpr real_t ZOOM_FREELOOK_INDICATOR_DELAY_S = 1.5; #ifdef REAL_T_IS_DOUBLE -#define ZOOM_FREELOOK_MAX 1'000'000'000'000 +constexpr double ZOOM_FREELOOK_MAX = 1'000'000'000'000; #else -#define ZOOM_FREELOOK_MAX 10'000 +constexpr float ZOOM_FREELOOK_MAX = 10'000; #endif -#define MIN_Z 0.01 -#define MAX_Z 1000000.0 +constexpr real_t MIN_Z = 0.01; +constexpr real_t MAX_Z = 1000000.0; -#define MIN_FOV 0.01 -#define MAX_FOV 179 +constexpr real_t MIN_FOV = 0.01; +constexpr real_t MAX_FOV = 179; void ViewportRotationControl::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - axis_menu_options.clear(); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); - - axis_colors.clear(); - axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); - axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); - axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); - update(); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + axis_menu_options.clear(); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); + + axis_colors.clear(); + axis_colors.push_back(get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + axis_colors.push_back(get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + axis_colors.push_back(get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); + update(); - if (!is_connected("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited))) { - connect("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited)); - } - } + if (!is_connected("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited))) { + connect("mouse_exited", callable_mp(this, &ViewportRotationControl::_on_mouse_exited)); + } + } break; - if (p_what == NOTIFICATION_DRAW && viewport != nullptr) { - _draw(); + case NOTIFICATION_DRAW: { + if (viewport != nullptr) { + _draw(); + } + } break; } } @@ -128,7 +135,7 @@ void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) { const Color axis_color = axis_colors[direction]; const double alpha = focused ? 1.0 : ((p_axis.z_axis + 1.0) / 2.0) * 0.5 + 0.5; - const Color c = focused ? Color(0.9, 0.9, 0.9) : Color(axis_color.r, axis_color.g, axis_color.b, alpha); + const Color c = focused ? Color(0.9, 0.9, 0.9) : Color(axis_color, alpha); if (positive) { // Draw axis lines for the positive axes. @@ -139,7 +146,7 @@ void ViewportRotationControl::_draw_axis(const Axis2D &p_axis) { // Draw the axis letter for the positive axes. const String axis_name = direction == 0 ? "X" : (direction == 1 ? "Y" : "Z"); - draw_char(get_theme_font(SNAME("rotation_control"), SNAME("EditorFonts")), p_axis.screen_point + Vector2i(-4, 5) * EDSCALE, axis_name, "", get_theme_font_size(SNAME("rotation_control_size"), SNAME("EditorFonts")), Color(0.0, 0.0, 0.0, alpha)); + draw_char(get_theme_font(SNAME("rotation_control"), SNAME("EditorFonts")), p_axis.screen_point + Vector2i(Math::round(-4.0 * EDSCALE), Math::round(5.0 * EDSCALE)), axis_name, get_theme_font_size(SNAME("rotation_control_size"), SNAME("EditorFonts")), Color(0.0, 0.0, 0.0, alpha)); } else { // Draw an outline around the negative axes. draw_circle(p_axis.screen_point, AXIS_CIRCLE_RADIUS, c); @@ -153,7 +160,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) { @@ -185,7 +192,7 @@ void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); const Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { Vector2 pos = mb->get_position(); if (mb->is_pressed()) { if (pos.distance_to(get_size() / 2.0) < get_size().x / 2.0) { @@ -199,7 +206,7 @@ void ViewportRotationControl::gui_input(const Ref<InputEvent> &p_event) { orbiting = false; if (Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) { Input::get_singleton()->set_mouse_mode(Input::MOUSE_MODE_VISIBLE); - Input::get_singleton()->warp_mouse_position(orbiting_mouse_start); + Input::get_singleton()->warp_mouse(orbiting_mouse_start); } } } @@ -252,6 +259,14 @@ void ViewportRotationControl::set_viewport(Node3DEditorViewport *p_viewport) { viewport = p_viewport; } +void Node3DEditorViewport::_view_settings_confirmed(real_t p_interp_delta) { + // Set FOV override multiplier back to the default, so that the FOV + // setting specified in the View menu is correctly applied. + cursor.fov_scale = 1.0; + + _update_camera(p_interp_delta); +} + void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { bool is_orthogonal = camera->get_projection() == Camera3D::PROJECTION_ORTHOGONAL; @@ -265,15 +280,13 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { if (is_freelook_active()) { // Higher inertia should increase "lag" (lerp with factor between 0 and 1) // Inertia of zero should produce instant movement (lerp with factor of 1) in this case it returns a really high value and gets clamped to 1. - real_t inertia = EDITOR_GET("editors/3d/freelook/freelook_inertia"); - inertia = MAX(0.001, inertia); + const real_t inertia = EDITOR_GET("editors/3d/freelook/freelook_inertia"); real_t factor = (1.0 / inertia) * p_interp_delta; // We interpolate a different point here, because in freelook mode the focus point (cursor.pos) orbits around eye_pos camera_cursor.eye_pos = old_camera_cursor.eye_pos.lerp(cursor.eye_pos, CLAMP(factor, 0, 1)); - real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia"); - orbit_inertia = MAX(0.0001, orbit_inertia); + const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia"); camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia))); camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia))); @@ -289,24 +302,9 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { camera_cursor.pos = camera_cursor.eye_pos + forward * camera_cursor.distance; } else { - //when not being manipulated, move softly - real_t free_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia"); - real_t free_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia"); - //when being manipulated, move more quickly - real_t manip_orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_orbit_inertia"); - real_t manip_translation_inertia = EDITOR_GET("editors/3d/navigation_feel/manipulation_translation_inertia"); - - real_t zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia"); - - //determine if being manipulated - bool manipulated = Input::get_singleton()->get_mouse_button_mask() & (2 | 4); - manipulated |= Input::get_singleton()->is_key_pressed(KEY_SHIFT); - manipulated |= Input::get_singleton()->is_key_pressed(KEY_ALT); - manipulated |= Input::get_singleton()->is_key_pressed(KEY_CTRL); - - real_t orbit_inertia = MAX(0.00001, manipulated ? manip_orbit_inertia : free_orbit_inertia); - real_t translation_inertia = MAX(0.0001, manipulated ? manip_translation_inertia : free_translation_inertia); - zoom_inertia = MAX(0.0001, zoom_inertia); + const real_t orbit_inertia = EDITOR_GET("editors/3d/navigation_feel/orbit_inertia"); + const real_t translation_inertia = EDITOR_GET("editors/3d/navigation_feel/translation_inertia"); + const real_t zoom_inertia = EDITOR_GET("editors/3d/navigation_feel/zoom_inertia"); camera_cursor.x_rot = Math::lerp(old_camera_cursor.x_rot, cursor.x_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia))); camera_cursor.y_rot = Math::lerp(old_camera_cursor.y_rot, cursor.y_rot, MIN(1.f, p_interp_delta * (1 / orbit_inertia))); @@ -335,6 +333,8 @@ void Node3DEditorViewport::_update_camera(real_t p_interp_delta) { equal = false; } else if (!Math::is_equal_approx(old_camera_cursor.distance, camera_cursor.distance, tolerance)) { equal = false; + } else if (!Math::is_equal_approx(old_camera_cursor.fov_scale, camera_cursor.fov_scale, tolerance)) { + equal = false; } if (!equal || p_interp_delta == 0 || is_orthogonal != orthogonal) { @@ -356,26 +356,26 @@ 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 { - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); int count = 0; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); if (!sp) { continue; } @@ -391,6 +391,33 @@ int Node3DEditorViewport::get_selected_count() const { return count; } +void Node3DEditorViewport::cancel_transform() { + List<Node *> &selection = editor_selection->get_selected_node_list(); + + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + Node3D *sp = Object::cast_to<Node3D>(E->get()); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + sp->set_global_transform(se->original); + } + + finish_transform(); + set_message(TTR("Transform Aborted."), 3); +} + +void Node3DEditorViewport::_update_shrink() { + bool shrink = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_HALF_RESOLUTION)); + subviewport_container->set_stretch_shrink(shrink ? 2 : 1); + subviewport_container->set_texture_filter(shrink ? TEXTURE_FILTER_NEAREST : TEXTURE_FILTER_PARENT_NODE); +} + float Node3DEditorViewport::get_znear() const { return CLAMP(spatial_editor->get_znear(), MIN_Z, MAX_Z); } @@ -400,7 +427,7 @@ float Node3DEditorViewport::get_zfar() const { } float Node3DEditorViewport::get_fov() const { - return CLAMP(spatial_editor->get_fov(), MIN_FOV, MAX_FOV); + return CLAMP(spatial_editor->get_fov() * cursor.fov_scale, MIN_FOV, MAX_FOV); } Transform3D Node3DEditorViewport::_get_camera_transform() const { @@ -420,7 +447,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 { @@ -430,6 +457,7 @@ Vector3 Node3DEditorViewport::_get_ray(const Vector2 &p_pos) const { void Node3DEditorViewport::_clear_selected() { _edit.gizmo = Ref<EditorNode3DGizmo>(); _edit.gizmo_handle = -1; + _edit.gizmo_handle_secondary = false; _edit.gizmo_initial_value = Variant(); Node3D *selected = spatial_editor->get_single_selected_node(); @@ -457,7 +485,7 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) { if (!p_allow_locked) { // Replace the node by the group if grouped - while (node && node != editor->get_edited_scene()->get_parent()) { + while (node && node != EditorNode::get_singleton()->get_edited_scene()->get_parent()) { Node3D *selected_tmp = Object::cast_to<Node3D>(node); if (selected_tmp && node->has_meta("_edit_group_")) { selected = selected_tmp; @@ -477,17 +505,17 @@ void Node3DEditorViewport::_select_clicked(bool p_allow_locked) { if (!editor_selection->is_selected(selected)) { editor_selection->clear(); editor_selection->add_node(selected); - editor->edit_node(selected); + EditorNode::get_singleton()->edit_node(selected); } } if (editor_selection->get_selected_node_list().size() == 1) { - editor->edit_node(editor_selection->get_selected_node_list()[0]); + EditorNode::get_singleton()->edit_node(editor_selection->get_selected_node_list()[0]); } } } -ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) { +ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) const { Vector3 ray = _get_ray(p_pos); Vector3 pos = _get_ray_pos(p_pos); Vector2 shrinked_pos = p_pos / subviewport_container->get_stretch_shrink(); @@ -496,8 +524,8 @@ ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos) { RS::get_singleton()->sdfgi_set_debug_probe_select(pos, ray); } - Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world_3d()->get_scenario()); - Set<Ref<EditorNode3DGizmo>> found_gizmos; + Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); + HashSet<Ref<EditorNode3DGizmo>> found_gizmos; Node *edited_scene = get_tree()->get_edited_scene_root(); ObjectID closest; @@ -559,8 +587,8 @@ void Node3DEditorViewport::_find_items_at_pos(const Point2 &p_pos, Vector<_RayRe Vector3 ray = _get_ray(p_pos); Vector3 pos = _get_ray_pos(p_pos); - Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, ray, get_tree()->get_root()->get_world_3d()->get_scenario()); - Set<Node3D *> found_nodes; + Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); + HashSet<Node3D *> found_nodes; for (int i = 0; i < instances.size(); i++) { Node3D *spat = Object::cast_to<Node3D>(ObjectDB::get_instance(instances[i])); @@ -614,7 +642,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 { @@ -623,10 +651,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))); } @@ -668,13 +696,13 @@ void Node3DEditorViewport::_select_region() { Vector3 a = _get_screen_to_space(box[i]); Vector3 b = _get_screen_to_space(box[(i + 1) % 4]); if (orthogonal) { - frustum.push_back(Plane(a, (a - b).normalized())); + frustum.push_back(Plane((a - b).normalized(), a)); } else { frustum.push_back(Plane(a, b, cam_pos)); } } - Plane near(cam_pos, -_get_camera_normal()); + Plane near(-_get_camera_normal(), cam_pos); near.d -= get_znear(); frustum.push_back(near); @@ -743,7 +771,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(); @@ -768,7 +796,7 @@ void Node3DEditorViewport::_select_region() { // Replace the node by the group if grouped if (item->is_class("Node3D")) { Node3D *sel = Object::cast_to<Node3D>(item); - while (item && item != editor->get_edited_scene()->get_parent()) { + while (item && item != EditorNode::get_singleton()->get_edited_scene()->get_parent()) { Node3D *selected_tmp = Object::cast_to<Node3D>(item); if (selected_tmp && item->has_meta("_edit_group_")) { sel = selected_tmp; @@ -802,7 +830,7 @@ void Node3DEditorViewport::_select_region() { } if (editor_selection->get_selected_node_list().size() == 1) { - editor->edit_node(editor_selection->get_selected_node_list()[0]); + EditorNode::get_singleton()->edit_node(editor_selection->get_selected_node_list()[0]); } } @@ -835,7 +863,7 @@ void Node3DEditorViewport::_update_name() { if (orthogonal) { name = TTR("Left Orthogonal"); } else { - name = TTR("Right Perspective"); + name = TTR("Left Perspective"); } } break; case VIEW_TYPE_RIGHT: { @@ -867,12 +895,13 @@ void Node3DEditorViewport::_update_name() { } view_menu->set_text(name); - view_menu->set_size(Vector2(0, 0)); // resets the button size + view_menu->reset_size(); } void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { - _edit.click_ray = _get_ray(Vector2(p_point.x, p_point.y)); - _edit.click_ray_pos = _get_ray_pos(Vector2(p_point.x, p_point.y)); + _edit.original_local = spatial_editor->are_local_coords_enabled(); + _edit.click_ray = _get_ray(p_point); + _edit.click_ray_pos = _get_ray_pos(p_point); _edit.plane = TRANSFORM_VIEW; spatial_editor->update_transform_gizmo(); _edit.center = spatial_editor->get_gizmo_transform().origin; @@ -881,8 +910,8 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; if (se && se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - int subgizmo_id = E->key(); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + int subgizmo_id = E.key; se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id); } se->original_local = selected->get_transform(); @@ -908,36 +937,36 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { } } -static int _get_key_modifier_setting(const String &p_property) { +static Key _get_key_modifier_setting(const String &p_property) { switch (EditorSettings::get_singleton()->get(p_property).operator int()) { case 0: - return 0; + return Key::NONE; case 1: - return KEY_SHIFT; + return Key::SHIFT; case 2: - return KEY_ALT; + return Key::ALT; case 3: - return KEY_META; + return Key::META; case 4: - return KEY_CTRL; + return Key::CTRL; } - return 0; + return Key::NONE; } -static int _get_key_modifier(Ref<InputEventWithModifiers> e) { +static Key _get_key_modifier(Ref<InputEventWithModifiers> e) { if (e->is_shift_pressed()) { - return KEY_SHIFT; + return Key::SHIFT; } if (e->is_alt_pressed()) { - return KEY_ALT; + return Key::ALT; } if (e->is_ctrl_pressed()) { - return KEY_CTRL; + return Key::CTRL; } if (e->is_meta_pressed()) { - return KEY_META; + return Key::META; } - return 0; + return Key::NONE; } bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, bool p_highlight_only) { @@ -951,8 +980,8 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b return false; } - Vector3 ray_pos = _get_ray_pos(Vector2(p_screenpos.x, p_screenpos.y)); - Vector3 ray = _get_ray(Vector2(p_screenpos.x, p_screenpos.y)); + Vector3 ray_pos = _get_ray_pos(p_screenpos); + Vector3 ray = _get_ray(p_screenpos); Transform3D gt = spatial_editor->get_gizmo_transform(); @@ -961,7 +990,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b real_t col_d = 1e20; for (int i = 0; i < 3; i++) { - const Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5)); + const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * (GIZMO_ARROW_OFFSET + (GIZMO_ARROW_SIZE * 0.5)); const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE; Vector3 r; @@ -981,15 +1010,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.origin, gt.basis.get_axis(i).normalized()); + 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); @@ -1015,7 +1044,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b } else { //handle plane translate _edit.mode = TRANSFORM_TRANSLATE; - _compute_edit(Point2(p_screenpos.x, p_screenpos.y)); + _compute_edit(p_screenpos); _edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_translate ? 3 : 0)); } return true; @@ -1024,24 +1053,40 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { int col_axis = -1; - float col_d = 1e20; - for (int i = 0; i < 3; i++) { - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); - Vector3 r; - if (!plane.intersects_ray(ray_pos, ray, &r)) { - continue; + Vector3 hit_position; + Vector3 hit_normal; + real_t ray_length = gt.origin.distance_to(ray_pos) + (GIZMO_CIRCLE_SIZE * gizmo_scale) * 4.0f; + if (Geometry3D::segment_intersects_sphere(ray_pos, ray_pos + ray * ray_length, gt.origin, gizmo_scale * (GIZMO_CIRCLE_SIZE), &hit_position, &hit_normal)) { + if (hit_normal.dot(_get_camera_normal()) < 0.05) { + hit_position = gt.xform_inv(hit_position).abs(); + int min_axis = hit_position.min_axis_index(); + if (hit_position[min_axis] < gizmo_scale * GIZMO_RING_HALF_WIDTH) { + col_axis = min_axis; + } } + } + + if (col_axis == -1) { + float col_d = 1e20; - const real_t dist = r.distance_to(gt.origin); - const Vector3 r_dir = (r - gt.origin).normalized(); + for (int i = 0; i < 3; i++) { + Plane plane(gt.basis.get_column(i).normalized(), gt.origin); + Vector3 r; + if (!plane.intersects_ray(ray_pos, ray, &r)) { + continue; + } - if (_get_camera_normal().dot(r_dir) <= 0.005) { - if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) { - const real_t d = ray_pos.distance_to(r); - if (d < col_d) { - col_d = d; - col_axis = i; + const real_t dist = r.distance_to(gt.origin); + const Vector3 r_dir = (r - gt.origin).normalized(); + + if (_get_camera_normal().dot(r_dir) <= 0.005) { + if (dist > gizmo_scale * (GIZMO_CIRCLE_SIZE - GIZMO_RING_HALF_WIDTH) && dist < gizmo_scale * (GIZMO_CIRCLE_SIZE + GIZMO_RING_HALF_WIDTH)) { + const real_t d = ray_pos.distance_to(r); + if (d < col_d) { + col_d = d; + col_axis = i; + } } } } @@ -1053,7 +1098,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b } else { //handle rotate _edit.mode = TRANSFORM_ROTATE; - _compute_edit(Point2(p_screenpos.x, p_screenpos.y)); + _compute_edit(p_screenpos); _edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis); } return true; @@ -1065,7 +1110,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b float col_d = 1e20; for (int i = 0; i < 3; i++) { - const Vector3 grabber_pos = gt.origin + gt.basis.get_axis(i) * gizmo_scale * GIZMO_SCALE_OFFSET; + const Vector3 grabber_pos = gt.origin + gt.basis.get_column(i).normalized() * gizmo_scale * GIZMO_SCALE_OFFSET; const real_t grabber_radius = gizmo_scale * GIZMO_ARROW_SIZE; Vector3 r; @@ -1085,15 +1130,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.origin, gt.basis.get_axis(i).normalized()); + 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); @@ -1119,7 +1164,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b } else { //handle scale _edit.mode = TRANSFORM_SCALE; - _compute_edit(Point2(p_screenpos.x, p_screenpos.y)); + _compute_edit(p_screenpos); _edit.plane = TransformPlane(TRANSFORM_X_AXIS + col_axis + (is_plane_scale ? 3 : 0)); } return true; @@ -1145,68 +1190,62 @@ void Node3DEditorViewport::_transform_gizmo_apply(Node3D *p_node, const Transfor } } -Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local) { +Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local, bool p_orthogonal) { switch (p_mode) { case TRANSFORM_SCALE: { + if (_edit.snap || spatial_editor->is_snap_enabled()) { + p_motion.snap(Vector3(p_extra, p_extra, p_extra)); + } + Transform3D s; if (p_local) { - Basis g = p_original.basis.orthonormalized(); - Vector3 local_motion = g.inverse().xform(p_motion); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - local_motion.snap(Vector3(p_extra, p_extra, p_extra)); - } - - Transform3D local_t; - local_t.basis = p_original_local.basis.scaled_local(local_motion + Vector3(1, 1, 1)); - local_t.origin = p_original_local.origin; - return local_t; + s.basis = p_original_local.basis.scaled_local(p_motion + Vector3(1, 1, 1)); + s.origin = p_original_local.origin; } else { + s.basis.scale(p_motion + Vector3(1, 1, 1)); Transform3D base = Transform3D(Basis(), _edit.center); - if (_edit.snap || spatial_editor->is_snap_enabled()) { - p_motion.snap(Vector3(p_extra, p_extra, p_extra)); + s = base * (s * (base.inverse() * p_original)); + + // Recalculate orthogonalized scale without moving origin. + if (p_orthogonal) { + s.basis = p_original_local.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1)); + // The scaled_orthogonal() does not require orthogonal Basis, + // but it may make a bit skew by precision problems. + s.basis.orthogonalize(); } - - Transform3D global_t; - global_t.basis.scale(p_motion + Vector3(1, 1, 1)); - return base * (global_t * (base.inverse() * p_original)); } + + return s; } case TRANSFORM_TRANSLATE: { - if (p_local) { - if (_edit.snap || spatial_editor->is_snap_enabled()) { - Basis g = p_original.basis.orthonormalized(); - Vector3 local_motion = g.inverse().xform(p_motion); - local_motion.snap(Vector3(p_extra, p_extra, p_extra)); - - p_motion = g.xform(local_motion); - } + if (_edit.snap || spatial_editor->is_snap_enabled()) { + p_motion.snap(Vector3(p_extra, p_extra, p_extra)); + } - } else { - if (_edit.snap || spatial_editor->is_snap_enabled()) { - p_motion.snap(Vector3(p_extra, p_extra, p_extra)); - } + if (p_local) { + p_motion = p_original.basis.xform(p_motion); } // Apply translation Transform3D t = p_original; t.origin += p_motion; + return t; } case TRANSFORM_ROTATE: { + Transform3D r; + if (p_local) { - Transform3D r; Vector3 axis = p_original_local.basis.xform(p_motion); r.basis = Basis(axis.normalized(), p_extra) * p_original_local.basis; r.origin = p_original_local.origin; - return r; } else { - Transform3D r; Basis local = p_original.basis * p_original_local.basis.inverse(); Vector3 axis = local.xform_inv(p_motion); r.basis = local * Basis(axis.normalized(), p_extra) * p_original_local.basis; r.origin = Basis(p_motion, p_extra).xform(p_original.origin - _edit.center) + _edit.center; - return r; } + + return r; } default: { ERR_FAIL_V_MSG(Transform3D(), "Invalid mode in '_compute_transform'"); @@ -1215,13 +1254,15 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const } void Node3DEditorViewport::_surface_mouse_enter() { - if (!surface->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) { + if (!surface->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) { surface->grab_focus(); } } void Node3DEditorViewport::_surface_mouse_exit() { - _remove_preview(); + _remove_preview_node(); + _reset_preview_material(); + _remove_preview_material(); } void Node3DEditorViewport::_surface_focus_enter() { @@ -1233,19 +1274,19 @@ void Node3DEditorViewport::_surface_focus_exit() { } bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) { - return p_node->has_meta("_edit_lock_") && p_node->get_meta("_edit_lock_"); + return p_node->get_meta("_edit_lock_", false); } void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { _find_items_at_pos(b->get_position(), selection_results, spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT); - Node *scene = editor->get_edited_scene(); + Node *scene = EditorNode::get_singleton()->get_edited_scene(); for (int i = 0; i < selection_results.size(); i++) { Node3D *item = selection_results[i].item; if (item != scene && item->get_owner() != scene && item != scene->get_deepest_editable_node(item)) { //invalid result - selection_results.remove(i); + selection_results.remove_at(i); i--; } } @@ -1274,7 +1315,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { if (_is_node_locked(spat)) { locked = 1; } else { - Node *ed_scene = editor->get_edited_scene(); + Node *ed_scene = EditorNode::get_singleton()->get_edited_scene(); Node *node = spat; while (node && node != ed_scene->get_parent()) { @@ -1298,7 +1339,8 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { selection_menu->set_item_tooltip(i, String(spat->get_name()) + "\nType: " + spat->get_class() + "\nPath: " + node_path); } - selection_menu->set_position(get_screen_transform().xform(b->get_position())); + selection_menu->set_position(get_screen_position() + b->get_position()); + selection_menu->reset_size(); selection_menu->popup(); } } @@ -1308,24 +1350,31 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; //do NONE } + EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; { - EditorNode *en = editor; + EditorNode *en = EditorNode::get_singleton(); EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding(); if (!force_input_forwarding_list->is_empty()) { - bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); - if (discard) { + EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } { - EditorNode *en = editor; + EditorNode *en = EditorNode::get_singleton(); EditorPluginList *over_plugin_list = en->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); - if (discard) { + EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } @@ -1336,26 +1385,26 @@ 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 MOUSE_BUTTON_WHEEL_UP: { + case MouseButton::WHEEL_UP: { if (is_freelook_active()) { scale_freelook_speed(zoom_factor); } else { scale_cursor_distance(1.0 / zoom_factor); } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { + case MouseButton::WHEEL_DOWN: { if (is_freelook_active()) { scale_freelook_speed(1.0 / zoom_factor); } else { scale_cursor_distance(zoom_factor); } } break; - case MOUSE_BUTTON_RIGHT: { + case MouseButton::RIGHT: { NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); if (b->is_pressed() && _edit.gizmo.is_valid()) { //restore - _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_initial_value, true); + _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, true); _edit.gizmo = Ref<EditorNode3DGizmo>(); } @@ -1371,43 +1420,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (_edit.mode != TRANSFORM_NONE && b->is_pressed()) { - //cancel motion - _edit.mode = TRANSFORM_NONE; - - List<Node *> &selection = editor_selection->get_selected_node_list(); - - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (se->gizmo.is_valid()) { - Vector<int> ids; - Vector<Transform3D> restore; - - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - ids.push_back(GE->key()); - restore.push_back(GE->value()); - } - - se->gizmo->commit_subgizmos(ids, restore, true); - spatial_editor->update_transform_gizmo(); - } else { - sp->set_global_transform(se->original); - } - } - surface->update(); - set_message(TTR("Transform Aborted."), 3); + cancel_transform(); } if (b->is_pressed()) { - const int mod = _get_key_modifier(b); + const Key mod = _get_key_modifier(b); if (!orthogonal) { if (mod == _get_key_modifier_setting("editors/3d/freelook/freelook_activation_modifier")) { set_freelook_active(true); @@ -1424,7 +1441,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } break; - case MOUSE_BUTTON_MIDDLE: { + case MouseButton::MIDDLE: { if (b->is_pressed() && _edit.mode != TRANSFORM_NONE) { switch (_edit.plane) { case TRANSFORM_VIEW: { @@ -1455,8 +1472,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } } break; - case MOUSE_BUTTON_LEFT: { + case MouseButton::LEFT: { if (b->is_pressed()) { + clicked_wants_append = b->is_shift_pressed(); + + if (_edit.mode != TRANSFORM_NONE && _edit.instant) { + commit_transform(); + break; // just commit the edit, stop processing the event so we don't deselect the object + } NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->is_alt_pressed()) { break; @@ -1471,6 +1494,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _edit.original_mouse_pos = b->get_position(); _edit.snap = spatial_editor->is_snap_enabled(); _edit.mode = TRANSFORM_NONE; + _edit.original = spatial_editor->get_gizmo_transform(); // To prevent to break when flipping with scale. bool can_select_gizmos = spatial_editor->get_single_selected_node(); @@ -1492,11 +1516,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } int gizmo_handle = -1; - seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle); + bool gizmo_secondary = false; + seg->handles_intersect_ray(camera, _edit.mouse_pos, b->is_shift_pressed(), gizmo_handle, gizmo_secondary); if (gizmo_handle != -1) { _edit.gizmo = seg; _edit.gizmo_handle = gizmo_handle; - _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle); + _edit.gizmo_handle_secondary = gizmo_secondary; + _edit.gizmo_initial_value = seg->get_handle_value(gizmo_handle, gizmo_secondary); intersected_handle = true; break; } @@ -1560,65 +1586,53 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { clicked = ObjectID(); if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { - /* HANDLE ROTATION */ - if (get_selected_count() == 0) { - break; //bye - } - //handle rotate - _edit.mode = TRANSFORM_ROTATE; - _compute_edit(b->get_position()); + begin_transform(TRANSFORM_ROTATE, false); break; } if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { - if (get_selected_count() == 0) { - break; //bye - } - //handle translate - _edit.mode = TRANSFORM_TRANSLATE; - _compute_edit(b->get_position()); + begin_transform(TRANSFORM_TRANSLATE, false); break; } if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { - if (get_selected_count() == 0) { - break; //bye - } - //handle scale - _edit.mode = TRANSFORM_SCALE; - _compute_edit(b->get_position()); + begin_transform(TRANSFORM_SCALE, false); break; } - clicked = _select_ray(b->get_position()); - - //clicking is always deferred to either move or release + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + //clicking is always deferred to either move or release + clicked = _select_ray(b->get_position()); + selection_in_progress = true; - clicked_wants_append = b->is_shift_pressed(); - - if (clicked.is_null()) { - //default to regionselect - cursor.region_select = true; - cursor.region_begin = b->get_position(); - cursor.region_end = b->get_position(); + if (clicked.is_null()) { + //default to regionselect + cursor.region_select = true; + cursor.region_begin = b->get_position(); + cursor.region_end = b->get_position(); + } } surface->update(); } else { if (_edit.gizmo.is_valid()) { - _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_initial_value, false); + _edit.gizmo->commit_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, _edit.gizmo_initial_value, false); _edit.gizmo = Ref<EditorNode3DGizmo>(); break; } - if (clicked.is_valid()) { - _select_clicked(false); - } + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + selection_in_progress = false; + + if (clicked.is_valid()) { + _select_clicked(false); + } - if (cursor.region_select) { - _select_region(); - cursor.region_select = false; - surface->update(); + if (cursor.region_select) { + _select_region(); + cursor.region_select = false; + surface->update(); + } } if (_edit.mode != TRANSFORM_NONE) { @@ -1629,40 +1643,15 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector<int> ids; Vector<Transform3D> restore; - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - ids.push_back(GE->key()); - restore.push_back(GE->value()); + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + ids.push_back(GE.key); + restore.push_back(GE.value); } se->gizmo->commit_subgizmos(ids, restore, false); spatial_editor->update_transform_gizmo(); } else { - static const char *_transform_name[4] = { - TTRC("None"), - TTRC("Rotate"), - // TRANSLATORS: This refers to the movement that changes the position of an object. - TTRC("Translate"), - TTRC("Scale"), - }; - undo_redo->create_action(TTRGET(_transform_name[_edit.mode])); - - List<Node *> &selection = editor_selection->get_selected_node_list(); - - for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->get()); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *sel_item = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!sel_item) { - continue; - } - - undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); - undo_redo->add_undo_method(sp, "set_global_transform", sel_item->original); - } - undo_redo->commit_action(); + commit_transform(); } _edit.mode = TRANSFORM_NONE; set_message(""); @@ -1686,6 +1675,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Ref<EditorNode3DGizmo> found_gizmo; int found_handle = -1; + bool found_handle_secondary = false; for (int i = 0; i < gizmos.size(); i++) { Ref<EditorNode3DGizmo> seg = gizmos[i]; @@ -1693,7 +1683,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { continue; } - seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle); + seg->handles_intersect_ray(camera, _edit.mouse_pos, false, found_handle, found_handle_secondary); if (found_handle != -1) { found_gizmo = seg; @@ -1705,14 +1695,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { spatial_editor->select_gizmo_highlight_axis(-1); } - if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != spatial_editor->get_current_hover_gizmo_handle()) { + bool current_hover_handle_secondary = false; + int curreny_hover_handle = spatial_editor->get_current_hover_gizmo_handle(current_hover_handle_secondary); + if (found_gizmo != spatial_editor->get_current_hover_gizmo() || found_handle != curreny_hover_handle || found_handle_secondary != current_hover_handle_secondary) { spatial_editor->set_current_hover_gizmo(found_gizmo); - spatial_editor->set_current_hover_gizmo_handle(found_handle); + spatial_editor->set_current_hover_gizmo_handle(found_handle, found_handle_secondary); spatial_editor->get_single_selected_node()->update_gizmos(); } } - if (spatial_editor->get_current_hover_gizmo().is_null() && !(m->get_button_mask() & 1) && !_edit.gizmo.is_valid()) { + if (spatial_editor->get_current_hover_gizmo().is_null() && (m->get_button_mask() & MouseButton::MASK_LEFT) == MouseButton::NONE && !_edit.gizmo.is_valid()) { _transform_gizmo_select(_edit.mouse_pos, true); } @@ -1720,12 +1712,12 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { NavigationMode nav_mode = NAVIGATION_NONE; if (_edit.gizmo.is_valid()) { - _edit.gizmo->set_handle(_edit.gizmo_handle, camera, m->get_position()); - Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle); - String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle); + _edit.gizmo->set_handle(_edit.gizmo_handle, _edit.gizmo_handle_secondary, camera, m->get_position()); + Variant v = _edit.gizmo->get_handle_value(_edit.gizmo_handle, _edit.gizmo_handle_secondary); + String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle, _edit.gizmo_handle_secondary); set_message(n + ": " + String(v)); - } else if (m->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + } else if ((m->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE || _edit.instant) { if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) { nav_mode = NAVIGATION_ORBIT; } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) { @@ -1736,11 +1728,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { nav_mode = NAVIGATION_ORBIT; } else { const bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 8 * EDSCALE; - if (clicked.is_valid() && movement_threshold_passed) { - _compute_edit(_edit.mouse_pos); - clicked = ObjectID(); - _edit.mode = TRANSFORM_TRANSLATE; + // enable region-select if nothing has been selected yet or multi-select (shift key) is active + if (selection_in_progress && movement_threshold_passed) { + if (get_selected_count() == 0 || clicked_wants_append) { + cursor.region_select = true; + cursor.region_begin = _edit.original_mouse_pos; + clicked = ObjectID(); + } } if (cursor.region_select) { @@ -1749,326 +1744,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } + if (clicked.is_valid() && movement_threshold_passed) { + _compute_edit(_edit.original_mouse_pos); + clicked = ObjectID(); + _edit.mode = TRANSFORM_TRANSLATE; + } + if (_edit.mode == TRANSFORM_NONE) { return; } - Vector3 ray_pos = _get_ray_pos(m->get_position()); - Vector3 ray = _get_ray(m->get_position()); - double snap = EDITOR_GET("interface/inspector/default_float_step"); - int snap_step_decimals = Math::range_step_decimals(snap); - - switch (_edit.mode) { - case TRANSFORM_SCALE: { - Vector3 motion_mask; - Plane plane; - bool plane_mv = false; - - switch (_edit.plane) { - case TRANSFORM_VIEW: - motion_mask = Vector3(0, 0, 0); - plane = Plane(_edit.center, _get_camera_normal()); - break; - case TRANSFORM_X_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); - break; - case TRANSFORM_Y_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); - break; - case TRANSFORM_Z_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); - break; - case TRANSFORM_YZ: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); - plane_mv = true; - break; - case TRANSFORM_XZ: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); - plane_mv = true; - break; - case TRANSFORM_XY: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); - plane_mv = true; - break; - } - - Vector3 intersection; - if (!plane.intersects_ray(ray_pos, ray, &intersection)) { - break; - } - - Vector3 click; - if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { - break; - } - - Vector3 motion = intersection - click; - if (_edit.plane != TRANSFORM_VIEW) { - if (!plane_mv) { - motion = motion_mask.dot(motion) * motion_mask; - - } else { - // Alternative planar scaling mode - if (_get_key_modifier(m) != KEY_SHIFT) { - motion = motion_mask.dot(motion) * motion_mask; - } - } - - } else { - const real_t center_click_dist = click.distance_to(_edit.center); - const real_t center_inters_dist = intersection.distance_to(_edit.center); - if (center_click_dist == 0) { - break; - } - - const real_t scale = center_inters_dist - center_click_dist; - motion = Vector3(scale, scale, scale); - } - - // Disable local transformation for TRANSFORM_VIEW - bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - snap = spatial_editor->get_scale_snap() / 100; - } - Vector3 motion_snapped = motion; - motion_snapped.snap(Vector3(snap, snap, snap)); - // This might not be necessary anymore after issue #288 is solved (in 4.0?). - set_message(TTR("Scaling: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " + - String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); - - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords); - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->update(); - - } break; - - case TRANSFORM_TRANSLATE: { - Vector3 motion_mask; - Plane plane; - bool plane_mv = false; - - switch (_edit.plane) { - case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); - break; - case TRANSFORM_X_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); - break; - case TRANSFORM_Y_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); - break; - case TRANSFORM_Z_AXIS: - motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); - break; - case TRANSFORM_YZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); - plane_mv = true; - break; - case TRANSFORM_XZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); - plane_mv = true; - break; - case TRANSFORM_XY: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); - plane_mv = true; - break; - } - - Vector3 intersection; - if (!plane.intersects_ray(ray_pos, ray, &intersection)) { - break; - } - - Vector3 click; - if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { - break; - } - - Vector3 motion = intersection - click; - if (_edit.plane != TRANSFORM_VIEW) { - if (!plane_mv) { - motion = motion_mask.dot(motion) * motion_mask; - } - } - - // Disable local transformation for TRANSFORM_VIEW - bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - snap = spatial_editor->get_translate_snap(); - } - Vector3 motion_snapped = motion; - motion_snapped.snap(Vector3(snap, snap, snap)); - set_message(TTR("Translating: ") + "(" + String::num(motion_snapped.x, snap_step_decimals) + ", " + - String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); - - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords); - new_xform = se->original.affine_inverse() * new_xform; - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords); - _transform_gizmo_apply(se->sp, new_xform, false); - } - } - - spatial_editor->update_transform_gizmo(); - surface->update(); - - } break; - - case TRANSFORM_ROTATE: { - Plane plane; - Vector3 axis; - - switch (_edit.plane) { - case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); - break; - case TRANSFORM_X_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); - axis = Vector3(1, 0, 0); - break; - case TRANSFORM_Y_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); - axis = Vector3(0, 1, 0); - break; - case TRANSFORM_Z_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); - axis = Vector3(0, 0, 1); - break; - case TRANSFORM_YZ: - case TRANSFORM_XZ: - case TRANSFORM_XY: - break; - } - - Vector3 intersection; - if (!plane.intersects_ray(ray_pos, ray, &intersection)) { - break; - } - - Vector3 click; - if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { - break; - } - - Vector3 y_axis = (click - _edit.center).normalized(); - Vector3 x_axis = plane.normal.cross(y_axis).normalized(); - - double angle = Math::atan2(x_axis.dot(intersection - _edit.center), y_axis.dot(intersection - _edit.center)); - - if (_edit.snap || spatial_editor->is_snap_enabled()) { - snap = spatial_editor->get_rotate_snap(); - } - angle = Math::rad2deg(angle) + snap * 0.5; //else it won't reach +180 - angle -= Math::fmod(angle, snap); - set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals))); - angle = Math::deg2rad(angle); - - bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW - - List<Node *> &selection = editor_selection->get_selected_node_list(); - for (Node *E : selection) { - Node3D *sp = Object::cast_to<Node3D>(E); - if (!sp) { - continue; - } - - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } - - if (sp->has_meta("_edit_lock_")) { - continue; - } - - Vector3 compute_axis = local_coords ? axis : plane.normal; - if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); - - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords); - if (!local_coords) { - new_xform = se->original.affine_inverse() * new_xform; - } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); - } - } else { - Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords); - _transform_gizmo_apply(se->sp, new_xform, local_coords); - } - } - - spatial_editor->update_transform_gizmo(); - surface->update(); - - } break; - default: { - } - } + update_transform(m->get_position(), _get_key_modifier(m) == Key::SHIFT); } - } else if ((m->get_button_mask() & MOUSE_BUTTON_MASK_RIGHT) || freelook_active) { + } else if ((m->get_button_mask() & MouseButton::MASK_RIGHT) != MouseButton::NONE || freelook_active) { if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) { nav_mode = NAVIGATION_ZOOM; } else if (freelook_active) { @@ -2077,14 +1765,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { nav_mode = NAVIGATION_PAN; } - } else if (m->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) { - const int mod = _get_key_modifier(m); + } else if ((m->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE) { + const Key mod = _get_key_modifier(m); if (nav_scheme == NAVIGATION_GODOT) { if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) { nav_mode = NAVIGATION_PAN; } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) { nav_mode = NAVIGATION_ZOOM; - } else if (mod == KEY_ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) { + } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) { // Always allow Alt as a modifier to better support graphic tablets. nav_mode = NAVIGATION_ORBIT; } @@ -2095,14 +1783,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } else if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_3_button_mouse")) { // Handle trackpad (no external mouse) use case - const int mod = _get_key_modifier(m); + const Key mod = _get_key_modifier(m); - if (mod) { + if (mod != Key::NONE) { if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) { nav_mode = NAVIGATION_PAN; } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) { nav_mode = NAVIGATION_ZOOM; - } else if (mod == KEY_ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) { + } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) { // Always allow Alt as a modifier to better support graphic tablets. nav_mode = NAVIGATION_ORBIT; } @@ -2150,13 +1838,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { NavigationMode nav_mode = NAVIGATION_NONE; if (nav_scheme == NAVIGATION_GODOT) { - const int mod = _get_key_modifier(pan_gesture); + const Key mod = _get_key_modifier(pan_gesture); if (mod == _get_key_modifier_setting("editors/3d/navigation/pan_modifier")) { nav_mode = NAVIGATION_PAN; } else if (mod == _get_key_modifier_setting("editors/3d/navigation/zoom_modifier")) { nav_mode = NAVIGATION_ZOOM; - } else if (mod == KEY_ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) { + } else if (mod == Key::ALT || mod == _get_key_modifier_setting("editors/3d/navigation/orbit_modifier")) { // Always allow Alt as a modifier to better support graphic tablets. nav_mode = NAVIGATION_ORBIT; } @@ -2201,12 +1889,62 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_numpad")) { - const uint32_t code = k->get_keycode(); - if (code >= KEY_0 && code <= KEY_9) { - k->set_keycode(code - KEY_0 + KEY_KP_0); + const Key code = k->get_physical_keycode(); + if (code >= Key::KEY_0 && code <= Key::KEY_9) { + k->set_keycode(code - Key::KEY_0 + Key::KP_0); } } + if (_edit.mode == TRANSFORM_NONE) { + if (k->get_keycode() == Key::ESCAPE && !cursor.region_select) { + _clear_selected(); + return; + } + } else { + // We're actively transforming, handle keys specially + TransformPlane new_plane = TRANSFORM_VIEW; + String new_message; + if (ED_IS_SHORTCUT("spatial_editor/lock_transform_x", p_event)) { + new_plane = TRANSFORM_X_AXIS; + new_message = TTR("X-Axis Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_y", p_event)) { + new_plane = TRANSFORM_Y_AXIS; + new_message = TTR("Y-Axis Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_z", p_event)) { + new_plane = TRANSFORM_Z_AXIS; + new_message = TTR("Z-Axis Transform."); + } else if (_edit.mode != TRANSFORM_ROTATE) { // rotating on a plane doesn't make sense + if (ED_IS_SHORTCUT("spatial_editor/lock_transform_yz", p_event)) { + new_plane = TRANSFORM_YZ; + new_message = TTR("YZ-Plane Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xz", p_event)) { + new_plane = TRANSFORM_XZ; + new_message = TTR("XZ-Plane Transform."); + } else if (ED_IS_SHORTCUT("spatial_editor/lock_transform_xy", p_event)) { + new_plane = TRANSFORM_XY; + new_message = TTR("XY-Plane Transform."); + } + } + + if (new_plane != TRANSFORM_VIEW) { + if (new_plane != _edit.plane) { + // lock me once and get a global constraint + _edit.plane = new_plane; + spatial_editor->set_local_coords_enabled(false); + } else if (!spatial_editor->are_local_coords_enabled()) { + // lock me twice and get a local constraint + spatial_editor->set_local_coords_enabled(true); + } else { + // lock me thrice and we're back where we started + _edit.plane = TRANSFORM_VIEW; + spatial_editor->set_local_coords_enabled(false); + } + update_transform(_edit.mouse_pos, Input::get_singleton()->is_key_pressed(Key::SHIFT)); + set_message(new_message, 2); + accept_event(); + return; + } + } if (ED_IS_SHORTCUT("spatial_editor/snap", p_event)) { if (_edit.mode != TRANSFORM_NONE) { _edit.snap = !_edit.snap; @@ -2230,6 +1968,33 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/right_view", p_event)) { _menu_option(VIEW_RIGHT); } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", p_event)) { + // Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented. + cursor.x_rot = CLAMP(cursor.x_rot - Math_PI / 12.0, -1.57, 1.57); + view_type = VIEW_TYPE_USER; + _update_name(); + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_up", p_event)) { + // Clamp rotation to roughly -90..90 degrees so the user can't look upside-down and end up disoriented. + cursor.x_rot = CLAMP(cursor.x_rot + Math_PI / 12.0, -1.57, 1.57); + view_type = VIEW_TYPE_USER; + _update_name(); + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_right", p_event)) { + cursor.y_rot -= Math_PI / 12.0; + view_type = VIEW_TYPE_USER; + _update_name(); + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_left", p_event)) { + cursor.y_rot += Math_PI / 12.0; + view_type = VIEW_TYPE_USER; + _update_name(); + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_180", p_event)) { + cursor.y_rot += Math_PI; + view_type = VIEW_TYPE_USER; + _update_name(); + } if (ED_IS_SHORTCUT("spatial_editor/focus_origin", p_event)) { _menu_option(VIEW_CENTER_TO_ORIGIN); } @@ -2252,7 +2017,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } - if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { + if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) { set_message(TTR("Keying is disabled (no key inserted).")); return; } @@ -2270,20 +2035,46 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { set_message(TTR("Animation Key Inserted.")); } + if (ED_IS_SHORTCUT("spatial_editor/cancel_transform", p_event) && _edit.mode != TRANSFORM_NONE) { + cancel_transform(); + } + if (!is_freelook_active()) { + if (ED_IS_SHORTCUT("spatial_editor/instant_translate", p_event)) { + begin_transform(TRANSFORM_TRANSLATE, true); + } + if (ED_IS_SHORTCUT("spatial_editor/instant_rotate", p_event)) { + begin_transform(TRANSFORM_ROTATE, true); + } + if (ED_IS_SHORTCUT("spatial_editor/instant_scale", p_event)) { + begin_transform(TRANSFORM_SCALE, true); + } + } // Freelook doesn't work in orthogonal mode. if (!orthogonal && ED_IS_SHORTCUT("spatial_editor/freelook_toggle", p_event)) { set_freelook_active(!is_freelook_active()); - } else if (k->get_keycode() == KEY_ESCAPE) { + } else if (k->get_keycode() == Key::ESCAPE) { set_freelook_active(false); } - if (k->get_keycode() == KEY_SPACE) { + if (k->get_keycode() == Key::SPACE) { if (!k->is_pressed()) { emit_signal(SNAME("toggle_maximize_view"), this); } } + + if (ED_IS_SHORTCUT("spatial_editor/decrease_fov", p_event)) { + scale_fov(-0.05); + } + + if (ED_IS_SHORTCUT("spatial_editor/increase_fov", p_event)) { + scale_fov(0.05); + } + + if (ED_IS_SHORTCUT("spatial_editor/reset_fov", p_event)) { + reset_fov(); + } } // freelook uses most of the useful shortcuts, like save, so its ok @@ -2297,14 +2088,13 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); real_t pan_speed = 1 / 150.0; - int pan_speed_modifier = 10; if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { - pan_speed *= pan_speed_modifier; + pan_speed *= 10; } Transform3D camera_transform; - camera_transform.translate(cursor.pos); + camera_transform.translate_local(cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); const bool invert_x_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_x_axis"); @@ -2314,7 +2104,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; } @@ -2322,9 +2112,8 @@ void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const const NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); real_t zoom_speed = 1 / 80.0; - int zoom_speed_modifier = 10; if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { - zoom_speed *= zoom_speed_modifier; + zoom_speed *= 10; } NavigationZoomStyle zoom_style = (NavigationZoomStyle)EditorSettings::get_singleton()->get("editors/3d/navigation/zoom_style").operator int(); @@ -2385,7 +2174,8 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const _menu_option(VIEW_PERSPECTIVE); } - const real_t degrees_per_pixel = EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_sensitivity"); + // Scale mouse sensitivity with camera FOV scale when zoomed in to make it easier to point at things. + const real_t degrees_per_pixel = real_t(EditorSettings::get_singleton()->get("editors/3d/freelook/freelook_sensitivity")) * MIN(1.0, cursor.fov_scale); const real_t radians_per_pixel = Math::deg2rad(degrees_per_pixel); const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); @@ -2451,6 +2241,16 @@ void Node3DEditorViewport::set_freelook_active(bool active_now) { freelook_active = active_now; } +void Node3DEditorViewport::scale_fov(real_t p_fov_offset) { + cursor.fov_scale = CLAMP(cursor.fov_scale + p_fov_offset, 0.1, 2.5); + surface->update(); +} + +void Node3DEditorViewport::reset_fov() { + cursor.fov_scale = 1.0; + surface->update(); +} + void Node3DEditorViewport::scale_cursor_distance(real_t scale) { real_t min_distance = MAX(camera->get_near() * 4, ZOOM_FREELOOK_MIN); real_t max_distance = MIN(camera->get_far() / 4, ZOOM_FREELOOK_MAX); @@ -2485,7 +2285,7 @@ void Node3DEditorViewport::scale_freelook_speed(real_t scale) { Point2i Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const { Point2i relative; - if (bool(EDITOR_DEF("editors/3d/navigation/warped_mouse_panning", false))) { + if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) { relative = Input::get_singleton()->warp_mouse_motion(p_ev_mouse_motion, surface->get_global_rect()); } else { relative = p_ev_mouse_motion->get_relative(); @@ -2493,20 +2293,6 @@ Point2i Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouse return relative; } -static bool is_shortcut_pressed(const String &p_path) { - Ref<Shortcut> shortcut = ED_GET_SHORTCUT(p_path); - if (shortcut.is_null()) { - return false; - } - InputEventKey *k = Object::cast_to<InputEventKey>(shortcut->get_event().ptr()); - if (k == nullptr) { - return false; - } - const Input &input = *Input::get_singleton(); - Key keycode = k->get_keycode(); - return input.is_key_pressed(keycode); -} - void Node3DEditorViewport::_update_freelook(real_t delta) { if (!is_freelook_active()) { return; @@ -2536,31 +2322,34 @@ void Node3DEditorViewport::_update_freelook(real_t delta) { Vector3 direction; - if (is_shortcut_pressed("spatial_editor/freelook_left")) { + // Use actions from the inputmap, as this is the only way to reliably detect input in this method. + // See #54469 for more discussion and explanation. + Input *inp = Input::get_singleton(); + if (inp->is_action_pressed("spatial_editor/freelook_left")) { direction -= right; } - if (is_shortcut_pressed("spatial_editor/freelook_right")) { + if (inp->is_action_pressed("spatial_editor/freelook_right")) { direction += right; } - if (is_shortcut_pressed("spatial_editor/freelook_forward")) { + if (inp->is_action_pressed("spatial_editor/freelook_forward")) { direction += forward; } - if (is_shortcut_pressed("spatial_editor/freelook_backwards")) { + if (inp->is_action_pressed("spatial_editor/freelook_backwards")) { direction -= forward; } - if (is_shortcut_pressed("spatial_editor/freelook_up")) { + if (inp->is_action_pressed("spatial_editor/freelook_up")) { direction += up; } - if (is_shortcut_pressed("spatial_editor/freelook_down")) { + if (inp->is_action_pressed("spatial_editor/freelook_down")) { direction -= up; } real_t speed = freelook_speed; - if (is_shortcut_pressed("spatial_editor/freelook_speed_modifier")) { + if (inp->is_action_pressed("spatial_editor/freelook_speed_modifier")) { speed *= 3.0; } - if (is_shortcut_pressed("spatial_editor/freelook_slow_modifier")) { + if (inp->is_action_pressed("spatial_editor/freelook_slow_modifier")) { speed *= 0.333333; } @@ -2585,25 +2374,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 @@ -2611,297 +2396,339 @@ 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); const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"); viewport->set_use_occlusion_culling(use_occlusion_culling); + + const float mesh_lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); + viewport->set_mesh_lod_threshold(mesh_lod_threshold); + + const Viewport::Scaling3DMode scaling_3d_mode = Viewport::Scaling3DMode(int(GLOBAL_GET("rendering/scaling_3d/mode"))); + viewport->set_scaling_3d_mode(scaling_3d_mode); + + const float scaling_3d_scale = GLOBAL_GET("rendering/scaling_3d/scale"); + viewport->set_scaling_3d_scale(scaling_3d_scale); + + const float fsr_sharpness = GLOBAL_GET("rendering/scaling_3d/fsr_sharpness"); + viewport->set_fsr_sharpness(fsr_sharpness); + + const float texture_mipmap_bias = GLOBAL_GET("rendering/textures/default_filters/texture_mipmap_bias"); + viewport->set_texture_mipmap_bias(texture_mipmap_bias); } void Node3DEditorViewport::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - EditorNode::get_singleton()->connect("project_settings_changed", callable_mp(this, &Node3DEditorViewport::_project_settings_changed)); - } + switch (p_what) { + case NOTIFICATION_READY: { + EditorNode::get_singleton()->connect("project_settings_changed", callable_mp(this, &Node3DEditorViewport::_project_settings_changed)); + } break; - if (p_what == NOTIFICATION_VISIBILITY_CHANGED) { - bool visible = is_visible_in_tree(); + case NOTIFICATION_VISIBILITY_CHANGED: { + bool visible = is_visible_in_tree(); - set_process(visible); + set_process(visible); - if (visible) { - orthogonal = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL)); - _update_name(); - _update_camera(0); - } else { - set_freelook_active(false); - } - call_deferred(SNAME("update_transform_gizmo_view")); - rotation_control->set_visible(EditorSettings::get_singleton()->get("editors/3d/navigation/show_viewport_rotation_gizmo")); - } + if (visible) { + orthogonal = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_ORTHOGONAL)); + _update_name(); + _update_camera(0); + } else { + set_freelook_active(false); + } + call_deferred(SNAME("update_transform_gizmo_view")); + rotation_control->set_visible(EditorSettings::get_singleton()->get("editors/3d/navigation/show_viewport_rotation_gizmo")); + } break; - if (p_what == NOTIFICATION_RESIZED) { - call_deferred(SNAME("update_transform_gizmo_view")); - } + case NOTIFICATION_RESIZED: { + call_deferred(SNAME("update_transform_gizmo_view")); + } break; - if (p_what == NOTIFICATION_PROCESS) { - real_t delta = get_process_delta_time(); + case NOTIFICATION_PROCESS: { + real_t delta = get_process_delta_time(); - if (zoom_indicator_delay > 0) { - zoom_indicator_delay -= delta; - if (zoom_indicator_delay <= 0) { - surface->update(); - zoom_limit_label->hide(); + if (zoom_indicator_delay > 0) { + zoom_indicator_delay -= delta; + if (zoom_indicator_delay <= 0) { + surface->update(); + zoom_limit_label->hide(); + } } - } - _update_freelook(delta); + _update_freelook(delta); - Node *scene_root = editor->get_scene_tree_dock()->get_editor_data()->get_edited_scene_root(); - if (previewing_cinema && scene_root != nullptr) { - Camera3D *cam = scene_root->get_viewport()->get_camera_3d(); - if (cam != nullptr && cam != previewing) { - //then switch the viewport's camera to the scene's viewport camera - if (previewing != nullptr) { - previewing->disconnect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); + Node *scene_root = SceneTreeDock::get_singleton()->get_editor_data()->get_edited_scene_root(); + if (previewing_cinema && scene_root != nullptr) { + Camera3D *cam = scene_root->get_viewport()->get_camera_3d(); + if (cam != nullptr && cam != previewing) { + //then switch the viewport's camera to the scene's viewport camera + if (previewing != nullptr) { + previewing->disconnect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); + } + previewing = cam; + previewing->connect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); + RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera()); + surface->update(); } - previewing = cam; - previewing->connect("tree_exited", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); - RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), cam->get_camera()); - surface->update(); } - } - _update_camera(delta); + _update_camera(delta); - Map<Node *, Object *> &selection = editor_selection->get_selection(); + const HashMap<Node *, Object *> &selection = editor_selection->get_selection(); - bool changed = false; - bool exist = false; + bool changed = false; + bool exist = false; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->key()); - if (!sp) { - continue; - } + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); + if (!sp) { + continue; + } - Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); - if (!se) { - continue; - } + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } - Transform3D t = sp->get_global_gizmo_transform(); - VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sp); - AABB new_aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp); + Transform3D t = sp->get_global_gizmo_transform(); + VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sp); + AABB new_aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp); - exist = true; - if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) { - continue; - } - changed = true; - se->last_xform_dirty = false; - se->last_xform = t; + exist = true; + if (se->last_xform == t && se->aabb == new_aabb && !se->last_xform_dirty) { + continue; + } + changed = true; + se->last_xform_dirty = false; + se->last_xform = t; - se->aabb = new_aabb; + se->aabb = new_aabb; - t.translate(se->aabb.position); + Transform3D t_offset = t; - // apply AABB scaling before item's global transform - Basis aabb_s; - aabb_s.scale(se->aabb.size); - t.basis = t.basis * aabb_s; + // apply AABB scaling before item's global transform + { + const Vector3 offset(0.005, 0.005, 0.005); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t.translate_local(se->aabb.position - offset / 2); + t.basis = t.basis * aabb_s; + } + { + const Vector3 offset(0.01, 0.01, 0.01); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t_offset.translate_local(se->aabb.position - offset / 2); + t_offset.basis = t_offset.basis * aabb_s; + } - RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t); - RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t); - } + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset); + } - if (changed || (spatial_editor->is_gizmo_visible() && !exist)) { - spatial_editor->update_transform_gizmo(); - } + if (changed || (spatial_editor->is_gizmo_visible() && !exist)) { + spatial_editor->update_transform_gizmo(); + } + + if (message_time > 0) { + if (message != last_message) { + surface->update(); + last_message = message; + } - if (message_time > 0) { - if (message != last_message) { - surface->update(); - last_message = message; + message_time -= get_physics_process_delta_time(); + if (message_time < 0) { + surface->update(); + } } - message_time -= get_physics_process_delta_time(); - if (message_time < 0) { - surface->update(); + bool show_info = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION)); + if (show_info != info_label->is_visible()) { + info_label->set_visible(show_info); } - } - bool show_info = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_INFORMATION)); - if (show_info != info_label->is_visible()) { - info_label->set_visible(show_info); - } + Camera3D *current_camera; - Camera3D *current_camera; + if (previewing) { + current_camera = previewing; + } else { + current_camera = camera; + } + + if (show_info) { + const String viewport_size = vformat(String::utf8("%d × %d"), viewport->get_size().x, viewport->get_size().y); + String text; + text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1)); + text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1)); + text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1)); + text += "\n"; + text += vformat( + TTR("Size: %s (%.1fMP)\n"), + viewport_size, + viewport->get_size().x * viewport->get_size().y * 0.000001); + + text += "\n"; + text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME)); + text += vformat(TTR("Primitive Indices: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME)); + text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)); + + info_label->set_text(text); + } + + // FPS Counter. + bool show_fps = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME)); + + if (show_fps != fps_label->is_visible()) { + cpu_time_label->set_visible(show_fps); + gpu_time_label->set_visible(show_fps); + fps_label->set_visible(show_fps); + RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps); + for (int i = 0; i < FRAME_TIME_HISTORY; i++) { + cpu_time_history[i] = 0; + gpu_time_history[i] = 0; + } + cpu_time_history_index = 0; + gpu_time_history_index = 0; + } + if (show_fps) { + cpu_time_history[cpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_cpu(viewport->get_viewport_rid()); + cpu_time_history_index = (cpu_time_history_index + 1) % FRAME_TIME_HISTORY; + double cpu_time = 0.0; + for (int i = 0; i < FRAME_TIME_HISTORY; i++) { + cpu_time += cpu_time_history[i]; + } + cpu_time /= FRAME_TIME_HISTORY; + // Prevent unrealistically low values. + cpu_time = MAX(0.01, cpu_time); + + gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid()); + gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY; + double gpu_time = 0.0; + for (int i = 0; i < FRAME_TIME_HISTORY; i++) { + gpu_time += gpu_time_history[i]; + } + gpu_time /= FRAME_TIME_HISTORY; + // Prevent division by zero for the FPS counter (and unrealistically low values). + // This limits the reported FPS to 100000. + gpu_time = MAX(0.01, gpu_time); + + // Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red). + // Middle point is at 15 ms. + cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(2))); + cpu_time_label->add_theme_color_override( + "font_color", + frame_time_gradient->get_color_at_offset( + Math::range_lerp(cpu_time, 0, 30, 0, 1))); + + gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(2))); + // Middle point is at 15 ms. + gpu_time_label->add_theme_color_override( + "font_color", + frame_time_gradient->get_color_at_offset( + Math::range_lerp(gpu_time, 0, 30, 0, 1))); + + const double fps = 1000.0 / gpu_time; + fps_label->set_text(vformat(TTR("FPS: %d"), fps)); + // Middle point is at 60 FPS. + fps_label->add_theme_color_override( + "font_color", + frame_time_gradient->get_color_at_offset( + Math::range_lerp(fps, 110, 10, 0, 1))); + } + + bool show_cinema = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW)); + cinema_label->set_visible(show_cinema); + if (show_cinema) { + float cinema_half_width = cinema_label->get_size().width / 2.0f; + cinema_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -cinema_half_width); + } - if (previewing) { - current_camera = previewing; - } else { - current_camera = camera; - } - - if (show_info) { - const String viewport_size = vformat(String::utf8("%d × %d"), viewport->get_size().x, viewport->get_size().y); - String text; - text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1)); - text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1)); - text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1)); - text += "\n"; - text += vformat( - TTR("Size: %s (%.1fMP)\n"), - viewport_size, - viewport->get_size().x * viewport->get_size().y * 0.000001); - - text += "\n"; - text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME)); - text += vformat(TTR("Primitives: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME)); - text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)); - - info_label->set_text(text); - } - - // FPS Counter. - bool show_fps = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_FRAME_TIME)); - - if (show_fps != fps_label->is_visible()) { - cpu_time_label->set_visible(show_fps); - gpu_time_label->set_visible(show_fps); - fps_label->set_visible(show_fps); - RS::get_singleton()->viewport_set_measure_render_time(viewport->get_viewport_rid(), show_fps); - for (int i = 0; i < FRAME_TIME_HISTORY; i++) { - cpu_time_history[i] = 0; - gpu_time_history[i] = 0; - } - cpu_time_history_index = 0; - gpu_time_history_index = 0; - } - if (show_fps) { - cpu_time_history[cpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_cpu(viewport->get_viewport_rid()); - cpu_time_history_index = (cpu_time_history_index + 1) % FRAME_TIME_HISTORY; - double cpu_time = 0.0; - for (int i = 0; i < FRAME_TIME_HISTORY; i++) { - cpu_time += cpu_time_history[i]; - } - cpu_time /= FRAME_TIME_HISTORY; - // Prevent unrealistically low values. - cpu_time = MAX(0.01, cpu_time); - - gpu_time_history[gpu_time_history_index] = RS::get_singleton()->viewport_get_measured_render_time_gpu(viewport->get_viewport_rid()); - gpu_time_history_index = (gpu_time_history_index + 1) % FRAME_TIME_HISTORY; - double gpu_time = 0.0; - for (int i = 0; i < FRAME_TIME_HISTORY; i++) { - gpu_time += gpu_time_history[i]; - } - gpu_time /= FRAME_TIME_HISTORY; - // Prevent division by zero for the FPS counter (and unrealistically low values). - // This limits the reported FPS to 100000. - gpu_time = MAX(0.01, gpu_time); - - // Color labels depending on performance level ("good" = green, "OK" = yellow, "bad" = red). - // Middle point is at 15 ms. - cpu_time_label->set_text(vformat(TTR("CPU Time: %s ms"), rtos(cpu_time).pad_decimals(1))); - cpu_time_label->add_theme_color_override( - "font_color", - frame_time_gradient->get_color_at_offset( - Math::range_lerp(cpu_time, 0, 30, 0, 1))); - - gpu_time_label->set_text(vformat(TTR("GPU Time: %s ms"), rtos(gpu_time).pad_decimals(1))); - // Middle point is at 15 ms. - gpu_time_label->add_theme_color_override( - "font_color", - frame_time_gradient->get_color_at_offset( - Math::range_lerp(gpu_time, 0, 30, 0, 1))); - - const double fps = 1000.0 / gpu_time; - fps_label->set_text(vformat(TTR("FPS: %d"), fps)); - // Middle point is at 60 FPS. - fps_label->add_theme_color_override( - "font_color", - frame_time_gradient->get_color_at_offset( - Math::range_lerp(fps, 110, 10, 0, 1))); - } - - bool show_cinema = view_menu->get_popup()->is_item_checked(view_menu->get_popup()->get_item_index(VIEW_CINEMATIC_PREVIEW)); - cinema_label->set_visible(show_cinema); - if (show_cinema) { - float cinema_half_width = cinema_label->get_size().width / 2.0f; - cinema_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -cinema_half_width); - } - - if (lock_rotation) { - float locked_half_width = locked_label->get_size().width / 2.0f; - locked_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -locked_half_width); - } - } - - if (p_what == NOTIFICATION_ENTER_TREE) { - surface->connect("draw", callable_mp(this, &Node3DEditorViewport::_draw)); - surface->connect("gui_input", callable_mp(this, &Node3DEditorViewport::_sinput)); - surface->connect("mouse_entered", callable_mp(this, &Node3DEditorViewport::_surface_mouse_enter)); - surface->connect("mouse_exited", callable_mp(this, &Node3DEditorViewport::_surface_mouse_exit)); - surface->connect("focus_entered", callable_mp(this, &Node3DEditorViewport::_surface_focus_enter)); - surface->connect("focus_exited", callable_mp(this, &Node3DEditorViewport::_surface_focus_exit)); - - _init_gizmo_instance(index); - } - - if (p_what == NOTIFICATION_EXIT_TREE) { - _finish_gizmo_instances(); - } + if (lock_rotation) { + float locked_half_width = locked_label->get_size().width / 2.0f; + locked_label->set_anchor_and_offset(SIDE_LEFT, 0.5f, -locked_half_width); + } + } break; - if (p_what == NOTIFICATION_THEME_CHANGED) { - view_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); - preview_camera->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); + case NOTIFICATION_ENTER_TREE: { + surface->connect("draw", callable_mp(this, &Node3DEditorViewport::_draw)); + surface->connect("gui_input", callable_mp(this, &Node3DEditorViewport::_sinput)); + surface->connect("mouse_entered", callable_mp(this, &Node3DEditorViewport::_surface_mouse_enter)); + surface->connect("mouse_exited", callable_mp(this, &Node3DEditorViewport::_surface_mouse_exit)); + surface->connect("focus_entered", callable_mp(this, &Node3DEditorViewport::_surface_focus_enter)); + surface->connect("focus_exited", callable_mp(this, &Node3DEditorViewport::_surface_focus_exit)); + + _init_gizmo_instance(index); + } break; - view_menu->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - view_menu->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + case NOTIFICATION_EXIT_TREE: { + _finish_gizmo_instances(); + } break; - preview_camera->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("hover", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("pressed", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("focus", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - preview_camera->add_theme_style_override("disabled", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - - frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color"), SNAME("Editor"))); - frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), SNAME("Editor"))); + case NOTIFICATION_THEME_CHANGED: { + view_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + preview_camera->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); + Control *gui_base = EditorNode::get_singleton()->get_gui_base(); + + view_menu->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("pressed", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("focus", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + view_menu->add_theme_style_override("disabled", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + + preview_camera->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("hover", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("pressed", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("focus", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + preview_camera->add_theme_style_override("disabled", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + + frame_time_gradient->set_color(0, get_theme_color(SNAME("success_color"), SNAME("Editor"))); + frame_time_gradient->set_color(1, get_theme_color(SNAME("warning_color"), SNAME("Editor"))); + frame_time_gradient->set_color(2, get_theme_color(SNAME("error_color"), SNAME("Editor"))); + + info_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + cpu_time_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + gpu_time_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + fps_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + cinema_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + locked_label->add_theme_style_override("normal", gui_base->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); + } break; - info_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - cpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - gpu_time_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - fps_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - cinema_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - locked_label->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("Information3dViewport"), SNAME("EditorStyles"))); - } + case NOTIFICATION_DRAG_END: { + // Clear preview material when dropped outside applicable object. + if (spatial_editor->get_preview_material().is_valid() && !is_drag_successful()) { + _remove_preview_material(); + } + } break; + } } -static void draw_indicator_bar(Control &surface, real_t fill, const Ref<Texture2D> icon, const Ref<Font> font, int font_size, const String &text) { +static void draw_indicator_bar(Control &p_surface, real_t p_fill, const Ref<Texture2D> p_icon, const Ref<Font> p_font, int p_font_size, const String &p_text, const Color &p_color) { // Adjust bar size from control height - const Vector2 surface_size = surface.get_size(); + const Vector2 surface_size = p_surface.get_size(); const real_t h = surface_size.y / 2.0; const real_t y = (surface_size.y - h) / 2.0; const Rect2 r(10 * EDSCALE, y, 6 * EDSCALE, h); - const real_t sy = r.size.y * fill; + const real_t sy = r.size.y * p_fill; // Note: because this bar appears over the viewport, it has to stay readable for any background color // Draw both neutral dark and bright colors to account this - surface.draw_rect(r, Color(1, 1, 1, 0.2)); - surface.draw_rect(Rect2(r.position.x, r.position.y + r.size.y - sy, r.size.x, sy), Color(1, 1, 1, 0.6)); - surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE)); + p_surface.draw_rect(r, p_color * Color(1, 1, 1, 0.2)); + p_surface.draw_rect(Rect2(r.position.x, r.position.y + r.size.y - sy, r.size.x, sy), p_color * Color(1, 1, 1, 0.6)); + p_surface.draw_rect(r.grow(1), Color(0, 0, 0, 0.7), false, Math::round(EDSCALE)); - const Vector2 icon_size = icon->get_size(); + const Vector2 icon_size = p_icon->get_size(); const Vector2 icon_pos = Vector2(r.position.x - (icon_size.x - r.size.x) / 2, r.position.y + r.size.y + 2 * EDSCALE); - surface.draw_texture(icon, icon_pos); + p_surface.draw_texture(p_icon, icon_pos, p_color); // Draw text below the bar (for speed/zoom information). - surface.draw_string(font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), text, HALIGN_LEFT, -1.f, font_size); + p_surface.draw_string_outline(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, Math::round(2 * EDSCALE), Color(0, 0, 0)); + p_surface.draw_string(p_font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), p_text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, p_font_size, p_color); } void Node3DEditorViewport::_draw() { @@ -2910,7 +2737,7 @@ void Node3DEditorViewport::_draw() { over_plugin_list->forward_spatial_draw_over_viewport(surface); } - EditorPluginList *force_over_plugin_list = editor->get_editor_plugins_force_over(); + EditorPluginList *force_over_plugin_list = EditorNode::get_singleton()->get_editor_plugins_force_over(); if (!force_over_plugin_list->is_empty()) { force_over_plugin_list->forward_spatial_force_draw_over_viewport(surface); } @@ -2941,12 +2768,12 @@ void Node3DEditorViewport::_draw() { 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(5, get_size().y - 20); - font->draw_string(ci, msgpos + Point2(1, 1), message, HALIGN_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); - font->draw_string(ci, msgpos + Point2(-1, -1), message, HALIGN_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); - font->draw_string(ci, msgpos, message, HALIGN_LEFT, -1, font_size, Color(1, 1, 1, 1)); + font->draw_string(ci, msgpos + Point2(1, 1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); + font->draw_string(ci, msgpos + Point2(-1, -1), message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(0, 0, 0, 0.8)); + font->draw_string(ci, msgpos, message, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, Color(1, 1, 1, 1)); } - if (_edit.mode == TRANSFORM_ROTATE) { + if (_edit.mode == TRANSFORM_ROTATE && _edit.show_rotation_line) { Point2 center = _point_to_screen(_edit.center); Color handle_color; @@ -2974,7 +2801,7 @@ void Node3DEditorViewport::_draw() { Math::round(2 * EDSCALE)); } if (previewing) { - Size2 ss = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); + Size2 ss = Size2(ProjectSettings::get_singleton()->get("display/window/size/viewport_width"), ProjectSettings::get_singleton()->get("display/window/size/viewport_height")); float aspect = ss.aspect(); Size2 s = get_size(); @@ -3009,7 +2836,7 @@ void Node3DEditorViewport::_draw() { real_t scale_length = (max_speed - min_speed); if (!Math::is_zero_approx(scale_length)) { - real_t logscale_t = 1.0 - Math::log(1 + freelook_speed - min_speed) / Math::log(1 + scale_length); + real_t logscale_t = 1.0 - Math::log1p(freelook_speed - min_speed) / Math::log1p(scale_length); // Display the freelook speed to help the user get a better sense of scale. const int precision = freelook_speed < 1.0 ? 2 : 1; @@ -3019,7 +2846,8 @@ void Node3DEditorViewport::_draw() { get_theme_icon(SNAME("ViewportSpeed"), SNAME("EditorIcons")), get_theme_font(SNAME("font"), SNAME("Label")), get_theme_font_size(SNAME("font_size"), SNAME("Label")), - vformat("%s u/s", String::num(freelook_speed).pad_decimals(precision))); + vformat("%s u/s", String::num(freelook_speed).pad_decimals(precision)), + Color(1.0, 0.95, 0.7)); } } else { @@ -3031,7 +2859,7 @@ void Node3DEditorViewport::_draw() { real_t scale_length = (max_distance - min_distance); if (!Math::is_zero_approx(scale_length)) { - real_t logscale_t = 1.0 - Math::log(1 + cursor.distance - min_distance) / Math::log(1 + scale_length); + real_t logscale_t = 1.0 - Math::log1p(cursor.distance - min_distance) / Math::log1p(scale_length); // Display the zoom center distance to help the user get a better sense of scale. const int precision = cursor.distance < 1.0 ? 2 : 1; @@ -3041,7 +2869,8 @@ void Node3DEditorViewport::_draw() { get_theme_icon(SNAME("ViewportZoom"), SNAME("EditorIcons")), get_theme_font(SNAME("font"), SNAME("Label")), get_theme_font_size(SNAME("font_size"), SNAME("Label")), - vformat("%s u", String::num(cursor.distance).pad_decimals(precision))); + vformat("%s u", String::num(cursor.distance).pad_decimals(precision)), + Color(0.7, 0.95, 1.0)); } } } @@ -3170,7 +2999,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { continue; } - undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_rotation()); + undo_redo->add_do_method(sp, "set_rotation", camera_transform.basis.get_euler_normalized()); undo_redo->add_undo_method(sp, "set_rotation", sp->get_rotation()); } undo_redo->commit_action(); @@ -3181,7 +3010,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()); } @@ -3276,8 +3105,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); @@ -3304,6 +3133,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION: case VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE: case VIEW_DISPLAY_DEBUG_SSAO: + case VIEW_DISPLAY_DEBUG_SSIL: case VIEW_DISPLAY_DEBUG_PSSM_SPLITS: case VIEW_DISPLAY_DEBUG_DECAL_ATLAS: case VIEW_DISPLAY_DEBUG_SDFGI: @@ -3314,7 +3144,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, @@ -3330,6 +3161,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, VIEW_DISPLAY_DEBUG_SSAO, + VIEW_DISPLAY_DEBUG_SSIL, VIEW_DISPLAY_DEBUG_GI_BUFFER, VIEW_DISPLAY_DEBUG_DISABLE_LOD, VIEW_DISPLAY_DEBUG_PSSM_SPLITS, @@ -3341,6 +3173,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[] = { @@ -3358,6 +3191,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { Viewport::DEBUG_DRAW_VOXEL_GI_EMISSION, Viewport::DEBUG_DRAW_SCENE_LUMINANCE, Viewport::DEBUG_DRAW_SSAO, + Viewport::DEBUG_DRAW_SSIL, Viewport::DEBUG_DRAW_GI_BUFFER, Viewport::DEBUG_DRAW_DISABLE_LOD, Viewport::DEBUG_DRAW_PSSM_SPLITS, @@ -3369,6 +3203,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; @@ -3419,6 +3254,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()); @@ -3427,6 +3263,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()); @@ -3435,6 +3272,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()); @@ -3443,6 +3281,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()); @@ -3451,6 +3290,16 @@ void Node3DEditorViewport::_init_gizmo_instance(int p_idx) { RS::get_singleton()->instance_geometry_set_cast_shadows_setting(scale_plane_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); RS::get_singleton()->instance_set_layer_mask(scale_plane_gizmo_instance[i], layer); RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(scale_plane_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); + + axis_gizmo_instance[i] = RS::get_singleton()->instance_create(); + RS::get_singleton()->instance_set_base(axis_gizmo_instance[i], spatial_editor->get_axis_gizmo(i)->get_rid()); + RS::get_singleton()->instance_set_scenario(axis_gizmo_instance[i], get_tree()->get_root()->get_world_3d()->get_scenario()); + RS::get_singleton()->instance_set_visible(axis_gizmo_instance[i], true); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting(axis_gizmo_instance[i], RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_set_layer_mask(axis_gizmo_instance[i], layer); + RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(axis_gizmo_instance[i], RS::INSTANCE_FLAG_USE_BAKED_LIGHT, false); } // Rotation white outline @@ -3461,6 +3310,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() { @@ -3470,6 +3320,7 @@ void Node3DEditorViewport::_finish_gizmo_instances() { RS::get_singleton()->free(rotate_gizmo_instance[i]); RS::get_singleton()->free(scale_gizmo_instance[i]); RS::get_singleton()->free(scale_plane_gizmo_instance[i]); + RS::get_singleton()->free(axis_gizmo_instance[i]); } // Rotation white outline RS::get_singleton()->free(rotate_gizmo_instance[3]); @@ -3535,7 +3386,7 @@ void Node3DEditorViewport::_selection_result_pressed(int p_result) { void Node3DEditorViewport::_selection_menu_hide() { selection_results.clear(); selection_menu->clear(); - selection_menu->set_size(Vector2(0, 0)); + selection_menu->reset_size(); } void Node3DEditorViewport::set_can_preview(Camera3D *p_preview) { @@ -3562,15 +3413,16 @@ void Node3DEditorViewport::update_transform_gizmo_view() { RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], false); RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], false); RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], false); + RenderingServer::get_singleton()->instance_set_visible(axis_gizmo_instance[i], false); } // Rotation white outline RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], false); return; } - const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized(); - const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized(); - const Plane p(camera_xform.origin, camz); + const Vector3 camz = -camera_xform.get_basis().get_column(2).normalized(); + const Vector3 camy = -camera_xform.get_basis().get_column(1).normalized(); + const Plane p = Plane(camz, camera_xform.origin); const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON); const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y; const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y; @@ -3586,8 +3438,6 @@ void Node3DEditorViewport::update_transform_gizmo_view() { subviewport_container->get_stretch_shrink(); Vector3 scale = Vector3(1, 1, 1) * gizmo_scale; - xform.basis.scale(scale); - // if the determinant is zero, we should disable the gizmo from being rendered // this prevents supplying bad values to the renderer and then having to filter it out again if (xform.basis.determinant() == 0) { @@ -3604,18 +3454,34 @@ void Node3DEditorViewport::update_transform_gizmo_view() { } for (int i = 0; i < 3; i++) { - RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], xform); + Transform3D axis_angle = Transform3D(); + if (xform.basis.get_column(i).normalized().dot(xform.basis.get_column((i + 1) % 3).normalized()) < 1.0) { + axis_angle = axis_angle.looking_at(xform.basis.get_column(i).normalized(), xform.basis.get_column((i + 1) % 3).normalized()); + } + axis_angle.basis.scale(scale); + axis_angle.origin = xform.origin; + RenderingServer::get_singleton()->instance_set_transform(move_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(move_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)); - RenderingServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(move_plane_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(move_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE)); - RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); - RenderingServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(scale_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(scale_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE)); - RenderingServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], xform); + RenderingServer::get_singleton()->instance_set_transform(scale_plane_gizmo_instance[i], axis_angle); RenderingServer::get_singleton()->instance_set_visible(scale_plane_gizmo_instance[i], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE)); + RenderingServer::get_singleton()->instance_set_transform(axis_gizmo_instance[i], xform); } + + bool show_axes = spatial_editor->is_gizmo_visible() && _edit.mode != TRANSFORM_NONE; + RenderingServer *rs = RenderingServer::get_singleton(); + rs->instance_set_visible(axis_gizmo_instance[0], show_axes && (_edit.plane == TRANSFORM_X_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_XZ)); + rs->instance_set_visible(axis_gizmo_instance[1], show_axes && (_edit.plane == TRANSFORM_Y_AXIS || _edit.plane == TRANSFORM_XY || _edit.plane == TRANSFORM_YZ)); + rs->instance_set_visible(axis_gizmo_instance[2], show_axes && (_edit.plane == TRANSFORM_Z_AXIS || _edit.plane == TRANSFORM_XZ || _edit.plane == TRANSFORM_YZ)); + // Rotation white outline + xform.orthonormalize(); + xform.basis.scale(scale); RenderingServer::get_singleton()->instance_set_transform(rotate_gizmo_instance[3], xform); RenderingServer::get_singleton()->instance_set_visible(rotate_gizmo_instance[3], spatial_editor->is_gizmo_visible() && (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE)); } @@ -3807,7 +3673,7 @@ void Node3DEditorViewport::focus_selection() { Vector3 center; int count = 0; - List<Node *> &selection = editor_selection->get_selected_node_list(); + const List<Node *> &selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { Node3D *sp = Object::cast_to<Node3D>(E); @@ -3821,8 +3687,8 @@ void Node3DEditorViewport::focus_selection() { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - center += se->gizmo->get_subgizmo_transform(GE->key()).origin; + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + center += se->gizmo->get_subgizmo_transform(GE.key).origin; count++; } } @@ -3845,7 +3711,7 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, } Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const { - const float MAX_DISTANCE = 10; + const float MAX_DISTANCE = 50.0; Vector3 world_ray = _get_ray(p_pos); Vector3 world_pos = _get_ray_pos(p_pos); @@ -3853,9 +3719,13 @@ Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const Vector3 point = world_pos + world_ray * MAX_DISTANCE; PhysicsDirectSpaceState3D *ss = get_tree()->get_root()->get_world_3d()->get_direct_space_state(); - PhysicsDirectSpaceState3D::RayResult result; - if (ss->intersect_ray(world_pos, world_pos + world_ray * MAX_DISTANCE, result)) { + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = world_pos; + ray_params.to = world_pos + world_ray * MAX_DISTANCE; + + PhysicsDirectSpaceState3D::RayResult result; + if (ss->intersect_ray(ray_params, result)) { point = result.position; } @@ -3875,7 +3745,7 @@ AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, boo if (child) { AABB child_bounds = _calculate_spatial_bounds(child, false); - if (bounds.size == Vector3() && p_parent->get_class_name() == StringName("Node3D")) { + if (bounds.size == Vector3() && Object::cast_to<Node3D>(p_parent)) { bounds = child_bounds; } else { bounds.merge_with(child_bounds); @@ -3883,7 +3753,7 @@ AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, boo } } - if (bounds.size == Vector3() && p_parent->get_class_name() != StringName("Node3D")) { + if (bounds.size == Vector3() && !Object::cast_to<Node3D>(p_parent)) { bounds = AABB(Vector3(-0.2, -0.2, -0.2), Vector3(0.4, 0.4, 0.4)); } @@ -3894,10 +3764,41 @@ AABB Node3DEditorViewport::_calculate_spatial_bounds(const Node3D *p_parent, boo return bounds; } -void Node3DEditorViewport::_create_preview(const Vector<String> &files) const { +Node *Node3DEditorViewport::_sanitize_preview_node(Node *p_node) const { + Node3D *node_3d = Object::cast_to<Node3D>(p_node); + if (node_3d == nullptr) { + Node3D *replacement_node = memnew(Node3D); + replacement_node->set_name(p_node->get_name()); + p_node->replace_by(replacement_node); + memdelete(p_node); + p_node = replacement_node; + } else { + VisualInstance3D *visual_instance = Object::cast_to<VisualInstance3D>(node_3d); + if (visual_instance == nullptr) { + Node3D *replacement_node = memnew(Node3D); + replacement_node->set_name(node_3d->get_name()); + replacement_node->set_visible(node_3d->is_visible()); + replacement_node->set_transform(node_3d->get_transform()); + replacement_node->set_rotation_edit_mode(node_3d->get_rotation_edit_mode()); + replacement_node->set_rotation_order(node_3d->get_rotation_order()); + replacement_node->set_as_top_level(node_3d->is_set_as_top_level()); + p_node->replace_by(replacement_node); + memdelete(p_node); + p_node = replacement_node; + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + _sanitize_preview_node(p_node->get_child(i)); + } + + return p_node; +} + +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)); @@ -3910,29 +3811,130 @@ void Node3DEditorViewport::_create_preview(const Vector<String> &files) const { if (scene.is_valid()) { Node *instance = scene->instantiate(); if (instance) { + instance = _sanitize_preview_node(instance); preview_node->add_child(instance); } } } - editor->get_scene_root()->add_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->add_child(preview_node); } } *preview_bounds = _calculate_spatial_bounds(preview_node); } -void Node3DEditorViewport::_remove_preview() { +void Node3DEditorViewport::_remove_preview_node() { if (preview_node->get_parent()) { for (int i = preview_node->get_child_count() - 1; i >= 0; i--) { Node *node = preview_node->get_child(i); node->queue_delete(); preview_node->remove_child(node); } - editor->get_scene_root()->remove_child(preview_node); + EditorNode::get_singleton()->get_scene_root()->remove_child(preview_node); + } +} + +bool Node3DEditorViewport::_apply_preview_material(ObjectID p_target, const Point2 &p_point) const { + _reset_preview_material(); + + if (p_target.is_null()) { + return false; + } + + spatial_editor->set_preview_material_target(p_target); + + Object *target_inst = ObjectDB::get_instance(p_target); + + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); + + MeshInstance3D *mesh_instance = Object::cast_to<MeshInstance3D>(target_inst); + if (is_ctrl && mesh_instance) { + Ref<Mesh> mesh = mesh_instance->get_mesh(); + int surface_count = mesh->get_surface_count(); + + Vector3 world_ray = _get_ray(p_point); + Vector3 world_pos = _get_ray_pos(p_point); + + int closest_surface = -1; + float closest_dist = 1e20; + + Transform3D gt = mesh_instance->get_global_transform(); + + Transform3D ai = gt.affine_inverse(); + Vector3 xform_ray = ai.basis.xform(world_ray).normalized(); + Vector3 xform_pos = ai.xform(world_pos); + + for (int surface = 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_filename() == p_target_scene_path) { + if (p_desired_node->get_scene_file_path() == p_target_scene_path) { return true; } @@ -3947,7 +3949,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)); @@ -3959,7 +3961,23 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po if (mesh != nullptr) { MeshInstance3D *mesh_instance = memnew(MeshInstance3D); mesh_instance->set_mesh(mesh); - mesh_instance->set_name(path.get_file().get_basename()); + + // Adjust casing according to project setting. The file name is expected to be in snake_case, but will work for others. + String name = path.get_file().get_basename(); + switch (ProjectSettings::get_singleton()->get("editor/node_naming/name_casing").operator int()) { + case NAME_CASING_PASCAL_CASE: + name = name.capitalize().replace(" ", ""); + break; + case NAME_CASING_CAMEL_CASE: + name = name.capitalize().replace(" ", ""); + name[0] = name.to_lower()[0]; + break; + case NAME_CASING_SNAKE_CASE: + name = name.capitalize().replace(" ", "_").to_lower(); + break; + } + mesh_instance->set_name(name); + instantiated_scene = mesh_instance; } else { if (!scene.is_valid()) { // invalid scene @@ -3974,26 +3992,26 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po return false; } - if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + if (!EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path().is_empty()) { // cyclical instancing + if (_cyclical_dependency_exists(EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } } if (scene != nullptr) { - instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); } - editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); - editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); + editor_data->get_undo_redo().add_do_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", editor->get_edited_scene()->get_path_to(parent), path, new_name); - editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); + 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) { @@ -4013,7 +4031,26 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po } 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; @@ -4021,7 +4058,7 @@ void Node3DEditorViewport::_perform_drop_data() { 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; } @@ -4051,7 +4088,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"]; @@ -4060,27 +4097,46 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant ResourceLoader::get_recognized_extensions_for_type("PackedScene", &scene_extensions); List<String> mesh_extensions; ResourceLoader::get_recognized_extensions_for_type("Mesh", &mesh_extensions); + List<String> material_extensions; + ResourceLoader::get_recognized_extensions_for_type("Material", &material_extensions); + List<String> texture_extensions; + ResourceLoader::get_recognized_extensions_for_type("Texture", &texture_extensions); for (int i = 0; i < files.size(); i++) { - if (mesh_extensions.find(files[i].get_extension()) || scene_extensions.find(files[i].get_extension())) { - RES res = ResourceLoader::load(files[i]); + // 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()) || + 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; } - - String type = res->get_class(); - if (type == "PackedScene") { - Ref<PackedScene> sdata = ResourceLoader::load(files[i]); - Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + Ref<PackedScene> scn = res; + Ref<Material> mat = res; + Ref<Texture2D> tex = res; + if (scn.is_valid()) { + Node *instantiated_scene = scn->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); if (!instantiated_scene) { continue; } memdelete(instantiated_scene); - } else if (ClassDB::is_parent_class(type, "Mesh")) { - Ref<Mesh> mesh = ResourceLoader::load(files[i]); - if (!mesh.is_valid()) { - continue; + } else if (mat.is_valid()) { + Ref<BaseMaterial3D> base_mat = res; + Ref<ShaderMaterial> shader_mat = res; + + if (base_mat.is_null() && !shader_mat.is_null()) { + break; } + + spatial_editor->set_preview_material(mat); + break; + } else if (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; } @@ -4089,19 +4145,30 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant } } 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; } - return can_instantiate; + if (spatial_editor->get_preview_material().is_valid()) { + preview_material_label->show(); + preview_material_label_desc->show(); + + ObjectID new_preview_material_target = _select_ray(p_point); + return _apply_preview_material(new_preview_material_target, p_point); + } + + return false; } void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { @@ -4109,8 +4176,8 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ return; } - bool is_shift = Input::get_singleton()->is_key_pressed(KEY_SHIFT); - bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); + bool is_shift = Input::get_singleton()->is_key_pressed(Key::SHIFT); + bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CTRL); selected_files.clear(); Dictionary d = p_data; @@ -4118,8 +4185,8 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ selected_files = d["files"]; } - List<Node *> selected_nodes = editor->get_editor_selection()->get_selected_node_list(); - Node *root_node = editor->get_edited_scene(); + List<Node *> selected_nodes = EditorNode::get_singleton()->get_editor_selection()->get_selected_node_list(); + Node *root_node = EditorNode::get_singleton()->get_edited_scene(); if (selected_nodes.size() == 1) { Node *selected_node = selected_nodes[0]; target_node = root_node; @@ -4132,15 +4199,14 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ if (root_node) { target_node = root_node; } else { - accept->set_text(TTR("Cannot drag and drop into scene with no root node.")); - accept->popup_centered(); - _remove_preview(); - return; + // Create a root node so we can add child nodes to it. + SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D)); + target_node = get_tree()->get_edited_scene_root(); } } else { accept->set_text(TTR("Cannot drag and drop into multiple selected nodes.")); accept->popup_centered(); - _remove_preview(); + _remove_preview_node(); return; } @@ -4149,20 +4215,432 @@ void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_ _perform_drop_data(); } -Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, EditorNode *p_editor, int p_index) { +void Node3DEditorViewport::begin_transform(TransformMode p_mode, bool instant) { + if (get_selected_count() > 0) { + _edit.mode = p_mode; + _compute_edit(_edit.mouse_pos); + _edit.instant = instant; + _edit.snap = spatial_editor->is_snap_enabled(); + } +} + +void Node3DEditorViewport::commit_transform() { + ERR_FAIL_COND(_edit.mode == TRANSFORM_NONE); + static const char *_transform_name[4] = { + TTRC("None"), + TTRC("Rotate"), + // TRANSLATORS: This refers to the movement that changes the position of an object. + TTRC("Translate"), + TTRC("Scale"), + }; + undo_redo->create_action(_transform_name[_edit.mode]); + + List<Node *> &selection = editor_selection->get_selected_node_list(); + + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + Node3D *sp = Object::cast_to<Node3D>(E->get()); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + undo_redo->add_do_method(sp, "set_global_transform", sp->get_global_gizmo_transform()); + undo_redo->add_undo_method(sp, "set_global_transform", se->original); + } + undo_redo->commit_action(); + + finish_transform(); + set_message(""); +} + +void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) { + Vector3 ray_pos = _get_ray_pos(p_mousepos); + Vector3 ray = _get_ray(p_mousepos); + double snap = EDITOR_GET("interface/inspector/default_float_step"); + int snap_step_decimals = Math::range_step_decimals(snap); + + switch (_edit.mode) { + case TRANSFORM_SCALE: { + Vector3 motion_mask; + Plane plane; + bool plane_mv = false; + + switch (_edit.plane) { + case TRANSFORM_VIEW: + motion_mask = Vector3(0, 0, 0); + plane = Plane(_get_camera_normal(), _edit.center); + break; + case TRANSFORM_X_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Y_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Z_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_YZ: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XZ: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XY: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized() + spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center); + plane_mv = true; + break; + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) { + break; + } + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { + break; + } + + Vector3 motion = intersection - click; + if (_edit.plane != TRANSFORM_VIEW) { + if (!plane_mv) { + motion = motion_mask.dot(motion) * motion_mask; + + } else { + // Alternative planar scaling mode + if (p_shift) { + motion = motion_mask.dot(motion) * motion_mask; + } + } + + } else { + const real_t center_click_dist = click.distance_to(_edit.center); + const real_t center_inters_dist = intersection.distance_to(_edit.center); + if (center_click_dist == 0) { + break; + } + + const real_t scale = center_inters_dist - center_click_dist; + motion = Vector3(scale, scale, scale); + } + + motion /= click.distance_to(_edit.center); + + // Disable local transformation for TRANSFORM_VIEW + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); + + if (_edit.snap || spatial_editor->is_snap_enabled()) { + snap = spatial_editor->get_scale_snap() / 100; + } + Vector3 motion_snapped = motion; + motion_snapped.snap(Vector3(snap, snap, snap)); + // This might not be necessary anymore after issue #288 is solved (in 4.0?). + // TRANSLATORS: Refers to changing the scale of a node in the 3D editor. + set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + + String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); + motion = _edit.original.basis.inverse().xform(motion); + + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo. + if (!local_coords) { + new_xform = se->original.affine_inverse() * new_xform; + } + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); + _transform_gizmo_apply(se->sp, new_xform, local_coords); + } + } + + spatial_editor->update_transform_gizmo(); + surface->update(); + + } break; + + case TRANSFORM_TRANSLATE: { + Vector3 motion_mask; + Plane plane; + bool plane_mv = false; + + switch (_edit.plane) { + case TRANSFORM_VIEW: + plane = Plane(_get_camera_normal(), _edit.center); + break; + case TRANSFORM_X_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Y_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_Z_AXIS: + motion_mask = spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); + break; + case TRANSFORM_YZ: + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(0).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XZ: + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(1).normalized(), _edit.center); + plane_mv = true; + break; + case TRANSFORM_XY: + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_column(2).normalized(), _edit.center); + plane_mv = true; + break; + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) { + break; + } + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { + break; + } + + Vector3 motion = intersection - click; + if (_edit.plane != TRANSFORM_VIEW) { + if (!plane_mv) { + motion = motion_mask.dot(motion) * motion_mask; + } + } + + // Disable local transformation for TRANSFORM_VIEW + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); + + if (_edit.snap || spatial_editor->is_snap_enabled()) { + snap = spatial_editor->get_translate_snap(); + } + Vector3 motion_snapped = motion; + motion_snapped.snap(Vector3(snap, snap, snap)); + // TRANSLATORS: Refers to changing the position of a node in the 3D editor. + set_message(TTR("Translating:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " + + String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")"); + motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion); + + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords, true); // Force orthogonal with subgizmo. + new_xform = se->original.affine_inverse() * new_xform; + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); + _transform_gizmo_apply(se->sp, new_xform, false); + } + } + + spatial_editor->update_transform_gizmo(); + surface->update(); + + } break; + + case TRANSFORM_ROTATE: { + Plane plane = Plane(_get_camera_normal(), _edit.center); + + Vector3 local_axis; + Vector3 global_axis; + switch (_edit.plane) { + case TRANSFORM_VIEW: + // local_axis unused + global_axis = _get_camera_normal(); + break; + case TRANSFORM_X_AXIS: + local_axis = Vector3(1, 0, 0); + break; + case TRANSFORM_Y_AXIS: + local_axis = Vector3(0, 1, 0); + break; + case TRANSFORM_Z_AXIS: + local_axis = Vector3(0, 0, 1); + break; + case TRANSFORM_YZ: + case TRANSFORM_XZ: + case TRANSFORM_XY: + break; + } + + if (_edit.plane != TRANSFORM_VIEW) { + global_axis = spatial_editor->get_gizmo_transform().basis.xform(local_axis).normalized(); + } + + Vector3 intersection; + if (!plane.intersects_ray(ray_pos, ray, &intersection)) { + break; + } + + Vector3 click; + if (!plane.intersects_ray(_edit.click_ray_pos, _edit.click_ray, &click)) { + break; + } + + static const float orthogonal_threshold = Math::cos(Math::deg2rad(87.0f)); + bool axis_is_orthogonal = ABS(plane.normal.dot(global_axis)) < orthogonal_threshold; + + double angle = 0.0f; + if (axis_is_orthogonal) { + _edit.show_rotation_line = false; + Vector3 projection_axis = plane.normal.cross(global_axis); + Vector3 delta = intersection - click; + float projection = delta.dot(projection_axis); + angle = (projection * (Math_PI / 2.0f)) / (gizmo_scale * GIZMO_CIRCLE_SIZE); + } else { + _edit.show_rotation_line = true; + Vector3 click_axis = (click - _edit.center).normalized(); + Vector3 current_axis = (intersection - _edit.center).normalized(); + angle = click_axis.signed_angle_to(current_axis, global_axis); + } + + if (_edit.snap || spatial_editor->is_snap_enabled()) { + snap = spatial_editor->get_rotate_snap(); + } + angle = Math::rad2deg(angle) + snap * 0.5; //else it won't reach +180 + angle -= Math::fmod(angle, snap); + set_message(vformat(TTR("Rotating %s degrees."), String::num(angle, snap_step_decimals))); + angle = Math::deg2rad(angle); + + bool local_coords = (spatial_editor->are_local_coords_enabled() && _edit.plane != TRANSFORM_VIEW); // Disable local transformation for TRANSFORM_VIEW + + List<Node *> &selection = editor_selection->get_selected_node_list(); + for (Node *E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E); + if (!sp) { + continue; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (!se) { + continue; + } + + if (sp->has_meta("_edit_lock_")) { + continue; + } + + Vector3 compute_axis = local_coords ? local_axis : global_axis; + if (se->gizmo.is_valid()) { + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; + + Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords, true); // Force orthogonal with subgizmo. + if (!local_coords) { + new_xform = se->original.affine_inverse() * new_xform; + } + se->gizmo->set_subgizmo_transform(GE.key, new_xform); + } + } else { + Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS); + _transform_gizmo_apply(se->sp, new_xform, local_coords); + } + } + + spatial_editor->update_transform_gizmo(); + surface->update(); + + } break; + default: { + } + } +} + +void Node3DEditorViewport::finish_transform() { + spatial_editor->set_local_coords_enabled(_edit.original_local); + spatial_editor->update_transform_gizmo(); + _edit.mode = TRANSFORM_NONE; + _edit.instant = false; + surface->update(); +} + +// Register a shortcut and also add it as an input action with the same events. +void Node3DEditorViewport::register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode) { + Ref<Shortcut> sc = ED_SHORTCUT(p_path, p_name, p_keycode); + shortcut_changed_callback(sc, p_path); + // Connect to the change event on the shortcut so the input binding can be updated. + sc->connect("changed", callable_mp(this, &Node3DEditorViewport::shortcut_changed_callback).bind(sc, p_path)); +} + +// Update the action in the InputMap to the provided shortcut events. +void Node3DEditorViewport::shortcut_changed_callback(const Ref<Shortcut> p_shortcut, const String &p_shortcut_path) { + InputMap *im = InputMap::get_singleton(); + if (im->has_action(p_shortcut_path)) { + im->action_erase_events(p_shortcut_path); + } else { + im->add_action(p_shortcut_path); + } + + for (int i = 0; i < p_shortcut->get_events().size(); i++) { + im->action_add_event(p_shortcut_path, p_shortcut->get_events()[i]); + } +} + +Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p_index) { cpu_time_history_index = 0; gpu_time_history_index = 0; _edit.mode = TRANSFORM_NONE; _edit.plane = TRANSFORM_VIEW; _edit.snap = true; + _edit.show_rotation_line = true; + _edit.instant = false; _edit.gizmo_handle = -1; + _edit.gizmo_handle_secondary = false; index = p_index; - editor = p_editor; - editor_data = editor->get_scene_tree_dock()->get_editor_data(); - editor_selection = editor->get_editor_selection(); - undo_redo = editor->get_undo_redo(); + editor_data = SceneTreeDock::get_singleton()->get_editor_data(); + editor_selection = EditorNode::get_singleton()->get_editor_selection(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); orthogonal = false; auto_orthogonal = false; @@ -4175,7 +4653,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito subviewport_container = c; c->set_stretch(true); add_child(c); - c->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + c->set_anchors_and_offsets_preset(Control::PRESET_FULL_RECT); viewport = memnew(SubViewport); viewport->set_disable_input(true); @@ -4183,7 +4661,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito 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); @@ -4194,7 +4672,8 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito VBoxContainer *vbox = memnew(VBoxContainer); surface->add_child(vbox); - vbox->set_position(Point2(10, 10) * EDSCALE); + vbox->set_offset(SIDE_LEFT, 10 * EDSCALE); + vbox->set_offset(SIDE_TOP, 10 * EDSCALE); view_menu = memnew(MenuButton); view_menu->set_flat(false); @@ -4203,6 +4682,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito vbox->add_child(view_menu); display_submenu = memnew(PopupMenu); + view_menu->get_popup()->set_hide_on_checkable_item_selection(false); view_menu->get_popup()->add_child(display_submenu); view_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("spatial_editor/top_view"), VIEW_TOP); @@ -4226,12 +4706,13 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_lighting", TTR("Display Lighting")), VIEW_DISPLAY_LIGHTING); view_menu->get_popup()->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/view_display_unshaded", TTR("Display Unshaded")), VIEW_DISPLAY_SHADELESS); view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(VIEW_DISPLAY_NORMAL), true); + display_submenu->set_hide_on_checkable_item_selection(false); display_submenu->add_radio_check_item(TTR("Directional Shadow Splits"), VIEW_DISPLAY_DEBUG_PSSM_SPLITS); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("Normal Buffer"), VIEW_DISPLAY_NORMAL_BUFFER); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("Shadow Atlas"), VIEW_DISPLAY_DEBUG_SHADOW_ATLAS); - display_submenu->add_radio_check_item(TTR("Directional Shadow"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS); + display_submenu->add_radio_check_item(TTR("Directional Shadow Map"), VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS); display_submenu->add_separator(); @@ -4245,16 +4726,18 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito display_submenu->add_radio_check_item(TTR("Scene Luminance"), VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("SSAO"), VIEW_DISPLAY_DEBUG_SSAO); + display_submenu->add_radio_check_item(TTR("SSIL"), VIEW_DISPLAY_DEBUG_SSIL); display_submenu->add_separator(); - display_submenu->add_radio_check_item(TTR("GI Buffer"), VIEW_DISPLAY_DEBUG_GI_BUFFER); + display_submenu->add_radio_check_item(TTR("VoxelGI/SDFGI Buffer"), VIEW_DISPLAY_DEBUG_GI_BUFFER); display_submenu->add_separator(); - display_submenu->add_radio_check_item(TTR("Disable LOD"), VIEW_DISPLAY_DEBUG_DISABLE_LOD); + display_submenu->add_radio_check_item(TTR("Disable Mesh LOD"), VIEW_DISPLAY_DEBUG_DISABLE_LOD); display_submenu->add_separator(); - display_submenu->add_radio_check_item(TTR("Omni Light Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS); - display_submenu->add_radio_check_item(TTR("Spot Light Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS); + display_submenu->add_radio_check_item(TTR("OmniLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_OMNI_LIGHTS); + display_submenu->add_radio_check_item(TTR("SpotLight3D Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_SPOT_LIGHTS); display_submenu->add_radio_check_item(TTR("Decal Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_DECALS); - display_submenu->add_radio_check_item(TTR("Reflection Probe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES); + display_submenu->add_radio_check_item(TTR("ReflectionProbe Cluster"), VIEW_DISPLAY_DEBUG_CLUSTER_REFLECTION_PROBES); display_submenu->add_radio_check_item(TTR("Occlusion Culling Buffer"), VIEW_DISPLAY_DEBUG_OCCLUDERS); + display_submenu->add_radio_check_item(TTR("Motion Vectors"), VIEW_DISPLAY_MOTION_VECTORS); display_submenu->set_name("display_advanced"); view_menu->get_popup()->add_submenu_item(TTR("Display Advanced..."), "display_advanced", VIEW_DISPLAY_ADVANCED); @@ -4292,7 +4775,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito const int wireframe_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_WIREFRAME); const int overdraw_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_OVERDRAW); const int shadeless_idx = view_menu->get_popup()->get_item_index(VIEW_DISPLAY_SHADELESS); - const String unsupported_tooltip = TTR("Not available when using the GLES2 renderer."); + const String unsupported_tooltip = TTR("Not available when using the OpenGL renderer."); view_menu->get_popup()->set_item_disabled(normal_idx, true); view_menu->get_popup()->set_item_tooltip(normal_idx, unsupported_tooltip); @@ -4304,18 +4787,29 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito view_menu->get_popup()->set_item_tooltip(shadeless_idx, unsupported_tooltip); } - ED_SHORTCUT("spatial_editor/freelook_left", TTR("Freelook Left"), KEY_A); - ED_SHORTCUT("spatial_editor/freelook_right", TTR("Freelook Right"), KEY_D); - ED_SHORTCUT("spatial_editor/freelook_forward", TTR("Freelook Forward"), KEY_W); - ED_SHORTCUT("spatial_editor/freelook_backwards", TTR("Freelook Backwards"), KEY_S); - ED_SHORTCUT("spatial_editor/freelook_up", TTR("Freelook Up"), KEY_E); - ED_SHORTCUT("spatial_editor/freelook_down", TTR("Freelook Down"), KEY_Q); - ED_SHORTCUT("spatial_editor/freelook_speed_modifier", TTR("Freelook Speed Modifier"), KEY_SHIFT); - ED_SHORTCUT("spatial_editor/freelook_slow_modifier", TTR("Freelook Slow Modifier"), KEY_ALT); + register_shortcut_action("spatial_editor/freelook_left", TTR("Freelook Left"), Key::A); + register_shortcut_action("spatial_editor/freelook_right", TTR("Freelook Right"), Key::D); + register_shortcut_action("spatial_editor/freelook_forward", TTR("Freelook Forward"), Key::W); + register_shortcut_action("spatial_editor/freelook_backwards", TTR("Freelook Backwards"), Key::S); + register_shortcut_action("spatial_editor/freelook_up", TTR("Freelook Up"), Key::E); + register_shortcut_action("spatial_editor/freelook_down", TTR("Freelook Down"), Key::Q); + register_shortcut_action("spatial_editor/freelook_speed_modifier", TTR("Freelook Speed Modifier"), Key::SHIFT); + register_shortcut_action("spatial_editor/freelook_slow_modifier", TTR("Freelook Slow Modifier"), Key::ALT); + + ED_SHORTCUT("spatial_editor/lock_transform_x", TTR("Lock Transformation to X axis"), Key::X); + ED_SHORTCUT("spatial_editor/lock_transform_y", TTR("Lock Transformation to Y axis"), Key::Y); + ED_SHORTCUT("spatial_editor/lock_transform_z", TTR("Lock Transformation to Z axis"), Key::Z); + ED_SHORTCUT("spatial_editor/lock_transform_yz", TTR("Lock Transformation to YZ plane"), KeyModifierMask::SHIFT | Key::X); + ED_SHORTCUT("spatial_editor/lock_transform_xz", TTR("Lock Transformation to XZ plane"), KeyModifierMask::SHIFT | Key::Y); + ED_SHORTCUT("spatial_editor/lock_transform_xy", TTR("Lock Transformation to XY plane"), KeyModifierMask::SHIFT | Key::Z); + ED_SHORTCUT("spatial_editor/cancel_transform", TTR("Cancel Transformation"), Key::ESCAPE); + ED_SHORTCUT("spatial_editor/instant_translate", TTR("Begin Translate Transformation")); + ED_SHORTCUT("spatial_editor/instant_rotate", TTR("Begin Rotate Transformation")); + ED_SHORTCUT("spatial_editor/instant_scale", TTR("Begin Scale Transformation")); preview_camera = memnew(CheckBox); preview_camera->set_text(TTR("Preview")); - preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KEY_MASK_CMD | KEY_P)); + preview_camera->set_shortcut(ED_SHORTCUT("spatial_editor/toggle_camera_preview", TTR("Toggle Camera Preview"), KeyModifierMask::CMD | Key::P)); vbox->add_child(preview_camera); preview_camera->set_h_size_flags(0); preview_camera->hide(); @@ -4338,7 +4832,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito cinema_label = memnew(Label); cinema_label->set_anchor_and_offset(SIDE_TOP, ANCHOR_BEGIN, 10 * EDSCALE); cinema_label->set_h_grow_direction(GROW_DIRECTION_END); - cinema_label->set_align(Label::ALIGN_CENTER); + cinema_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); surface->add_child(cinema_label); cinema_label->set_text(TTR("Cinematic Preview")); cinema_label->hide(); @@ -4349,7 +4843,7 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito locked_label->set_anchor_and_offset(SIDE_BOTTOM, ANCHOR_END, -10 * EDSCALE); locked_label->set_h_grow_direction(GROW_DIRECTION_END); locked_label->set_v_grow_direction(GROW_DIRECTION_BEGIN); - locked_label->set_align(Label::ALIGN_CENTER); + locked_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); surface->add_child(locked_label); locked_label->set_text(TTR("View Rotation Locked")); locked_label->hide(); @@ -4363,12 +4857,29 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito 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); @@ -4429,7 +4940,7 @@ void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { Vector2 size = get_size(); @@ -4513,197 +5024,202 @@ void Node3DEditorViewportContainer::gui_input(const Ref<InputEvent> &p_event) { } void Node3DEditorViewportContainer::_notification(int p_what) { - if (p_what == NOTIFICATION_MOUSE_ENTER || p_what == NOTIFICATION_MOUSE_EXIT) { - mouseover = (p_what == NOTIFICATION_MOUSE_ENTER); - update(); - } + switch (p_what) { + case NOTIFICATION_MOUSE_ENTER: + case NOTIFICATION_MOUSE_EXIT: { + mouseover = (p_what == NOTIFICATION_MOUSE_ENTER); + update(); + } break; - if (p_what == NOTIFICATION_DRAW && mouseover) { - Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer")); - Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer")); + case NOTIFICATION_DRAW: { + if (mouseover) { + Ref<Texture2D> h_grabber = get_theme_icon(SNAME("grabber"), SNAME("HSplitContainer")); + Ref<Texture2D> v_grabber = get_theme_icon(SNAME("grabber"), SNAME("VSplitContainer")); - Ref<Texture2D> hdiag_grabber = get_theme_icon(SNAME("GuiViewportHdiagsplitter"), SNAME("EditorIcons")); - Ref<Texture2D> vdiag_grabber = get_theme_icon(SNAME("GuiViewportVdiagsplitter"), SNAME("EditorIcons")); - Ref<Texture2D> vh_grabber = get_theme_icon(SNAME("GuiViewportVhsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> hdiag_grabber = get_theme_icon(SNAME("GuiViewportHdiagsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> vdiag_grabber = get_theme_icon(SNAME("GuiViewportVdiagsplitter"), SNAME("EditorIcons")); + Ref<Texture2D> vh_grabber = get_theme_icon(SNAME("GuiViewportVhsplitter"), SNAME("EditorIcons")); - Vector2 size = get_size(); + Vector2 size = get_size(); - int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); - int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); - int mid_w = size.width * ratio_h; - int mid_h = size.height * ratio_v; + int mid_w = size.width * ratio_h; + int mid_h = size.height * ratio_v; - int size_left = mid_w - h_sep / 2; - int size_bottom = size.height - mid_h - v_sep / 2; + int size_left = mid_w - h_sep / 2; + int size_bottom = size.height - mid_h - v_sep / 2; - switch (view) { - case VIEW_USE_1_VIEWPORT: { - // Nothing to show. + switch (view) { + case VIEW_USE_1_VIEWPORT: { + // Nothing to show. - } break; - case VIEW_USE_2_VIEWPORTS: { - draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_VSPLIT); + } break; + case VIEW_USE_2_VIEWPORTS: { + draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_VSPLIT); - } break; - case VIEW_USE_2_VIEWPORTS_ALT: { - draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); - set_default_cursor_shape(CURSOR_HSPLIT); + } break; + case VIEW_USE_2_VIEWPORTS_ALT: { + draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); + set_default_cursor_shape(CURSOR_HSPLIT); - } break; - case VIEW_USE_3_VIEWPORTS: { - if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { - draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4)); - set_default_cursor_shape(CURSOR_DRAG); - } else if ((hovering_v && !dragging_h) || dragging_v) { - draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_VSPLIT); - } else if (hovering_h || dragging_h) { - draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, mid_h + v_grabber->get_height() / 2 + (size_bottom - h_grabber->get_height()) / 2)); - set_default_cursor_shape(CURSOR_HSPLIT); - } + } break; + case VIEW_USE_3_VIEWPORTS: { + if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { + draw_texture(hdiag_grabber, Vector2(mid_w - hdiag_grabber->get_width() / 2, mid_h - v_grabber->get_height() / 4)); + set_default_cursor_shape(CURSOR_DRAG); + } else if ((hovering_v && !dragging_h) || dragging_v) { + draw_texture(v_grabber, Vector2((size.width - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_VSPLIT); + } else if (hovering_h || dragging_h) { + draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, mid_h + v_grabber->get_height() / 2 + (size_bottom - h_grabber->get_height()) / 2)); + set_default_cursor_shape(CURSOR_HSPLIT); + } - } break; - case VIEW_USE_3_VIEWPORTS_ALT: { - if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { - draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_DRAG); - } else if ((hovering_v && !dragging_h) || dragging_v) { - draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); - set_default_cursor_shape(CURSOR_VSPLIT); - } else if (hovering_h || dragging_h) { - draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); - set_default_cursor_shape(CURSOR_HSPLIT); - } + } break; + case VIEW_USE_3_VIEWPORTS_ALT: { + if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { + draw_texture(vdiag_grabber, Vector2(mid_w - vdiag_grabber->get_width() + v_grabber->get_height() / 4, mid_h - vdiag_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_DRAG); + } else if ((hovering_v && !dragging_h) || dragging_v) { + draw_texture(v_grabber, Vector2((size_left - v_grabber->get_width()) / 2, mid_h - v_grabber->get_height() / 2)); + set_default_cursor_shape(CURSOR_VSPLIT); + } else if (hovering_h || dragging_h) { + draw_texture(h_grabber, Vector2(mid_w - h_grabber->get_width() / 2, (size.height - h_grabber->get_height()) / 2)); + set_default_cursor_shape(CURSOR_HSPLIT); + } - } break; - case VIEW_USE_4_VIEWPORTS: { - Vector2 half(mid_w, mid_h); - if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { - draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0); - set_default_cursor_shape(CURSOR_DRAG); - } else if ((hovering_v && !dragging_h) || dragging_v) { - draw_texture(v_grabber, half - v_grabber->get_size() / 2.0); - set_default_cursor_shape(CURSOR_VSPLIT); - } else if (hovering_h || dragging_h) { - draw_texture(h_grabber, half - h_grabber->get_size() / 2.0); - set_default_cursor_shape(CURSOR_HSPLIT); - } + } break; + case VIEW_USE_4_VIEWPORTS: { + Vector2 half(mid_w, mid_h); + if ((hovering_v && hovering_h && !dragging_v && !dragging_h) || (dragging_v && dragging_h)) { + draw_texture(vh_grabber, half - vh_grabber->get_size() / 2.0); + set_default_cursor_shape(CURSOR_DRAG); + } else if ((hovering_v && !dragging_h) || dragging_v) { + draw_texture(v_grabber, half - v_grabber->get_size() / 2.0); + set_default_cursor_shape(CURSOR_VSPLIT); + } else if (hovering_h || dragging_h) { + draw_texture(h_grabber, half - h_grabber->get_size() / 2.0); + set_default_cursor_shape(CURSOR_HSPLIT); + } - } break; - } - } + } break; + } + } + } break; - if (p_what == NOTIFICATION_SORT_CHILDREN) { - Node3DEditorViewport *viewports[4]; - int vc = 0; - for (int i = 0; i < get_child_count(); i++) { - viewports[vc] = Object::cast_to<Node3DEditorViewport>(get_child(i)); - if (viewports[vc]) { - vc++; + case NOTIFICATION_SORT_CHILDREN: { + Node3DEditorViewport *viewports[4]; + int vc = 0; + for (int i = 0; i < get_child_count(); i++) { + viewports[vc] = Object::cast_to<Node3DEditorViewport>(get_child(i)); + if (viewports[vc]) { + vc++; + } } - } - ERR_FAIL_COND(vc != 4); + ERR_FAIL_COND(vc != 4); - Size2 size = get_size(); + Size2 size = get_size(); - if (size.x < 10 || size.y < 10) { - for (int i = 0; i < 4; i++) { - viewports[i]->hide(); + if (size.x < 10 || size.y < 10) { + for (int i = 0; i < 4; i++) { + viewports[i]->hide(); + } + return; } - return; - } - int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); + int h_sep = get_theme_constant(SNAME("separation"), SNAME("HSplitContainer")); - int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); + int v_sep = get_theme_constant(SNAME("separation"), SNAME("VSplitContainer")); - int mid_w = size.width * ratio_h; - int mid_h = size.height * ratio_v; + int mid_w = size.width * ratio_h; + int mid_h = size.height * ratio_v; - int size_left = mid_w - h_sep / 2; - int size_right = size.width - mid_w - h_sep / 2; + int size_left = mid_w - h_sep / 2; + int size_right = size.width - mid_w - h_sep / 2; - int size_top = mid_h - v_sep / 2; - int size_bottom = size.height - mid_h - v_sep / 2; + int size_top = mid_h - v_sep / 2; + int size_bottom = size.height - mid_h - v_sep / 2; - switch (view) { - case VIEW_USE_1_VIEWPORT: { - viewports[0]->show(); - for (int i = 1; i < 4; i++) { - viewports[i]->hide(); - } + switch (view) { + case VIEW_USE_1_VIEWPORT: { + viewports[0]->show(); + for (int i = 1; i < 4; i++) { + viewports[i]->hide(); + } - fit_child_in_rect(viewports[0], Rect2(Vector2(), size)); + fit_child_in_rect(viewports[0], Rect2(Vector2(), size)); - } break; - case VIEW_USE_2_VIEWPORTS: { - for (int i = 0; i < 4; i++) { - if (i == 1 || i == 3) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_2_VIEWPORTS: { + for (int i = 0; i < 4; i++) { + if (i == 1 || i == 3) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size.width, size_bottom))); - } break; - case VIEW_USE_2_VIEWPORTS_ALT: { - for (int i = 0; i < 4; i++) { - if (i == 1 || i == 3) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_2_VIEWPORTS_ALT: { + for (int i = 0; i < 4; i++) { + if (i == 1 || i == 3) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height))); - fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size.height))); + fit_child_in_rect(viewports[2], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); - } break; - case VIEW_USE_3_VIEWPORTS: { - for (int i = 0; i < 4; i++) { - if (i == 1) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_3_VIEWPORTS: { + for (int i = 0; i < 4; i++) { + if (i == 1) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); - fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size.width, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); + fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); - } break; - case VIEW_USE_3_VIEWPORTS_ALT: { - for (int i = 0; i < 4; i++) { - if (i == 1) { - viewports[i]->hide(); - } else { - viewports[i]->show(); + } break; + case VIEW_USE_3_VIEWPORTS_ALT: { + for (int i = 0; i < 4; i++) { + if (i == 1) { + viewports[i]->hide(); + } else { + viewports[i]->show(); + } } - } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); - fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); + fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size.height))); - } break; - case VIEW_USE_4_VIEWPORTS: { - for (int i = 0; i < 4; i++) { - viewports[i]->show(); - } + } break; + case VIEW_USE_4_VIEWPORTS: { + for (int i = 0; i < 4; i++) { + viewports[i]->show(); + } - fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); - fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top))); - fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); - fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); + fit_child_in_rect(viewports[0], Rect2(Vector2(), Vector2(size_left, size_top))); + fit_child_in_rect(viewports[1], Rect2(Vector2(mid_w + h_sep / 2, 0), Vector2(size_right, size_top))); + fit_child_in_rect(viewports[2], Rect2(Vector2(0, mid_h + v_sep / 2), Vector2(size_left, size_bottom))); + fit_child_in_rect(viewports[3], Rect2(Vector2(mid_w + h_sep / 2, mid_h + v_sep / 2), Vector2(size_right, size_bottom))); - } break; - } + } break; + } + } break; } } @@ -4736,9 +5252,15 @@ Node3DEditorSelectedItem::~Node3DEditorSelectedItem() { if (sbox_instance.is_valid()) { RenderingServer::get_singleton()->free(sbox_instance); } + if (sbox_instance_offset.is_valid()) { + RenderingServer::get_singleton()->free(sbox_instance_offset); + } if (sbox_instance_xray.is_valid()) { RenderingServer::get_singleton()->free(sbox_instance_xray); } + if (sbox_instance_xray_offset.is_valid()) { + RenderingServer::get_singleton()->free(sbox_instance_xray_offset); + } } void Node3DEditor::select_gizmo_highlight_axis(int p_axis) { @@ -4761,12 +5283,11 @@ void Node3DEditor::update_transform_gizmo() { Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; if (se && se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E->key()); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E.key); gizmo_center += xf.origin; if (count == 0 && local_gizmo_coords) { gizmo_basis = xf.basis; - gizmo_basis.orthonormalize(); } count++; } @@ -4791,7 +5312,6 @@ void Node3DEditor::update_transform_gizmo() { gizmo_center += xf.origin; if (count == 0 && local_gizmo_coords) { gizmo_basis = xf.basis; - gizmo_basis.orthonormalize(); } count++; } @@ -4818,7 +5338,7 @@ void _update_all_gizmos(Node *p_node) { } void Node3DEditor::update_all_gizmos(Node *p_node) { - if (!p_node && get_tree()) { + if (!p_node && is_inside_tree()) { p_node = get_tree()->get_edited_scene_root(); } @@ -4841,23 +5361,43 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { si->sbox_instance = RenderingServer::get_singleton()->instance_create2( selection_box->get_rid(), sp->get_world_3d()->get_scenario()); + si->sbox_instance_offset = RenderingServer::get_singleton()->instance_create2( + selection_box->get_rid(), + sp->get_world_3d()->get_scenario()); RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting( + si->sbox_instance_offset, + RS::SHADOW_CASTING_SETTING_OFF); // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting. // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position. 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()); + si->sbox_instance_xray_offset = RenderingServer::get_singleton()->instance_create2( + selection_box_xray->get_rid(), + sp->get_world_3d()->get_scenario()); RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance_xray, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting( + si->sbox_instance_xray_offset, + RS::SHADOW_CASTING_SETTING_OFF); // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting. // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position. RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 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_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; } @@ -4865,10 +5405,6 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { void Node3DEditor::_generate_selection_boxes() { // Use two AABBs to create the illusion of a slightly thicker line. AABB aabb(Vector3(), Vector3(1, 1, 1)); - AABB aabb_offset(Vector3(), Vector3(1, 1, 1)); - // Grow the bounding boxes slightly to avoid Z-fighting with the mesh's edges. - aabb.grow_by(0.005); - aabb_offset.grow_by(0.01); // Create a x-ray (visible through solid surfaces) and standard version of the selection box. // Both will be drawn at the same position, but with different opacity. @@ -4888,16 +5424,6 @@ void Node3DEditor::_generate_selection_boxes() { st_xray->add_vertex(b); } - for (int i = 0; i < 12; i++) { - Vector3 a, b; - aabb_offset.get_edge(i, a, b); - - st->add_vertex(a); - st->add_vertex(b); - st_xray->add_vertex(a); - st_xray->add_vertex(b); - } - Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color"); @@ -5154,6 +5680,7 @@ void Node3DEditor::edit(Node3D *p_spatial) { selected = p_spatial; current_hover_gizmo = Ref<EditorNode3DGizmo>(); current_hover_gizmo_handle = -1; + current_hover_gizmo_handle_secondary = false; if (selected) { Vector<Ref<Node3DGizmo>> gizmos = selected->get_gizmos(); @@ -5200,7 +5727,7 @@ void Node3DEditor::_xform_dialog_action() { undo_redo->create_action(TTR("XForm Dialog")); - List<Node *> &selection = editor_selection->get_selected_node_list(); + const List<Node *> &selection = editor_selection->get_selected_node_list(); for (Node *E : selection) { Node3D *sp = Object::cast_to<Node3D>(E); @@ -5620,9 +6147,9 @@ void fragment() { float angle_fade = abs(dot(dir, NORMAL)); angle_fade = smoothstep(0.05, 0.2, angle_fade); - vec3 world_pos = (CAMERA_MATRIX * vec4(VERTEX, 1.0)).xyz; - vec3 world_normal = (CAMERA_MATRIX * vec4(NORMAL, 0.0)).xyz; - vec3 camera_world_pos = CAMERA_MATRIX[3].xyz; + vec3 world_pos = (INV_VIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; + vec3 world_normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz; + vec3 camera_world_pos = INV_VIEW_MATRIX[3].xyz; vec3 camera_world_pos_on_plane = camera_world_pos * (1.0 - world_normal); float dist_fade = 1.0 - (distance(world_pos, camera_world_pos_on_plane) / grid_size); dist_fade = smoothstep(0.02, 0.3, dist_fade); @@ -5657,6 +6184,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); } @@ -5664,6 +6192,12 @@ void fragment() { { //move gizmo + // Inverted zxy. + Vector3 ivec = Vector3(0, 0, -1); + Vector3 nivec = Vector3(-1, -1, 0); + Vector3 ivec2 = Vector3(-1, 0, 0); + Vector3 ivec3 = Vector3(0, -1, 0); + for (int i = 0; i < 3; i++) { Color col; switch (i) { @@ -5688,6 +6222,7 @@ void fragment() { rotate_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); scale_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); scale_plane_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); + axis_gizmo[i] = Ref<ArrayMesh>(memnew(ArrayMesh)); Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); @@ -5701,16 +6236,6 @@ void fragment() { mat_hl->set_albedo(albedo); gizmo_color_hl[i] = mat_hl; - Vector3 ivec; - ivec[i] = 1; - Vector3 nivec; - nivec[(i + 1) % 3] = 1; - nivec[(i + 2) % 3] = 1; - Vector3 ivec2; - ivec2[(i + 1) % 3] = 1; - Vector3 ivec3; - ivec3[(i + 2) % 3] = 1; - //translate { Ref<SurfaceTool> surftool = memnew(SurfaceTool); @@ -5875,7 +6400,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(); @@ -5883,7 +6408,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 @@ -5924,7 +6449,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); @@ -6019,24 +6544,28 @@ void fragment() { plane_mat_hl->set_albedo(col.from_hsv(col.get_h(), 0.25, 1.0, 1)); plane_gizmo_color_hl[i] = plane_mat_hl; // needed, so we can draw planes from both sides } + + // Lines to visualize transforms locked to an axis/plane + { + Ref<SurfaceTool> surftool = memnew(SurfaceTool); + surftool->begin(Mesh::PRIMITIVE_LINE_STRIP); + + Vector3 vec; + vec[i] = 1; + + // line extending through infinity(ish) + surftool->add_vertex(vec * -1048576); + surftool->add_vertex(Vector3()); + surftool->add_vertex(vec * 1048576); + surftool->set_material(mat_hl); + surftool->commit(axis_gizmo[i]); + } } } _generate_selection_boxes(); } -void Node3DEditor::_update_context_menu_stylebox() { - // This must be called when the theme changes to follow the new accent color. - Ref<StyleBoxFlat> context_menu_stylebox = memnew(StyleBoxFlat); - const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_theme_color("accent_color", "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(); @@ -6143,8 +6672,8 @@ 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); - Plane grid_plane = Plane(Vector3(), normal); + 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)) { camera_position = intersection; @@ -6173,8 +6702,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]; @@ -6266,6 +6795,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); } } @@ -6284,7 +6814,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 @@ -6294,7 +6824,7 @@ void Node3DEditor::update_grid() { // Gets a orthogonal or perspective position correctly (for the grid comparison) const Vector3 camera_position = get_editor_viewport(0)->camera->get_position(); - if (!grid_init_draw || (camera_position - grid_camera_last_update_position).length() >= 10.0f) { + if (!grid_init_draw || grid_camera_last_update_position.distance_squared_to(camera_position) >= 100.0f) { _finish_grid(); _init_grid(); grid_init_draw = true; @@ -6359,8 +6889,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); @@ -6368,31 +6898,31 @@ Set<T *> _get_child_nodes(Node *parent_node) { for (int i = 0; i < parent_node->get_child_count(); i++) { Node *child_node = parent_node->get_child(i); - Set<T *> child_nodes = _get_child_nodes<T>(child_node); - for (typename Set<T *>::Element *I = child_nodes.front(); I; I = I->next()) { - nodes.insert(I->get()); + HashSet<T *> child_nodes = _get_child_nodes<T>(child_node); + for (T *I : child_nodes) { + nodes.insert(I); } } return nodes; } -Set<RID> _get_physics_bodies_rid(Node *node) { - Set<RID> rids = Set<RID>(); +HashSet<RID> _get_physics_bodies_rid(Node *node) { + HashSet<RID> rids = HashSet<RID>(); PhysicsBody3D *pb = Node::cast_to<PhysicsBody3D>(node); if (pb) { rids.insert(pb->get_rid()); } - Set<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node); - for (Set<PhysicsBody3D *>::Element *I = child_nodes.front(); I; I = I->next()) { - rids.insert(I->get()->get_rid()); + HashSet<PhysicsBody3D *> child_nodes = _get_child_nodes<PhysicsBody3D>(node); + for (const PhysicsBody3D *I : child_nodes) { + rids.insert(I->get_rid()); } return rids; } void Node3DEditor::snap_selected_nodes_to_floor() { - List<Node *> &selection = editor_selection->get_selected_node_list(); + const List<Node *> &selection = editor_selection->get_selected_node_list(); Dictionary snap_data; for (Node *E : selection) { @@ -6402,20 +6932,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; @@ -6428,9 +6959,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; @@ -6442,7 +6973,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { // We add a bit of margin to the from position to avoid it from snapping // when the spatial is already on a floor and there's another floor under // it - from = from + Vector3(0.0, 0.2, 0.0); + from = from + Vector3(0.0, 1, 0.0); Dictionary d; @@ -6458,7 +6989,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { Array keys = snap_data.keys(); // The maximum height an object can travel to be snapped - const float max_snap_height = 20.0; + const float max_snap_height = 500.0; // Will be set to `true` if at least one node from the selection was successfully snapped bool snapped_to_floor = false; @@ -6467,14 +6998,19 @@ void Node3DEditor::snap_selected_nodes_to_floor() { // For snapping to be performed, there must be solid geometry under at least one of the selected nodes. // We need to check this before snapping to register the undo/redo action only if needed. for (int i = 0; i < keys.size(); i++) { - Node *node = keys[i]; + Node *node = Object::cast_to<Node>(keys[i]); Node3D *sp = Object::cast_to<Node3D>(node); Dictionary d = snap_data[node]; Vector3 from = d["from"]; Vector3 to = from - Vector3(0.0, max_snap_height, 0.0); - Set<RID> excluded = _get_physics_bodies_rid(sp); + HashSet<RID> excluded = _get_physics_bodies_rid(sp); + + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = from; + ray_params.to = to; + ray_params.exclude = excluded; - if (ss->intersect_ray(from, to, result, excluded)) { + if (ss->intersect_ray(ray_params, result)) { snapped_to_floor = true; } } @@ -6484,14 +7020,19 @@ void Node3DEditor::snap_selected_nodes_to_floor() { // Perform snapping if at least one node can be snapped for (int i = 0; i < keys.size(); i++) { - Node *node = keys[i]; + Node *node = Object::cast_to<Node>(keys[i]); Node3D *sp = Object::cast_to<Node3D>(node); Dictionary d = snap_data[node]; Vector3 from = d["from"]; Vector3 to = from - Vector3(0.0, max_snap_height, 0.0); - Set<RID> excluded = _get_physics_bodies_rid(sp); + HashSet<RID> excluded = _get_physics_bodies_rid(sp); - if (ss->intersect_ray(from, to, result, excluded)) { + PhysicsDirectSpaceState3D::RayParameters ray_params; + ray_params.from = from; + ray_params.to = to; + ray_params.exclude = excluded; + + if (ss->intersect_ray(ray_params, result)) { Vector3 position_offset = d["position_offset"]; Transform3D new_transform = sp->get_global_transform(); @@ -6510,26 +7051,27 @@ void Node3DEditor::snap_selected_nodes_to_floor() { } } -void Node3DEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { +void Node3DEditor::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_visible_in_tree()) { return; } - snap_key_enabled = Input::get_singleton()->is_key_pressed(KEY_CTRL); + snap_key_enabled = Input::get_singleton()->is_key_pressed(Key::CTRL); } void Node3DEditor::_sun_environ_settings_pressed() { Vector2 pos = sun_environ_settings->get_screen_position() + sun_environ_settings->get_size(); sun_environ_popup->set_position(pos - Vector2(sun_environ_popup->get_contents_minimum_size().width / 2, 0)); + sun_environ_popup->reset_size(); sun_environ_popup->popup(); } void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { sun_environ_popup->hide(); - if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) { // Prevent infinite feedback loop between the sun and environment methods. _add_environment_to_scene(true); } @@ -6537,14 +7079,14 @@ void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { Node *base = get_tree()->get_edited_scene_root(); if (!base) { // Create a root node so we can add child nodes to it. - EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D)); + SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D)); base = get_tree()->get_edited_scene_root(); } ERR_FAIL_COND(!base); Node *new_sun = preview_sun->duplicate(); undo_redo->create_action(TTR("Add Preview Sun to Scene")); - undo_redo->add_do_method(base, "add_child", new_sun); + undo_redo->add_do_method(base, "add_child", new_sun, true); // Move to the beginning of the scene tree since more "global" nodes // generally look better when placed at the top. undo_redo->add_do_method(base, "move_child", new_sun, 0); @@ -6557,7 +7099,7 @@ void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) { sun_environ_popup->hide(); - if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(Key::SHIFT)) { // Prevent infinite feedback loop between the sun and environment methods. _add_sun_to_scene(true); } @@ -6565,7 +7107,7 @@ void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) { Node *base = get_tree()->get_edited_scene_root(); if (!base) { // Create a root node so we can add child nodes to it. - EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D)); + SceneTreeDock::get_singleton()->add_root_node(memnew(Node3D)); base = get_tree()->get_edited_scene_root(); } ERR_FAIL_COND(!base); @@ -6574,7 +7116,7 @@ void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) { new_env->set_environment(preview_environment->get_environment()->duplicate(true)); undo_redo->create_action(TTR("Add Preview Environment to Scene")); - undo_redo->add_do_method(base, "add_child", new_env); + undo_redo->add_do_method(base, "add_child", new_env, true); // Move to the beginning of the scene tree since more "global" nodes // generally look better when placed at the top. undo_redo->add_do_method(base, "move_child", new_env, 0); @@ -6585,19 +7127,19 @@ void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) { } void Node3DEditor::_update_theme() { - tool_button[Node3DEditor::TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); - tool_button[Node3DEditor::TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); - - tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); - tool_option_button[Node3DEditor::TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - tool_option_button[Node3DEditor::TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_SELECT]->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_MOVE]->set_icon(get_theme_icon(SNAME("ToolMove"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_ROTATE]->set_icon(get_theme_icon(SNAME("ToolRotate"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_SCALE]->set_icon(get_theme_icon(SNAME("ToolScale"), SNAME("EditorIcons"))); + tool_button[TOOL_MODE_LIST_SELECT]->set_icon(get_theme_icon(SNAME("ListSelect"), SNAME("EditorIcons"))); + tool_button[TOOL_LOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Lock"), SNAME("EditorIcons"))); + tool_button[TOOL_UNLOCK_SELECTED]->set_icon(get_theme_icon(SNAME("Unlock"), SNAME("EditorIcons"))); + tool_button[TOOL_GROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Group"), SNAME("EditorIcons"))); + tool_button[TOOL_UNGROUP_SELECTED]->set_icon(get_theme_icon(SNAME("Ungroup"), SNAME("EditorIcons"))); + + tool_option_button[TOOL_OPT_LOCAL_COORDS]->set_icon(get_theme_icon(SNAME("Object"), SNAME("EditorIcons"))); + tool_option_button[TOOL_OPT_USE_SNAP]->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->set_icon(get_theme_icon(SNAME("Camera3D"), SNAME("EditorIcons"))); view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_1_VIEWPORT), get_theme_icon(SNAME("Panels1"), SNAME("EditorIcons"))); view_menu->get_popup()->set_item_icon(view_menu->get_popup()->get_item_index(MENU_VIEW_USE_2_VIEWPORTS), get_theme_icon(SNAME("Panels2"), SNAME("EditorIcons"))); @@ -6612,6 +7154,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) { @@ -6623,17 +7171,18 @@ void Node3DEditor::_notification(int p_what) { get_tree()->connect("node_removed", callable_mp(this, &Node3DEditor::_node_removed)); get_tree()->connect("node_added", callable_mp(this, &Node3DEditor::_node_added)); - EditorNode::get_singleton()->get_scene_tree_dock()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons)); + SceneTreeDock::get_singleton()->get_tree_editor()->connect("node_changed", callable_mp(this, &Node3DEditor::_refresh_menu_icons)); editor_selection->connect("selection_changed", callable_mp(this, &Node3DEditor::_selection_changed)); - editor->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(false)); - editor->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button), make_binds(true)); + EditorNode::get_singleton()->connect("stop_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(false)); + EditorNode::get_singleton()->connect("play_pressed", callable_mp(this, &Node3DEditor::_update_camera_override_button).bind(true)); _update_preview_environment(); sun_state->set_custom_minimum_size(sun_vb->get_combined_minimum_size()); environ_state->set_custom_minimum_size(environ_vb->get_combined_minimum_size()); } break; + case NOTIFICATION_ENTER_TREE: { _update_theme(); _register_all_gizmos(); @@ -6641,21 +7190,24 @@ void Node3DEditor::_notification(int p_what) { _init_indicators(); update_all_gizmos(); } break; + case NOTIFICATION_EXIT_TREE: { _finish_indicators(); } break; + case NOTIFICATION_THEME_CHANGED: { _update_theme(); _update_gizmos_menu_theme(); - _update_context_menu_stylebox(); sun_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); environ_title->add_theme_font_override("font", get_theme_font(SNAME("title_font"), SNAME("Window"))); } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { // Update grid color by rebuilding grid. _finish_grid(); _init_grid(); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible() && tool_option_button[TOOL_OPT_OVERRIDE_CAMERA]->is_pressed()) { EditorDebuggerNode *debugger = EditorDebuggerNode::get_singleton(); @@ -6688,19 +7240,19 @@ Vector<int> Node3DEditor::get_subgizmo_selection() { Vector<int> ret; if (se) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - ret.push_back(E->key()); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + ret.push_back(E.key); } } return ret; } 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) { @@ -6713,8 +7265,46 @@ VSplitContainer *Node3DEditor::get_shader_split() { return shader_split; } -HSplitContainer *Node3DEditor::get_palette_split() { - return palette_split; +void Node3DEditor::add_control_to_left_panel(Control *p_control) { + left_panel_split->add_child(p_control); + left_panel_split->move_child(p_control, 0); +} + +void Node3DEditor::add_control_to_right_panel(Control *p_control) { + right_panel_split->add_child(p_control); + right_panel_split->move_child(p_control, 1); +} + +void Node3DEditor::remove_control_from_left_panel(Control *p_control) { + left_panel_split->remove_child(p_control); +} + +void Node3DEditor::remove_control_from_right_panel(Control *p_control) { + right_panel_split->remove_child(p_control); +} + +void Node3DEditor::move_control_to_left_panel(Control *p_control) { + ERR_FAIL_NULL(p_control); + if (p_control->get_parent() == left_panel_split) { + return; + } + + ERR_FAIL_COND(p_control->get_parent() != right_panel_split); + right_panel_split->remove_child(p_control); + + add_control_to_left_panel(p_control); +} + +void Node3DEditor::move_control_to_right_panel(Control *p_control) { + ERR_FAIL_NULL(p_control); + if (p_control->get_parent() == right_panel_split) { + return; + } + + ERR_FAIL_COND(p_control->get_parent() != left_panel_split); + left_panel_split->remove_child(p_control); + + add_control_to_right_panel(p_control); } void Node3DEditor::_request_gizmo(Object *p_obj) { @@ -6725,7 +7315,8 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { bool is_selected = (sp == selected); - if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) { + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + if (edited_scene && (sp == edited_scene || (sp->get_owner() && edited_scene->is_ancestor_of(sp)))) { for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) { Ref<EditorNode3DGizmo> seg = gizmo_plugins_by_priority.write[i]->get_gizmo(sp); @@ -6737,7 +7328,36 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { } } } + if (!sp->get_gizmos().is_empty()) { + sp->update_gizmos(); + } + } +} + +void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) { + if (p_id == -1) { + _clear_subgizmo_selection(p_obj); + return; + } + + Node3D *sp = nullptr; + if (p_obj) { + sp = Object::cast_to<Node3D>(p_obj); + } else { + sp = selected; + } + + if (!sp) { + return; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (se) { + se->subgizmos.clear(); + se->subgizmos.insert(p_id, p_transform); + se->gizmo = p_gizmo; sp->update_gizmos(); + update_transform_gizmo(); } } @@ -6789,7 +7409,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(); } @@ -6861,13 +7481,15 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<Camera3DGizmoPlugin>(memnew(Camera3DGizmoPlugin))); add_gizmo_plugin(Ref<Light3DGizmoPlugin>(memnew(Light3DGizmoPlugin))); add_gizmo_plugin(Ref<AudioStreamPlayer3DGizmoPlugin>(memnew(AudioStreamPlayer3DGizmoPlugin))); + add_gizmo_plugin(Ref<AudioListener3DGizmoPlugin>(memnew(AudioListener3DGizmoPlugin))); add_gizmo_plugin(Ref<MeshInstance3DGizmoPlugin>(memnew(MeshInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin))); - add_gizmo_plugin(Ref<SoftBody3DGizmoPlugin>(memnew(SoftBody3DGizmoPlugin))); + add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin))); add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin))); - add_gizmo_plugin(Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin))); + add_gizmo_plugin(Ref<Label3DGizmoPlugin>(memnew(Label3DGizmoPlugin))); add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin))); 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))); @@ -6885,11 +7507,13 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<NavigationRegion3DGizmoPlugin>(memnew(NavigationRegion3DGizmoPlugin))); add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin))); add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin))); + add_gizmo_plugin(Ref<FogVolumeGizmoPlugin>(memnew(FogVolumeGizmoPlugin))); } void Node3DEditor::_bind_methods() { ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data); ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo); + ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection); ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection); ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons); @@ -6925,11 +7549,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() { @@ -6976,8 +7600,8 @@ void Node3DEditor::_load_default_preview_settings() { sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x)); sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y)); sun_direction->update(); - environ_sky_color->set_pick_color(Color::hex(0x91b2ceff)); - environ_ground_color->set_pick_color(Color::hex(0x1f1f21ff)); + environ_sky_color->set_pick_color(Color(0.385, 0.454, 0.55)); + environ_ground_color->set_pick_color(Color(0.2, 0.169, 0.133)); environ_energy->set_value(1.0); environ_glow_button->set_pressed(true); environ_tonemap_button->set_pressed(true); @@ -7011,7 +7635,7 @@ void Node3DEditor::_update_preview_environment() { } else { if (!preview_sun->get_parent()) { - add_child(preview_sun); + add_child(preview_sun, true); sun_state->hide(); sun_vb->show(); } @@ -7047,7 +7671,7 @@ void Node3DEditor::_update_preview_environment() { void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE); sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE); sun_rotation.x = CLAMP(sun_rotation.x, -Math_TAU / 4, Math_TAU / 4); @@ -7063,18 +7687,17 @@ void Node3DEditor::_sun_direction_angle_set() { _preview_settings_changed(); } -Node3DEditor::Node3DEditor(EditorNode *p_editor) { +Node3DEditor::Node3DEditor() { gizmo.visible = true; gizmo.scale = 1.0; viewport_environment = Ref<Environment>(memnew(Environment)); - undo_redo = p_editor->get_undo_redo(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); VBoxContainer *vbc = this; custom_camera = nullptr; singleton = this; - editor = p_editor; - editor_selection = editor->get_editor_selection(); + editor_selection = EditorNode::get_singleton()->get_editor_selection(); editor_selection->add_editor_plugin(this); snap_enabled = false; @@ -7083,181 +7706,181 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { camera_override_viewport_id = 0; - hbc_menu = memnew(HBoxContainer); - vbc->add_child(hbc_menu); + // A fluid container for all toolbars. + HFlowContainer *main_flow = memnew(HFlowContainer); + vbc->add_child(main_flow); + + // Main toolbars. + HBoxContainer *main_menu_hbox = memnew(HBoxContainer); + main_flow->add_child(main_menu_hbox); - Vector<Variant> button_binds; - button_binds.resize(1); String sct; // Add some margin to the left for better aesthetics. // This prevents the first button's hover/pressed effect from "touching" the panel's border, // which looks ugly. Control *margin_left = memnew(Control); - hbc_menu->add_child(margin_left); + main_menu_hbox->add_child(margin_left); margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE); tool_button[TOOL_MODE_SELECT] = memnew(Button); - hbc_menu->add_child(tool_button[TOOL_MODE_SELECT]); + main_menu_hbox->add_child(tool_button[TOOL_MODE_SELECT]); tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true); tool_button[TOOL_MODE_SELECT]->set_flat(true); tool_button[TOOL_MODE_SELECT]->set_pressed(true); - button_binds.write[0] = MENU_TOOL_SELECT; - tool_button[TOOL_MODE_SELECT]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); - tool_button[TOOL_MODE_SELECT]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_select", TTR("Select Mode"), KEY_Q)); + 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_MASK_CMD) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.")); - hbc_menu->add_child(memnew(VSeparator)); + tool_button[TOOL_MODE_SELECT]->set_tooltip(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.")); + 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]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_move", TTR("Move Mode"), KEY_W)); + + 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]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_rotate", TTR("Rotate Mode"), KEY_E)); + 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]->set_shortcut(ED_SHORTCUT("spatial_editor/tool_scale", TTR("Scale Mode"), KEY_R)); + 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)"), KEY_MASK_CMD | KEY_L)); + 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)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_L)); + 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)"), KEY_MASK_CMD | KEY_G)); + 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)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G)); + 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]->set_shortcut(ED_SHORTCUT("spatial_editor/local_coords", TTR("Use Local Space"), KEY_T)); + 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]->set_shortcut(ED_SHORTCUT("spatial_editor/snap", TTR("Use Snap"), KEY_Y)); + 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); preview_bounds = AABB(); - ED_SHORTCUT("spatial_editor/bottom_view", TTR("Bottom View"), KEY_MASK_ALT + KEY_KP_7); - ED_SHORTCUT("spatial_editor/top_view", TTR("Top View"), KEY_KP_7); - ED_SHORTCUT("spatial_editor/rear_view", TTR("Rear View"), KEY_MASK_ALT + KEY_KP_1); - ED_SHORTCUT("spatial_editor/front_view", TTR("Front View"), KEY_KP_1); - ED_SHORTCUT("spatial_editor/left_view", TTR("Left View"), KEY_MASK_ALT + KEY_KP_3); - ED_SHORTCUT("spatial_editor/right_view", TTR("Right View"), KEY_KP_3); - ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTR("Switch Perspective/Orthogonal View"), KEY_KP_5); - ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), KEY_K); - ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), KEY_O); - ED_SHORTCUT("spatial_editor/focus_selection", TTR("Focus Selection"), KEY_F); - ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_M); - ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_F); - ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KEY_MASK_SHIFT + KEY_F); + ED_SHORTCUT("spatial_editor/bottom_view", TTR("Bottom View"), KeyModifierMask::ALT + Key::KP_7); + ED_SHORTCUT("spatial_editor/top_view", TTR("Top View"), Key::KP_7); + ED_SHORTCUT("spatial_editor/rear_view", TTR("Rear View"), KeyModifierMask::ALT + Key::KP_1); + ED_SHORTCUT("spatial_editor/front_view", TTR("Front View"), Key::KP_1); + ED_SHORTCUT("spatial_editor/left_view", TTR("Left View"), KeyModifierMask::ALT + Key::KP_3); + ED_SHORTCUT("spatial_editor/right_view", TTR("Right View"), Key::KP_3); + ED_SHORTCUT("spatial_editor/orbit_view_down", TTR("Orbit View Down"), Key::KP_2); + ED_SHORTCUT("spatial_editor/orbit_view_left", TTR("Orbit View Left"), Key::KP_4); + ED_SHORTCUT("spatial_editor/orbit_view_right", TTR("Orbit View Right"), Key::KP_6); + ED_SHORTCUT("spatial_editor/orbit_view_up", TTR("Orbit View Up"), Key::KP_8); + ED_SHORTCUT("spatial_editor/orbit_view_180", TTR("Orbit View 180"), Key::KP_9); + ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTR("Switch Perspective/Orthogonal View"), Key::KP_5); + ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), Key::K); + ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), Key::O); + ED_SHORTCUT("spatial_editor/focus_selection", TTR("Focus Selection"), Key::F); + ED_SHORTCUT("spatial_editor/align_transform_with_view", TTR("Align Transform with View"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::M); + ED_SHORTCUT("spatial_editor/align_rotation_with_view", TTR("Align Rotation with View"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::F); + ED_SHORTCUT("spatial_editor/freelook_toggle", TTR("Toggle Freelook"), KeyModifierMask::SHIFT + Key::F); + ED_SHORTCUT("spatial_editor/decrease_fov", TTR("Decrease Field of View"), KeyModifierMask::CMD + Key::EQUAL); // Usually direct access key for `KEY_PLUS`. + ED_SHORTCUT("spatial_editor/increase_fov", TTR("Increase Field of View"), KeyModifierMask::CMD + Key::MINUS); + ED_SHORTCUT("spatial_editor/reset_fov", TTR("Reset Field of View to Default"), KeyModifierMask::CMD + Key::KEY_0); PopupMenu *p; @@ -7265,10 +7888,10 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { transform_menu->set_text(TTR("Transform")); transform_menu->set_switch_on_hover(true); transform_menu->set_shortcut_context(this); - hbc_menu->add_child(transform_menu); + main_menu_hbox->add_child(transform_menu); p = transform_menu->get_popup(); - p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTR("Snap Object to Floor"), KEY_PAGEDOWN), MENU_SNAP_TO_FLOOR); + p->add_shortcut(ED_SHORTCUT("spatial_editor/snap_to_floor", TTR("Snap Object to Floor"), Key::PAGEDOWN), MENU_SNAP_TO_FLOOR); p->add_shortcut(ED_SHORTCUT("spatial_editor/transform_dialog", TTR("Transform Dialog...")), MENU_TRANSFORM_DIALOG); p->add_separator(); @@ -7277,42 +7900,39 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { p->connect("id_pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed)); view_menu = memnew(MenuButton); + // TRANSLATORS: Noun, name of the 2D/3D View menus. view_menu->set_text(TTR("View")); view_menu->set_switch_on_hover(true); view_menu->set_shortcut_context(this); - hbc_menu->add_child(view_menu); + main_menu_hbox->add_child(view_menu); - hbc_menu->add_child(memnew(VSeparator)); + main_menu_hbox->add_child(memnew(VSeparator)); - context_menu_container = memnew(PanelContainer); - hbc_context_menu = memnew(HBoxContainer); - context_menu_container->add_child(hbc_context_menu); - // Use a custom stylebox to make contextual menu items stand out from the rest. - // This helps with editor usability as contextual menu items change when selecting nodes, - // even though it may not be immediately obvious at first. - hbc_menu->add_child(context_menu_container); - _update_context_menu_stylebox(); + context_menu_panel = memnew(PanelContainer); + context_menu_hbox = memnew(HBoxContainer); + context_menu_panel->add_child(context_menu_hbox); + main_flow->add_child(context_menu_panel); // Get the view menu popup and have it stay open when a checkable item is selected p = view_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); accept = memnew(AcceptDialog); - editor->get_gui_base()->add_child(accept); - - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KEY_MASK_CMD + KEY_1), MENU_VIEW_USE_1_VIEWPORT); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KEY_MASK_ALT + KEY_MASK_CMD + KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT); - p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KEY_MASK_CMD + KEY_4), MENU_VIEW_USE_4_VIEWPORTS); + EditorNode::get_singleton()->get_gui_base()->add_child(accept); + + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/1_viewport", TTR("1 Viewport"), KeyModifierMask::CMD + Key::KEY_1), MENU_VIEW_USE_1_VIEWPORT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports", TTR("2 Viewports"), KeyModifierMask::CMD + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/2_viewports_alt", TTR("2 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::KEY_2), MENU_VIEW_USE_2_VIEWPORTS_ALT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports", TTR("3 Viewports"), KeyModifierMask::CMD + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/3_viewports_alt", TTR("3 Viewports (Alt)"), KeyModifierMask::ALT + KeyModifierMask::CMD + Key::KEY_3), MENU_VIEW_USE_3_VIEWPORTS_ALT); + p->add_radio_check_shortcut(ED_SHORTCUT("spatial_editor/4_viewports", TTR("4 Viewports"), KeyModifierMask::CMD + Key::KEY_4), MENU_VIEW_USE_4_VIEWPORTS); p->add_separator(); p->add_submenu_item(TTR("Gizmos"), "GizmosMenu"); p->add_separator(); p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), KEY_NUMBERSIGN), MENU_VIEW_GRID); + p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), Key::NUMBERSIGN), MENU_VIEW_GRID); p->add_separator(); p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS); @@ -7330,18 +7950,22 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { /* REST OF MENU */ - palette_split = memnew(HSplitContainer); - palette_split->set_v_size_flags(SIZE_EXPAND_FILL); - vbc->add_child(palette_split); + left_panel_split = memnew(HSplitContainer); + left_panel_split->set_v_size_flags(SIZE_EXPAND_FILL); + vbc->add_child(left_panel_split); + + right_panel_split = memnew(HSplitContainer); + right_panel_split->set_v_size_flags(SIZE_EXPAND_FILL); + left_panel_split->add_child(right_panel_split); shader_split = memnew(VSplitContainer); shader_split->set_h_size_flags(SIZE_EXPAND_FILL); - palette_split->add_child(shader_split); + right_panel_split->add_child(shader_split); viewport_base = memnew(Node3DEditorViewportContainer); shader_split->add_child(viewport_base); viewport_base->set_v_size_flags(SIZE_EXPAND_FILL); for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) { - viewports[i] = memnew(Node3DEditorViewport(this, editor, i)); + viewports[i] = memnew(Node3DEditorViewport(this, i)); viewports[i]->connect("toggle_maximize_view", callable_mp(this, &Node3DEditor::_toggle_maximize_view)); viewports[i]->connect("clicked", callable_mp(this, &Node3DEditor::_update_camera_override_viewport)); viewports[i]->assign_pending_data_pointers(preview_node, &preview_bounds, accept); @@ -7405,7 +8029,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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::_update_camera), varray(0.0)); + settings_dialog->connect("confirmed", callable_mp(viewports[i], &Node3DEditorViewport::_view_settings_confirmed).bind(0.0)); } /* XFORM DIALOG */ @@ -7470,7 +8094,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { selected = nullptr; - set_process_unhandled_key_input(true); + set_process_shortcut_input(true); add_to_group("_spatial_editor_group"); EDITOR_DEF("editors/3d/manipulator_gizmo_size", 80); @@ -7480,6 +8104,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { EDITOR_DEF("editors/3d/navigation/show_viewport_rotation_gizmo", true); current_hover_gizmo_handle = -1; + current_hover_gizmo_handle_secondary = false; { //sun popup @@ -7499,7 +8124,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { sun_title->set_theme_type_variation("HeaderSmall"); sun_vb->add_child(sun_title); sun_title->set_text(TTR("Preview Sun")); - sun_title->set_align(Label::ALIGN_CENTER); + sun_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); CenterContainer *sun_direction_center = memnew(CenterContainer); sun_direction = memnew(Control); @@ -7529,8 +8154,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); @@ -7566,6 +8191,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); @@ -7581,14 +8207,14 @@ 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); sun_state = memnew(Label); sun_environ_hb->add_child(sun_state); - sun_state->set_align(Label::ALIGN_CENTER); - sun_state->set_valign(Label::VALIGN_CENTER); + sun_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + sun_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); sun_state->set_h_size_flags(SIZE_EXPAND_FILL); VSeparator *sc = memnew(VSeparator); @@ -7606,15 +8232,17 @@ void fragment() { environ_vb->add_child(environ_title); environ_title->set_text(TTR("Preview Environment")); - environ_title->set_align(Label::ALIGN_CENTER); + environ_title->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); 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)); @@ -7626,36 +8254,36 @@ 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); environ_state = memnew(Label); sun_environ_hb->add_child(environ_state); - environ_state->set_align(Label::ALIGN_CENTER); - environ_state->set_valign(Label::VALIGN_CENTER); + environ_state->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + environ_state->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); environ_state->set_h_size_flags(SIZE_EXPAND_FILL); preview_sun = memnew(DirectionalLight3D); @@ -7675,7 +8303,6 @@ void fragment() { _preview_settings_changed(); } } - Node3DEditor::~Node3DEditor() { memdelete(preview_node); } @@ -7696,7 +8323,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 { @@ -7716,9 +8349,16 @@ Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const { return p_target; } +bool Node3DEditor::is_gizmo_visible() const { + if (selected) { + return gizmo.visible && selected->is_transform_gizmo_visible(); + } + return gizmo.visible; +} + double Node3DEditor::get_translate_snap() const { double snap_value; - if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { snap_value = snap_translate->get_text().to_float() / 10.0; } else { snap_value = snap_translate->get_text().to_float(); @@ -7729,7 +8369,7 @@ double Node3DEditor::get_translate_snap() const { double Node3DEditor::get_rotate_snap() const { double snap_value; - if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { snap_value = snap_rotate->get_text().to_float() / 3.0; } else { snap_value = snap_rotate->get_text().to_float(); @@ -7740,7 +8380,7 @@ double Node3DEditor::get_rotate_snap() const { double Node3DEditor::get_scale_snap() const { double snap_value; - if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { snap_value = snap_scale->get_text().to_float() / 2.0; } else { snap_value = snap_scale->get_text().to_float(); @@ -7782,14 +8422,12 @@ void Node3DEditor::remove_gizmo_plugin(Ref<EditorNode3DGizmoPlugin> p_plugin) { _update_gizmos_menu(); } -Node3DEditorPlugin::Node3DEditorPlugin(EditorNode *p_node) { - editor = p_node; - spatial_editor = memnew(Node3DEditor(p_node)); +Node3DEditorPlugin::Node3DEditorPlugin() { + spatial_editor = memnew(Node3DEditor); spatial_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); - editor->get_main_control()->add_child(spatial_editor); + EditorNode::get_singleton()->get_main_control()->add_child(spatial_editor); spatial_editor->hide(); - spatial_editor->connect("transform_key_request", Callable(editor->get_inspector_dock(), "_transform_keyed")); } Node3DEditorPlugin::~Node3DEditorPlugin() { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 59f3ec6fcd..4469271a38 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,18 +31,23 @@ #ifndef NODE_3D_EDITOR_PLUGIN_H #define NODE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_scale.h" +#include "editor/editor_spin_slider.h" #include "editor/plugins/node_3d_editor_gizmos.h" #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/visual_instance_3d.h" #include "scene/3d/world_environment.h" +#include "scene/gui/color_picker.h" #include "scene/gui/panel_container.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/split_container.h" #include "scene/resources/environment.h" +#include "scene/resources/fog_material.h" #include "scene/resources/sky_material.h" +class EditorData; class Node3DEditor; class Node3DEditorViewport; class SubViewportContainer; @@ -124,6 +129,7 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, VIEW_DISPLAY_DEBUG_SSAO, + VIEW_DISPLAY_DEBUG_SSIL, VIEW_DISPLAY_DEBUG_PSSM_SPLITS, VIEW_DISPLAY_DEBUG_DECAL_ATLAS, VIEW_DISPLAY_DEBUG_SDFGI, @@ -135,6 +141,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, @@ -184,29 +191,28 @@ private: ViewType view_type; void _menu_option(int p_option); void _set_auto_orthogonal(); - Node3D *preview_node; - AABB *preview_bounds; + Node3D *preview_node = nullptr; + AABB *preview_bounds = nullptr; Vector<String> selected_files; - AcceptDialog *accept; + AcceptDialog *accept = nullptr; - Node *target_node; + Node *target_node = nullptr; Point2 drop_pos; - EditorNode *editor; - EditorData *editor_data; - EditorSelection *editor_selection; - UndoRedo *undo_redo; + EditorData *editor_data = nullptr; + EditorSelection *editor_selection = nullptr; + UndoRedo *undo_redo = nullptr; - CheckBox *preview_camera; - SubViewportContainer *subviewport_container; + CheckBox *preview_camera = nullptr; + SubViewportContainer *subviewport_container = nullptr; - MenuButton *view_menu; - PopupMenu *display_submenu; + MenuButton *view_menu = nullptr; + PopupMenu *display_submenu = nullptr; - Control *surface; - SubViewport *viewport; - Camera3D *camera; - bool transforming; + Control *surface = nullptr; + SubViewport *viewport = nullptr; + Camera3D *camera = nullptr; + bool transforming = false; bool orthogonal; bool auto_orthogonal; bool lock_rotation; @@ -216,17 +222,20 @@ private: real_t freelook_speed; Vector2 previous_mouse_position; - Label *info_label; - Label *cinema_label; - Label *locked_label; - Label *zoom_limit_label; + Label *info_label = nullptr; + Label *cinema_label = nullptr; + Label *locked_label = nullptr; + Label *zoom_limit_label = nullptr; - VBoxContainer *top_right_vbox; - ViewportRotationControl *rotation_control; - Gradient *frame_time_gradient; - Label *cpu_time_label; - Label *gpu_time_label; - Label *fps_label; + 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; + Label *cpu_time_label = nullptr; + Label *gpu_time_label = nullptr; + Label *fps_label = nullptr; struct _RayResult { Node3D *item = nullptr; @@ -238,13 +247,15 @@ private: void _compute_edit(const Point2 &p_point); void _clear_selected(); void _select_clicked(bool p_allow_locked); - ObjectID _select_ray(const Point2 &p_pos); + ObjectID _select_ray(const Point2 &p_pos) const; void _find_items_at_pos(const Point2 &p_pos, Vector<_RayResult> &r_results, bool p_include_locked); Vector3 _get_ray_pos(const Vector2 &p_pos) const; Vector3 _get_ray(const Vector2 &p_pos) const; Point2 _point_to_screen(const Vector3 &p_point); Transform3D _get_camera_transform() const; int get_selected_count() const; + void cancel_transform(); + void _update_shrink(); Vector3 _get_camera_position() const; Vector3 _get_camera_normal() const; @@ -264,10 +275,12 @@ private: float get_fov() const; ObjectID clicked; + ObjectID material_target; Vector<_RayResult> selection_results; - bool clicked_wants_append; + bool clicked_wants_append = false; + bool selection_in_progress = false; - PopupMenu *selection_menu; + PopupMenu *selection_menu = nullptr; enum NavigationZoomStyle { NAVIGATION_ZOOM_VERTICAL, @@ -308,14 +321,18 @@ private: Point2 mouse_pos; Point2 original_mouse_pos; bool snap = false; + bool show_rotation_line = false; Ref<EditorNode3DGizmo> gizmo; int gizmo_handle = 0; + bool gizmo_handle_secondary = false; Variant gizmo_initial_value; + bool original_local; + bool instant; } _edit; struct Cursor { Vector3 pos; - real_t x_rot, y_rot, distance; + real_t x_rot, y_rot, distance, fov_scale; Vector3 eye_pos; // Used in freelook mode bool region_select; Point2 region_begin, region_end; @@ -325,6 +342,7 @@ private: x_rot = 0.5; y_rot = -0.5; distance = 4; + fov_scale = 1.0; region_select = false; } }; @@ -333,6 +351,8 @@ private: Cursor cursor; // Immediate cursor Cursor camera_cursor; // That one may be interpolated (don't modify this one except for smoothing purposes) + void scale_fov(real_t p_fov_offset); + void reset_fov(); void scale_cursor_distance(real_t scale); void set_freelook_active(bool active_now); @@ -341,7 +361,7 @@ private: real_t zoom_indicator_delay; int zoom_failed_attempts_count = 0; - RID move_gizmo_instance[3], move_plane_gizmo_instance[3], rotate_gizmo_instance[4], scale_gizmo_instance[3], scale_plane_gizmo_instance[3]; + RID move_gizmo_instance[3], move_plane_gizmo_instance[3], rotate_gizmo_instance[4], scale_gizmo_instance[3], scale_plane_gizmo_instance[3], axis_gizmo_instance[3]; String last_message; String message; @@ -349,6 +369,7 @@ private: void set_message(String p_message, float p_time = 5); + void _view_settings_confirmed(real_t p_interp_delta); void _update_camera(real_t p_interp_delta); Transform3D to_camera_transform(const Cursor &p_cursor) const; void _draw(); @@ -360,10 +381,10 @@ private: void _sinput(const Ref<InputEvent> &p_event); void _update_freelook(real_t delta); - Node3DEditor *spatial_editor; + Node3DEditor *spatial_editor = nullptr; - Camera3D *previewing; - Camera3D *preview; + Camera3D *previewing = nullptr; + Camera3D *preview = nullptr; bool previewing_cinema; bool _is_node_locked(const Node *p_node); @@ -379,8 +400,14 @@ private: Vector3 _get_instance_position(const Point2 &p_pos) const; static AABB _calculate_spatial_bounds(const Node3D *p_parent, bool p_exclude_top_level_transform = true); - void _create_preview(const Vector<String> &files) const; - void _remove_preview(); + + Node *_sanitize_preview_node(Node *p_node) const; + + 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(); @@ -390,7 +417,15 @@ private: void _project_settings_changed(); - Transform3D _compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local); + Transform3D _compute_transform(TransformMode p_mode, const Transform3D &p_original, const Transform3D &p_original_local, Vector3 p_motion, double p_extra, bool p_local, bool p_orthogonal); + + void begin_transform(TransformMode p_mode, bool instant); + void commit_transform(); + void update_transform(Point2 p_mousepos, bool p_shift); + void finish_transform(); + + void register_shortcut_action(const String &p_path, const String &p_name, Key p_keycode); + void shortcut_changed_callback(const Ref<Shortcut> p_shortcut, const String &p_shortcut_path); protected: void _notification(int p_what); @@ -416,7 +451,7 @@ public: SubViewport *get_viewport_node() { return viewport; } Camera3D *get_camera_3d() { return camera; } // return the default camera object. - Node3DEditorViewport(Node3DEditor *p_spatial_editor, EditorNode *p_editor, int p_index); + Node3DEditorViewport(Node3DEditor *p_spatial_editor, int p_index); ~Node3DEditorViewport(); }; @@ -429,11 +464,13 @@ public: Transform3D original_local; Transform3D last_xform; // last transform bool last_xform_dirty; - Node3D *sp; + Node3D *sp = nullptr; RID sbox_instance; + RID sbox_instance_offset; RID sbox_instance_xray; + RID sbox_instance_xray_offset; Ref<EditorNode3DGizmo> gizmo; - Map<int, Transform3D> subgizmos; // map ID -> initial transform + HashMap<int, Transform3D> subgizmos; // map ID -> initial transform Node3DEditorSelectedItem() { sp = nullptr; @@ -509,13 +546,13 @@ public: }; private: - EditorNode *editor; - EditorSelection *editor_selection; + EditorSelection *editor_selection = nullptr; - Node3DEditorViewportContainer *viewport_base; + Node3DEditorViewportContainer *viewport_base = nullptr; Node3DEditorViewport *viewports[VIEWPORTS_COUNT]; - VSplitContainer *shader_split; - HSplitContainer *palette_split; + VSplitContainer *shader_split = nullptr; + HSplitContainer *left_panel_split = nullptr; + HSplitContainer *right_panel_split = nullptr; ///// @@ -523,17 +560,17 @@ private: RID origin; RID origin_instance; - bool origin_enabled; + bool origin_enabled = false; RID grid[3]; RID grid_instance[3]; bool grid_visible[3]; //currently visible bool grid_enable[3]; //should be always visible if true - bool grid_enabled; + bool grid_enabled = false; bool grid_init_draw = false; - Camera3D::Projection grid_camera_last_update_perspective = Camera3D::PROJECTION_PERSPECTIVE; + 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]; + Ref<ArrayMesh> move_gizmo[3], move_plane_gizmo[3], rotate_gizmo[4], scale_gizmo[3], scale_plane_gizmo[3], axis_gizmo[3]; Ref<StandardMaterial3D> gizmo_color[3]; Ref<StandardMaterial3D> plane_gizmo_color[3]; Ref<ShaderMaterial> rotate_gizmo_color[3]; @@ -543,6 +580,7 @@ private: Ref<Node3DGizmo> current_hover_gizmo; int current_hover_gizmo_handle; + bool current_hover_gizmo_handle_secondary; real_t snap_translate_value; real_t snap_rotate_value; @@ -559,9 +597,14 @@ private: Ref<StandardMaterial3D> cursor_material; // Scene drag and drop support - Node3D *preview_node; + Node3D *preview_node = nullptr; AABB preview_bounds; + Ref<Material> preview_material; + Ref<Material> preview_reset_material; + ObjectID preview_material_target; + int preview_material_surface = -1; + struct Gizmo { bool visible = false; real_t scale = 0; @@ -599,31 +642,31 @@ private: Button *tool_button[TOOL_MAX]; Button *tool_option_button[TOOL_OPT_MAX]; - MenuButton *transform_menu; - PopupMenu *gizmos_menu; - MenuButton *view_menu; + MenuButton *transform_menu = nullptr; + PopupMenu *gizmos_menu = nullptr; + MenuButton *view_menu = nullptr; - AcceptDialog *accept; + AcceptDialog *accept = nullptr; - ConfirmationDialog *snap_dialog; - ConfirmationDialog *xform_dialog; - ConfirmationDialog *settings_dialog; + ConfirmationDialog *snap_dialog = nullptr; + ConfirmationDialog *xform_dialog = nullptr; + ConfirmationDialog *settings_dialog = nullptr; bool snap_enabled; bool snap_key_enabled; - LineEdit *snap_translate; - LineEdit *snap_rotate; - LineEdit *snap_scale; + LineEdit *snap_translate = nullptr; + LineEdit *snap_rotate = nullptr; + LineEdit *snap_scale = nullptr; LineEdit *xform_translate[3]; LineEdit *xform_rotate[3]; LineEdit *xform_scale[3]; - OptionButton *xform_type; + OptionButton *xform_type = nullptr; - VBoxContainer *settings_vbc; - SpinBox *settings_fov; - SpinBox *settings_znear; - SpinBox *settings_zfar; + VBoxContainer *settings_vbc = nullptr; + SpinBox *settings_fov = nullptr; + SpinBox *settings_znear = nullptr; + SpinBox *settings_zfar = nullptr; void _snap_changed(); void _snap_update(); @@ -633,19 +676,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; // Used for secondary menu items which are displayed depending on the currently selected node // (such as MeshInstance's "Mesh" menu). - PanelContainer *context_menu_container; - HBoxContainer *hbc_context_menu; + PanelContainer *context_menu_panel = nullptr; + HBoxContainer *context_menu_hbox = nullptr; void _generate_selection_boxes(); - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; 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(); @@ -654,15 +695,16 @@ private: void _toggle_maximize_view(Object *p_viewport); - Node *custom_camera; + Node *custom_camera = nullptr; Object *_get_editor_data(Object *p_what); Ref<Environment> viewport_environment; - Node3D *selected; + Node3D *selected = nullptr; void _request_gizmo(Object *p_obj); + void _set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D()); void _clear_subgizmo_selection(Object *p_obj = nullptr); static Node3DEditor *singleton; @@ -674,8 +716,6 @@ private: void _register_all_gizmos(); - Node3DEditor(); - void _selection_changed(); void _refresh_menu_icons(); @@ -684,18 +724,18 @@ private: uint32_t world_env_count = 0; uint32_t directional_light_count = 0; - Button *sun_button; - Label *sun_state; - Label *sun_title; - VBoxContainer *sun_vb; - Popup *sun_environ_popup; - Control *sun_direction; - EditorSpinSlider *sun_angle_altitude; - EditorSpinSlider *sun_angle_azimuth; - ColorPickerButton *sun_color; - EditorSpinSlider *sun_energy; - EditorSpinSlider *sun_max_distance; - Button *sun_add_to_scene; + Button *sun_button = nullptr; + Label *sun_state = nullptr; + Label *sun_title = nullptr; + VBoxContainer *sun_vb = nullptr; + Popup *sun_environ_popup = nullptr; + Control *sun_direction = nullptr; + EditorSpinSlider *sun_angle_altitude = nullptr; + EditorSpinSlider *sun_angle_azimuth = nullptr; + ColorPickerButton *sun_color = nullptr; + EditorSpinSlider *sun_energy = nullptr; + EditorSpinSlider *sun_max_distance = nullptr; + Button *sun_add_to_scene = nullptr; void _sun_direction_draw(); void _sun_direction_input(const Ref<InputEvent> &p_event); @@ -706,23 +746,23 @@ private: Ref<Shader> sun_direction_shader; Ref<ShaderMaterial> sun_direction_material; - Button *environ_button; - Label *environ_state; - Label *environ_title; - VBoxContainer *environ_vb; - ColorPickerButton *environ_sky_color; - ColorPickerButton *environ_ground_color; - EditorSpinSlider *environ_energy; - Button *environ_ao_button; - Button *environ_glow_button; - Button *environ_tonemap_button; - Button *environ_gi_button; - Button *environ_add_to_scene; - - Button *sun_environ_settings; - - DirectionalLight3D *preview_sun; - WorldEnvironment *preview_environment; + Button *environ_button = nullptr; + Label *environ_state = nullptr; + Label *environ_title = nullptr; + VBoxContainer *environ_vb = nullptr; + ColorPickerButton *environ_sky_color = nullptr; + ColorPickerButton *environ_ground_color = nullptr; + EditorSpinSlider *environ_energy = nullptr; + Button *environ_ao_button = nullptr; + Button *environ_glow_button = nullptr; + Button *environ_tonemap_button = nullptr; + Button *environ_gi_button = nullptr; + Button *environ_add_to_scene = nullptr; + + Button *sun_environ_settings = nullptr; + + DirectionalLight3D *preview_sun = nullptr; + WorldEnvironment *preview_environment = nullptr; Ref<Environment> environment; Ref<ProceduralSkyMaterial> sky_material; @@ -742,7 +782,7 @@ private: protected: void _notification(int p_what); //void _gui_input(InputEvent p_event); - virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); @@ -756,16 +796,18 @@ public: float get_fov() const { return settings_fov->get_value(); } Transform3D get_gizmo_transform() const { return gizmo.transform; } - bool is_gizmo_visible() const { return gizmo.visible; } + bool is_gizmo_visible() const; ToolMode get_tool_mode() const { return tool_mode; } bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); } + void set_local_coords_enabled(bool on) const { tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->set_pressed(on); } bool is_snap_enabled() const { return snap_enabled ^ snap_key_enabled; } double get_translate_snap() const; double get_rotate_snap() const; double get_scale_snap() const; Ref<ArrayMesh> get_move_gizmo(int idx) const { return move_gizmo[idx]; } + Ref<ArrayMesh> get_axis_gizmo(int idx) const { return axis_gizmo[idx]; } Ref<ArrayMesh> get_move_plane_gizmo(int idx) const { return move_plane_gizmo[idx]; } Ref<ArrayMesh> get_rotate_gizmo(int idx) const { return rotate_gizmo[idx]; } Ref<ArrayMesh> get_scale_gizmo(int idx) const { return scale_gizmo[idx]; } @@ -789,8 +831,16 @@ public: void add_control_to_menu_panel(Control *p_control); void remove_control_from_menu_panel(Control *p_control); + void add_control_to_left_panel(Control *p_control); + void remove_control_from_left_panel(Control *p_control); + + void add_control_to_right_panel(Control *p_control); + void remove_control_from_right_panel(Control *p_control); + + void move_control_to_left_panel(Control *p_control); + void move_control_to_right_panel(Control *p_control); + VSplitContainer *get_shader_split(); - HSplitContainer *get_palette_split(); Node3D *get_single_selected_node() { return selected; } bool is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo); @@ -800,11 +850,27 @@ public: Ref<EditorNode3DGizmo> get_current_hover_gizmo() const { return current_hover_gizmo; } void set_current_hover_gizmo(Ref<EditorNode3DGizmo> p_gizmo) { current_hover_gizmo = p_gizmo; } - void set_current_hover_gizmo_handle(int p_id) { current_hover_gizmo_handle = p_id; } - int get_current_hover_gizmo_handle() const { return current_hover_gizmo_handle; } + void set_current_hover_gizmo_handle(int p_id, bool p_secondary) { + current_hover_gizmo_handle = p_id; + current_hover_gizmo_handle_secondary = p_secondary; + } + + int get_current_hover_gizmo_handle(bool &r_secondary) const { + r_secondary = current_hover_gizmo_handle_secondary; + return current_hover_gizmo_handle; + } void set_can_preview(Camera3D *p_preview); + void set_preview_material(Ref<Material> p_material) { preview_material = p_material; } + Ref<Material> get_preview_material() { return preview_material; } + void set_preview_reset_material(Ref<Material> p_material) { preview_reset_material = p_material; } + Ref<Material> get_preview_reset_material() const { return preview_reset_material; } + void set_preview_material_target(ObjectID p_object_id) { preview_material_target = p_object_id; } + ObjectID get_preview_material_target() const { return preview_material_target; } + void set_preview_material_surface(int p_surface) { preview_material_surface = p_surface; } + int get_preview_material_surface() const { return preview_material_surface; } + Node3DEditorViewport *get_editor_viewport(int p_idx) { ERR_FAIL_INDEX_V(p_idx, static_cast<int>(VIEWPORTS_COUNT), nullptr); return viewports[p_idx]; @@ -816,15 +882,14 @@ public: void edit(Node3D *p_spatial); void clear(); - Node3DEditor(EditorNode *p_editor); + Node3DEditor(); ~Node3DEditor(); }; class Node3DEditorPlugin : public EditorPlugin { GDCLASS(Node3DEditorPlugin, EditorPlugin); - Node3DEditor *spatial_editor; - EditorNode *editor; + Node3DEditor *spatial_editor = nullptr; public: Node3DEditor *get_spatial_editor() { return spatial_editor; } @@ -840,7 +905,7 @@ public: virtual void edited_scene_changed() override; - Node3DEditorPlugin(EditorNode *p_node); + Node3DEditorPlugin(); ~Node3DEditorPlugin(); }; diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index ab88b9f00d..365f74d7a3 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,22 +30,25 @@ #include "occluder_instance_3d_editor_plugin.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { if (occluder_instance) { OccluderInstance3D::BakeError err; if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == occluder_instance) { - err = occluder_instance->bake(occluder_instance, p_file); + err = occluder_instance->bake_scene(occluder_instance, p_file); } else { - err = occluder_instance->bake(occluder_instance->get_parent(), p_file); + err = occluder_instance->bake_scene(occluder_instance->get_parent(), p_file); } switch (err) { case OccluderInstance3D::BAKE_ERROR_NO_SAVE_PATH: { - String scene_path = occluder_instance->get_filename(); - if (scene_path == String()) { - scene_path = occluder_instance->get_owner()->get_filename(); + String scene_path = occluder_instance->get_scene_file_path(); + if (scene_path.is_empty()) { + scene_path = occluder_instance->get_owner()->get_scene_file_path(); } - if (scene_path == String()) { + if (scene_path.is_empty()) { EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for the occluder.\nSave your scene and try again.")); break; } @@ -59,6 +62,10 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.\nMake sure there is at least one MeshInstance3D node in the scene whose visual layers are part of the OccluderInstance3D's Bake Mask property.")); break; } + case OccluderInstance3D::BAKE_ERROR_CANT_SAVE: { + EditorNode::get_singleton()->show_warning(TTR("Could not save the new occluder at the specified path:") + " " + p_file); + break; + } default: { } } @@ -94,11 +101,10 @@ void OccluderInstance3DEditorPlugin::_bind_methods() { ClassDB::bind_method("_bake", &OccluderInstance3DEditorPlugin::_bake); } -OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin() { bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); bake->set_text(TTR("Bake Occluders")); bake->hide(); bake->connect("pressed", Callable(this, "_bake")); @@ -107,7 +113,7 @@ OccluderInstance3DEditorPlugin::OccluderInstance3DEditorPlugin(EditorNode *p_nod file_dialog = memnew(EditorFileDialog); file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); - file_dialog->add_filter("*.occ ; Occluder3D"); + file_dialog->add_filter("*.occ", "Occluder3D"); file_dialog->set_title(TTR("Select occluder bake file:")); file_dialog->connect("file_selected", callable_mp(this, &OccluderInstance3DEditorPlugin::_bake_select_file)); bake->add_child(file_dialog); diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.h b/editor/plugins/occluder_instance_3d_editor_plugin.h index 161b17811c..e8d98927f4 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.h +++ b/editor/plugins/occluder_instance_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,20 +31,20 @@ #ifndef OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H #define OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/occluder_instance_3d.h" #include "scene/resources/material.h" +class EditorFileDialog; + class OccluderInstance3DEditorPlugin : public EditorPlugin { GDCLASS(OccluderInstance3DEditorPlugin, EditorPlugin); - OccluderInstance3D *occluder_instance; + OccluderInstance3D *occluder_instance = nullptr; - Button *bake; - EditorNode *editor; + Button *bake = nullptr; - EditorFileDialog *file_dialog; + EditorFileDialog *file_dialog = nullptr; void _bake_select_file(const String &p_file); void _bake(); @@ -59,8 +59,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - OccluderInstance3DEditorPlugin(EditorNode *p_node); + OccluderInstance3DEditorPlugin(); ~OccluderInstance3DEditorPlugin(); }; -#endif +#endif // OCCLUDER_INSTANCE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/ot_features_plugin.cpp b/editor/plugins/ot_features_plugin.cpp deleted file mode 100644 index fd42bce06e..0000000000 --- a/editor/plugins/ot_features_plugin.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/*************************************************************************/ -/* ot_features_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "ot_features_plugin.h" - -#include "editor/editor_scale.h" - -void OpenTypeFeaturesEditor::_value_changed(double val) { - if (setting) { - return; - } - - emit_changed(get_edited_property(), spin->get_value()); -} - -void OpenTypeFeaturesEditor::update_property() { - double val = get_edited_object()->get(get_edited_property()); - setting = true; - spin->set_value(val); - setting = false; -} - -void OpenTypeFeaturesEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - Color base = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - - button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); - button->set_size(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))->get_size()); - spin->set_custom_label_color(true, base); - } -} - -void OpenTypeFeaturesEditor::_remove_feature() { - get_edited_object()->set(get_edited_property(), -1); -} - -void OpenTypeFeaturesEditor::_bind_methods() { -} - -OpenTypeFeaturesEditor::OpenTypeFeaturesEditor() { - HBoxContainer *bc = memnew(HBoxContainer); - add_child(bc); - - spin = memnew(EditorSpinSlider); - spin->set_flat(true); - bc->add_child(spin); - add_focusable(spin); - spin->connect("value_changed", callable_mp(this, &OpenTypeFeaturesEditor::_value_changed)); - spin->set_h_size_flags(SIZE_EXPAND_FILL); - - spin->set_min(0); - spin->set_max(65536); - spin->set_step(1); - spin->set_hide_slider(false); - spin->set_allow_greater(false); - spin->set_allow_lesser(false); - - button = memnew(Button); - button->set_tooltip(RTR("Remove feature")); - button->set_flat(true); - bc->add_child(button); - - button->connect("pressed", callable_mp(this, &OpenTypeFeaturesEditor::_remove_feature)); - - setting = false; -} - -/*************************************************************************/ - -void OpenTypeFeaturesAdd::_add_feature(int p_option) { - get_edited_object()->set("opentype_features/" + TS->tag_to_name(p_option), 1); -} - -void OpenTypeFeaturesAdd::update_property() { - menu->clear(); - menu_ss->clear(); - menu_cv->clear(); - menu_cu->clear(); - bool have_ss = false; - bool have_cv = false; - bool have_cu = false; - Dictionary features = Object::cast_to<Control>(get_edited_object())->get_theme_font(SNAME("font"))->get_feature_list(); - for (const Variant *ftr = features.next(nullptr); ftr != nullptr; ftr = features.next(ftr)) { - String ftr_name = TS->tag_to_name(*ftr); - if (ftr_name.begins_with("stylistic_set_")) { - menu_ss->add_item(ftr_name.capitalize(), (int32_t)*ftr); - have_ss = true; - } else if (ftr_name.begins_with("character_variant_")) { - menu_cv->add_item(ftr_name.capitalize(), (int32_t)*ftr); - have_cv = true; - } else if (ftr_name.begins_with("custom_")) { - menu_cu->add_item(ftr_name.replace("custom_", ""), (int32_t)*ftr); - have_cu = true; - } else { - menu->add_item(ftr_name.capitalize(), (int32_t)*ftr); - } - } - if (have_ss) { - menu->add_submenu_item(RTR("Stylistic Sets"), "SSMenu"); - } - if (have_cv) { - menu->add_submenu_item(RTR("Character Variants"), "CVMenu"); - } - if (have_cu) { - menu->add_submenu_item(RTR("Custom"), "CUMenu"); - } -} - -void OpenTypeFeaturesAdd::_features_menu() { - Size2 size = get_size(); - menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); - menu->popup(); -} - -void OpenTypeFeaturesAdd::_notification(int p_what) { - if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) { - set_label(""); - button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); - button->set_size(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))->get_size()); - } -} - -void OpenTypeFeaturesAdd::_bind_methods() { -} - -OpenTypeFeaturesAdd::OpenTypeFeaturesAdd() { - menu = memnew(PopupMenu); - add_child(menu); - - menu_cv = memnew(PopupMenu); - menu_cv->set_name("CVMenu"); - menu->add_child(menu_cv); - - menu_ss = memnew(PopupMenu); - menu_ss->set_name("SSMenu"); - menu->add_child(menu_ss); - - menu_cu = memnew(PopupMenu); - menu_cu->set_name("CUMenu"); - menu->add_child(menu_cu); - - button = memnew(Button); - button->set_flat(true); - button->set_text(RTR("Add feature...")); - button->set_tooltip(RTR("Add feature...")); - add_child(button); - - button->connect("pressed", callable_mp(this, &OpenTypeFeaturesAdd::_features_menu)); - menu->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); - menu_cv->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); - menu_ss->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); - menu_cu->connect("id_pressed", callable_mp(this, &OpenTypeFeaturesAdd::_add_feature)); -} - -/*************************************************************************/ - -bool EditorInspectorPluginOpenTypeFeatures::can_handle(Object *p_object) { - return (Object::cast_to<Control>(p_object) != nullptr); -} - -void EditorInspectorPluginOpenTypeFeatures::parse_begin(Object *p_object) { -} - -void EditorInspectorPluginOpenTypeFeatures::parse_category(Object *p_object, const String &p_parse_category) { -} - -bool EditorInspectorPluginOpenTypeFeatures::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { - if (p_path == "opentype_features/_new") { - OpenTypeFeaturesAdd *editor = memnew(OpenTypeFeaturesAdd); - add_property_editor(p_path, editor); - return true; - } else if (p_path.begins_with("opentype_features")) { - OpenTypeFeaturesEditor *editor = memnew(OpenTypeFeaturesEditor); - add_property_editor(p_path, editor); - return true; - } - return false; -} - -/*************************************************************************/ - -OpenTypeFeaturesEditorPlugin::OpenTypeFeaturesEditorPlugin(EditorNode *p_node) { - Ref<EditorInspectorPluginOpenTypeFeatures> ftr_plugin; - ftr_plugin.instantiate(); - EditorInspector::add_inspector_plugin(ftr_plugin); -} diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index 0a949c8610..8d083d28b2 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,6 +31,7 @@ #include "packed_scene_translation_parser_plugin.h" #include "core/io/resource_loader.h" +#include "scene/gui/option_button.h" #include "scene/resources/packed_scene.h" void PackedSceneEditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const { @@ -42,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; @@ -50,21 +51,31 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, Ref<SceneState> state = Ref<PackedScene>(loaded_res)->get_state(); Vector<String> parsed_strings; - String property_name; - Variant property_value; for (int i = 0; i < state->get_node_count(); i++) { - if (!ClassDB::is_parent_class(state->get_node_type(i), "Control") && !ClassDB::is_parent_class(state->get_node_type(i), "Viewport")) { + String node_type = state->get_node_type(i); + if (!ClassDB::is_parent_class(node_type, "Control") && !ClassDB::is_parent_class(node_type, "Window")) { continue; } + // Find the `auto_translate` property, and abort the string parsing of the node if disabled. + bool auto_translating = true; for (int j = 0; j < state->get_node_property_count(i); j++) { - property_name = state->get_node_property_name(i, j); - if (!lookup_properties.has(property_name)) { - continue; + if (state->get_node_property_name(i, j) == "auto_translate" && (bool)state->get_node_property_value(i, j) == false) { + auto_translating = false; + break; } + } + if (!auto_translating) { + continue; + } - property_value = state->get_node_property_value(i, j); + for (int j = 0; j < state->get_node_property_count(i); j++) { + String property_name = state->get_node_property_name(i, j); + if (!lookup_properties.has(property_name) || (exception_list.has(node_type) && exception_list[node_type].has(property_name))) { + continue; + } + Variant property_value = state->get_node_property_value(i, j); if (property_name == "script" && property_value.get_type() == Variant::OBJECT && !property_value.is_null()) { // Parse built-in script. Ref<Script> s = Object::cast_to<Script>(property_value); @@ -76,7 +87,16 @@ Error PackedSceneEditorTranslationParserPlugin::parse_file(const String &p_path, parsed_strings.append_array(temp); r_ids_ctx_plural->append_array(ids_context_plural); } - } else if (property_name == "filters") { + } else if ((node_type == "MenuButton" || node_type == "OptionButton") && property_name == "items") { + Vector<String> str_values = property_value; + int incr_value = node_type == "MenuButton" ? PopupMenu::ITEM_PROPERTY_SIZE : OptionButton::ITEM_PROPERTY_SIZE; + for (int k = 0; k < str_values.size(); k += incr_value) { + String desc = str_values[k].get_slice(";", 1).strip_edges(); + if (!desc.is_empty()) { + parsed_strings.push_back(desc); + } + } + } else if (node_type == "FileDialog" && property_name == "filters") { // Extract FileDialog's filters property with values in format "*.png ; PNG Images","*.gd ; GDScript Files". Vector<String> str_values = property_value; for (int k = 0; k < str_values.size(); k++) { @@ -105,12 +125,14 @@ PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlug lookup_properties.insert("text"); lookup_properties.insert("hint_tooltip"); lookup_properties.insert("placeholder_text"); + lookup_properties.insert("items"); + lookup_properties.insert("title"); lookup_properties.insert("dialog_text"); lookup_properties.insert("filters"); lookup_properties.insert("script"); - //Add exception list (to prevent false positives) - //line edit, text edit, richtextlabel - //Set<String> exception_list; - //exception_list.insert("RichTextLabel"); + // Exception list (to prevent false positives). + exception_list.insert("LineEdit", { "text" }); + exception_list.insert("TextEdit", { "text" }); + exception_list.insert("CodeEdit", { "text" }); } diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h index e51d65414e..1bfb500933 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.h +++ b/editor/plugins/packed_scene_translation_parser_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,7 +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. + 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 119ecddf63..fd331c4127 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,19 +33,19 @@ #include "canvas_item_editor_plugin.h" #include "core/io/file_access.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" void Path2DEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: { - //button_create->set_icon( get_icon("Edit","EditorIcons")); - //button_edit->set_icon( get_icon("MovePoint","EditorIcons")); - //set_pressed_button(button_edit); - //button_edit->set_pressed(true); - - } break; - case NOTIFICATION_PHYSICS_PROCESS: { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + curve_edit->set_icon(get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + curve_edit_curve->set_icon(get_theme_icon(SNAME("CurveCurve"), SNAME("EditorIcons"))); + curve_create->set_icon(get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + curve_del->set_icon(get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + curve_close->set_icon(get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); } break; } } @@ -88,7 +88,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { real_t dist_to_p_in = gpoint.distance_to(xform.xform(curve->get_point_position(i) + curve->get_point_in(i))); // Check for point movement start (for point + in/out controls). - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mode == MODE_EDIT && !mb->is_shift_pressed() && dist_to_p < grab_threshold) { // Points can only be moved in edit mode. @@ -118,7 +118,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point deletion. - if ((mb->get_button_index() == MOUSE_BUTTON_RIGHT && mode == MODE_EDIT) || (mb->get_button_index() == MOUSE_BUTTON_LEFT && mode == MODE_DELETE)) { + if ((mb->get_button_index() == MouseButton::RIGHT && mode == MODE_EDIT) || (mb->get_button_index() == MouseButton::LEFT && mode == MODE_DELETE)) { if (dist_to_p < grab_threshold) { undo_redo->create_action(TTR("Remove Point from Curve")); undo_redo->add_do_method(curve.ptr(), "remove_point", i); @@ -149,7 +149,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point creation. - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && ((mb->is_command_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && ((mb->is_command_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) { Ref<Curve2D> curve = node->get_curve(); undo_redo->create_action(TTR("Add Point to Curve")); @@ -170,7 +170,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for segment split. - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && mode == MODE_EDIT && on_edge) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && mode == MODE_EDIT && on_edge) { Vector2 gpoint2 = mb->get_position(); Ref<Curve2D> curve = node->get_curve(); @@ -207,7 +207,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point movement completion. - if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && action != ACTION_NONE) { + if (!mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && action != ACTION_NONE) { Ref<Curve2D> curve = node->get_curve(); Vector2 new_pos = moving_from + xform.affine_inverse().basis_xform(gpoint - moving_screen_from); @@ -516,10 +516,9 @@ void Path2DEditor::_handle_option_pressed(int p_option) { } } -Path2DEditor::Path2DEditor(EditorNode *p_editor) { +Path2DEditor::Path2DEditor() { canvas_item_editor = nullptr; - editor = p_editor; - undo_redo = editor->get_undo_redo(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); mirror_handle_angle = true; mirror_handle_length = true; on_edge = false; @@ -532,44 +531,44 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { sep = memnew(VSeparator); base_hb->add_child(sep); + curve_edit = memnew(Button); curve_edit->set_flat(true); - curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); curve_edit->set_toggle_mode(true); curve_edit->set_focus_mode(Control::FOCUS_NONE); - curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string(KEY_MASK_CMD) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point")); - curve_edit->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_EDIT)); + curve_edit->set_tooltip(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).bind(MODE_EDIT)); base_hb->add_child(curve_edit); + curve_edit_curve = memnew(Button); curve_edit_curve->set_flat(true); - curve_edit_curve->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCurve"), SNAME("EditorIcons"))); curve_edit_curve->set_toggle_mode(true); curve_edit_curve->set_focus_mode(Control::FOCUS_NONE); curve_edit_curve->set_tooltip(TTR("Select Control Points (Shift+Drag)")); - curve_edit_curve->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_EDIT_CURVE)); + curve_edit_curve->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_EDIT_CURVE)); base_hb->add_child(curve_edit_curve); + curve_create = memnew(Button); curve_create->set_flat(true); - curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); curve_create->set_toggle_mode(true); curve_create->set_focus_mode(Control::FOCUS_NONE); curve_create->set_tooltip(TTR("Add Point (in empty space)")); - curve_create->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_CREATE)); + curve_create->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_CREATE)); base_hb->add_child(curve_create); + curve_del = memnew(Button); curve_del->set_flat(true); - curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); curve_del->set_toggle_mode(true); curve_del->set_focus_mode(Control::FOCUS_NONE); curve_del->set_tooltip(TTR("Delete Point")); - curve_del->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_DELETE)); + curve_del->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(MODE_DELETE)); base_hb->add_child(curve_del); + curve_close = memnew(Button); curve_close->set_flat(true); - curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip(TTR("Close Curve")); - curve_close->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(ACTION_CLOSE)); + curve_close->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected).bind(ACTION_CLOSE)); base_hb->add_child(curve_close); PopupMenu *menu; @@ -610,9 +609,8 @@ void Path2DEditorPlugin::make_visible(bool p_visible) { } } -Path2DEditorPlugin::Path2DEditorPlugin(EditorNode *p_node) { - editor = p_node; - path2d_editor = memnew(Path2DEditor(p_node)); +Path2DEditorPlugin::Path2DEditorPlugin() { + path2d_editor = memnew(Path2DEditor); CanvasItemEditor::get_singleton()->add_control_to_menu_panel(path2d_editor); path2d_editor->hide(); } diff --git a/editor/plugins/path_2d_editor_plugin.h b/editor/plugins/path_2d_editor_plugin.h index 867e0ce74f..720f5c090f 100644 --- a/editor/plugins/path_2d_editor_plugin.h +++ b/editor/plugins/path_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,24 +31,23 @@ #ifndef PATH_2D_EDITOR_PLUGIN_H #define PATH_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/path_2d.h" +#include "scene/gui/separator.h" class CanvasItemEditor; class Path2DEditor : public HBoxContainer { GDCLASS(Path2DEditor, HBoxContainer); - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; - CanvasItemEditor *canvas_item_editor; - EditorNode *editor; - Panel *panel; - Path2D *node; + CanvasItemEditor *canvas_item_editor = nullptr; + Panel *panel = nullptr; + Path2D *node = nullptr; - HBoxContainer *base_hb; - Separator *sep; + HBoxContainer *base_hb = nullptr; + Separator *sep = nullptr; enum Mode { MODE_CREATE, @@ -59,12 +58,12 @@ class Path2DEditor : public HBoxContainer { }; Mode mode; - Button *curve_create; - Button *curve_edit; - Button *curve_edit_curve; - Button *curve_del; - Button *curve_close; - MenuButton *handle_menu; + Button *curve_create = nullptr; + Button *curve_edit = nullptr; + Button *curve_edit_curve = nullptr; + Button *curve_del = nullptr; + Button *curve_close = nullptr; + MenuButton *handle_menu = nullptr; bool mirror_handle_angle; bool mirror_handle_length; @@ -83,11 +82,11 @@ class Path2DEditor : public HBoxContainer { }; Action action; - int action_point; + int action_point = 0; Point2 moving_from; Point2 moving_screen_from; - float orig_in_length; - float orig_out_length; + float orig_in_length = 0.0f; + float orig_out_length = 0.0f; Vector2 edge_point; void _mode_selected(int p_mode); @@ -105,14 +104,13 @@ public: bool forward_gui_input(const Ref<InputEvent> &p_event); void forward_canvas_draw_over_viewport(Control *p_overlay); void edit(Node *p_path2d); - Path2DEditor(EditorNode *p_editor); + Path2DEditor(); }; class Path2DEditorPlugin : public EditorPlugin { GDCLASS(Path2DEditorPlugin, EditorPlugin); - Path2DEditor *path2d_editor; - EditorNode *editor; + Path2DEditor *path2d_editor = nullptr; public: virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return path2d_editor->forward_gui_input(p_event); } @@ -124,7 +122,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Path2DEditorPlugin(EditorNode *p_node); + Path2DEditorPlugin(); ~Path2DEditorPlugin(); }; diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 13f7908170..65b15a6001 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,25 +33,38 @@ #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" #include "node_3d_editor_plugin.h" #include "scene/resources/curve.h" -String Path3DGizmo::get_handle_name(int p_id) const { +static bool _is_in_handle(int p_id, int p_num_points) { + int t = (p_id + 1) % 2; + int idx = (p_id + 1) / 2; + // order of points is [out_0, out_1, in_1, out_2, in_2, ... out_n-1, in_n-1, in_n] + if (idx == 0) { + return false; + } else if (idx == (p_num_points - 1)) { + return true; + } else { + return (t == 1); + } +} + +String Path3DGizmo::get_handle_name(int p_id, bool p_secondary) const { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return ""; } - if (p_id < c->get_point_count()) { + if (!p_secondary) { return TTR("Curve Point #") + itos(p_id); } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; String n = TTR("Curve Point #") + itos(idx); - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { n += " In"; } else { n += " Out"; @@ -60,24 +73,22 @@ String Path3DGizmo::get_handle_name(int p_id) const { return n; } -Variant Path3DGizmo::get_handle_value(int p_id) const { +Variant Path3DGizmo::get_handle_value(int p_id, bool p_secondary) const { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return Variant(); } - if (p_id < c->get_point_count()) { + if (!p_secondary) { original = c->get_point_position(p_id); return original; } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; Vector3 ofs; - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { ofs = c->get_point_in(idx); } else { ofs = c->get_point_out(idx); @@ -88,7 +99,7 @@ Variant Path3DGizmo::get_handle_value(int p_id) const { return ofs; } -void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) { +void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return; @@ -100,8 +111,8 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point Vector3 ray_dir = p_camera->project_ray_normal(p_point); // Setting curve point positions - if (p_id < c->get_point_count()) { - Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2)); + if (!p_secondary) { + const Plane p = Plane(p_camera->get_transform().basis.get_column(2), gt.xform(original)); Vector3 inters; @@ -118,14 +129,12 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point return; } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; Vector3 base = c->get_point_position(idx); - Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2)); + Plane p(p_camera->get_transform().basis.get_column(2), gt.xform(original)); Vector3 inters; @@ -143,7 +152,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point local.snap(Vector3(snap, snap, snap)); } - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { c->set_point_in(idx, local); if (Path3DEditorPlugin::singleton->mirror_angle_enabled()) { c->set_point_out(idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -local : (-local.normalized() * orig_out_length)); @@ -157,7 +166,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point } } -void Path3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cancel) { +void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { return; @@ -165,7 +174,7 @@ void Path3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cance UndoRedo *ur = Node3DEditor::get_singleton()->get_undo_redo(); - if (p_id < c->get_point_count()) { + if (!p_secondary) { if (p_cancel) { c->set_point_position(p_id, p_restore); return; @@ -178,12 +187,10 @@ void Path3DGizmo::commit_handle(int p_id, const Variant &p_restore, bool p_cance return; } - p_id = p_id - c->get_point_count() + 1; - - int idx = p_id / 2; - int t = p_id % 2; + // (p_id + 1) Accounts for the first point only having an "out" handle + int idx = (p_id + 1) / 2; - if (t == 0) { + if (_is_in_handle(p_id, c->get_point_count())) { if (p_cancel) { c->set_point_in(p_id, p_restore); return; @@ -262,17 +269,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) { @@ -294,13 +301,13 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path) { orig_out_length = 0; } -bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!path) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Transform3D gt = path->get_global_transform(); Transform3D it = gt.affine_inverse(); @@ -316,27 +323,27 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref set_handle_clicked(false); } - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_ctrl_pressed()))) { + if (mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_ctrl_pressed()))) { //click into curve, break it down Vector<Vector3> v3a = c->tessellate(); - int idx = 0; int rc = v3a.size(); int closest_seg = -1; Vector3 closest_seg_point; - float closest_d = 1e20; if (rc >= 2) { + int idx = 0; const Vector3 *r = v3a.ptr(); + float closest_d = 1e20; if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } for (int i = 0; i < c->get_point_count() - 1; i++) { //find the offset and point index of the place to break up int j = idx; if (p_camera->unproject_position(gt.xform(c->get_point_position(i + 1))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } while (j < rc && c->get_point_position(i + 1) != r[j]) { @@ -378,7 +385,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref } } - UndoRedo *ur = editor->get_undo_redo(); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); if (closest_seg != -1) { //subdivide @@ -386,16 +393,16 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", closest_seg_point, Vector3(), Vector3(), closest_seg + 1); ur->add_undo_method(c.ptr(), "remove_point", closest_seg + 1); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { - Vector3 org; + Vector3 origin; if (c->get_point_count() == 0) { - org = path->get_transform().get_origin(); + origin = path->get_transform().get_origin(); } else { - org = gt.xform(c->get_point_position(c->get_point_count() - 1)); + origin = gt.xform(c->get_point_position(c->get_point_count() - 1)); } - Plane p(org, p_camera->get_transform().basis.get_axis(2)); + 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); @@ -405,13 +412,13 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1); ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //add new at pos } - } else if (mb->is_pressed() && ((mb->get_button_index() == MOUSE_BUTTON_LEFT && curve_del->is_pressed()) || (mb->get_button_index() == MOUSE_BUTTON_RIGHT && curve_edit->is_pressed()))) { + } else if (mb->is_pressed() && ((mb->get_button_index() == MouseButton::LEFT && curve_del->is_pressed()) || (mb->get_button_index() == MouseButton::RIGHT && curve_edit->is_pressed()))) { for (int i = 0; i < c->get_point_count(); i++) { real_t dist_to_p = p_camera->unproject_position(gt.xform(c->get_point_position(i))).distance_to(mbpos); real_t dist_to_p_out = p_camera->unproject_position(gt.xform(c->get_point_position(i) + c->get_point_out(i))).distance_to(mbpos); @@ -420,32 +427,32 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref // Find the offset and point index of the place to break up. // Also check for the control points. if (dist_to_p < click_dist) { - UndoRedo *ur = editor->get_undo_redo(); + UndoRedo *ur = EditorNode::get_singleton()->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 true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_out < click_dist) { - UndoRedo *ur = editor->get_undo_redo(); + UndoRedo *ur = EditorNode::get_singleton()->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 true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_in < click_dist) { - UndoRedo *ur = editor->get_undo_redo(); + UndoRedo *ur = EditorNode::get_singleton()->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)); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } void Path3DEditorPlugin::edit(Object *p_object) { @@ -510,7 +517,14 @@ void Path3DEditorPlugin::_close_curve() { if (c->get_point_count() < 2) { return; } - c->add_point(c->get_point_position(0), c->get_point_in(0), c->get_point_out(0)); + if (c->get_point_position(0) == c->get_point_position(c->get_point_count() - 1)) { + return; + } + UndoRedo *ur = EditorNode::get_singleton()->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()); + ur->commit_action(); } void Path3DEditorPlugin::_handle_option_pressed(int p_option) { @@ -532,12 +546,29 @@ void Path3DEditorPlugin::_handle_option_pressed(int p_option) { } } +void Path3DEditorPlugin::_update_theme() { + // TODO: Split the EditorPlugin instance from the UI instance and connect this properly. + // See the 2D path editor for inspiration. + curve_edit->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + curve_create->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + curve_del->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + curve_close->set_icon(Node3DEditor::get_singleton()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); +} + void Path3DEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - curve_create->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed), make_binds(0)); - curve_edit->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed), make_binds(1)); - curve_del->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed), make_binds(2)); - curve_close->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_close_curve)); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + curve_create->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(0)); + curve_edit->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(1)); + curve_del->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(2)); + curve_close->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_close_curve)); + + _update_theme(); + } break; + + case NOTIFICATION_READY: { + Node3DEditor::get_singleton()->connect("theme_changed", callable_mp(this, &Path3DEditorPlugin::_update_theme)); + } break; } } @@ -546,9 +577,8 @@ void Path3DEditorPlugin::_bind_methods() { Path3DEditorPlugin *Path3DEditorPlugin::singleton = nullptr; -Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { +Path3DEditorPlugin::Path3DEditorPlugin() { path = nullptr; - editor = p_node; singleton = this; mirror_handle_angle = true; mirror_handle_length = true; @@ -560,33 +590,33 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { sep = memnew(VSeparator); sep->hide(); Node3DEditor::get_singleton()->add_control_to_menu_panel(sep); + curve_edit = memnew(Button); curve_edit->set_flat(true); - curve_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); curve_edit->set_toggle_mode(true); curve_edit->hide(); curve_edit->set_focus_mode(Control::FOCUS_NONE); - curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string(KEY_MASK_CMD) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point")); + curve_edit->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point")); Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_edit); + curve_create = memnew(Button); curve_create->set_flat(true); - curve_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); curve_create->set_toggle_mode(true); curve_create->hide(); curve_create->set_focus_mode(Control::FOCUS_NONE); curve_create->set_tooltip(TTR("Add Point (in empty space)") + "\n" + TTR("Split Segment (in curve)")); Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_create); + curve_del = memnew(Button); curve_del->set_flat(true); - curve_del->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); curve_del->set_toggle_mode(true); curve_del->hide(); curve_del->set_focus_mode(Control::FOCUS_NONE); curve_del->set_tooltip(TTR("Delete Point")); Node3DEditor::get_singleton()->add_control_to_menu_panel(curve_del); + curve_close = memnew(Button); curve_close->set_flat(true); - curve_close->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveClose"), SNAME("EditorIcons"))); curve_close->hide(); curve_close->set_focus_mode(Control::FOCUS_NONE); curve_close->set_tooltip(TTR("Close Curve")); @@ -607,15 +637,6 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { menu->connect("id_pressed", callable_mp(this, &Path3DEditorPlugin::_handle_option_pressed)); curve_edit->set_pressed(true); - /* - collision_polygon_editor = memnew( PathEditor(p_node) ); - editor->get_main_control()->add_child(collision_polygon_editor); - collision_polygon_editor->set_margin(MARGIN_LEFT,200); - collision_polygon_editor->set_margin(MARGIN_RIGHT,230); - collision_polygon_editor->set_margin(MARGIN_TOP,0); - collision_polygon_editor->set_margin(MARGIN_BOTTOM,10); - collision_polygon_editor->hide(); - */ } Path3DEditorPlugin::~Path3DEditorPlugin() { diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index b74d7cc59e..53e4e2efa8 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,27 +28,28 @@ /* 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" #include "scene/3d/camera_3d.h" #include "scene/3d/path_3d.h" +#include "scene/gui/separator.h" class Path3DGizmo : public EditorNode3DGizmo { GDCLASS(Path3DGizmo, EditorNode3DGizmo); - Path3D *path; + Path3D *path = nullptr; mutable Vector3 original; mutable float orig_in_length; mutable float orig_out_length; public: - virtual String get_handle_name(int p_idx) const override; - virtual Variant get_handle_value(int p_id) const override; - virtual void set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point) override; - virtual void commit_handle(int p_id, const Variant &p_restore, bool p_cancel = false) override; + virtual String get_handle_name(int p_id, bool p_secondary) const override; + virtual Variant get_handle_value(int p_id, bool p_secondary) const override; + virtual void set_handle(int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + virtual void commit_handle(int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; virtual void redraw() override; Path3DGizmo(Path3D *p_path = nullptr); @@ -69,21 +70,21 @@ public: class Path3DEditorPlugin : public EditorPlugin { GDCLASS(Path3DEditorPlugin, EditorPlugin); - Separator *sep; - Button *curve_create; - Button *curve_edit; - Button *curve_del; - Button *curve_close; - MenuButton *handle_menu; + Separator *sep = nullptr; + Button *curve_create = nullptr; + Button *curve_edit = nullptr; + Button *curve_del = nullptr; + Button *curve_close = nullptr; + MenuButton *handle_menu = nullptr; - EditorNode *editor; + Path3D *path = nullptr; - Path3D *path; + void _update_theme(); void _mode_changed(int p_idx); void _close_curve(); void _handle_option_pressed(int p_option); - bool handle_clicked; + bool handle_clicked = false; bool mirror_handle_angle; bool mirror_handle_length; @@ -100,7 +101,7 @@ public: Path3D *get_edited_path() { return path; } static Path3DEditorPlugin *singleton; - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; virtual String get_name() const override { return "Path3D"; } bool has_main_screen() const override { return false; } @@ -113,8 +114,8 @@ public: bool is_handle_clicked() { return handle_clicked; } void set_handle_clicked(bool clicked) { handle_clicked = clicked; } - Path3DEditorPlugin(EditorNode *p_node); + Path3DEditorPlugin(); ~Path3DEditorPlugin(); }; -#endif // PATH_EDITOR_PLUGIN_H +#endif // PATH_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index f92f50f826..9dc89133c4 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #include "physical_bone_3d_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" -#include "scene/3d/physics_body_3d.h" void PhysicalBone3DEditor::_bind_methods() { } @@ -43,14 +42,14 @@ void PhysicalBone3DEditor::_on_toggle_button_transform_joint(bool p_is_pressed) void PhysicalBone3DEditor::_set_move_joint() { if (selected) { selected->_set_gizmo_move_joint(button_transform_joint->is_pressed()); + Node3DEditor::get_singleton()->update_transform_gizmo(); } } -PhysicalBone3DEditor::PhysicalBone3DEditor(EditorNode *p_editor) : - editor(p_editor) { +PhysicalBone3DEditor::PhysicalBone3DEditor() { spatial_editor_hb = memnew(HBoxContainer); spatial_editor_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); - spatial_editor_hb->set_alignment(BoxContainer::ALIGN_BEGIN); + spatial_editor_hb->set_alignment(BoxContainer::ALIGNMENT_BEGIN); Node3DEditor::get_singleton()->add_control_to_menu_panel(spatial_editor_hb); spatial_editor_hb->add_child(memnew(VSeparator)); @@ -83,9 +82,7 @@ void PhysicalBone3DEditor::show() { spatial_editor_hb->show(); } -PhysicalBone3DEditorPlugin::PhysicalBone3DEditorPlugin(EditorNode *p_editor) : - editor(p_editor), - physical_bone_editor(editor) {} +PhysicalBone3DEditorPlugin::PhysicalBone3DEditorPlugin() {} void PhysicalBone3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { diff --git a/editor/plugins/physical_bone_3d_editor_plugin.h b/editor/plugins/physical_bone_3d_editor_plugin.h index 248aad9298..f15eab7991 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.h +++ b/editor/plugins/physical_bone_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,17 +28,19 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef PHYSICAL_BONE_PLUGIN_H -#define PHYSICAL_BONE_PLUGIN_H +#ifndef PHYSICAL_BONE_3D_EDITOR_PLUGIN_H +#define PHYSICAL_BONE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "scene/3d/physics_body_3d.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" class PhysicalBone3DEditor : public Object { GDCLASS(PhysicalBone3DEditor, Object); - EditorNode *editor; - HBoxContainer *spatial_editor_hb; - Button *button_transform_joint; + HBoxContainer *spatial_editor_hb = nullptr; + Button *button_transform_joint = nullptr; PhysicalBone3D *selected = nullptr; @@ -50,7 +52,7 @@ private: void _set_move_joint(); public: - PhysicalBone3DEditor(EditorNode *p_editor); + PhysicalBone3DEditor(); ~PhysicalBone3DEditor() {} void set_selected(PhysicalBone3D *p_pb); @@ -62,7 +64,6 @@ public: class PhysicalBone3DEditorPlugin : public EditorPlugin { GDCLASS(PhysicalBone3DEditorPlugin, EditorPlugin); - EditorNode *editor; PhysicalBone3D *selected = nullptr; PhysicalBone3DEditor physical_bone_editor; @@ -72,7 +73,7 @@ public: virtual void make_visible(bool p_visible) override; virtual void edit(Object *p_node) override; - PhysicalBone3DEditorPlugin(EditorNode *p_editor); + PhysicalBone3DEditorPlugin(); }; -#endif +#endif // PHYSICAL_BONE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 782152b002..4f46c99a04 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,14 +30,17 @@ #include "polygon_2d_editor_plugin.h" -#include "canvas_item_editor_plugin.h" -#include "core/input/input.h" -#include "core/io/file_access.h" +#include "core/input/input_event.h" #include "core/math/geometry_2d.h" -#include "core/os/keyboard.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/2d/skeleton_2d.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/slider.h" +#include "scene/gui/view_panner.h" Node2D *Polygon2DEditor::_get_node() const { return node; @@ -63,10 +66,10 @@ int Polygon2DEditor::_get_polygon_count() const { void Polygon2DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - uv_edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); - bone_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + uv_panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); } break; + case NOTIFICATION_READY: { button_uv->set_icon(get_theme_icon(SNAME("Uv"), SNAME("EditorIcons"))); @@ -88,7 +91,13 @@ void Polygon2DEditor::_notification(int p_what) { uv_vscroll->set_anchors_and_offsets_preset(PRESET_RIGHT_WIDE); uv_hscroll->set_anchors_and_offsets_preset(PRESET_BOTTOM_WIDE); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + uv_edit_draw->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); + bone_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); } break; + case NOTIFICATION_VISIBILITY_CHANGED: { if (!is_visible()) { uv_edit->hide(); @@ -170,7 +179,7 @@ void Polygon2DEditor::_update_bone_list() { if (np.get_name_count()) { name = np.get_name(np.get_name_count() - 1); } - if (name == String()) { + if (name.is_empty()) { name = "Bone " + itos(i); } cb->set_text(name); @@ -183,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(); @@ -440,16 +449,21 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { return; } + if (uv_panner->gui_input(p_input)) { + accept_event(); + return; + } + Transform2D mtx; - mtx.elements[2] = -uv_draw_ofs; + mtx.columns[2] = -uv_draw_ofs; mtx.scale_basis(Vector2(uv_draw_zoom, uv_draw_zoom)); Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { - uv_drag_from = snap_point(Vector2(mb->get_position().x, mb->get_position().y)); + uv_drag_from = snap_point(mb->get_position()); uv_drag = true; points_prev = node->get_uv(); @@ -462,8 +476,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_move_current = uv_mode; if (uv_move_current == UV_MODE_CREATE) { if (!uv_create) { - points_prev.resize(0); - Vector2 tuv = mtx.affine_inverse().xform(snap_point(Vector2(mb->get_position().x, mb->get_position().y))); + points_prev.clear(); + Vector2 tuv = mtx.affine_inverse().xform(snap_point(mb->get_position())); points_prev.push_back(tuv); uv_create_to = tuv; point_drag_index = 0; @@ -483,7 +497,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_edit_draw->update(); } else { - Vector2 tuv = mtx.affine_inverse().xform(snap_point(Vector2(mb->get_position().x, mb->get_position().y))); + Vector2 tuv = mtx.affine_inverse().xform(snap_point(mb->get_position())); // Close the polygon if selected point is near start. Threshold for closing scaled by zoom level if (points_prev.size() > 2 && tuv.distance_to(points_prev[0]) < (8 / uv_draw_zoom)) { @@ -527,7 +541,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_create_bones_prev = node->call("_get_bones"); int internal_vertices = node->get_internal_vertex_count(); - Vector2 pos = mtx.affine_inverse().xform(snap_point(Vector2(mb->get_position().x, mb->get_position().y))); + Vector2 pos = mtx.affine_inverse().xform(snap_point(mb->get_position())); uv_create_poly_prev.push_back(pos); uv_create_uv_prev.push_back(pos); @@ -573,7 +587,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { for (int i = points_prev.size() - internal_vertices; i < points_prev.size(); i++) { Vector2 tuv = mtx.xform(uv_create_poly_prev[i]); - real_t dist = tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y)); + real_t dist = tuv.distance_to(mb->get_position()); if (dist < 8 && dist < closest_dist) { closest = i; closest_dist = dist; @@ -584,10 +598,10 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { return; } - uv_create_poly_prev.remove(closest); - uv_create_uv_prev.remove(closest); + uv_create_poly_prev.remove_at(closest); + uv_create_uv_prev.remove_at(closest); if (uv_create_colors_prev.size()) { - uv_create_colors_prev.remove(closest); + uv_create_colors_prev.remove_at(closest); } undo_redo->create_action(TTR("Remove Internal Vertex")); @@ -599,7 +613,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { undo_redo->add_undo_method(node, "set_vertex_colors", node->get_vertex_colors()); for (int i = 0; i < node->get_bone_count(); i++) { Vector<float> bonew = node->get_bone_weights(i); - bonew.remove(closest); + bonew.remove_at(closest); undo_redo->add_do_method(node, "set_bone_weights", i, bonew); undo_redo->add_undo_method(node, "set_bone_weights", i, node->get_bone_weights(i)); } @@ -626,7 +640,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { point_drag_index = -1; for (int i = 0; i < points_prev.size(); i++) { Vector2 tuv = mtx.xform(points_prev[i]); - if (tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y)) < 8) { + if (tuv.distance_to(mb->get_position()) < 8) { uv_drag_from = tuv; point_drag_index = i; } @@ -643,7 +657,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { for (int i = 0; i < points_prev.size(); i++) { Vector2 tuv = mtx.xform(points_prev[i]); - real_t dist = tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y)); + real_t dist = tuv.distance_to(mb->get_position()); if (dist < 8 && dist < closest_dist) { closest = i; closest_dist = dist; @@ -671,7 +685,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { } polygon_create.clear(); - } else if (polygon_create.find(closest) == -1) { + } else if (!polygon_create.has(closest)) { //add temporarily if not exists polygon_create.push_back(closest); } @@ -695,14 +709,14 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { polys.write[j] = mtx.xform(points_prev[idx]); } - if (Geometry2D::is_point_in_polygon(Vector2(mb->get_position().x, mb->get_position().y), polys)) { + if (Geometry2D::is_point_in_polygon(mb->get_position(), polys)) { erase_index = i; break; } } if (erase_index != -1) { - polygons.remove(erase_index); + polygons.remove_at(erase_index); undo_redo->create_action(TTR("Remove Custom Polygon")); undo_redo->add_do_method(node, "set_polygons", polygons); undo_redo->add_undo_method(node, "set_polygons", node->get_polygons()); @@ -759,7 +773,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { bone_painting = false; } } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { + } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { _cancel_editing(); if (bone_painting) { @@ -767,23 +781,13 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { } uv_edit_draw->update(); - - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { - uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * mb->get_factor()))); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { - uv_zoom->set_value(uv_zoom->get_value() * (1 - (0.1 * mb->get_factor()))); } } Ref<InputEventMouseMotion> mm = p_input; if (mm.is_valid()) { - if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { - Vector2 drag(mm->get_relative().x, mm->get_relative().y); - uv_hscroll->set_value(uv_hscroll->get_value() - drag.x); - uv_vscroll->set_value(uv_vscroll->get_value() - drag.y); - - } else if (uv_drag) { + if (uv_drag) { Vector2 uv_drag_to = mm->get_position(); uv_drag_to = snap_point(uv_drag_to); // FIXME: Only works correctly with 'UV_MODE_EDIT_POINT', it's imprecise with the rest. Vector2 drag = mtx.affine_inverse().xform(uv_drag_to) - mtx.affine_inverse().xform(uv_drag_from); @@ -791,7 +795,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { switch (uv_move_current) { case UV_MODE_CREATE: { if (uv_create) { - uv_create_to = mtx.affine_inverse().xform(snap_point(Vector2(mm->get_position().x, mm->get_position().y))); + uv_create_to = mtx.affine_inverse().xform(snap_point(mm->get_position())); } } break; case UV_MODE_EDIT_POINT: { @@ -870,7 +874,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { } break; case UV_MODE_PAINT_WEIGHT: case UV_MODE_CLEAR_WEIGHT: { - bone_paint_pos = Vector2(mm->get_position().x, mm->get_position().y); + bone_paint_pos = mm->get_position(); } break; default: { } @@ -905,10 +909,10 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { uv_edit_draw->update(); CanvasItemEditor::get_singleton()->update_viewport(); } else if (polygon_create.size()) { - uv_create_to = mtx.affine_inverse().xform(Vector2(mm->get_position().x, mm->get_position().y)); + uv_create_to = mtx.affine_inverse().xform(mm->get_position()); uv_edit_draw->update(); } else if (uv_mode == UV_MODE_PAINT_WEIGHT || uv_mode == UV_MODE_CLEAR_WEIGHT) { - bone_paint_pos = Vector2(mm->get_position().x, mm->get_position().y); + bone_paint_pos = mm->get_position(); uv_edit_draw->update(); } } @@ -925,6 +929,23 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { } } +void Polygon2DEditor::_uv_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { + _uv_pan_callback(-p_scroll_vec * 32); +} + +void Polygon2DEditor::_uv_pan_callback(Vector2 p_scroll_vec) { + uv_hscroll->set_value(uv_hscroll->get_value() - p_scroll_vec.x); + uv_vscroll->set_value(uv_vscroll->get_value() - p_scroll_vec.y); +} + +void Polygon2DEditor::_uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { + if (p_scroll_vec.y < 0) { + uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * Math::abs(p_scroll_vec.y)))); + } else { + uv_zoom->set_value(uv_zoom->get_value() * (1 - (0.1 * Math::abs(p_scroll_vec.y)))); + } +} + void Polygon2DEditor::_uv_scroll_changed(real_t) { if (updating_uv_scroll) { return; @@ -949,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); @@ -1041,7 +1062,7 @@ void Polygon2DEditor::_uv_draw() { for (int i = 0; i < uvs.size(); i++) { int next = uv_draw_max > 0 ? (i + 1) % uv_draw_max : 0; - if (i < uv_draw_max && uv_drag && uv_move_current == UV_MODE_EDIT_POINT && EDITOR_DEF("editors/polygon_editor/show_previous_outline", true)) { + if (i < uv_draw_max && uv_drag && uv_move_current == UV_MODE_EDIT_POINT && EDITOR_GET("editors/polygon_editor/show_previous_outline")) { uv_edit_draw->draw_line(mtx.xform(points_prev[i]), mtx.xform(points_prev[next]), prev_color, Math::round(EDSCALE)); } @@ -1049,7 +1070,7 @@ void Polygon2DEditor::_uv_draw() { if (uv_create && i == uvs.size() - 1) { next_point = uv_create_to; } - if (i < uv_draw_max /*&& polygons.size() == 0 && polygon_create.size() == 0*/) { //if using or creating polygons, do not show outline (will show polygons instead) + if (i < uv_draw_max) { // If using or creating polygons, do not show outline (will show polygons instead). uv_edit_draw->draw_line(mtx.xform(uvs[i]), mtx.xform(next_point), poly_line_color, Math::round(EDSCALE)); } } @@ -1207,9 +1228,7 @@ Vector2 Polygon2DEditor::snap_point(Vector2 p_target) const { return p_target; } -Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : - AbstractPolygon2DEditor(p_editor) { - node = nullptr; +Polygon2DEditor::Polygon2DEditor() { snap_offset = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_offset", Vector2()); snap_step = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_step", Vector2(10, 10)); use_snap = EditorSettings::get_singleton()->get_project_metadata("polygon_2d_uv_editor", "snap_enabled", false); @@ -1219,7 +1238,7 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : 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); @@ -1257,10 +1276,10 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_edit_mode[2]->set_button_group(uv_edit_group); uv_edit_mode[3]->set_button_group(uv_edit_group); - uv_edit_mode[0]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(0)); - uv_edit_mode[1]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(1)); - uv_edit_mode[2]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(2)); - uv_edit_mode[3]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select), varray(3)); + uv_edit_mode[0]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(0)); + uv_edit_mode[1]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(1)); + uv_edit_mode[2]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(2)); + uv_edit_mode[3]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_edit_mode_select).bind(3)); uv_mode_hb->add_child(memnew(VSeparator)); @@ -1270,7 +1289,7 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_button[i]->set_flat(true); uv_button[i]->set_toggle_mode(true); uv_mode_hb->add_child(uv_button[i]); - uv_button[i]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_mode), varray(i)); + uv_button[i]->connect("pressed", callable_mp(this, &Polygon2DEditor::_uv_mode).bind(i)); uv_button[i]->set_focus_mode(FOCUS_NONE); } @@ -1448,8 +1467,13 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : bone_scroll_vb = memnew(VBoxContainer); bone_scroll->add_child(bone_scroll_vb); + uv_panner.instantiate(); + uv_panner->set_callbacks(callable_mp(this, &Polygon2DEditor::_uv_scroll_callback), callable_mp(this, &Polygon2DEditor::_uv_pan_callback), callable_mp(this, &Polygon2DEditor::_uv_zoom_callback)); + uv_edit_draw->connect("draw", callable_mp(this, &Polygon2DEditor::_uv_draw)); uv_edit_draw->connect("gui_input", callable_mp(this, &Polygon2DEditor::_uv_input)); + uv_edit_draw->connect("focus_exited", callable_mp(uv_panner.ptr(), &ViewPanner::release_pan_key)); + uv_edit_draw->set_focus_mode(FOCUS_CLICK); uv_draw_zoom = 1.0; point_drag_index = -1; uv_drag = false; @@ -1463,6 +1487,6 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_edit_draw->set_clip_contents(true); } -Polygon2DEditorPlugin::Polygon2DEditorPlugin(EditorNode *p_node) : - AbstractPolygon2DEditorPlugin(p_node, memnew(Polygon2DEditor(p_node)), "Polygon2D") { +Polygon2DEditorPlugin::Polygon2DEditorPlugin() : + AbstractPolygon2DEditorPlugin(memnew(Polygon2DEditor), "Polygon2D") { } diff --git a/editor/plugins/polygon_2d_editor_plugin.h b/editor/plugins/polygon_2d_editor_plugin.h index cbe7ecf360..d878d3f9af 100644 --- a/editor/plugins/polygon_2d_editor_plugin.h +++ b/editor/plugins/polygon_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,7 +32,12 @@ #define POLYGON_2D_EDITOR_PLUGIN_H #include "editor/plugins/abstract_polygon_2d_editor.h" -#include "scene/gui/scroll_container.h" + +class HSlider; +class Panel; +class ScrollContainer; +class SpinBox; +class ViewPanner; class Polygon2DEditor : public AbstractPolygon2DEditor { GDCLASS(Polygon2DEditor, AbstractPolygon2DEditor); @@ -63,33 +68,38 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { Button *uv_edit_mode[4]; Ref<ButtonGroup> uv_edit_group; - Polygon2D *node; + Polygon2D *node = nullptr; UVMode uv_mode; - AcceptDialog *uv_edit; + AcceptDialog *uv_edit = nullptr; Button *uv_button[UV_MODE_MAX]; - Button *b_snap_enable; - Button *b_snap_grid; - Panel *uv_edit_draw; - HSlider *uv_zoom; - SpinBox *uv_zoom_value; - HScrollBar *uv_hscroll; - VScrollBar *uv_vscroll; - MenuButton *uv_menu; - TextureRect *uv_icon_zoom; - - VBoxContainer *bone_scroll_main_vb; - ScrollContainer *bone_scroll; - VBoxContainer *bone_scroll_vb; - Button *sync_bones; - HSlider *bone_paint_strength; - SpinBox *bone_paint_radius; - Label *bone_paint_radius_label; + Button *b_snap_enable = nullptr; + Button *b_snap_grid = nullptr; + Panel *uv_edit_draw = nullptr; + HSlider *uv_zoom = nullptr; + SpinBox *uv_zoom_value = nullptr; + HScrollBar *uv_hscroll = nullptr; + VScrollBar *uv_vscroll = nullptr; + MenuButton *uv_menu = nullptr; + TextureRect *uv_icon_zoom = nullptr; + + Ref<ViewPanner> uv_panner; + void _uv_scroll_callback(Vector2 p_scroll_vec, bool p_alt); + void _uv_pan_callback(Vector2 p_scroll_vec); + void _uv_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); + + VBoxContainer *bone_scroll_main_vb = nullptr; + ScrollContainer *bone_scroll = nullptr; + VBoxContainer *bone_scroll_vb = nullptr; + Button *sync_bones = nullptr; + HSlider *bone_paint_strength = nullptr; + SpinBox *bone_paint_radius = nullptr; + Label *bone_paint_radius_label = nullptr; bool bone_painting; - int bone_painting_bone; + int bone_painting_bone = 0; Vector<float> prev_weights; Vector2 bone_paint_pos; - AcceptDialog *grid_settings; + AcceptDialog *grid_settings = nullptr; void _sync_bones(); void _update_bone_list(); @@ -100,7 +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; @@ -113,9 +123,9 @@ class Polygon2DEditor : public AbstractPolygon2DEditor { Vector2 uv_drag_from; bool updating_uv_scroll; - AcceptDialog *error; + AcceptDialog *error = nullptr; - Button *button_uv; + Button *button_uv = nullptr; bool use_snap; bool snap_show_grid; @@ -160,14 +170,14 @@ protected: Vector2 snap_point(Vector2 p_target) const; public: - Polygon2DEditor(EditorNode *p_editor); + Polygon2DEditor(); }; class Polygon2DEditorPlugin : public AbstractPolygon2DEditorPlugin { GDCLASS(Polygon2DEditorPlugin, AbstractPolygon2DEditorPlugin); public: - Polygon2DEditorPlugin(EditorNode *p_node); + Polygon2DEditorPlugin(); }; #endif // POLYGON_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/polygon_3d_editor_plugin.cpp index 8b354c33a1..83092f990f 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/polygon_3d_editor_plugin.cpp @@ -1,12 +1,12 @@ /*************************************************************************/ -/* collision_polygon_3d_editor_plugin.cpp */ +/* polygon_3d_editor_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,26 +28,29 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "collision_polygon_3d_editor_plugin.h" +#include "polygon_3d_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "core/core_string_names.h" #include "core/input/input.h" #include "core/io/file_access.h" #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_settings.h" #include "node_3d_editor_plugin.h" #include "scene/3d/camera_3d.h" -void CollisionPolygon3DEditor::_notification(int p_what) { +void Polygon3DEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_READY: { button_create->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); button_edit->set_icon(get_theme_icon(SNAME("MovePoint"), SNAME("EditorIcons"))); button_edit->set_pressed(true); - get_tree()->connect("node_removed", callable_mp(this, &CollisionPolygon3DEditor::_node_removed)); + get_tree()->connect("node_removed", callable_mp(this, &Polygon3DEditor::_node_removed)); } break; + case NOTIFICATION_PROCESS: { if (!node) { return; @@ -62,7 +65,7 @@ void CollisionPolygon3DEditor::_notification(int p_what) { } } -void CollisionPolygon3DEditor::_node_removed(Node *p_node) { +void Polygon3DEditor::_node_removed(Node *p_node) { if (p_node == node) { node = nullptr; if (imgeom->get_parent() == p_node) { @@ -73,7 +76,7 @@ void CollisionPolygon3DEditor::_node_removed(Node *p_node) { } } -void CollisionPolygon3DEditor::_menu_option(int p_option) { +void Polygon3DEditor::_menu_option(int p_option) { switch (p_option) { case MODE_CREATE: { mode = MODE_CREATE; @@ -88,10 +91,12 @@ void CollisionPolygon3DEditor::_menu_option(int p_option) { } } -void CollisionPolygon3DEditor::_wip_close() { +void Polygon3DEditor::_wip_close() { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_MSG(!obj, "Edited object is not valid."); undo_redo->create_action(TTR("Create Polygon3D")); - undo_redo->add_undo_method(node, "set_polygon", node->call("get_polygon")); - undo_redo->add_do_method(node, "set_polygon", wip); + undo_redo->add_undo_method(obj, "set_polygon", obj->call("get_polygon")); + undo_redo->add_do_method(obj, "set_polygon", wip); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); wip.clear(); @@ -103,16 +108,17 @@ void CollisionPolygon3DEditor::_wip_close() { undo_redo->commit_action(); } -bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Polygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; Transform3D gt = node->get_global_transform(); Transform3D gi = gt.affine_inverse(); float depth = _get_depth() * 0.5; - Vector3 n = gt.basis.get_axis(2).normalized(); - Plane p(gt.origin + n * depth, n); + Vector3 n = gt.basis.get_column(2).normalized(); + Plane p(n, gt.origin + n * depth); Ref<InputEventMouseButton> mb = p_event; @@ -124,7 +130,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); @@ -135,14 +141,14 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con //Let the snap happen when the point is being moved, instead. //cpoint = CanvasItemEditor::get_singleton()->snap_point(cpoint); - Vector<Vector2> poly = node->call("get_polygon"); + PackedVector2Array poly = _get_polygon(); //first check if a point is to be added (segment split) real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); switch (mode) { case MODE_CREATE: { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT && mb->is_pressed()) { if (!wip_active) { wip.clear(); wip.push_back(cpoint); @@ -151,40 +157,40 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con snap_ignore = false; _polygon_draw(); edited_point = 1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, wip[0].y, depth))).distance_to(gpoint) < grab_threshold) { //wip closed _wip_close(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { wip.push_back(cpoint); edited_point = wip.size(); snap_ignore = false; _polygon_draw(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) { + } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && wip_active) { _wip_close(); } } break; case MODE_EDIT: { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (mb->is_ctrl_pressed()) { if (poly.size() < 3) { undo_redo->create_action(TTR("Edit Poly")); - undo_redo->add_undo_method(node, "set_polygon", poly); + undo_redo->add_undo_method(obj, "set_polygon", poly); poly.push_back(cpoint); - undo_redo->add_do_method(node, "set_polygon", poly); + undo_redo->add_do_method(obj, "set_polygon", poly); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //search edges @@ -215,11 +221,11 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con poly.insert(closest_idx + 1, cpoint); edited_point = closest_idx + 1; edited_point_pos = cpoint; - node->call("set_polygon", poly); + _set_polygon(poly); _polygon_draw(); snap_ignore = true; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } else { //look for points to move @@ -244,7 +250,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con edited_point_pos = poly[closest_idx]; _polygon_draw(); snap_ignore = false; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } else { @@ -253,21 +259,21 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con if (edited_point != -1) { //apply - ERR_FAIL_INDEX_V(edited_point, poly.size(), false); + ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS); poly.write[edited_point] = edited_point_pos; undo_redo->create_action(TTR("Edit Poly")); - undo_redo->add_do_method(node, "set_polygon", poly); - undo_redo->add_undo_method(node, "set_polygon", pre_move_edit); + undo_redo->add_do_method(obj, "set_polygon", poly); + undo_redo->add_undo_method(obj, "set_polygon", pre_move_edit); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); edited_point = -1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && edited_point == -1) { + if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed() && edited_point == -1) { int closest_idx = -1; Vector2 closest_pos; real_t closest_dist = 1e10; @@ -284,13 +290,13 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con if (closest_idx >= 0) { undo_redo->create_action(TTR("Edit Poly (Remove Point)")); - undo_redo->add_undo_method(node, "set_polygon", poly); - poly.remove(closest_idx); - undo_redo->add_do_method(node, "set_polygon", poly); + undo_redo->add_undo_method(obj, "set_polygon", poly); + poly.remove_at(closest_idx); + undo_redo->add_do_method(obj, "set_polygon", poly); undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } @@ -301,7 +307,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { - if (edited_point != -1 && (wip_active || mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT)) { + if (edited_point != -1 && (wip_active || (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE)) { Vector2 gpoint = mm->get_position(); Vector3 ray_from = p_camera->project_ray_origin(gpoint); @@ -310,14 +316,14 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); Vector2 cpoint(spoint.x, spoint.y); - if (snap_ignore && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (snap_ignore && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { snap_ignore = false; } @@ -332,32 +338,48 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } -float CollisionPolygon3DEditor::_get_depth() { - if (bool(node->call("_has_editable_3d_polygon_no_depth"))) { - return 0; +float Polygon3DEditor::_get_depth() { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_V_MSG(!obj, 0.0f, "Edited object is not valid."); + + if (bool(obj->call("_has_editable_3d_polygon_no_depth"))) { + return 0.0f; } - return float(node->call("get_depth")); + return float(obj->call("get_depth")); } -void CollisionPolygon3DEditor::_polygon_draw() { +PackedVector2Array Polygon3DEditor::_get_polygon() { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_V_MSG(!obj, PackedVector2Array(), "Edited object is not valid."); + return PackedVector2Array(obj->call("get_polygon")); +} + +void Polygon3DEditor::_set_polygon(PackedVector2Array p_poly) { + Object *obj = node_resource.is_valid() ? (Object *)node_resource.ptr() : node; + ERR_FAIL_COND_MSG(!obj, "Edited object is not valid."); + obj->call("set_polygon", p_poly); +} + +void Polygon3DEditor::_polygon_draw() { if (!node) { return; } - Vector<Vector2> poly; + PackedVector2Array poly; if (wip_active) { poly = wip; } else { - poly = node->call("get_polygon"); + poly = _get_polygon(); } float depth = _get_depth() * 0.5; + m->clear_surfaces(); imesh->clear_surfaces(); imgeom->set_material_override(line_material); imesh->surface_begin(Mesh::PRIMITIVE_LINES); @@ -463,23 +485,32 @@ void CollisionPolygon3DEditor::_polygon_draw() { m->surface_set_material(0, handle_material); } -void CollisionPolygon3DEditor::edit(Node *p_collision_polygon) { - if (p_collision_polygon) { - node = Object::cast_to<Node3D>(p_collision_polygon); +void Polygon3DEditor::edit(Node *p_node) { + if (p_node) { + node = Object::cast_to<Node3D>(p_node); + node_resource = node->call("_get_editable_3d_polygon_resource"); + + if (node_resource.is_valid()) { + node_resource->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Polygon3DEditor::_polygon_draw)); + } //Enable the pencil tool if the polygon is empty - if (Vector<Vector2>(node->call("get_polygon")).size() == 0) { + if (_get_polygon().is_empty()) { _menu_option(MODE_CREATE); } wip.clear(); wip_active = false; edited_point = -1; - p_collision_polygon->add_child(imgeom); + p_node->add_child(imgeom); _polygon_draw(); set_process(true); prev_depth = -1; } else { node = nullptr; + if (node_resource.is_valid()) { + node_resource->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Polygon3DEditor::_polygon_draw)); + } + node_resource.unref(); if (imgeom->get_parent()) { imgeom->get_parent()->remove_child(imgeom); @@ -489,26 +520,25 @@ void CollisionPolygon3DEditor::edit(Node *p_collision_polygon) { } } -void CollisionPolygon3DEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_polygon_draw"), &CollisionPolygon3DEditor::_polygon_draw); +void Polygon3DEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_polygon_draw"), &Polygon3DEditor::_polygon_draw); } -CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { +Polygon3DEditor::Polygon3DEditor() { node = nullptr; - editor = p_editor; undo_redo = EditorNode::get_undo_redo(); add_child(memnew(VSeparator)); button_create = memnew(Button); button_create->set_flat(true); add_child(button_create); - button_create->connect("pressed", callable_mp(this, &CollisionPolygon3DEditor::_menu_option), varray(MODE_CREATE)); + button_create->connect("pressed", callable_mp(this, &Polygon3DEditor::_menu_option).bind(MODE_CREATE)); button_create->set_toggle_mode(true); button_edit = memnew(Button); button_edit->set_flat(true); add_child(button_edit); - button_edit->connect("pressed", callable_mp(this, &CollisionPolygon3DEditor::_menu_option), varray(MODE_EDIT)); + button_edit->connect("pressed", callable_mp(this, &Polygon3DEditor::_menu_option).bind(MODE_EDIT)); button_edit->set_toggle_mode(true); mode = MODE_EDIT; @@ -531,7 +561,7 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { handle_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); handle_material->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); handle_material->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); - Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons")); + Ref<Texture2D> handle = EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Editor3DHandle"), SNAME("EditorIcons")); handle_material->set_point_size(handle->get_width()); handle_material->set_texture(StandardMaterial3D::TEXTURE_ALBEDO, handle); @@ -544,12 +574,12 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { snap_ignore = false; } -CollisionPolygon3DEditor::~CollisionPolygon3DEditor() { +Polygon3DEditor::~Polygon3DEditor() { memdelete(imgeom); } void Polygon3DEditorPlugin::edit(Object *p_object) { - collision_polygon_editor->edit(Object::cast_to<Node>(p_object)); + polygon_editor->edit(Object::cast_to<Node>(p_object)); } bool Polygon3DEditorPlugin::handles(Object *p_object) const { @@ -558,19 +588,18 @@ bool Polygon3DEditorPlugin::handles(Object *p_object) const { void Polygon3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - collision_polygon_editor->show(); + polygon_editor->show(); } else { - collision_polygon_editor->hide(); - collision_polygon_editor->edit(nullptr); + polygon_editor->hide(); + polygon_editor->edit(nullptr); } } -Polygon3DEditorPlugin::Polygon3DEditorPlugin(EditorNode *p_node) { - editor = p_node; - collision_polygon_editor = memnew(CollisionPolygon3DEditor(p_node)); - Node3DEditor::get_singleton()->add_control_to_menu_panel(collision_polygon_editor); +Polygon3DEditorPlugin::Polygon3DEditorPlugin() { + polygon_editor = memnew(Polygon3DEditor); + Node3DEditor::get_singleton()->add_control_to_menu_panel(polygon_editor); - collision_polygon_editor->hide(); + polygon_editor->hide(); } Polygon3DEditorPlugin::~Polygon3DEditorPlugin() { diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/polygon_3d_editor_plugin.h index 5db0f7308a..e1e1261250 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.h +++ b/editor/plugins/polygon_3d_editor_plugin.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* collision_polygon_3d_editor_plugin.h */ +/* polygon_3d_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,10 +28,9 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef COLLISION_POLYGON_EDITOR_PLUGIN_H -#define COLLISION_POLYGON_EDITOR_PLUGIN_H +#ifndef POLYGON_3D_EDITOR_PLUGIN_H +#define POLYGON_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/collision_polygon_3d.h" #include "scene/3d/mesh_instance_3d.h" @@ -39,10 +38,10 @@ class CanvasItemEditor; -class CollisionPolygon3DEditor : public HBoxContainer { - GDCLASS(CollisionPolygon3DEditor, HBoxContainer); +class Polygon3DEditor : public HBoxContainer { + GDCLASS(Polygon3DEditor, HBoxContainer); - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; enum Mode { MODE_CREATE, MODE_EDIT, @@ -51,36 +50,38 @@ class CollisionPolygon3DEditor : public HBoxContainer { Mode mode; - Button *button_create; - Button *button_edit; + Button *button_create = nullptr; + Button *button_edit = nullptr; Ref<StandardMaterial3D> line_material; Ref<StandardMaterial3D> handle_material; - EditorNode *editor; - Panel *panel; - Node3D *node; + Panel *panel = nullptr; + Node3D *node = nullptr; + Ref<Resource> node_resource; Ref<ImmediateMesh> imesh; - MeshInstance3D *imgeom; - MeshInstance3D *pointsm; + MeshInstance3D *imgeom = nullptr; + MeshInstance3D *pointsm = nullptr; Ref<ArrayMesh> m; - MenuButton *options; + MenuButton *options = nullptr; - int edited_point; + int edited_point = 0; Vector2 edited_point_pos; - Vector<Vector2> pre_move_edit; - Vector<Vector2> wip; + PackedVector2Array pre_move_edit; + PackedVector2Array wip; bool wip_active; bool snap_ignore; - float prev_depth; + float prev_depth = 0.0f; void _wip_close(); void _polygon_draw(); void _menu_option(int p_option); float _get_depth(); + PackedVector2Array _get_polygon(); + void _set_polygon(PackedVector2Array p_poly); protected: void _notification(int p_what); @@ -88,20 +89,19 @@ protected: static void _bind_methods(); public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); - void edit(Node *p_collision_polygon); - CollisionPolygon3DEditor(EditorNode *p_editor); - ~CollisionPolygon3DEditor(); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + void edit(Node *p_node); + Polygon3DEditor(); + ~Polygon3DEditor(); }; class Polygon3DEditorPlugin : public EditorPlugin { GDCLASS(Polygon3DEditorPlugin, EditorPlugin); - CollisionPolygon3DEditor *collision_polygon_editor; - EditorNode *editor; + Polygon3DEditor *polygon_editor = nullptr; public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return polygon_editor->forward_spatial_gui_input(p_camera, p_event); } virtual String get_name() const override { return "Polygon3DEditor"; } bool has_main_screen() const override { return false; } @@ -109,8 +109,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Polygon3DEditorPlugin(EditorNode *p_node); + Polygon3DEditorPlugin(); ~Polygon3DEditorPlugin(); }; -#endif // COLLISION_POLYGON_EDITOR_PLUGIN_H +#endif // POLYGON_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index eae6916a92..4e528ef066 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,19 +32,17 @@ #include "core/config/project_settings.h" #include "core/io/resource_loader.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" void ResourcePreloaderEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - load->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); - } - - if (p_what == NOTIFICATION_READY) { - //NodePath("/root")->connect("node_removed", this,"_node_removed",Vector<Variant>(),true); - } - - if (p_what == NOTIFICATION_DRAW) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + load->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); + } break; } } @@ -52,14 +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 } @@ -110,12 +108,12 @@ void ResourcePreloaderEditor::_item_edited() { return; } - if (new_name == "" || new_name.find("\\") != -1 || new_name.find("/") != -1 || preloader->has_resource(new_name)) { + if (new_name.is_empty() || new_name.contains("\\") || new_name.contains("/") || preloader->has_resource(new_name)) { s->set_text(0, old_name); return; } - RES samp = preloader->get_resource(old_name); + Ref<Resource> samp = preloader->get_resource(old_name); 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); @@ -137,20 +135,20 @@ 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 } String name = r->get_name(); - if (name == "") { + if (name.is_empty()) { name = r->get_path().get_file(); } - if (name == "") { + if (name.is_empty()) { name = r->get_class(); } @@ -192,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()); @@ -215,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); @@ -224,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) { @@ -251,7 +253,7 @@ Variant ResourcePreloaderEditor::get_drag_data_fw(const Point2 &p_point, Control String name = ti->get_metadata(0); - RES res = preloader->get_resource(name); + Ref<Resource> res = preloader->get_resource(name); if (!res.is_valid()) { return Variant(); } @@ -271,7 +273,7 @@ bool ResourcePreloaderEditor::can_drop_data_fw(const Point2 &p_point, const Vari } if (String(d["type"]) == "resource" && d.has("resource")) { - RES r = d["resource"]; + Ref<Resource> r = d["resource"]; return r.is_valid(); } @@ -296,11 +298,11 @@ 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; - if (r->get_name() != "") { + if (!r->get_name().is_empty()) { basename = r->get_name(); } else if (r->get_path().is_resource_file()) { basename = r->get_path().get_basename(); @@ -361,7 +363,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); @@ -402,11 +404,11 @@ void ResourcePreloaderEditorPlugin::make_visible(bool p_visible) { if (p_visible) { //preloader_editor->show(); button->show(); - editor->make_bottom_panel_item_visible(preloader_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(preloader_editor); //preloader_editor->set_process(true); } else { if (preloader_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } button->hide(); //preloader_editor->hide(); @@ -414,16 +416,12 @@ void ResourcePreloaderEditorPlugin::make_visible(bool p_visible) { } } -ResourcePreloaderEditorPlugin::ResourcePreloaderEditorPlugin(EditorNode *p_node) { - editor = p_node; +ResourcePreloaderEditorPlugin::ResourcePreloaderEditorPlugin() { preloader_editor = memnew(ResourcePreloaderEditor); preloader_editor->set_custom_minimum_size(Size2(0, 250) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("ResourcePreloader"), preloader_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item("ResourcePreloader", preloader_editor); button->hide(); - - //preloader_editor->set_anchor( MARGIN_TOP, Control::ANCHOR_END); - //preloader_editor->set_margin( MARGIN_TOP, 120 ); } ResourcePreloaderEditorPlugin::~ResourcePreloaderEditorPlugin() { diff --git a/editor/plugins/resource_preloader_editor_plugin.h b/editor/plugins/resource_preloader_editor_plugin.h index 04ab458eb5..96cef3de21 100644 --- a/editor/plugins/resource_preloader_editor_plugin.h +++ b/editor/plugins/resource_preloader_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,13 +31,13 @@ #ifndef RESOURCE_PRELOADER_EDITOR_PLUGIN_H #define RESOURCE_PRELOADER_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/gui/dialogs.h" -#include "scene/gui/file_dialog.h" #include "scene/gui/tree.h" #include "scene/main/resource_preloader.h" +class EditorFileDialog; + class ResourcePreloaderEditor : public PanelContainer { GDCLASS(ResourcePreloaderEditor, PanelContainer); @@ -47,27 +47,26 @@ class ResourcePreloaderEditor : public PanelContainer { BUTTON_REMOVE }; - Button *load; - Button *paste; - Tree *tree; + Button *load = nullptr; + Button *paste = nullptr; + Tree *tree = nullptr; bool loading_scene; - EditorFileDialog *file; + EditorFileDialog *file = nullptr; - AcceptDialog *dialog; + AcceptDialog *dialog = nullptr; - ResourcePreloader *preloader; + ResourcePreloader *preloader = nullptr; void _load_pressed(); - void _load_scene_pressed(); void _files_load_request(const Vector<String> &p_paths); void _paste_pressed(); void _remove_resource(const String &p_to_remove); void _update_library(); - void _cell_button_pressed(Object *p_item, int p_column, int p_id); + void _cell_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _item_edited(); - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; 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; @@ -88,9 +87,8 @@ public: class ResourcePreloaderEditorPlugin : public EditorPlugin { GDCLASS(ResourcePreloaderEditorPlugin, EditorPlugin); - ResourcePreloaderEditor *preloader_editor; - EditorNode *editor; - Button *button; + ResourcePreloaderEditor *preloader_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "ResourcePreloader"; } @@ -99,7 +97,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - ResourcePreloaderEditorPlugin(EditorNode *p_node); + ResourcePreloaderEditorPlugin(); ~ResourcePreloaderEditorPlugin(); }; diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp index ed91f174d1..681dd476e3 100644 --- a/editor/plugins/root_motion_editor_plugin.cpp +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -65,7 +65,7 @@ void EditorPropertyRootMotion::_node_assign() { return; } - Set<String> paths; + HashSet<String> paths; { List<StringName> animations; player->get_animation_list(&animations); @@ -81,15 +81,15 @@ 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++) { String name = path.get_name(i); - if (accum != String()) { + if (!accum.is_empty()) { accum += "/"; } accum += name; @@ -233,9 +233,12 @@ void EditorPropertyRootMotion::setup(const NodePath &p_base_hint) { } void EditorPropertyRootMotion::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - Ref<Texture2D> t = get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")); - clear->set_icon(t); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + Ref<Texture2D> t = get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")); + clear->set_icon(t); + } break; } } @@ -271,26 +274,18 @@ EditorPropertyRootMotion::EditorPropertyRootMotion() { ////////////////////////// bool EditorInspectorRootMotionPlugin::can_handle(Object *p_object) { - return true; //can handle everything -} - -void EditorInspectorRootMotionPlugin::parse_begin(Object *p_object) { - //do none + return true; // Can handle everything. } bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) { EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion); - if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) { + if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && !p_hint_text.is_empty()) { editor->setup(p_hint_text); } add_property_editor(p_path, editor); return true; } - return false; //can be overridden, although it will most likely be last anyway -} - -void EditorInspectorRootMotionPlugin::parse_end() { - //do none + return false; } diff --git a/editor/plugins/root_motion_editor_plugin.h b/editor/plugins/root_motion_editor_plugin.h index 1484af62e8..5b8c1d77b3 100644 --- a/editor/plugins/root_motion_editor_plugin.h +++ b/editor/plugins/root_motion_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,12 +38,12 @@ class EditorPropertyRootMotion : public EditorProperty { GDCLASS(EditorPropertyRootMotion, EditorProperty); - Button *assign; - Button *clear; + Button *assign = nullptr; + Button *clear = nullptr; NodePath base_hint; - ConfirmationDialog *filter_dialog; - Tree *filters; + ConfirmationDialog *filter_dialog = nullptr; + Tree *filters = nullptr; void _confirmed(); void _node_assign(); @@ -64,9 +64,7 @@ class EditorInspectorRootMotionPlugin : public 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; - virtual void parse_end() override; }; #endif // ROOT_MOTION_EDITOR_PLUGIN_H diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index a24249b95b..c53ac59c11 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,8 +36,12 @@ #include "core/io/resource_loader.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/version.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" +#include "editor/editor_file_dialog.h" #include "editor/editor_node.h" +#include "editor/editor_paths.h" #include "editor/editor_run_script.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -45,6 +49,7 @@ #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" @@ -115,9 +120,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); } @@ -157,7 +162,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) { continue; } - if (name.find("/") != -1) { + if (name.contains("/")) { continue; } highlighter->add_member_keyword_color(name, member_variable_color); @@ -177,7 +182,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { for (const String &comment : comments) { String beg = comment.get_slice(" ", 0); String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); - highlighter->add_color_region(beg, end, comment_color, end == ""); + highlighter->add_color_region(beg, end, comment_color, end.is_empty()); } /* Strings */ @@ -187,7 +192,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { for (const String &string : strings) { String beg = string.get_slice(" ", 0); String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); - highlighter->add_color_region(beg, end, string_color, end == ""); + highlighter->add_color_region(beg, end, string_color, end.is_empty()); } } } @@ -212,6 +217,7 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { void ScriptEditorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor); + ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptEditorBase::add_syntax_highlighter); ADD_SIGNAL(MethodInfo("name_changed")); ADD_SIGNAL(MethodInfo("edited_script_changed")); @@ -224,33 +230,27 @@ void ScriptEditorBase::_bind_methods() { ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text"))); } -static bool _is_built_in_script(Script *p_script) { - String path = p_script->get_path(); - - return path.find("::") != -1; -} - 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()) { @@ -259,35 +259,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() {} @@ -312,10 +312,7 @@ void ScriptEditorQuickOpen::_text_changed(const String &p_newtext) { void ScriptEditorQuickOpen::_sbox_input(const Ref<InputEvent> &p_ie) { Ref<InputEventKey> k = p_ie; - if (k.is_valid() && (k->get_keycode() == KEY_UP || - k->get_keycode() == KEY_DOWN || - k->get_keycode() == KEY_PAGEUP || - k->get_keycode() == KEY_PAGEDOWN)) { + if (k.is_valid() && (k->get_keycode() == Key::UP || k->get_keycode() == Key::DOWN || k->get_keycode() == Key::PAGEUP || k->get_keycode() == Key::PAGEDOWN)) { search_options->gui_input(k); search_box->accept_event(); } @@ -327,7 +324,7 @@ void ScriptEditorQuickOpen::_update_search() { for (int i = 0; i < functions.size(); i++) { String file = functions[i]; - if ((search_box->get_text() == "" || file.findn(search_box->get_text()) != -1)) { + if ((search_box->get_text().is_empty() || file.findn(search_box->get_text()) != -1)) { TreeItem *ti = search_options->create_item(root); ti->set_text(0, file); if (root->get_first_child() == ti) { @@ -361,6 +358,7 @@ void ScriptEditorQuickOpen::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { search_box->set_right_icon(search_options->get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); } break; + case NOTIFICATION_EXIT_TREE: { disconnect("confirmed", callable_mp(this, &ScriptEditorQuickOpen::_confirmed)); } break; @@ -380,7 +378,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); @@ -398,7 +396,7 @@ ScriptEditor *ScriptEditor::script_editor = nullptr; String ScriptEditor::_get_debug_tooltip(const String &p_text, Node *_se) { String val = EditorDebuggerNode::get_singleton()->get_var_value(p_text); - if (val != String()) { + if (!val.is_empty()) { return p_text + ": " + val; } else { return String(); @@ -410,8 +408,8 @@ void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) { return; } - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -421,7 +419,7 @@ void ScriptEditor::_breaked(bool p_breaked, bool p_can_debug) { } void ScriptEditor::_script_created(Ref<Script> p_script) { - editor->push_item(p_script.operator->()); + EditorNode::get_singleton()->push_item(p_script.operator->()); } void ScriptEditor::_goto_script_line2(int p_line) { @@ -431,11 +429,11 @@ 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)) { - editor->push_item(p_script.ptr()); + EditorNode::get_singleton()->push_item(p_script.ptr()); ScriptEditorBase *current = _get_current_editor(); if (ScriptTextEditor *script_text_editor = Object::cast_to<ScriptTextEditor>(current)) { @@ -447,11 +445,11 @@ 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_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -463,11 +461,11 @@ 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_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -479,13 +477,82 @@ void ScriptEditor::_clear_execution(REF p_script) { } } +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. + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (se && se->get_edited_resource()->get_path() == script->get_path()) { + se->set_breakpoint(p_line, p_enabled); + return; + } + } + + // Handle closed. + Dictionary state = script_editor_cache->get_value(script->get_path(), "state"); + Array breakpoints; + if (state.has("breakpoints")) { + breakpoints = state["breakpoints"]; + } + + if (breakpoints.has(p_line)) { + if (!p_enabled) { + breakpoints.erase(p_line); + } + } else if (p_enabled) { + breakpoints.push_back(p_line); + } + state["breakpoints"] = breakpoints; + script_editor_cache->set_value(script->get_path(), "state", state); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_line + 1, false); + } +} + +void ScriptEditor::_clear_breakpoints() { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (se) { + se->clear_breakpoints(); + } + } + + // Clear from closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false); + } + + if (breakpoints.size() > 0) { + Dictionary state = script_editor_cache->get_value(E, "state"); + state["breakpoints"] = Array(); + script_editor_cache->set_value(E, "state", state); + } + } +} + +Array ScriptEditor::_get_cached_breakpoints_for_script(const String &p_path) const { + if (!ResourceLoader::exists(p_path, "Script") || p_path.begins_with("local://") || !script_editor_cache->has_section_key(p_path, "state")) { + return Array(); + } + + Dictionary state = script_editor_cache->get_value(p_path, "state"); + if (!state.has("breakpoints")) { + return Array(); + } + return state["breakpoints"]; +} + ScriptEditorBase *ScriptEditor::_get_current_editor() const { int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return nullptr; } - return Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); + return Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected)); } void ScriptEditor::_update_history_arrows() { @@ -524,7 +591,7 @@ void ScriptEditor::_go_to_tab(int p_idx) { } } - Control *c = Object::cast_to<Control>(tab_container->get_child(p_idx)); + Control *c = tab_container->get_tab_control(p_idx); if (!c) { return; } @@ -614,8 +681,9 @@ void ScriptEditor::_update_recent_scripts() { recent_scripts->add_separator(); recent_scripts->add_shortcut(ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Files"))); + recent_scripts->set_item_disabled(recent_scripts->get_item_id(recent_scripts->get_item_count() - 1), rc.is_empty()); - recent_scripts->set_as_minsize(); + recent_scripts->reset_size(); } void ScriptEditor::_open_recent_script(int p_idx) { @@ -650,7 +718,7 @@ void ScriptEditor::_open_recent_script(int p_idx) { return; } // if it's a path then it's most likely a deleted file not help - } else if (path.find("::") != -1) { + } else if (path.contains("::")) { // built-in script String res_path = path.get_slice("::", 0); if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { @@ -670,7 +738,7 @@ void ScriptEditor::_open_recent_script(int p_idx) { return; } - rc.remove(p_idx); + rc.remove_at(p_idx); EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", rc); _update_recent_scripts(); _show_error_dialog(path); @@ -683,28 +751,32 @@ void ScriptEditor::_show_error_dialog(String p_path) { void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { int selected = p_idx; - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return; } - Node *tselected = tab_container->get_child(selected); + Node *tselected = tab_container->get_tab_control(selected); ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected); if (current) { - Ref<Script> script = current->get_edited_resource(); - if (p_save && script.is_valid()) { + Ref<Resource> file = current->get_edited_resource(); + if (p_save && file.is_valid()) { // Do not try to save internal scripts, but prompt to save in-memory // scripts which are not saved to disk yet (have empty path). - if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { + if (!file->is_built_in()) { save_current_script(); } } - if (script.is_valid()) { - if (!script->get_path().is_empty()) { + if (file.is_valid()) { + if (!file->get_path().is_empty()) { // Only saved scripts can be restored. - previous_scripts.push_back(script->get_path()); + previous_scripts.push_back(file->get_path()); + } + + Ref<Script> script = file; + if (script.is_valid()) { + notify_script_close(script); } - notify_script_close(script); } } @@ -718,7 +790,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { for (int i = 0; i < history.size(); i++) { if (history[i].control == tselected) { - history.remove(i); + history.remove_at(i); i--; history_pos--; } @@ -731,16 +803,17 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { int idx = tab_container->get_current_tab(); if (current) { current->clear_edit_menu(); + _save_editor_state(current); } memdelete(tselected); - if (idx >= tab_container->get_child_count()) { - idx = tab_container->get_child_count() - 1; + if (idx >= tab_container->get_tab_count()) { + idx = tab_container->get_tab_count() - 1; } if (idx >= 0) { if (history_pos >= 0) { - idx = history[history_pos].control->get_index(); + idx = tab_container->get_tab_idx_from_control(history[history_pos].control); } - tab_container->set_current_tab(idx); + _go_to_tab(idx); } else { _update_selected_editor_menu(); } @@ -764,9 +837,9 @@ void ScriptEditor::_close_discard_current_tab(const String &p_str) { } void ScriptEditor::_close_docs_tab() { - int child_count = tab_container->get_child_count(); + int child_count = tab_container->get_tab_count(); for (int i = child_count - 1; i >= 0; i--) { - EditorHelp *se = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + EditorHelp *se = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (se) { _close_tab(i, true, false); @@ -777,45 +850,41 @@ 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()); } } void ScriptEditor::_close_other_tabs() { - int child_count = tab_container->get_child_count(); int current_idx = tab_container->get_current_tab(); - for (int i = child_count - 1; i >= 0; i--) { - if (i == current_idx) { - continue; + for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) { + if (i != current_idx) { + script_close_queue.push_back(i); } - - tab_container->set_current_tab(i); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); - - if (se) { - // Maybe there are unsaved changes - if (se->is_unsaved()) { - _ask_close_current_unsaved_tab(se); - continue; - } - } - - _close_current_tab(false); } + _queue_close_tabs(); } void ScriptEditor::_close_all_tabs() { - int child_count = tab_container->get_child_count(); - for (int i = child_count - 1; i >= 0; i--) { - tab_container->set_current_tab(i); - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = tab_container->get_tab_count() - 1; i >= 0; i--) { + script_close_queue.push_back(i); + } + _queue_close_tabs(); +} +void ScriptEditor::_queue_close_tabs() { + while (!script_close_queue.is_empty()) { + int idx = script_close_queue.front()->get(); + script_close_queue.pop_front(); + + tab_container->set_current_tab(idx); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(idx)); if (se) { - // Maybe there are unsaved changes + // Maybe there are unsaved changes. if (se->is_unsaved()) { _ask_close_current_unsaved_tab(se); - continue; + erase_tab_confirm->connect(SceneStringNames::get_singleton()->visibility_changed, callable_mp(this, &ScriptEditor::_queue_close_tabs), CONNECT_ONESHOT); + break; } } @@ -832,15 +901,15 @@ void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) { void ScriptEditor::_resave_scripts(const String &p_str) { apply_scripts(); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - RES script = se->get_edited_resource(); + Ref<Resource> script = se->get_edited_resource(); - if (script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1) { + if (script->is_built_in()) { continue; //internal script, who cares } @@ -864,7 +933,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) { _save_text_file(text_file, text_file->get_path()); break; } else { - editor->save_resource(script); + EditorNode::get_singleton()->save_resource(script); } se->tag_saved_version(); } @@ -873,15 +942,15 @@ void ScriptEditor::_resave_scripts(const String &p_str) { } void ScriptEditor::_reload_scripts() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - RES edited_res = se->get_edited_resource(); + Ref<Resource> edited_res = se->get_edited_resource(); - if (edited_res->get_path() == "" || edited_res->get_path().find("local://") != -1 || edited_res->get_path().find("::") != -1) { + if (edited_res->is_built_in()) { continue; //internal script, who cares } @@ -898,7 +967,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; @@ -917,17 +986,13 @@ void ScriptEditor::_reload_scripts() { } void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } - RES script = se->get_edited_resource(); - - if (script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1) { - continue; //internal script, who cares - } + Ref<Resource> script = se->get_edited_resource(); if (script == p_res) { se->tag_saved_version(); @@ -938,6 +1003,31 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { _trigger_live_script_reload(); } +void ScriptEditor::_scene_saved_callback(const String &p_path) { + // If scene was saved, mark all built-in scripts from that scene as saved. + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); + if (!se) { + continue; + } + + Ref<Resource> edited_res = se->get_edited_resource(); + + if (!edited_res->is_built_in()) { + continue; // External script, who cares. + } + + if (edited_res->get_path().get_slice("::", 0) == p_path) { + se->tag_saved_version(); + } + + Ref<Script> scr = edited_res; + if (scr.is_valid() && scr->is_tool()) { + scr->reload(true); + } + } +} + void ScriptEditor::_trigger_live_script_reload() { if (!pending_auto_reload && auto_reload_running_scripts) { call_deferred(SNAME("_live_auto_reload_running_scripts")); @@ -950,24 +1040,24 @@ void ScriptEditor::_live_auto_reload_running_scripts() { EditorDebuggerNode::get_singleton()->reload_scripts(); } -bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { +bool ScriptEditor::_test_script_times_on_disk(Ref<Resource> p_for_script) { disk_changed_list->clear(); TreeItem *r = disk_changed_list->create_item(); disk_changed_list->set_hide_root(true); bool need_ask = false; bool need_reload = false; - bool use_autoreload = bool(EDITOR_DEF("text_editor/behavior/files/auto_reload_scripts_on_external_change", false)); + bool use_autoreload = bool(EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change")); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { - RES edited_res = se->get_edited_resource(); + Ref<Resource> edited_res = se->get_edited_resource(); if (p_for_script.is_valid() && edited_res.is_valid() && p_for_script != edited_res) { continue; } - if (edited_res->get_path() == "" || edited_res->get_path().find("local://") != -1 || edited_res->get_path().find("::") != -1) { + if (edited_res->is_built_in()) { continue; //internal script, who cares } @@ -1002,52 +1092,38 @@ void ScriptEditor::_file_dialog_action(String p_file) { switch (file_dialog_option) { case FILE_NEW_TEXTFILE: { Error err; - FileAccess *file = FileAccess::open(p_file, FileAccess::WRITE, &err); - if (err) { - editor->show_warning(TTR("Error writing TextFile:") + "\n" + p_file, TTR("Error!")); - break; - } - file->close(); - memdelete(file); - [[fallthrough]]; - } - case FILE_OPEN: { - 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()) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); - file_dialog_option = -1; - return; + { + Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err); + if (err) { + EditorNode::get_singleton()->show_warning(TTR("Error writing TextFile:") + "\n" + p_file, TTR("Error!")); + break; } - - edit(scr); - file_dialog_option = -1; - return; } - Error error; - Ref<TextFile> text_file = _load_text_file(p_file, &error); - if (error != OK) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + if (EditorFileSystem::get_singleton()) { + if (textfile_extensions.has(p_file.get_extension())) { + EditorFileSystem::get_singleton()->update_file(p_file); + } } - if (text_file.is_valid()) { - edit(text_file); - file_dialog_option = -1; + if (!open_textfile_after_create) { return; } + [[fallthrough]]; + } + case FILE_OPEN: { + open_file(p_file); + file_dialog_option = -1; } break; case FILE_SAVE_AS: { ScriptEditorBase *current = _get_current_editor(); if (current) { - RES resource = current->get_edited_resource(); + Ref<Resource> resource = current->get_edited_resource(); String path = ProjectSettings::get_singleton()->localize_path(p_file); Error err = _save_text_file(resource, path); if (err != OK) { - editor->show_accept(TTR("Error saving file!"), TTR("OK")); + EditorNode::get_singleton()->show_accept(TTR("Error saving file!"), TTR("OK")); return; } @@ -1057,12 +1133,12 @@ void ScriptEditor::_file_dialog_action(String p_file) { } break; case THEME_SAVE_AS: { if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) { - editor->show_warning(TTR("Error while saving theme."), TTR("Error Saving")); + EditorNode::get_singleton()->show_warning(TTR("Error while saving theme."), TTR("Error Saving")); } } break; case THEME_IMPORT: { if (!EditorSettings::get_singleton()->import_text_editor_theme(p_file)) { - editor->show_warning(TTR("Error importing theme."), TTR("Error Importing")); + EditorNode::get_singleton()->show_warning(TTR("Error importing theme."), TTR("Error Importing")); } } break; } @@ -1112,8 +1188,12 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog_option = FILE_NEW_TEXTFILE; file_dialog->clear_filters(); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E, E.to_upper()); + } file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("New Text File...")); + open_textfile_after_create = true; } break; case FILE_OPEN: { file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); @@ -1124,7 +1204,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->popup_file_dialog(); @@ -1149,9 +1233,6 @@ void ScriptEditor::_menu_option(int p_option) { if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { if (!EditorNode::get_singleton()->is_scene_open(res_path)) { EditorNode::get_singleton()->load_scene(res_path); - script_editor->call_deferred(SNAME("_menu_option"), p_option); - previous_scripts.push_back(path); //repeat the operation - return; } } else { EditorNode::get_singleton()->load_resource(res_path); @@ -1160,25 +1241,23 @@ void ScriptEditor::_menu_option(int p_option) { Ref<Script> scr = ResourceLoader::load(path); if (!scr.is_valid()) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); + EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); file_dialog_option = -1; return; } edit(scr); file_dialog_option = -1; - return; } else { Error error; Ref<TextFile> text_file = _load_text_file(path, &error); if (error != OK) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); + EditorNode::get_singleton()->show_warning(TTR("Could not load file at:") + "\n\n" + path, TTR("Error!")); } if (text_file.is_valid()) { edit(text_file); file_dialog_option = -1; - return; } } } break; @@ -1199,7 +1278,7 @@ void ScriptEditor::_menu_option(int p_option) { help_search_dialog->popup_dialog(); } break; case SEARCH_WEBSITE: { - OS::get_singleton()->shell_open("https://docs.godotengine.org/"); + OS::get_singleton()->shell_open(VERSION_DOCS_URL "/"); } break; case WINDOW_NEXT: { _history_forward(); @@ -1212,14 +1291,15 @@ void ScriptEditor::_menu_option(int p_option) { _update_script_names(); } break; case TOGGLE_SCRIPTS_PANEL: { + toggle_scripts_panel(); if (current) { - ScriptTextEditor *editor = Object::cast_to<ScriptTextEditor>(current); - toggle_scripts_panel(); - if (editor) { - editor->update_toggle_scripts_button(); - } + current->update_toggle_scripts_button(); } else { - toggle_scripts_panel(); + Control *tab = tab_container->get_current_tab_control(); + EditorHelp *editor_help = Object::cast_to<EditorHelp>(tab); + if (editor_help) { + editor_help->update_toggle_scripts_button(); + } } } } @@ -1244,7 +1324,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; @@ -1264,7 +1344,7 @@ void ScriptEditor::_menu_option(int p_option) { } if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + Vector<DocData::ClassDoc> documentations = script->get_documentation(); for (int j = 0; j < documentations.size(); j++) { const DocData::ClassDoc &doc = documentations.get(j); if (EditorHelp::get_doc_data()->has_doc(doc.name)) { @@ -1273,11 +1353,11 @@ void ScriptEditor::_menu_option(int p_option) { } } - editor->push_item(resource.ptr()); - editor->save_resource_as(resource); + EditorNode::get_singleton()->push_item(resource.ptr()); + EditorNode::get_singleton()->save_resource_as(resource); if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + Vector<DocData::ClassDoc> documentations = script->get_documentation(); for (int j = 0; j < documentations.size(); j++) { const DocData::ClassDoc &doc = documentations.get(j); EditorHelp::get_doc_data()->add_doc(doc); @@ -1286,9 +1366,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: { @@ -1297,6 +1385,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 @@ -1305,10 +1397,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.")); @@ -1334,14 +1422,18 @@ void ScriptEditor::_menu_option(int p_option) { _copy_script_path(); } break; case SHOW_IN_FILE_SYSTEM: { - const RES script = current->get_edited_resource(); - const String path = script->get_path(); + const Ref<Resource> script = current->get_edited_resource(); + String path = script->get_path(); if (!path.is_empty()) { - FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); + if (script->is_built_in()) { + path = path.get_slice("::", 0); // Show the scene instead. + } + + FileSystemDock *file_system_dock = FileSystemDock::get_singleton(); file_system_dock->navigate_to_path(path); // Ensure that the FileSystem dock is visible. TabContainer *tab_container = (TabContainer *)file_system_dock->get_parent_control(); - tab_container->set_current_tab(file_system_dock->get_index()); + tab_container->set_current_tab(tab_container->get_tab_idx_from_control(file_system_dock)); } } break; case CLOSE_DOCS: { @@ -1356,20 +1448,20 @@ void ScriptEditor::_menu_option(int p_option) { case WINDOW_MOVE_UP: { if (tab_container->get_current_tab() > 0) { tab_container->move_child(current, tab_container->get_current_tab() - 1); - tab_container->set_current_tab(tab_container->get_current_tab() - 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; case WINDOW_MOVE_DOWN: { - if (tab_container->get_current_tab() < tab_container->get_child_count() - 1) { + if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) { tab_container->move_child(current, tab_container->get_current_tab() + 1); - tab_container->set_current_tab(tab_container->get_current_tab() + 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; default: { if (p_option >= WINDOW_SELECT_BASE) { - tab_container->set_current_tab(p_option - WINDOW_SELECT_BASE); + _go_to_tab(p_option - WINDOW_SELECT_BASE); _update_script_names(); } } @@ -1402,14 +1494,14 @@ void ScriptEditor::_menu_option(int p_option) { case WINDOW_MOVE_UP: { if (tab_container->get_current_tab() > 0) { tab_container->move_child(help, tab_container->get_current_tab() - 1); - tab_container->set_current_tab(tab_container->get_current_tab() - 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; case WINDOW_MOVE_DOWN: { - if (tab_container->get_current_tab() < tab_container->get_child_count() - 1) { + if (tab_container->get_current_tab() < tab_container->get_tab_count() - 1) { tab_container->move_child(help, tab_container->get_current_tab() + 1); - tab_container->set_current_tab(tab_container->get_current_tab() + 1); + tab_container->set_current_tab(tab_container->get_current_tab()); _update_script_names(); } } break; @@ -1436,7 +1528,7 @@ void ScriptEditor::_theme_option(int p_option) { if (EditorSettings::get_singleton()->is_default_text_editor_theme()) { ScriptEditor::_show_save_theme_as_dialog(); } else if (!EditorSettings::get_singleton()->save_text_editor_theme()) { - editor->show_warning(TTR("Error while saving theme"), TTR("Error saving")); + EditorNode::get_singleton()->show_warning(TTR("Error while saving theme"), TTR("Error saving")); } } break; case THEME_SAVE_AS: { @@ -1451,11 +1543,56 @@ void ScriptEditor::_show_save_theme_as_dialog() { file_dialog_option = THEME_SAVE_AS; file_dialog->clear_filters(); file_dialog->add_filter("*.tet"); - file_dialog->set_current_path(EditorSettings::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); + file_dialog->set_current_path(EditorPaths::get_singleton()->get_text_editor_themes_dir().plus_file(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))); file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("Save Theme As...")); } +bool ScriptEditor::_has_docs_tab() const { + const int child_count = tab_container->get_tab_count(); + for (int i = 0; i < child_count; i++) { + if (Object::cast_to<EditorHelp>(tab_container->get_tab_control(i))) { + return true; + } + } + return false; +} + +bool ScriptEditor::_has_script_tab() const { + const int child_count = tab_container->get_tab_count(); + for (int i = 0; i < child_count; i++) { + if (Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i))) { + return true; + } + } + return false; +} + +void ScriptEditor::_prepare_file_menu() { + PopupMenu *menu = file_menu->get_popup(); + const bool current_is_doc = _get_current_editor() == nullptr; + + menu->set_item_disabled(menu->get_item_index(FILE_REOPEN_CLOSED), previous_scripts.is_empty()); + + menu->set_item_disabled(menu->get_item_index(FILE_SAVE), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_SAVE_AS), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_SAVE_ALL), !_has_script_tab()); + + menu->set_item_disabled(menu->get_item_index(FILE_TOOL_RELOAD_SOFT), current_is_doc); + menu->set_item_disabled(menu->get_item_index(FILE_COPY_PATH), current_is_doc); + menu->set_item_disabled(menu->get_item_index(SHOW_IN_FILE_SYSTEM), current_is_doc); + + menu->set_item_disabled(menu->get_item_index(WINDOW_PREV), history_pos <= 0); + menu->set_item_disabled(menu->get_item_index(WINDOW_NEXT), history_pos >= history.size() - 1); + + menu->set_item_disabled(menu->get_item_index(FILE_CLOSE), tab_container->get_tab_count() < 1); + menu->set_item_disabled(menu->get_item_index(CLOSE_ALL), tab_container->get_tab_count() < 1); + menu->set_item_disabled(menu->get_item_index(CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1); + menu->set_item_disabled(menu->get_item_index(CLOSE_DOCS), !_has_docs_tab()); + + menu->set_item_disabled(menu->get_item_index(FILE_RUN), current_is_doc); +} + void ScriptEditor::_tab_changed(int p_which) { ensure_select_current(); } @@ -1463,25 +1600,29 @@ void ScriptEditor::_tab_changed(int p_which) { void ScriptEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { - editor->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); - editor->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); - editor->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); - editor->get_filesystem_dock()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); + EditorNode::get_singleton()->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); + EditorNode::get_singleton()->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); + EditorNode::get_singleton()->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); + EditorNode::get_singleton()->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); + FileSystemDock::get_singleton()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); + FileSystemDock::get_singleton()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected)); help_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_help_overview_selected)); - script_split->connect("dragged", callable_mp(this, &ScriptEditor::_script_split_dragged)); + script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); + list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged)); EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); + _editor_settings_changed(); [[fallthrough]]; } case NOTIFICATION_TRANSLATION_CHANGED: case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: case NOTIFICATION_THEME_CHANGED: { help_search->set_icon(get_theme_icon(SNAME("HelpSearch"), SNAME("EditorIcons"))); - site_search->set_icon(get_theme_icon(SNAME("Instance"), SNAME("EditorIcons"))); + site_search->set_icon(get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); if (is_layout_rtl()) { script_forward->set_icon(get_theme_icon(SNAME("Back"), SNAME("EditorIcons"))); @@ -1496,9 +1637,9 @@ void ScriptEditor::_notification(int p_what) { filter_scripts->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); filter_methods->set_right_icon(get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - filename->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))); + filename->add_theme_style_override("normal", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("normal"), SNAME("LineEdit"))); - recent_scripts->set_as_minsize(); + recent_scripts->reset_size(); if (is_inside_tree()) { _update_script_colors(); @@ -1508,12 +1649,12 @@ void ScriptEditor::_notification(int p_what) { case NOTIFICATION_READY: { get_tree()->connect("tree_changed", callable_mp(this, &ScriptEditor::_tree_changed)); - editor->get_inspector_dock()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open)); - editor->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search)); + InspectorDock::get_singleton()->connect("request_help", callable_mp(this, &ScriptEditor::_help_class_open)); + EditorNode::get_singleton()->connect("request_help_search", callable_mp(this, &ScriptEditor::_help_search)); } break; case NOTIFICATION_EXIT_TREE: { - editor->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); + EditorNode::get_singleton()->disconnect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); } break; case NOTIFICATION_WM_WINDOW_FOCUS_IN: { @@ -1526,15 +1667,12 @@ void ScriptEditor::_notification(int p_what) { find_in_files_button->show(); } else { if (find_in_files->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } find_in_files_button->hide(); } } break; - - default: - break; } } @@ -1548,8 +1686,8 @@ bool ScriptEditor::can_take_away_focus() const { } void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { Ref<Script> script = se->get_edited_resource(); @@ -1557,8 +1695,8 @@ void ScriptEditor::close_builtin_scripts_from_scene(const String &p_scene) { continue; } - if (script->get_path().find("::") != -1 && script->get_path().begins_with(p_scene)) { //is an internal script and belongs to scene being closed - _close_tab(i); + if (script->is_built_in() && script->get_path().begins_with(p_scene)) { //is an internal script and belongs to scene being closed + _close_tab(i, false); i--; } } @@ -1578,8 +1716,9 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + HashSet<String> loaded_scripts; + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -1590,7 +1729,8 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } String base = script->get_path(); - if (base.begins_with("local://") || base == "") { + loaded_scripts.insert(base); + if (base.begins_with("local://") || base.is_empty()) { continue; } @@ -1599,6 +1739,20 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1)); } } + + // Load breakpoints that are in closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1)); + } + } } void ScriptEditor::_members_overview_selected(int p_idx) { @@ -1616,7 +1770,7 @@ void ScriptEditor::_members_overview_selected(int p_idx) { } void ScriptEditor::_help_overview_selected(int p_idx) { - Node *current = tab_container->get_child(tab_container->get_current_tab()); + Node *current = tab_container->get_tab_control(tab_container->get_current_tab()); EditorHelp *se = Object::cast_to<EditorHelp>(current); if (!se) { return; @@ -1625,14 +1779,14 @@ void ScriptEditor::_help_overview_selected(int p_idx) { } void ScriptEditor::_script_selected(int p_idx) { - grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT); //amazing hack, simply amazing + grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT); //amazing hack, simply amazing _go_to_tab(script_list->get_item_metadata(p_idx)); grab_focus_block = false; } void ScriptEditor::ensure_select_current() { - if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) { + if (tab_container->get_tab_count() && tab_container->get_current_tab() >= 0) { ScriptEditorBase *se = _get_current_editor(); if (se) { se->enable_editor(); @@ -1647,7 +1801,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; } @@ -1668,6 +1822,7 @@ struct _ScriptEditorItemData { String name; String sort_key; Ref<Texture2D> icon; + bool tool = false; int index = 0; String tooltip; bool used = false; @@ -1698,10 +1853,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); } @@ -1728,9 +1885,9 @@ void ScriptEditor::_update_members_overview() { for (int i = 0; i < functions.size(); i++) { String filter = filter_methods->get_text(); String name = functions[i].get_slice(":", 0); - if (filter == "" || filter.is_subsequence_ofi(name)) { + if (filter.is_empty() || filter.is_subsequence_ofn(name)) { members_overview->add_item(name); - members_overview->set_item_metadata(members_overview->get_item_count() - 1, functions[i].get_slice(":", 1).to_int() - 1); + members_overview->set_item_metadata(-1, functions[i].get_slice(":", 1).to_int() - 1); } } @@ -1742,12 +1899,12 @@ void ScriptEditor::_update_members_overview() { void ScriptEditor::_update_help_overview_visibility() { int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { help_overview->set_visible(false); return; } - Node *current = tab_container->get_child(tab_container->get_current_tab()); + Node *current = tab_container->get_tab_control(tab_container->get_current_tab()); EditorHelp *se = Object::cast_to<EditorHelp>(current); if (!se) { help_overview->set_visible(false); @@ -1756,6 +1913,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()); @@ -1769,11 +1927,11 @@ void ScriptEditor::_update_help_overview() { help_overview->clear(); int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return; } - Node *current = tab_container->get_child(tab_container->get_current_tab()); + Node *current = tab_container->get_tab_control(tab_container->get_current_tab()); EditorHelp *se = Object::cast_to<EditorHelp>(current); if (!se) { return; @@ -1791,11 +1949,12 @@ void ScriptEditor::_update_script_colors() { int hist_size = EditorSettings::get_singleton()->get("text_editor/script_list/script_temperature_history_size"); Color hot_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + hot_color.set_s(hot_color.get_s() * 0.9); Color cold_color = get_theme_color(SNAME("font_color"), SNAME("Editor")); for (int i = 0; i < script_list->get_item_count(); i++) { int c = script_list->get_item_metadata(i); - Node *n = tab_container->get_child(c); + Node *n = tab_container->get_tab_control(c); if (!n) { continue; } @@ -1803,11 +1962,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; @@ -1825,7 +1984,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); @@ -1838,8 +1997,8 @@ void ScriptEditor::_update_script_names() { Vector<_ScriptEditorItemData> sedata; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { Ref<Texture2D> icon = se->get_theme_icon(); String path = se->get_edited_resource()->get_path(); @@ -1849,20 +2008,8 @@ void ScriptEditor::_update_script_names() { // to update original path to previously edited resource. se->set_meta("_edit_res_path", path); } - bool built_in = !path.is_resource_file(); - String name; - - if (built_in) { - name = path.get_file(); - const String &resource_name = se->get_edited_resource()->get_name(); - if (resource_name != "") { - // If the built-in script has a custom resource name defined, - // display the built-in script name as follows: `ResourceName (scene_file.tscn)` - name = vformat("%s (%s)", resource_name, name.substr(0, name.find("::", 0))); - } - } else { - name = se->get_name(); - } + String name = se->get_name(); + Ref<Script> scr = se->get_edited_resource(); _ScriptEditorItemData sd; sd.icon = icon; @@ -1872,6 +2019,9 @@ void ScriptEditor::_update_script_names() { sd.used = used.has(se->get_edited_resource()); sd.category = 0; sd.ref = se; + if (scr.is_valid()) { + sd.tool = scr->is_tool(); + } switch (sort_by) { case SORT_BY_NAME: { @@ -1937,7 +2087,7 @@ void ScriptEditor::_update_script_names() { } } - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh) { String name = eh->get_class(); Ref<Texture2D> icon = get_theme_icon(SNAME("Help"), SNAME("EditorIcons")); @@ -1978,21 +2128,27 @@ void ScriptEditor::_update_script_names() { sd.index = i; sedata.set(i, sd); } - tab_container->set_current_tab(new_prev_tab); - tab_container->set_current_tab(new_cur_tab); + _go_to_tab(new_prev_tab); + _go_to_tab(new_cur_tab); _sort_list_on_update = false; } Vector<_ScriptEditorItemData> sedata_filtered; for (int i = 0; i < sedata.size(); i++) { String filter = filter_scripts->get_text(); - if (filter == "" || filter.is_subsequence_ofi(sedata[i].name)) { + if (filter.is_empty() || filter.is_subsequence_ofn(sedata[i].name)) { sedata_filtered.push_back(sedata[i]); } } + Color tool_color = get_theme_color(SNAME("accent_color"), SNAME("Editor")); + tool_color.set_s(tool_color.get_s() * 1.5); for (int i = 0; i < sedata_filtered.size(); i++) { script_list->add_item(sedata_filtered[i].name, sedata_filtered[i].icon); + if (sedata_filtered[i].tool) { + script_list->set_item_icon_modulate(-1, tool_color); + } + int index = script_list->get_item_count() - 1; script_list->set_item_tooltip(index, sedata_filtered[i].tooltip); script_list->set_item_metadata(index, sedata_filtered[i].index); /* Saving as metadata the script's index in the tab container and not the filtered one */ @@ -2001,8 +2157,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(); @@ -2020,13 +2178,11 @@ void ScriptEditor::_update_script_names() { _update_members_overview_visibility(); _update_help_overview_visibility(); _update_script_colors(); - - file_menu->get_popup()->set_item_disabled(file_menu->get_popup()->get_item_index(FILE_REOPEN_CLOSED), previous_scripts.is_empty()); } void ScriptEditor::_update_script_connections() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptTextEditor *ste = Object::cast_to<ScriptTextEditor>(tab_container->get_tab_control(i)); if (!ste) { continue; } @@ -2034,7 +2190,7 @@ void ScriptEditor::_update_script_connections() { } } -Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) { +Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -2046,7 +2202,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); @@ -2069,17 +2225,16 @@ Error ScriptEditor::_save_text_file(Ref<TextFile> p_text_file, const String &p_p String source = sqscr->get_text(); Error err; - FileAccess *file = FileAccess::open(p_path, FileAccess::WRITE, &err); + { + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); - ERR_FAIL_COND_V_MSG(err, err, "Cannot save text file '" + p_path + "'."); + ERR_FAIL_COND_V_MSG(err, err, "Cannot save text file '" + p_path + "'."); - file->store_string(source); - if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { - memdelete(file); - return ERR_CANT_CREATE; + file->store_string(source); + if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { + return ERR_CANT_CREATE; + } } - file->close(); - memdelete(file); if (ResourceSaver::get_timestamp_on_save()) { p_text_file->set_last_modified_time(FileAccess::get_modified_time(p_path)); @@ -2089,7 +2244,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; } @@ -2097,9 +2252,10 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra Ref<Script> script = p_resource; // Don't open dominant script if using an external editor. - const bool use_external_editor = + bool use_external_editor = EditorSettings::get_singleton()->get("text_editor/external/use_external_editor") || (script.is_valid() && script->get_language()->overrides_external_editor()); + use_external_editor = use_external_editor && !(script.is_valid() && script->is_built_in()); // Ignore external editor for built-in scripts. const bool open_dominant = EditorSettings::get_singleton()->get("text_editor/behavior/files/open_dominant_script_on_scene_change"); const bool should_open = (open_dominant && !use_external_editor) || !EditorNode::get_singleton()->is_changing_scene(); @@ -2117,7 +2273,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra if (use_external_editor && (EditorDebuggerNode::get_singleton()->get_dump_stack_script() != p_resource || EditorDebuggerNode::get_singleton()->get_debug_with_external_editor()) && p_resource->get_path().is_resource_file() && - p_resource->get_class_name() != StringName("VisualScript")) { + !p_resource->is_class("VisualScript")) { String path = EditorSettings::get_singleton()->get("text_editor/external/exec_path"); String flags = EditorSettings::get_singleton()->get("text_editor/external/exec_flags"); @@ -2145,7 +2301,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra } else if (flags[i] == '\0' || (!inside_quotes && flags[i] == ' ')) { String arg = flags.substr(from, num_chars); - if (arg.find("{file}") != -1) { + if (arg.contains("{file}")) { has_file_flag = true; } @@ -2174,8 +2330,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra WARN_PRINT("Couldn't open external text editor, using internal"); } - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2216,7 +2372,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra se->set_edited_resource(p_resource); - if (p_resource->get_class_name() != StringName("VisualScript")) { + if (!p_resource->is_class("VisualScript")) { bool highlighter_set = false; for (int i = 0; i < syntax_highlighters.size(); i++) { Ref<EditorSyntaxHighlighter> highlighter = syntax_highlighters[i]->_create(); @@ -2244,7 +2400,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra // If we delete a script within the filesystem, the original resource path // is lost, so keep it as metadata to figure out the exact tab to delete. se->set_meta("_edit_res_path", p_resource->get_path()); - se->set_tooltip_request_func("_get_debug_tooltip", this); + se->set_tooltip_request_func(callable_mp(this, &ScriptEditor::_get_debug_tooltip)); if (se->get_edit_menu()) { se->get_edit_menu()->hide(); menu_hb->add_child(se->get_edit_menu()); @@ -2256,6 +2412,10 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra _add_recent_script(p_resource->get_path()); } + if (script_editor_cache->has_section(p_resource->get_path())) { + se->set_edit_state(script_editor_cache->get_value(p_resource->get_path(), "state")); + } + _sort_list_on_update = true; _update_script_names(); _save_layout(); @@ -2301,7 +2461,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; @@ -2312,7 +2472,7 @@ void ScriptEditor::save_current_script() { } if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + Vector<DocData::ClassDoc> documentations = script->get_documentation(); for (int j = 0; j < documentations.size(); j++) { const DocData::ClassDoc &doc = documentations.get(j); if (EditorHelp::get_doc_data()->has_doc(doc.name)) { @@ -2321,10 +2481,20 @@ void ScriptEditor::save_current_script() { } } - editor->save_resource(resource); + if (resource->is_built_in()) { + // If built-in script, save the scene instead. + const String scene_path = resource->get_path().get_slice("::", 0); + if (!scene_path.is_empty()) { + Vector<String> scene_to_save; + scene_to_save.push_back(scene_path); + EditorNode::get_singleton()->save_scene_list(scene_to_save); + } + } else { + EditorNode::get_singleton()->save_resource(resource); + } if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + Vector<DocData::ClassDoc> documentations = script->get_documentation(); for (int j = 0; j < documentations.size(); j++) { const DocData::ClassDoc &doc = documentations.get(j); EditorHelp::get_doc_data()->add_doc(doc); @@ -2334,8 +2504,10 @@ void ScriptEditor::save_current_script() { } void ScriptEditor::save_all_scripts() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + Vector<String> scenes_to_save; + + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2358,12 +2530,12 @@ void ScriptEditor::save_all_scripts() { continue; } - RES edited_res = se->get_edited_resource(); + Ref<Resource> edited_res = se->get_edited_resource(); if (edited_res.is_valid()) { se->apply_code(); } - if (edited_res->get_path() != "" && edited_res->get_path().find("local://") == -1 && edited_res->get_path().find("::") == -1) { + if (!edited_res->is_built_in()) { Ref<TextFile> text_file = edited_res; Ref<Script> script = edited_res; @@ -2373,7 +2545,7 @@ void ScriptEditor::save_all_scripts() { } if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + Vector<DocData::ClassDoc> documentations = script->get_documentation(); for (int j = 0; j < documentations.size(); j++) { const DocData::ClassDoc &doc = documentations.get(j); if (EditorHelp::get_doc_data()->has_doc(doc.name)) { @@ -2382,26 +2554,36 @@ void ScriptEditor::save_all_scripts() { } } - editor->save_resource(edited_res); //external script, save it + EditorNode::get_singleton()->save_resource(edited_res); //external script, save it if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + Vector<DocData::ClassDoc> documentations = script->get_documentation(); for (int j = 0; j < documentations.size(); j++) { const DocData::ClassDoc &doc = documentations.get(j); EditorHelp::get_doc_data()->add_doc(doc); update_doc(doc.name); } } + } else { + // For built-in scripts, save their scenes instead. + const String scene_path = edited_res->get_path().get_slice("::", 0); + if (!scenes_to_save.has(scene_path)) { + scenes_to_save.push_back(scene_path); + } } } + if (!scenes_to_save.is_empty()) { + EditorNode::get_singleton()->save_scene_list(scenes_to_save); + } + _update_script_names(); EditorFileSystem::get_singleton()->update_script_classes(); } void ScriptEditor::apply_scripts() const { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2414,9 +2596,44 @@ void ScriptEditor::open_script_create_dialog(const String &p_base_name, const St script_create_dialog->config(p_base_name, p_base_path); } +void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const String &p_base_name) { + file_dialog->set_current_file(p_base_name); + file_dialog->set_current_dir(p_base_path); + _menu_option(FILE_NEW_TEXTFILE); + open_textfile_after_create = false; +} + +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 Ref<Resource>(); + } + + edit(scr); + return scr; + } + + Error error; + 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 Ref<Resource>(); + } + + if (text_file.is_valid()) { + edit(text_file); + return text_file; + } + return Ref<Resource>(); +} + void ScriptEditor::_editor_stop() { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2430,10 +2647,10 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const Ref<Script> script = p_obj->get_script(); ERR_FAIL_COND(!script.is_valid()); - editor->push_item(script.ptr()); + EditorNode::get_singleton()->push_item(script.ptr()); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2448,7 +2665,7 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const script_list->select(script_list->find_metadata(i)); // Save the current script so the changes can be picked up by an external editor. - if (!_is_built_in_script(script.ptr())) { // But only if it's not built-in script. + if (!script.ptr()->is_built_in()) { // But only if it's not built-in script. save_current_script(); } @@ -2456,15 +2673,35 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const } } +void ScriptEditor::_save_editor_state(ScriptEditorBase *p_editor) { + if (restoring_layout) { + return; + } + + const String &path = p_editor->get_edited_resource()->get_path(); + if (!path.is_resource_file()) { + return; + } + + script_editor_cache->set_value(path, "state", p_editor->get_edit_state()); + // This is saved later when we save the editor layout. +} + void ScriptEditor::_save_layout() { if (restoring_layout) { return; } - editor->save_layout(); + EditorNode::get_singleton()->save_layout(); } void ScriptEditor::_editor_settings_changed() { + textfile_extensions.clear(); + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + for (const String &E : textfile_ext) { + textfile_extensions.insert(E); + } + trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/trim_trailing_whitespace_on_save"); convert_indent_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/convert_indent_on_save"); use_space_indentation = EditorSettings::get_singleton()->get("text_editor/behavior/indent/type"); @@ -2476,15 +2713,15 @@ void ScriptEditor::_editor_settings_changed() { _update_autosave_timer(); - if (current_theme == "") { + if (current_theme.is_empty()) { current_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); } else if (current_theme != String(EditorSettings::get_singleton()->get("text_editor/theme/color_theme"))) { current_theme = EditorSettings::get_singleton()->get("text_editor/theme/color_theme"); EditorSettings::get_singleton()->load_text_editor_theme(); } - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2494,16 +2731,36 @@ void ScriptEditor::_editor_settings_changed() { _update_script_colors(); _update_script_names(); - ScriptServer::set_reload_scripts_on_save(EDITOR_DEF("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save", true)); + ScriptServer::set_reload_scripts_on_save(EDITOR_GET("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save")); } void ScriptEditor::_filesystem_changed() { _update_script_names(); } +void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_file) { + if (!script_editor_cache->has_section(p_old_file)) { + return; + } + Variant state = script_editor_cache->get_value(p_old_file, "state"); + script_editor_cache->erase_section(p_old_file); + script_editor_cache->set_value(p_new_file, "state", state); + + // If Script, update breakpoints with debugger. + Array breakpoints = _get_cached_breakpoints_for_script(p_new_file); + for (int i = 0; i < breakpoints.size(); i++) { + int line = (int)breakpoints[i] + 1; + EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false); + if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true); + } + } + // This is saved later when we save the editor layout. +} + void ScriptEditor::_file_removed(const String &p_removed_file) { - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -2512,6 +2769,15 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { _close_tab(i, false, false); } } + + // Check closed. + if (script_editor_cache->has_section(p_removed_file)) { + Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false); + } + script_editor_cache->erase_section(p_removed_file); + } } void ScriptEditor::_update_find_replace_bar() { @@ -2552,16 +2818,16 @@ void ScriptEditor::_tree_changed() { call_deferred(SNAME("_update_script_connections")); } -void ScriptEditor::_script_split_dragged(float) { +void ScriptEditor::_split_dragged(float) { _save_layout(); } Variant ScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { - if (tab_container->get_child_count() == 0) { + if (tab_container->get_tab_count() == 0) { return Variant(); } - Node *cur_node = tab_container->get_child(tab_container->get_current_tab()); + Node *cur_node = tab_container->get_tab_control(tab_container->get_current_tab()); HBoxContainer *drag_preview = memnew(HBoxContainer); String preview_name = ""; @@ -2581,6 +2847,7 @@ Variant ScriptEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { if (!preview_icon.is_null()) { TextureRect *tf = memnew(TextureRect); tf->set_texture(preview_icon); + tf->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); drag_preview->add_child(tf); } Label *label = memnew(Label(preview_name)); @@ -2601,7 +2868,7 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data } if (String(d["type"]) == "script_list_element") { - Node *node = d["script_list_element"]; + Node *node = Object::cast_to<Node>(d["script_list_element"]); ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node); if (se) { @@ -2639,15 +2906,25 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data for (int i = 0; i < files.size(); i++) { String file = files[i]; - if (file == "" || !FileAccess::exists(file)) { + if (file.is_empty() || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - return true; + if (ResourceLoader::exists(file, "Script")) { + Ref<Script> scr = ResourceLoader::load(file); + if (scr.is_valid()) { + return true; + } + } + + if (textfile_extensions.has(file.get_extension())) { + Error err; + Ref<TextFile> text_file = _load_text_file(file, &err); + if (text_file.is_valid() && err == OK) { + return true; + } } } - return true; + return false; } return false; @@ -2664,7 +2941,7 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } if (String(d["type"]) == "script_list_element") { - Node *node = d["script_list_element"]; + Node *node = Object::cast_to<Node>(d["script_list_element"]); ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(node); EditorHelp *eh = Object::cast_to<EditorHelp>(node); @@ -2706,20 +2983,24 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co if (script_list->get_item_count() > 0) { new_index = script_list->get_item_metadata(script_list->get_item_at_position(p_point)); } - int num_tabs_before = tab_container->get_child_count(); + int num_tabs_before = tab_container->get_tab_count(); for (int i = 0; i < files.size(); i++) { String file = files[i]; - if (file == "" || !FileAccess::exists(file)) { + if (file.is_empty() || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - edit(scr); - if (tab_container->get_child_count() > num_tabs_before) { - tab_container->move_child(tab_container->get_child(tab_container->get_child_count() - 1), new_index); - num_tabs_before = tab_container->get_child_count(); + + if (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) { + continue; + } + + 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); + num_tabs_before = tab_container->get_tab_count(); } else { /* Maybe script was already open */ - tab_container->move_child(tab_container->get_child(tab_container->get_current_tab()), new_index); + tab_container->move_child(tab_container->get_tab_control(tab_container->get_current_tab()), new_index); } } } @@ -2728,7 +3009,30 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } } -void ScriptEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { +void ScriptEditor::input(const Ref<InputEvent> &p_event) { + // This is implemented in `input()` rather than `unhandled_input()` to allow + // the shortcut to be used regardless of the click location. + // This feature can be disabled to avoid interfering with other uses of the additional + // mouse buttons, such as push-to-talk in a VoIP program. + if (EDITOR_GET("interface/editor/mouse_extra_buttons_navigate_history")) { + const Ref<InputEventMouseButton> mb = p_event; + + // Navigate the script history using additional mouse buttons present on some mice. + // This must be hardcoded as the editor shortcuts dialog doesn't allow assigning + // more than one shortcut per action. + if (mb.is_valid() && mb->is_pressed() && is_visible_in_tree()) { + if (mb->get_button_index() == MouseButton::MB_XBUTTON1) { + _history_back(); + } + + if (mb->get_button_index() == MouseButton::MB_XBUTTON2) { + _history_forward(); + } + } + } +} + +void ScriptEditor::shortcut_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!is_visible_in_tree() || !p_event->is_pressed() || p_event->is_echo()) { @@ -2762,7 +3066,7 @@ void ScriptEditor::_script_list_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventMouseButton> mb = ev; if (mb.is_valid() && mb->is_pressed()) { switch (mb->get_button_index()) { - case MOUSE_BUTTON_MIDDLE: { + case MouseButton::MIDDLE: { // Right-click selects automatically; middle-click does not. int idx = script_list->get_item_at_position(mb->get_position(), true); if (idx >= 0) { @@ -2772,7 +3076,7 @@ void ScriptEditor::_script_list_gui_input(const Ref<InputEvent> &ev) { } } break; - case MOUSE_BUTTON_RIGHT: { + case MouseButton::RIGHT: { _make_script_list_context_menu(); } break; default: @@ -2785,11 +3089,11 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->clear(); int selected = tab_container->get_current_tab(); - if (selected < 0 || selected >= tab_container->get_child_count()) { + if (selected < 0 || selected >= tab_container->get_tab_count()) { return; } - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(selected)); if (se) { context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save"), FILE_SAVE); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/save_as"), FILE_SAVE_AS); @@ -2801,8 +3105,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(); } @@ -2817,13 +3121,19 @@ void ScriptEditor::_make_script_list_context_menu() { context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/window_sort"), WINDOW_SORT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_editor/toggle_scripts_panel"), TOGGLE_SCRIPTS_PANEL); - context_menu->set_position(get_global_transform().xform(get_local_mouse_position())); - context_menu->set_size(Vector2(1, 1)); + context_menu->set_item_disabled(context_menu->get_item_index(CLOSE_ALL), tab_container->get_tab_count() <= 0); + context_menu->set_item_disabled(context_menu->get_item_index(CLOSE_OTHER_TABS), tab_container->get_tab_count() <= 1); + context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_MOVE_UP), tab_container->get_current_tab() <= 0); + context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_MOVE_DOWN), tab_container->get_current_tab() >= tab_container->get_tab_count() - 1); + context_menu->set_item_disabled(context_menu->get_item_index(WINDOW_SORT), tab_container->get_tab_count() <= 1); + + context_menu->set_position(get_screen_position() + get_local_mouse_position()); + context_menu->reset_size(); context_menu->popup(); } void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { - if (!bool(EDITOR_DEF("text_editor/behavior/files/restore_scripts_on_load", true))) { + if (!bool(EDITOR_GET("text_editor/behavior/files/restore_scripts_on_load"))) { return; } @@ -2839,6 +3149,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = true; + HashSet<String> loaded_scripts; List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); @@ -2851,8 +3162,12 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } if (!FileAccess::exists(path)) { + if (script_editor_cache->has_section(path)) { + script_editor_cache->erase_section(path); + } continue; } + loaded_scripts.insert(path); if (extensions.find(path.get_extension())) { Ref<Script> scr = ResourceLoader::load(path); @@ -2874,7 +3189,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } if (!script_info.is_empty()) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(tab_container->get_tab_count() - 1)); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(tab_container->get_tab_count() - 1)); if (se) { se->set_edit_state(script_info["state"]); } @@ -2883,18 +3198,42 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { for (int i = 0; i < helps.size(); i++) { String path = helps[i]; - if (path == "") { // invalid, skip + if (path.is_empty()) { // invalid, skip continue; } _help_class_open(path); } - for (int i = 0; i < tab_container->get_child_count(); i++) { - tab_container->get_child(i)->set_meta("__editor_pass", Variant()); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + tab_container->get_tab_control(i)->set_meta("__editor_pass", Variant()); + } + + if (p_layout->has_section_key("ScriptEditor", "script_split_offset")) { + script_split->set_split_offset(p_layout->get_value("ScriptEditor", "script_split_offset")); } - if (p_layout->has_section_key("ScriptEditor", "split_offset")) { - script_split->set_split_offset(p_layout->get_value("ScriptEditor", "split_offset")); + if (p_layout->has_section_key("ScriptEditor", "list_split_offset")) { + list_split->set_split_offset(p_layout->get_value("ScriptEditor", "list_split_offset")); + } + + // Remove any deleted editors that have been removed between launches. + // and if a Script, register breakpoints with the debugger. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + if (!FileAccess::exists(E)) { + script_editor_cache->erase_section(E); + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true); + } } restoring_layout = false; @@ -2906,22 +3245,19 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { Array scripts; Array helps; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se) { String path = se->get_edited_resource()->get_path(); if (!path.is_resource_file()) { continue; } - Dictionary script_info; - script_info["path"] = path; - script_info["state"] = se->get_edit_state(); - - scripts.push_back(script_info); + _save_editor_state(se); + scripts.push_back(path); } - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh) { helps.push_back(eh->get_class()); @@ -2930,16 +3266,20 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { p_layout->set_value("ScriptEditor", "open_scripts", scripts); p_layout->set_value("ScriptEditor", "open_help", helps); - p_layout->set_value("ScriptEditor", "split_offset", script_split->get_split_offset()); + p_layout->set_value("ScriptEditor", "script_split_offset", script_split->get_split_offset()); + p_layout->set_value("ScriptEditor", "list_split_offset", list_split->get_split_offset()); + + // Save the cache. + script_editor_cache->save(EditorPaths::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); } void ScriptEditor::_help_class_open(const String &p_class) { - if (p_class == "") { + if (p_class.is_empty()) { return; } - for (int i = 0; i < tab_container->get_child_count(); i++) { - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh && eh->get_class() == p_class) { _go_to_tab(i); @@ -2964,15 +3304,8 @@ void ScriptEditor::_help_class_open(const String &p_class) { void ScriptEditor::_help_class_goto(const String &p_desc) { String cname = p_desc.get_slice(":", 1); - for (int i = 0; i < tab_container->get_child_count(); i++) { - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); - - if (eh && eh->get_class() == cname) { - _go_to_tab(i); - eh->go_to_help(p_desc); - _update_script_names(); - return; - } + if (_help_tab_goto(cname, p_desc)) { + return; } EditorHelp *eh = memnew(EditorHelp); @@ -2986,13 +3319,29 @@ void ScriptEditor::_help_class_goto(const String &p_desc) { _sort_list_on_update = true; _update_script_names(); _save_layout(); + + call_deferred("_help_tab_goto", cname, p_desc); +} + +bool ScriptEditor::_help_tab_goto(const String &p_name, const String &p_desc) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); + + if (eh && eh->get_class() == p_name) { + _go_to_tab(i); + eh->go_to_help(p_desc); + _update_script_names(); + return true; + } + } + return false; } void ScriptEditor::update_doc(const String &p_name) { ERR_FAIL_COND(!EditorHelp::get_doc_data()->has_doc(p_name)); - for (int i = 0; i < tab_container->get_child_count(); i++) { - EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_tab_control(i)); if (eh && eh->get_class() == p_name) { eh->update_doc(); return; @@ -3001,10 +3350,10 @@ void ScriptEditor::update_doc(const String &p_name) { } void ScriptEditor::_update_selected_editor_menu() { - for (int i = 0; i < tab_container->get_child_count(); i++) { + for (int i = 0; i < tab_container->get_tab_count(); i++) { bool current = tab_container->get_current_tab() == i; - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (se && se->get_edit_menu()) { if (current) { se->get_edit_menu()->show(); @@ -3017,15 +3366,15 @@ void ScriptEditor::_update_selected_editor_menu() { EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_current_tab_control()); script_search_menu->get_popup()->clear(); if (eh) { - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find", TTR("Find..."), KEY_MASK_CMD | KEY_F), HELP_SEARCH_FIND); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_next", TTR("Find Next"), KEY_F3), HELP_SEARCH_FIND_NEXT); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3), HELP_SEARCH_FIND_PREVIOUS); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find", TTR("Find..."), KeyModifierMask::CMD | Key::F), HELP_SEARCH_FIND); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_next", TTR("Find Next"), Key::F3), HELP_SEARCH_FIND_NEXT); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_previous", TTR("Find Previous"), KeyModifierMask::SHIFT | Key::F3), HELP_SEARCH_FIND_PREVIOUS); script_search_menu->get_popup()->add_separator(); - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F), SEARCH_IN_FILES); + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); script_search_menu->show(); } else { - if (tab_container->get_child_count() == 0) { - script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F), SEARCH_IN_FILES); + if (tab_container->get_tab_count() == 0) { + script_search_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/find_in_files", TTR("Find in Files"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F), SEARCH_IN_FILES); script_search_menu->show(); } else { script_search_menu->hide(); @@ -3044,7 +3393,7 @@ void ScriptEditor::_update_history_pos(int p_new_pos) { } history_pos = p_new_pos; - tab_container->set_current_tab(history[history_pos].control->get_index()); + tab_container->set_current_tab(tab_container->get_tab_idx_from_control(history[history_pos].control)); n = history[history_pos].control; @@ -3084,8 +3433,8 @@ void ScriptEditor::_history_back() { Vector<Ref<Script>> ScriptEditor::get_open_scripts() const { Vector<Ref<Script>> out_scripts = Vector<Ref<Script>>(); - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -3101,8 +3450,8 @@ Vector<Ref<Script>> ScriptEditor::get_open_scripts() const { Array ScriptEditor::_get_open_script_editors() const { Array script_editors; - for (int i = 0; i < tab_container->get_child_count(); i++) { - ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + for (int i = 0; i < tab_container->get_tab_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i)); if (!se) { continue; } @@ -3113,9 +3462,10 @@ Array ScriptEditor::_get_open_script_editors() const { void ScriptEditor::set_scene_root_script(Ref<Script> p_script) { // Don't open dominant script if using an external editor. - const bool use_external_editor = + bool use_external_editor = EditorSettings::get_singleton()->get("text_editor/external/use_external_editor") || (p_script.is_valid() && p_script->get_language()->overrides_external_editor()); + use_external_editor = use_external_editor && !(p_script.is_valid() && p_script->is_built_in()); // Ignore external editor for built-in scripts. const bool open_dominant = EditorSettings::get_singleton()->get("text_editor/behavior/files/open_dominant_script_on_scene_change"); if (open_dominant && !use_external_editor && p_script.is_valid()) { @@ -3159,7 +3509,7 @@ void ScriptEditor::_open_script_request(const String &p_path) { void ScriptEditor::register_syntax_highlighter(const Ref<EditorSyntaxHighlighter> &p_syntax_highlighter) { ERR_FAIL_COND(p_syntax_highlighter.is_null()); - if (syntax_highlighters.find(p_syntax_highlighter) == -1) { + if (!syntax_highlighters.has(p_syntax_highlighter)) { syntax_highlighters.push_back(p_syntax_highlighter); } } @@ -3179,7 +3529,7 @@ void ScriptEditor::register_create_script_editor_function(CreateScriptEditorFunc } void ScriptEditor::_script_changed() { - NodeDock::singleton->update_lists(); + NodeDock::get_singleton()->update_lists(); } void ScriptEditor::_on_find_in_files_requested(String text) { @@ -3197,13 +3547,16 @@ 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); return; } else { Ref<Script> script = res; @@ -3245,7 +3598,7 @@ void ScriptEditor::_start_find_in_files(bool with_replace) { find_in_files->set_replace_text(find_in_files_dialog->get_replace_text()); find_in_files->start_search(); - editor->make_bottom_panel_item_visible(find_in_files); + EditorNode::get_singleton()->make_bottom_panel_item_visible(find_in_files); } void ScriptEditor::_on_find_in_files_modified_files(PackedStringArray paths) { @@ -3268,11 +3621,10 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_goto_script_line2", &ScriptEditor::_goto_script_line2); ClassDB::bind_method("_copy_script_path", &ScriptEditor::_copy_script_path); - ClassDB::bind_method("_get_debug_tooltip", &ScriptEditor::_get_debug_tooltip); ClassDB::bind_method("_update_script_connections", &ScriptEditor::_update_script_connections); ClassDB::bind_method("_help_class_open", &ScriptEditor::_help_class_open); + ClassDB::bind_method("_help_tab_goto", &ScriptEditor::_help_tab_goto); ClassDB::bind_method("_live_auto_reload_running_scripts", &ScriptEditor::_live_auto_reload_running_scripts); - ClassDB::bind_method("_update_members_overview", &ScriptEditor::_update_members_overview); ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); @@ -3295,9 +3647,12 @@ void ScriptEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("script_close", PropertyInfo(Variant::OBJECT, "script", PROPERTY_HINT_RESOURCE_TYPE, "Script"))); } -ScriptEditor::ScriptEditor(EditorNode *p_editor) { +ScriptEditor::ScriptEditor() { current_theme = ""; + script_editor_cache.instantiate(); + script_editor_cache->load(EditorPaths::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); + completion_cache = memnew(EditorScriptCodeCompletionCache); restoring_layout = false; waiting_update_names = false; @@ -3305,7 +3660,6 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { auto_reload_running_scripts = true; members_overview_enabled = EditorSettings::get_singleton()->get("text_editor/script_list/show_members_overview"); help_overview_enabled = EditorSettings::get_singleton()->get("text_editor/help/show_help_index"); - editor = p_editor; VBoxContainer *main_container = memnew(VBoxContainer); add_child(main_container); @@ -3326,7 +3680,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { list_split->add_child(scripts_vbox); filter_scripts = memnew(LineEdit); - filter_scripts->set_placeholder(TTR("Filter scripts")); + filter_scripts->set_placeholder(TTR("Filter Scripts")); filter_scripts->set_clear_button_enabled(true); filter_scripts->connect("text_changed", callable_mp(this, &ScriptEditor::_filter_scripts_text_changed)); scripts_vbox->add_child(filter_scripts); @@ -3337,7 +3691,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { script_list->set_v_size_flags(SIZE_EXPAND_FILL); script_split->set_split_offset(70 * EDSCALE); _sort_list_on_update = true; - script_list->connect("gui_input", callable_mp(this, &ScriptEditor::_script_list_gui_input), varray(), CONNECT_DEFERRED); + script_list->connect("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); @@ -3369,7 +3723,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { 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); @@ -3403,11 +3757,13 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { find_replace_bar->hide(); ED_SHORTCUT("script_editor/window_sort", TTR("Sort")); - ED_SHORTCUT("script_editor/window_move_up", TTR("Move Up"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_UP); - ED_SHORTCUT("script_editor/window_move_down", TTR("Move Down"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_DOWN); - ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_PERIOD); // these should be KEY_GREATER and KEY_LESS but those don't work - ED_SHORTCUT("script_editor/prev_script", TTR("Previous Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_COMMA); - set_process_unhandled_key_input(true); + ED_SHORTCUT("script_editor/window_move_up", TTR("Move Up"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::UP); + ED_SHORTCUT("script_editor/window_move_down", TTR("Move Down"), KeyModifierMask::SHIFT | KeyModifierMask::ALT | Key::DOWN); + // FIXME: These should be `Key::GREATER` and `Key::LESS` but those don't work. + ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::PERIOD); + ED_SHORTCUT("script_editor/prev_script", TTR("Previous Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::COMMA); + set_process_input(true); + set_process_shortcut_input(true); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); @@ -3415,10 +3771,10 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { file_menu->set_shortcut_context(this); menu_hb->add_child(file_menu); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTR("New Script...")), FILE_NEW); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New Text File...")), FILE_NEW_TEXTFILE); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new", TTR("New Script..."), KeyModifierMask::CMD | Key::N), FILE_NEW); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/new_textfile", TTR("New Text File..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::N), FILE_NEW_TEXTFILE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/open", TTR("Open...")), FILE_OPEN); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_T), FILE_REOPEN_CLOSED); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::T), FILE_REOPEN_CLOSED); file_menu->get_popup()->add_submenu_item(TTR("Open Recent"), "RecentScripts", FILE_OPEN_RECENT); recent_scripts = memnew(PopupMenu); @@ -3428,17 +3784,17 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { _update_recent_scripts(); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save", TTR("Save"), KEY_MASK_ALT | KEY_MASK_CMD | KEY_S), FILE_SAVE); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save", TTR("Save"), KeyModifierMask::ALT | KeyModifierMask::CMD | Key::S), FILE_SAVE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_as", TTR("Save As...")), FILE_SAVE_AS); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/save_all", TTR("Save All"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_S), FILE_SAVE_ALL); + 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"), KEY_MASK_CMD | KEY_MASK_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(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_previous", TTR("History Previous"), KEY_MASK_ALT | KEY_LEFT), WINDOW_PREV); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_next", TTR("History Next"), KEY_MASK_ALT | KEY_RIGHT), WINDOW_NEXT); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_previous", TTR("History Previous"), KeyModifierMask::ALT | Key::LEFT), WINDOW_PREV); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/history_next", TTR("History Next"), KeyModifierMask::ALT | Key::RIGHT), WINDOW_NEXT); file_menu->get_popup()->add_separator(); file_menu->get_popup()->add_submenu_item(TTR("Theme"), "Theme", FILE_THEME); @@ -3455,17 +3811,18 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { theme_submenu->add_shortcut(ED_SHORTCUT("script_editor/save_theme_as", TTR("Save Theme As...")), THEME_SAVE_AS); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTR("Close"), KEY_MASK_CMD | KEY_W), FILE_CLOSE); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_file", TTR("Close"), KeyModifierMask::CMD | Key::W), FILE_CLOSE); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_all", TTR("Close All")), CLOSE_ALL); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_other_tabs", TTR("Close Other Tabs")), CLOSE_OTHER_TABS); file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/close_docs", TTR("Close Docs")), CLOSE_DOCS); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/run_file", TTR("Run"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_X), FILE_RUN); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/run_file", TTR("Run"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::X), FILE_RUN); file_menu->get_popup()->add_separator(); - file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/toggle_scripts_panel", TTR("Toggle Scripts Panel"), KEY_MASK_CMD | KEY_BACKSLASH), TOGGLE_SCRIPTS_PANEL); + file_menu->get_popup()->add_shortcut(ED_SHORTCUT("script_editor/toggle_scripts_panel", TTR("Toggle Scripts Panel"), KeyModifierMask::CMD | Key::BACKSLASH), TOGGLE_SCRIPTS_PANEL); file_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptEditor::_menu_option)); + file_menu->get_popup()->connect("about_to_popup", callable_mp(this, &ScriptEditor::_prepare_file_menu)); script_search_menu = memnew(MenuButton); script_search_menu->set_text(TTR("Search")); @@ -3484,6 +3841,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { debugger->connect("set_execution", callable_mp(this, &ScriptEditor::_set_execution)); debugger->connect("clear_execution", callable_mp(this, &ScriptEditor::_clear_execution)); debugger->connect("breaked", callable_mp(this, &ScriptEditor::_breaked)); + debugger->get_default_debugger()->connect("set_breakpoint", callable_mp(this, &ScriptEditor::_set_breakpoint)); + debugger->get_default_debugger()->connect("clear_breakpoints", callable_mp(this, &ScriptEditor::_clear_breakpoints)); menu_hb->add_spacer(); @@ -3500,14 +3859,14 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { site_search = memnew(Button); site_search->set_flat(true); site_search->set_text(TTR("Online Docs")); - site_search->connect("pressed", callable_mp(this, &ScriptEditor::_menu_option), varray(SEARCH_WEBSITE)); + site_search->connect("pressed", callable_mp(this, &ScriptEditor::_menu_option).bind(SEARCH_WEBSITE)); menu_hb->add_child(site_search); site_search->set_tooltip(TTR("Open Godot online documentation.")); 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.")); @@ -3530,9 +3889,9 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { 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); @@ -3563,7 +3922,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { 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)); @@ -3586,11 +3945,11 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { help_search_dialog->connect("go_to_help", callable_mp(this, &ScriptEditor::_help_class_goto)); find_in_files_dialog = memnew(FindInFilesDialog); - find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files), varray(false)); - find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files), varray(true)); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_FIND_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(false)); + find_in_files_dialog->connect(FindInFilesDialog::SIGNAL_REPLACE_REQUESTED, callable_mp(this, &ScriptEditor::_start_find_in_files).bind(true)); add_child(find_in_files_dialog); find_in_files = memnew(FindInFilesPanel); - find_in_files_button = editor->add_bottom_panel_item(TTR("Search Results"), find_in_files); + find_in_files_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Search Results"), find_in_files); find_in_files->set_custom_minimum_size(Size2(0, 200) * EDSCALE); find_in_files->connect(FindInFilesPanel::SIGNAL_RESULT_SELECTED, callable_mp(this, &ScriptEditor::_on_find_in_files_result_selected)); find_in_files->connect(FindInFilesPanel::SIGNAL_FILES_MODIFIED, callable_mp(this, &ScriptEditor::_on_find_in_files_modified_files)); @@ -3606,8 +3965,8 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { ScriptServer::edit_request_func = _open_script_request; - add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); - tab_container->add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditor"), SNAME("EditorStyles"))); + add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); + tab_container->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditor"), SNAME("EditorStyles"))); } ScriptEditor::~ScriptEditor() { @@ -3619,7 +3978,7 @@ void ScriptEditorPlugin::edit(Object *p_object) { Script *p_script = Object::cast_to<Script>(p_object); String res_path = p_script->get_path().get_slice("::", 0); - if (_is_built_in_script(p_script)) { + if (p_script->is_built_in() && !res_path.is_empty()) { if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { if (!EditorNode::get_singleton()->is_scene_open(res_path)) { EditorNode::get_singleton()->load_scene(res_path); @@ -3691,15 +4050,14 @@ void ScriptEditorPlugin::edited_scene_changed() { script_editor->edited_scene_changed(); } -ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) { - editor = p_node; - script_editor = memnew(ScriptEditor(p_node)); - editor->get_main_control()->add_child(script_editor); +ScriptEditorPlugin::ScriptEditorPlugin() { + script_editor = memnew(ScriptEditor); + EditorNode::get_singleton()->get_main_control()->add_child(script_editor); script_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); script_editor->hide(); - EDITOR_DEF("text_editor/behavior/files/auto_reload_scripts_on_external_change", true); + EDITOR_GET("text_editor/behavior/files/auto_reload_scripts_on_external_change"); ScriptServer::set_reload_scripts_on_save(EDITOR_DEF("text_editor/behavior/files/auto_reload_and_parse_scripts_on_save", true)); EDITOR_DEF("text_editor/behavior/files/open_dominant_script_on_scene_change", true); EDITOR_DEF("text_editor/external/use_external_editor", false); @@ -3715,7 +4073,7 @@ ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) { EDITOR_DEF("text_editor/external/exec_flags", "{file}"); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "text_editor/external/exec_flags", PROPERTY_HINT_PLACEHOLDER_TEXT, "Call flags with placeholders: {project}, {file}, {col}, {line}.")); - ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_T); + ED_SHORTCUT("script_editor/reopen_closed_script", TTR("Reopen Closed Script"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::T); ED_SHORTCUT("script_editor/clear_recent", TTR("Clear Recent Scripts")); } diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index a57aeea5c0..9f088aac49 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -47,11 +47,13 @@ #include "scene/main/timer.h" #include "scene/resources/text_file.h" +class EditorFileDialog; + class EditorSyntaxHighlighter : public SyntaxHighlighter { GDCLASS(EditorSyntaxHighlighter, SyntaxHighlighter) private: - REF edited_resourse; + Ref<RefCounted> edited_resourse; protected: static void _bind_methods(); @@ -63,8 +65,8 @@ public: virtual String _get_name() const; virtual Array _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; }; @@ -100,8 +102,8 @@ public: class ScriptEditorQuickOpen : public ConfirmationDialog { GDCLASS(ScriptEditorQuickOpen, ConfirmationDialog); - LineEdit *search_box; - Tree *search_options; + LineEdit *search_box = nullptr; + Tree *search_options = nullptr; String function; void _update_search(); @@ -134,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; @@ -155,14 +157,17 @@ public: virtual void tag_saved_version() = 0; virtual void reload(bool p_soft) {} virtual Array 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; virtual void update_settings() = 0; virtual void set_debugger_active(bool p_active) = 0; virtual bool can_lose_focus_on_node_selection() { return true; } + virtual void update_toggle_scripts_button() {} virtual bool show_members_overview() = 0; - virtual void set_tooltip_request_func(String p_method, Object *p_obj) = 0; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) = 0; virtual Control *get_edit_menu() = 0; virtual void clear_edit_menu() = 0; virtual void set_find_replace_bar(FindReplaceBar *p_bar) = 0; @@ -174,7 +179,7 @@ public: ScriptEditorBase() {} }; -typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const RES &p_resource); +typedef ScriptEditorBase *(*CreateScriptEditorFunc)(const Ref<Resource> &p_resource); class EditorScriptCodeCompletionCache; class FindInFilesDialog; @@ -183,7 +188,6 @@ class FindInFilesPanel; class ScriptEditor : public PanelContainer { GDCLASS(ScriptEditor, PanelContainer); - EditorNode *editor; enum { FILE_NEW, FILE_NEW_TEXTFILE, @@ -202,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, @@ -238,55 +241,55 @@ class ScriptEditor : public PanelContainer { DISPLAY_FULL_PATH, }; - HBoxContainer *menu_hb; - MenuButton *file_menu; - MenuButton *edit_menu; - MenuButton *script_search_menu; - MenuButton *debug_menu; - PopupMenu *context_menu; - Timer *autosave_timer; - uint64_t idle; - - PopupMenu *recent_scripts; - PopupMenu *theme_submenu; - - Button *help_search; - Button *site_search; - EditorHelpSearch *help_search_dialog; - - ItemList *script_list; - HSplitContainer *script_split; - ItemList *members_overview; - LineEdit *filter_scripts; - LineEdit *filter_methods; - VBoxContainer *scripts_vbox; - VBoxContainer *overview_vbox; - HBoxContainer *buttons_hbox; - Label *filename; - Button *members_overview_alphabeta_sort_button; + HBoxContainer *menu_hb = nullptr; + MenuButton *file_menu = nullptr; + MenuButton *edit_menu = nullptr; + MenuButton *script_search_menu = nullptr; + MenuButton *debug_menu = nullptr; + PopupMenu *context_menu = nullptr; + Timer *autosave_timer = nullptr; + uint64_t idle = 0; + + PopupMenu *recent_scripts = nullptr; + PopupMenu *theme_submenu = nullptr; + + Button *help_search = nullptr; + Button *site_search = nullptr; + EditorHelpSearch *help_search_dialog = nullptr; + + ItemList *script_list = nullptr; + HSplitContainer *script_split = nullptr; + ItemList *members_overview = nullptr; + LineEdit *filter_scripts = nullptr; + LineEdit *filter_methods = nullptr; + VBoxContainer *scripts_vbox = nullptr; + VBoxContainer *overview_vbox = nullptr; + HBoxContainer *buttons_hbox = nullptr; + Label *filename = nullptr; + Button *members_overview_alphabeta_sort_button = nullptr; bool members_overview_enabled; - ItemList *help_overview; + ItemList *help_overview = nullptr; bool help_overview_enabled; - VSplitContainer *list_split; - TabContainer *tab_container; - EditorFileDialog *file_dialog; - AcceptDialog *error_dialog; - ConfirmationDialog *erase_tab_confirm; - ScriptCreateDialog *script_create_dialog; - Button *scripts_visible; - FindReplaceBar *find_replace_bar; + VSplitContainer *list_split = nullptr; + TabContainer *tab_container = nullptr; + EditorFileDialog *file_dialog = nullptr; + AcceptDialog *error_dialog = nullptr; + ConfirmationDialog *erase_tab_confirm = nullptr; + ScriptCreateDialog *script_create_dialog = nullptr; + Button *scripts_visible = nullptr; + FindReplaceBar *find_replace_bar = nullptr; String current_theme; - TextureRect *script_icon; - Label *script_name_label; + TextureRect *script_icon = nullptr; + Label *script_name_label = nullptr; - Button *script_back; - Button *script_forward; + Button *script_back = nullptr; + Button *script_forward = nullptr; - FindInFilesDialog *find_in_files_dialog; - FindInFilesPanel *find_in_files; - Button *find_in_files_button; + FindInFilesDialog *find_in_files_dialog = nullptr; + FindInFilesPanel *find_in_files = nullptr; + Button *find_in_files_button = nullptr; enum { SCRIPT_EDITOR_FUNC_MAX = 32, @@ -306,14 +309,18 @@ class ScriptEditor : public PanelContainer { int history_pos; List<String> previous_scripts; + List<int> script_close_queue; void _tab_changed(int p_which); void _menu_option(int p_option); void _theme_option(int p_option); void _show_save_theme_as_dialog(); + bool _has_docs_tab() const; + bool _has_script_tab() const; + void _prepare_file_menu(); - Tree *disk_changed_list; - ConfirmationDialog *disk_changed; + Tree *disk_changed_list = nullptr; + ConfirmationDialog *disk_changed = nullptr; bool restoring_layout; @@ -322,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(); @@ -338,6 +345,7 @@ class ScriptEditor : public PanelContainer { void _close_docs_tab(); void _close_other_tabs(); void _close_all_tabs(); + void _queue_close_tabs(); void _copy_script_path(); @@ -352,7 +360,7 @@ class ScriptEditor : public PanelContainer { void _update_selected_editor_menu(); - EditorScriptCodeCompletionCache *completion_cache; + EditorScriptCodeCompletionCache *completion_cache = nullptr; void _editor_stop(); @@ -360,27 +368,32 @@ class ScriptEditor : public PanelContainer { void _add_callback(Object *p_obj, const String &p_function, const PackedStringArray &p_args); void _res_saved_callback(const Ref<Resource> &p_res); + void _scene_saved_callback(const String &p_path); + bool open_textfile_after_create = true; bool trim_trailing_whitespace_on_save; bool use_space_indentation; bool convert_indent_on_save; - void _trim_trailing_whitespace(TextEdit *tx); - 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 _update_window_menu(); void _script_created(Ref<Script> p_script); + 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; + Ref<ConfigFile> script_editor_cache; + void _save_editor_state(ScriptEditorBase *p_editor); void _save_layout(); void _editor_settings_changed(); void _filesystem_changed(); + void _files_moved(const String &p_old_file, const String &p_new_file); void _file_removed(const String &p_file); void _autosave_scripts(); void _update_autosave_timer(); @@ -401,23 +414,23 @@ class ScriptEditor : public PanelContainer { void _update_help_overview(); void _help_overview_selected(int p_idx); - void _find_scripts(Node *p_base, Node *p_current, Set<Ref<Script>> &used); + void _find_scripts(Node *p_base, Node *p_current, HashSet<Ref<Script>> &used); void _tree_changed(); - void _script_split_dragged(float); + void _split_dragged(float); Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); - virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; + virtual void input(const Ref<InputEvent> &p_event) override; + virtual void shortcut_input(const Ref<InputEvent> &p_event) override; void _script_list_gui_input(const Ref<InputEvent> &ev); void _make_script_list_context_menu(); void _help_search(String p_text); - void _help_index(String p_text); void _history_forward(); void _history_back(); @@ -426,6 +439,7 @@ class ScriptEditor : public PanelContainer { void _help_class_open(const String &p_class); void _help_class_goto(const String &p_desc); + bool _help_tab_goto(const String &p_name, const String &p_desc); void _update_history_arrows(); void _save_history(); void _go_to_tab(int p_idx); @@ -440,7 +454,8 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; - Ref<TextFile> _load_text_file(const String &p_path, Error *r_error); + 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); void _on_find_in_files_requested(String text); @@ -464,11 +479,13 @@ public: bool is_scripts_panel_toggled(); 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 = ""); + 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); @@ -504,15 +521,14 @@ public: static void register_create_script_editor_function(CreateScriptEditorFunc p_func); - ScriptEditor(EditorNode *p_editor); + ScriptEditor(); ~ScriptEditor(); }; class ScriptEditorPlugin : public EditorPlugin { GDCLASS(ScriptEditorPlugin, EditorPlugin); - ScriptEditor *script_editor; - EditorNode *editor; + ScriptEditor *script_editor = nullptr; public: virtual String get_name() const override { return "Script"; } @@ -535,7 +551,7 @@ public: virtual void edited_scene_changed() override; - ScriptEditorPlugin(EditorNode *p_node); + ScriptEditorPlugin(); ~ScriptEditorPlugin(); }; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 5f48106afc..5d5f452390 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,7 @@ #include "script_text_editor.h" +#include "core/config/project_settings.h" #include "core/math/expression.h" #include "core/os/keyboard.h" #include "editor/debugger/editor_debugger_node.h" @@ -90,7 +91,7 @@ ConnectionInfoDialog::ConnectionInfoDialog() { add_child(vbc); method = memnew(Label); - method->set_align(Label::ALIGN_CENTER); + method->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); vbc->add_child(method); tree = memnew(Tree); @@ -132,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()); @@ -158,7 +159,6 @@ void ScriptTextEditor::enable_editor() { editor_enabled = true; _enable_code_editor(); - _set_theme_for_script(); _validate_script(); } @@ -206,7 +206,7 @@ void ScriptTextEditor::_set_theme_for_script() { String beg = string.get_slice(" ", 0); String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); if (!text_edit->has_string_delimiter(beg)) { - text_edit->add_string_delimiter(beg, end, end == ""); + text_edit->add_string_delimiter(beg, end, end.is_empty()); } if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) { @@ -220,7 +220,7 @@ void ScriptTextEditor::_set_theme_for_script() { for (const String &comment : comments) { String beg = comment.get_slice(" ", 0); String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); - text_edit->add_comment_delimiter(beg, end, end == ""); + text_edit->add_comment_delimiter(beg, end, end.is_empty()); if (!end.is_empty() && !text_edit->has_auto_brace_completion_open_key(beg)) { text_edit->add_auto_brace_completion_pair(beg, end); @@ -241,7 +241,26 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) { goto_line_centered(p_line.operator int64_t()); } else if (p_line.get_type() == Variant::DICTIONARY) { Dictionary meta = p_line.operator Dictionary(); - code_editor->get_text_editor()->insert_line_at(meta["line"].operator int64_t() - 1, "# warning-ignore:" + meta["code"].operator String()); + const int line = meta["line"].operator int64_t() - 1; + + CodeEdit *text_editor = code_editor->get_text_editor(); + String prev_line = line > 0 ? text_editor->get_line(line - 1) : ""; + if (prev_line.contains("@warning_ignore")) { + const int closing_bracket_idx = prev_line.find(")"); + const String text_to_insert = ", " + meta["code"].operator String(); + prev_line = prev_line.insert(closing_bracket_idx, text_to_insert); + text_editor->set_line(line - 1, prev_line); + } else { + const int indent = text_editor->get_indent_level(line) / text_editor->get_indent_size(); + String annotation_indent; + if (!text_editor->is_indent_using_spaces()) { + annotation_indent = String("\t").repeat(indent); + } else { + annotation_indent = String(" ").repeat(text_editor->get_indent_size() * indent); + } + text_editor->insert_line_at(line, annotation_indent + "@warning_ignore(" + meta["code"].operator String() + ")"); + } + _validate_script(); } } @@ -376,26 +395,38 @@ void ScriptTextEditor::ensure_focus() { String ScriptTextEditor::get_name() { String name; - if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { - name = script->get_path().get_file(); - if (is_unsaved()) { - if (script->get_path().is_empty()) { - name = TTR("[unsaved]"); - } - name += "(*)"; + name = script->get_path().get_file(); + if (name.is_empty()) { + // This appears for newly created built-in scripts before saving the scene. + name = TTR("[unsaved]"); + } else if (script->is_built_in()) { + const String &script_name = script->get_name(); + if (!script_name.is_empty()) { + // If the built-in script has a custom resource name defined, + // display the built-in script name as follows: `ResourceName (scene_file.tscn)` + name = vformat("%s (%s)", script_name, name.get_slice("::", 0)); } - } else if (script->get_name() != "") { - name = script->get_name(); - } else { - name = script->get_class() + "(" + itos(script->get_instance_id()) + ")"; + } + + if (is_unsaved()) { + name += "(*)"; } return name; } Ref<Texture2D> ScriptTextEditor::get_theme_icon() { - if (get_parent_control() && get_parent_control()->has_theme_icon(script->get_class(), "EditorIcons")) { - return get_parent_control()->get_theme_icon(script->get_class(), "EditorIcons"); + if (get_parent_control()) { + String icon_name = script->get_class(); + if (script->is_built_in()) { + icon_name += "Internal"; + } + + if (get_parent_control()->has_theme_icon(icon_name, SNAME("EditorIcons"))) { + return get_parent_control()->get_theme_icon(icon_name, SNAME("EditorIcons")); + } else if (get_parent_control()->has_theme_icon(script->get_class(), SNAME("EditorIcons"))) { + return get_parent_control()->get_theme_icon(script->get_class(), SNAME("EditorIcons")); + } } return Ref<Texture2D>(); @@ -406,12 +437,14 @@ void ScriptTextEditor::_validate_script() { String text = te->get_text(); List<String> fnc; - Set<int> safe_lines; - List<ScriptLanguage::Warning> warnings; - List<ScriptLanguage::ScriptError> errors; + + warnings.clear(); + errors.clear(); + safe_lines.clear(); if (!script->get_language()->validate(text, script->get_path(), &fnc, &errors, &warnings, &safe_lines)) { - String error_text = TTR("Error at ") + "(" + itos(errors[0].line) + "," + itos(errors[0].column) + "): " + errors[0].message; + // TRANSLATORS: Script error pointing to a line and column number. + String error_text = vformat(TTR("Error at (%d, %d):"), errors[0].line, errors[0].column) + " " + errors[0].message; code_editor->set_error(error_text); code_editor->set_error_pos(errors[0].line - 1, errors[0].column - 1); script_is_valid = false; @@ -430,14 +463,23 @@ void ScriptTextEditor::_validate_script() { script_is_valid = true; } _update_connected_methods(); + _update_warnings(); + _update_errors(); + emit_signal(SNAME("name_changed")); + emit_signal(SNAME("edited_script_changed")); +} + +void ScriptTextEditor::_update_warnings() { int warning_nb = warnings.size(); warnings_panel->clear(); + bool has_connections_table = false; // Add missing connections. if (GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { Node *base = get_tree()->get_edited_scene_root(); if (base && missing_connections.size() > 0) { + has_connections_table = true; warnings_panel->push_table(1); for (const Connection &connection : missing_connections) { String base_path = base->get_name(); @@ -456,9 +498,12 @@ void ScriptTextEditor::_validate_script() { } } - code_editor->set_error_count(errors.size()); code_editor->set_warning_count(warning_nb); + if (has_connections_table) { + warnings_panel->add_newline(); + } + // Add script warnings. warnings_panel->push_table(3); for (const ScriptLanguage::Warning &w : warnings) { @@ -468,7 +513,7 @@ void ScriptTextEditor::_validate_script() { warnings_panel->push_cell(); warnings_panel->push_meta(ignore_meta); warnings_panel->push_color( - warnings_panel->get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(warnings_panel->get_theme_color(SNAME("mono_color"), SNAME("Editor")), 0.5)); + warnings_panel->get_theme_color(SNAME("accent_color"), SNAME("Editor")).lerp(warnings_panel->get_theme_color(SNAME("mono_color"), SNAME("Editor")), 0.5f)); warnings_panel->add_text(TTR("[Ignore]")); warnings_panel->pop(); // Color. warnings_panel->pop(); // Meta ignore. @@ -488,6 +533,10 @@ void ScriptTextEditor::_validate_script() { warnings_panel->pop(); // Cell. } warnings_panel->pop(); // Table. +} + +void ScriptTextEditor::_update_errors() { + code_editor->set_error_count(errors.size()); errors_panel->clear(); errors_panel->push_table(2); @@ -506,7 +555,8 @@ void ScriptTextEditor::_validate_script() { } errors_panel->pop(); // Table - bool highlight_safe = EDITOR_DEF("text_editor/appearance/gutters/highlight_type_safe_lines", true); + CodeEdit *te = code_editor->get_text_editor(); + bool highlight_safe = EDITOR_GET("text_editor/appearance/gutters/highlight_type_safe_lines"); bool last_is_safe = false; for (int i = 0; i < te->get_line_count(); i++) { if (errors.is_empty()) { @@ -535,14 +585,11 @@ void ScriptTextEditor::_validate_script() { te->set_line_gutter_item_color(i, 1, default_line_number_color); } } - - emit_signal(SNAME("name_changed")); - emit_signal(SNAME("edited_script_changed")); } void ScriptTextEditor::_update_bookmark_list() { bookmarks_menu->clear(); - bookmarks_menu->set_size(Size2(1, 1)); + bookmarks_menu->reset_size(); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); bookmarks_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_bookmarks"), BOOKMARK_REMOVE_ALL); @@ -567,7 +614,7 @@ void ScriptTextEditor::_update_bookmark_list() { } bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - `" + line + "`"); - bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); + bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } @@ -617,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; } @@ -639,21 +686,21 @@ 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; } - if (script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1) { + if (script->is_built_in()) { continue; //internal script, who cares, though weird } @@ -672,12 +719,12 @@ void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_fo } } -void ScriptTextEditor::_code_complete_scripts(void *p_ud, const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force) { +void ScriptTextEditor::_code_complete_scripts(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) { ScriptTextEditor *ste = (ScriptTextEditor *)p_ud; ste->_code_complete_script(p_code, r_options, r_force); } -void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force) { +void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force) { if (color_panel->is_visible()) { return; } @@ -687,6 +734,9 @@ void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptCo } String hint; Error err = script->get_language()->complete_code(p_code, script->get_path(), base, r_options, r_force, hint); + + r_options->sort_custom_inplace<CodeCompletionOptionCompare>(); + if (err == OK) { code_editor->get_text_editor()->set_code_hint(hint); } @@ -694,7 +744,7 @@ void ScriptTextEditor::_code_complete_script(const String &p_code, List<ScriptCo void ScriptTextEditor::_update_breakpoint_list() { breakpoints_menu->clear(); - breakpoints_menu->set_size(Size2(1, 1)); + breakpoints_menu->reset_size(); breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_breakpoint"), DEBUG_TOGGLE_BREAKPOINT); breakpoints_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/remove_all_breakpoints"), DEBUG_REMOVE_ALL_BREAKPOINTS); @@ -719,7 +769,7 @@ void ScriptTextEditor::_update_breakpoint_list() { } breakpoints_menu->add_item(String::num((int)breakpoint_list[i] + 1) + " - `" + line + "`"); - breakpoints_menu->set_item_metadata(breakpoints_menu->get_item_count() - 1, breakpoint_list[i]); + breakpoints_menu->set_item_metadata(-1, breakpoint_list[i]); } } @@ -759,7 +809,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c _goto_line(p_row); switch (result.type) { - case ScriptLanguage::LookupResult::RESULT_SCRIPT_LOCATION: { + case ScriptLanguage::LOOKUP_RESULT_SCRIPT_LOCATION: { if (result.script.is_valid()) { emit_signal(SNAME("request_open_script_at_line"), result.script, result.location - 1); } else { @@ -767,10 +817,10 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c goto_line_centered(result.location - 1); } } break; - case ScriptLanguage::LookupResult::RESULT_CLASS: { + case ScriptLanguage::LOOKUP_RESULT_CLASS: { emit_signal(SNAME("go_to_help"), "class_name:" + result.class_name); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_CONSTANT: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_CONSTANT: { StringName cname = result.class_name; bool success; while (true) { @@ -786,11 +836,11 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c emit_signal(SNAME("go_to_help"), "class_constant:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_PROPERTY: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_PROPERTY: { emit_signal(SNAME("go_to_help"), "class_property:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_METHOD: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_METHOD: { StringName cname = result.class_name; while (true) { @@ -805,7 +855,22 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c emit_signal(SNAME("go_to_help"), "class_method:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_ENUM: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_SIGNAL: { + StringName cname = result.class_name; + + while (true) { + if (ClassDB::has_signal(cname, result.class_member)) { + result.class_name = cname; + cname = ClassDB::get_parent_class(cname); + } else { + break; + } + } + + emit_signal(SNAME("go_to_help"), "class_signal:" + result.class_name + ":" + result.class_member); + + } break; + case ScriptLanguage::LOOKUP_RESULT_CLASS_ENUM: { StringName cname = result.class_name; StringName success; while (true) { @@ -821,9 +886,14 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c emit_signal(SNAME("go_to_help"), "class_enum:" + result.class_name + ":" + result.class_member); } break; - case ScriptLanguage::LookupResult::RESULT_CLASS_TBD_GLOBALSCOPE: { + case ScriptLanguage::LOOKUP_RESULT_CLASS_ANNOTATION: { + emit_signal(SNAME("go_to_help"), "class_annotation:" + result.class_name + ":" + result.class_member); + } break; + case ScriptLanguage::LOOKUP_RESULT_CLASS_TBD_GLOBALSCOPE: { emit_signal(SNAME("go_to_help"), "class_global:" + result.class_name + ":" + result.class_member); } break; + default: { + } } } else if (ProjectSettings::get_singleton()->has_autoload(p_symbol)) { // Check for Autoload scenes. @@ -831,7 +901,7 @@ void ScriptTextEditor::_lookup_symbol(const String &p_symbol, int p_row, int p_c if (info.is_singleton) { EditorNode::get_singleton()->load_scene(info.path); } - } else if (p_symbol.is_rel_path()) { + } else if (p_symbol.is_relative_path()) { // Every symbol other than absolute path is relative path so keep this condition at last. String path = _get_absolute_path(p_symbol); if (FileAccess::exists(path)) { @@ -858,7 +928,7 @@ void ScriptTextEditor::_validate_symbol(const String &p_symbol) { ScriptLanguage::LookupResult result; if (ScriptServer::is_global_class(p_symbol) || p_symbol.is_resource_file() || script->get_language()->lookup_code(code_editor->get_text_editor()->get_text_for_symbol_lookup(), p_symbol, script->get_path(), base, result) == OK || (ProjectSettings::get_singleton()->has_autoload(p_symbol) && ProjectSettings::get_singleton()->get_autoload(p_symbol).is_singleton)) { text_edit->set_symbol_lookup_word_as_valid(true); - } else if (p_symbol.is_rel_path()) { + } else if (p_symbol.is_relative_path()) { String path = _get_absolute_path(p_symbol); if (FileAccess::exists(path)) { text_edit->set_symbol_lookup_word_as_valid(true); @@ -877,9 +947,7 @@ String ScriptTextEditor::_get_absolute_path(const String &rel_path) { } void ScriptTextEditor::update_toggle_scripts_button() { - if (code_editor != nullptr) { - code_editor->update_toggle_scripts_button(); - } + code_editor->update_toggle_scripts_button(); } void ScriptTextEditor::_update_connected_methods() { @@ -905,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); @@ -921,21 +989,22 @@ void ScriptTextEditor::_update_connected_methods() { continue; } - if (methods_found.has(connection.callable.get_method())) { + const StringName method = connection.callable.get_method(); + if (methods_found.has(method)) { continue; } - if (!ClassDB::has_method(script->get_instance_base_type(), connection.callable.get_method())) { + if (!ClassDB::has_method(script->get_instance_base_type(), method)) { int line = -1; for (int j = 0; j < functions.size(); j++) { String name = functions[j].get_slice(":", 0); - if (name == connection.callable.get_method()) { + if (name == method) { line = functions[j].get_slice(":", 1).to_int() - 1; - text_edit->set_line_gutter_metadata(line, connection_gutter, connection.callable.get_method()); + text_edit->set_line_gutter_metadata(line, connection_gutter, method); text_edit->set_line_gutter_icon(line, connection_gutter, get_parent_control()->get_theme_icon(SNAME("Slot"), SNAME("EditorIcons"))); text_edit->set_line_gutter_clickable(line, connection_gutter, true); - methods_found.insert(connection.callable.get_method()); + methods_found.insert(method); break; } } @@ -948,7 +1017,7 @@ void ScriptTextEditor::_update_connected_methods() { bool found_inherited_function = false; Ref<Script> inherited_script = script->get_base_script(); while (!inherited_script.is_null()) { - if (inherited_script->has_method(connection.callable.get_method())) { + if (inherited_script->has_method(method)) { found_inherited_function = true; break; } @@ -984,7 +1053,7 @@ void ScriptTextEditor::_gutter_clicked(int p_line, int p_gutter) { } String method = code_editor->get_text_editor()->get_line_gutter_metadata(p_line, p_gutter); - if (method == "") { + if (method.is_empty()) { return; } @@ -1130,8 +1199,8 @@ 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); - if (expression.get_error_text() == "") { + Variant result = expression.execute(Array(), Variant(), false, true); + if (expression.get_error_text().is_empty()) { results.push_back(whitespace + result.get_construct_string()); } else { results.push_back(line); @@ -1243,7 +1312,7 @@ void ScriptTextEditor::_edit_option(int p_op) { tx->set_caret_line(bpoints[bpoints.size() - 1]); tx->center_viewport_to_caret(); } else { - for (int i = bpoints.size(); i >= 0; i--) { + for (int i = bpoints.size() - 1; i >= 0; i--) { int bline = bpoints[i]; if (bline < line) { tx->unfold_line(bline); @@ -1257,19 +1326,19 @@ void ScriptTextEditor::_edit_option(int p_op) { } break; case HELP_CONTEXTUAL: { String text = tx->get_selected_text(); - if (text == "") { + if (text.is_empty()) { text = tx->get_word_under_caret(); } - if (text != "") { + if (!text.is_empty()) { emit_signal(SNAME("request_help"), text); } } break; case LOOKUP_SYMBOL: { String text = tx->get_word_under_caret(); - if (text == "") { + if (text.is_empty()) { text = tx->get_selected_text(); } - if (text != "") { + if (!text.is_empty()) { _lookup_symbol(text, tx->get_caret_line(), tx->get_caret_column()); } } break; @@ -1286,7 +1355,7 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() { script->get_language()->get_comment_delimiters(&comment_delimiters); for (const String &script_delimiter : comment_delimiters) { - if (script_delimiter.find(" ") == -1) { + if (!script_delimiter.contains(" ")) { delimiter = script_delimiter; break; } @@ -1305,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(); @@ -1324,11 +1393,17 @@ void ScriptTextEditor::_change_syntax_highlighter(int p_idx) { void ScriptTextEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: + if (!editor_enabled) { + break; + } + if (is_visible_in_tree()) { + _update_warnings(); + _update_errors(); + } + [[fallthrough]]; case NOTIFICATION_ENTER_TREE: { code_editor->get_text_editor()->set_gutter_width(connection_gutter, code_editor->get_text_editor()->get_line_height()); } break; - default: - break; } } @@ -1338,8 +1413,6 @@ void ScriptTextEditor::_bind_methods() { ClassDB::bind_method("_get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw); ClassDB::bind_method("_can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw); ClassDB::bind_method("_drop_data_fw", &ScriptTextEditor::drop_data_fw); - - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptTextEditor::add_syntax_highlighter); } Control *ScriptTextEditor::get_edit_menu() { @@ -1347,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) { @@ -1361,7 +1436,7 @@ void ScriptTextEditor::reload(bool p_soft) { return; } scr->set_source_code(te->get_text()); - bool soft = p_soft || scr->get_instance_base_type() == "EditorPlugin"; //always soft-reload editor plugins + bool soft = p_soft || scr->get_instance_base_type() == "EditorPlugin"; // Always soft-reload editor plugins. scr->get_language()->reload_tool_script(scr, soft); } @@ -1370,8 +1445,18 @@ Array ScriptTextEditor::get_breakpoints() { return code_editor->get_text_editor()->get_breakpointed_lines(); } -void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) { - code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this); +void ScriptTextEditor::set_breakpoint(int p_line, bool p_enabled) { + code_editor->get_text_editor()->set_line_as_breakpoint(p_line, p_enabled); +} + +void ScriptTextEditor::clear_breakpoints() { + code_editor->get_text_editor()->clear_breakpointed_lines(); +} + +void ScriptTextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { + Variant args[1] = { this }; + const Variant *argp[] = { &args[0] }; + code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); } void ScriptTextEditor::set_debugger_active(bool p_active) { @@ -1387,11 +1472,12 @@ Variant ScriptTextEditor::get_drag_data_fw(const Point2 &p_point, Control *p_fro bool ScriptTextEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { Dictionary d = p_data; - if (d.has("type") && (String(d["type"]) == "resource" || - String(d["type"]) == "files" || - String(d["type"]) == "nodes" || - String(d["type"]) == "obj_property" || - String(d["type"]) == "files_and_dirs")) { + if (d.has("type") && + (String(d["type"]) == "resource" || + String(d["type"]) == "files" || + String(d["type"]) == "nodes" || + String(d["type"]) == "obj_property" || + String(d["type"]) == "files_and_dirs")) { return true; } @@ -1443,13 +1529,14 @@ 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")) { Array files = d["files"]; String text_to_drop; - bool preload = Input::get_singleton()->is_key_pressed(KEY_CTRL); + bool preload = Input::get_singleton()->is_key_pressed(Key::CTRL); for (int i = 0; i < files.size(); i++) { if (i > 0) { text_to_drop += ", "; @@ -1465,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") { @@ -1477,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") { @@ -1503,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(); } } @@ -1513,7 +1651,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { bool create_menu = false; CodeEdit *tx = code_editor->get_text_editor(); - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { local_pos = mb->get_global_position() - tx->get_global_position(); create_menu = true; } else if (k.is_valid() && k->is_action("ui_menu", true)) { @@ -1547,10 +1685,10 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { } String word_at_pos = tx->get_word_at_pos(local_pos); - if (word_at_pos == "") { + if (word_at_pos.is_empty()) { word_at_pos = tx->get_word_under_caret(); } - if (word_at_pos == "") { + if (word_at_pos.is_empty()) { word_at_pos = tx->get_selected_text(); } @@ -1598,7 +1736,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { float alpha = color.size() > 3 ? color[3] : 1.0f; color_picker->set_pick_color(Color(color[0], color[1], color[2], alpha)); } - color_panel->set_position(get_global_transform().xform(local_pos)); + color_panel->set_position(get_screen_position() + local_pos); } else { has_color = false; } @@ -1616,10 +1754,7 @@ void ScriptTextEditor::_color_changed(const Color &p_color) { } String line = code_editor->get_text_editor()->get_line(color_position.x); - int color_args_pos = line.find(color_args, color_position.y); - String line_with_replaced_args = line; - line_with_replaced_args.erase(color_args_pos, color_args.length()); - line_with_replaced_args = line_with_replaced_args.insert(color_args_pos, new_args); + String line_with_replaced_args = line.replace(color_args, new_args); color_args = new_args; code_editor->get_text_editor()->begin_complex_operation(); @@ -1678,8 +1813,8 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); - context_menu->set_position(get_global_transform().xform(p_pos)); - context_menu->set_size(Vector2(1, 1)); + context_menu->set_position(get_screen_position() + p_pos); + context_menu->reset_size(); context_menu->popup(); } @@ -1688,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); @@ -1696,7 +1831,6 @@ void ScriptTextEditor::_enable_code_editor() { code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel)); code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script)); code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings)); - code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol)); code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol)); code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); @@ -1728,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); @@ -1795,9 +1919,9 @@ void ScriptTextEditor::_enable_code_editor() { edit_menu->get_popup()->add_child(convert_case); edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case"); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KEY_MASK_SHIFT | KEY_F4), EDIT_TO_UPPERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KEY_MASK_SHIFT | KEY_F5), EDIT_TO_LOWERCASE); - convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KEY_MASK_SHIFT | KEY_F6), EDIT_CAPITALIZE); + convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KeyModifierMask::SHIFT | Key::F4), EDIT_TO_UPPERCASE); + convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_lowercase", TTR("Lowercase"), KeyModifierMask::SHIFT | Key::F5), EDIT_TO_LOWERCASE); + convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KeyModifierMask::SHIFT | Key::F6), EDIT_CAPITALIZE); convert_case->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); edit_menu->get_popup()->add_child(highlighter_menu); @@ -1830,12 +1954,13 @@ 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); code_editor->get_text_editor()->set_draw_breakpoints_gutter(true); code_editor->get_text_editor()->set_draw_executing_lines_gutter(true); + code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); connection_gutter = 1; code_editor->get_text_editor()->add_gutter(connection_gutter); @@ -1935,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); } @@ -1943,70 +2068,60 @@ static ScriptEditorBase *create_editor(const RES &p_resource) { } void ScriptTextEditor::register_editor() { - ED_SHORTCUT("script_text_editor/move_up", TTR("Move Up"), KEY_MASK_ALT | KEY_UP); - ED_SHORTCUT("script_text_editor/move_down", TTR("Move Down"), KEY_MASK_ALT | KEY_DOWN); - ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_K); + ED_SHORTCUT("script_text_editor/move_up", TTR("Move Up"), KeyModifierMask::ALT | Key::UP); + ED_SHORTCUT("script_text_editor/move_down", TTR("Move Down"), KeyModifierMask::ALT | Key::DOWN); + ED_SHORTCUT("script_text_editor/delete_line", TTR("Delete Line"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::K); // Leave these at zero, same can be accomplished with tab/shift-tab, including selection. // The next/previous in history shortcut in this case makes a lot more sense. - ED_SHORTCUT("script_text_editor/indent_left", TTR("Indent Left"), KEY_NONE); - ED_SHORTCUT("script_text_editor/indent_right", TTR("Indent Right"), KEY_NONE); - ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KEY_MASK_CMD | KEY_K); - ED_SHORTCUT("script_text_editor/toggle_fold_line", TTR("Fold/Unfold Line"), KEY_MASK_ALT | KEY_F); - ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), KEY_NONE); - ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), KEY_NONE); -#ifdef OSX_ENABLED - ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_C); -#else - ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_D); -#endif - ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_E); - ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CMD | KEY_MASK_ALT | KEY_T); - ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent to Spaces"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_Y); - ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent to Tabs"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_I); - ED_SHORTCUT("script_text_editor/auto_indent", TTR("Auto Indent"), KEY_MASK_CMD | KEY_I); - - ED_SHORTCUT_AND_COMMAND("script_text_editor/find", TTR("Find..."), KEY_MASK_CMD | KEY_F); -#ifdef OSX_ENABLED - ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), KEY_MASK_CMD | KEY_G); - ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G); - ED_SHORTCUT_AND_COMMAND("script_text_editor/replace", TTR("Replace..."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F); -#else - ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), KEY_F3); - ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KEY_MASK_SHIFT | KEY_F3); - ED_SHORTCUT_AND_COMMAND("script_text_editor/replace", TTR("Replace..."), KEY_MASK_CMD | KEY_R); -#endif - - ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in Files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F); - ED_SHORTCUT("script_text_editor/replace_in_files", TTR("Replace in Files..."), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_R); - -#ifdef OSX_ENABLED - ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_ALT | KEY_MASK_SHIFT | KEY_SPACE); -#else - ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KEY_MASK_ALT | KEY_F1); -#endif - - ED_SHORTCUT("script_text_editor/toggle_bookmark", TTR("Toggle Bookmark"), KEY_MASK_CMD | KEY_MASK_ALT | KEY_B); - ED_SHORTCUT("script_text_editor/goto_next_bookmark", TTR("Go to Next Bookmark"), KEY_MASK_CMD | KEY_B); - ED_SHORTCUT("script_text_editor/goto_previous_bookmark", TTR("Go to Previous Bookmark"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B); - ED_SHORTCUT("script_text_editor/remove_all_bookmarks", TTR("Remove All Bookmarks"), KEY_NONE); - -#ifdef OSX_ENABLED - ED_SHORTCUT("script_text_editor/goto_function", TTR("Go to Function..."), KEY_MASK_CTRL | KEY_MASK_CMD | KEY_J); -#else - ED_SHORTCUT("script_text_editor/goto_function", TTR("Go to Function..."), KEY_MASK_ALT | KEY_MASK_CMD | KEY_F); -#endif - ED_SHORTCUT("script_text_editor/goto_line", TTR("Go to Line..."), KEY_MASK_CMD | KEY_L); - -#ifdef OSX_ENABLED - ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B); -#else - ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), KEY_F9); -#endif - ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_F9); - ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KEY_MASK_CMD | KEY_PERIOD); - ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KEY_MASK_CMD | KEY_COMMA); + ED_SHORTCUT("script_text_editor/indent_left", TTR("Indent Left"), Key::NONE); + ED_SHORTCUT("script_text_editor/indent_right", TTR("Indent Right"), Key::NONE); + ED_SHORTCUT("script_text_editor/toggle_comment", TTR("Toggle Comment"), KeyModifierMask::CMD | Key::K); + ED_SHORTCUT("script_text_editor/toggle_fold_line", TTR("Fold/Unfold Line"), KeyModifierMask::ALT | Key::F); + ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), Key::NONE); + ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), Key::NONE); + ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::D); + ED_SHORTCUT_OVERRIDE("script_text_editor/duplicate_selection", "macos", KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::C); + ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::E); + ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KeyModifierMask::CMD | KeyModifierMask::ALT | Key::T); + ED_SHORTCUT("script_text_editor/convert_indent_to_spaces", TTR("Convert Indent to Spaces"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::Y); + ED_SHORTCUT("script_text_editor/convert_indent_to_tabs", TTR("Convert Indent to Tabs"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::I); + ED_SHORTCUT("script_text_editor/auto_indent", TTR("Auto Indent"), KeyModifierMask::CMD | Key::I); + + ED_SHORTCUT_AND_COMMAND("script_text_editor/find", TTR("Find..."), KeyModifierMask::CMD | Key::F); + + ED_SHORTCUT("script_text_editor/find_next", TTR("Find Next"), Key::F3); + ED_SHORTCUT_OVERRIDE("script_text_editor/find_next", "macos", KeyModifierMask::CMD | Key::G); + + ED_SHORTCUT("script_text_editor/find_previous", TTR("Find Previous"), KeyModifierMask::SHIFT | Key::F3); + ED_SHORTCUT_OVERRIDE("script_text_editor/find_previous", "macos", KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::G); + + ED_SHORTCUT_AND_COMMAND("script_text_editor/replace", TTR("Replace..."), KeyModifierMask::CMD | Key::R); + ED_SHORTCUT_OVERRIDE("script_text_editor/replace", "macos", KeyModifierMask::ALT | KeyModifierMask::CMD | Key::F); + + ED_SHORTCUT("script_text_editor/find_in_files", TTR("Find in Files..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F); + ED_SHORTCUT("script_text_editor/replace_in_files", TTR("Replace in Files..."), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::R); + + ED_SHORTCUT("script_text_editor/contextual_help", TTR("Contextual Help"), KeyModifierMask::ALT | Key::F1); + ED_SHORTCUT_OVERRIDE("script_text_editor/contextual_help", "macos", KeyModifierMask::ALT | KeyModifierMask::SHIFT | Key::SPACE); + + ED_SHORTCUT("script_text_editor/toggle_bookmark", TTR("Toggle Bookmark"), KeyModifierMask::CMD | KeyModifierMask::ALT | Key::B); + ED_SHORTCUT("script_text_editor/goto_next_bookmark", TTR("Go to Next Bookmark"), KeyModifierMask::CMD | Key::B); + ED_SHORTCUT("script_text_editor/goto_previous_bookmark", TTR("Go to Previous Bookmark"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::B); + ED_SHORTCUT("script_text_editor/remove_all_bookmarks", TTR("Remove All Bookmarks"), Key::NONE); + + ED_SHORTCUT("script_text_editor/goto_function", TTR("Go to Function..."), KeyModifierMask::ALT | KeyModifierMask::CMD | Key::F); + ED_SHORTCUT_OVERRIDE("script_text_editor/goto_function", "macos", KeyModifierMask::CTRL | KeyModifierMask::CMD | Key::J); + + ED_SHORTCUT("script_text_editor/goto_line", TTR("Go to Line..."), KeyModifierMask::CMD | Key::L); + + ED_SHORTCUT("script_text_editor/toggle_breakpoint", TTR("Toggle Breakpoint"), Key::F9); + ED_SHORTCUT_OVERRIDE("script_text_editor/toggle_breakpoint", "macos", KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::B); + + ED_SHORTCUT("script_text_editor/remove_all_breakpoints", TTR("Remove All Breakpoints"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F9); + ED_SHORTCUT("script_text_editor/goto_next_breakpoint", TTR("Go to Next Breakpoint"), KeyModifierMask::CMD | Key::PERIOD); + ED_SHORTCUT("script_text_editor/goto_previous_breakpoint", TTR("Go to Previous Breakpoint"), KeyModifierMask::CMD | Key::COMMA); ScriptEditor::register_create_script_editor_function(create_editor); } diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 1ca6f56ea1..fc87c84a2c 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -62,6 +62,9 @@ class ScriptTextEditor : public ScriptEditorBase { bool editor_enabled = false; Vector<String> functions; + List<ScriptLanguage::Warning> warnings; + List<ScriptLanguage::ScriptError> errors; + HashSet<int> safe_lines; List<Connection> missing_connections; @@ -154,11 +157,13 @@ protected: void _breakpoint_toggled(int p_row); void _validate_script(); // No longer virtual. + void _update_warnings(); + void _update_errors(); void _update_bookmark_list(); void _bookmark_item_pressed(int p_idx); - static void _code_complete_scripts(void *p_ud, const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force); - void _code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options, bool &r_force); + static void _code_complete_scripts(void *p_ud, const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force); + void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options, bool &r_force); void _load_theme_settings(); void _set_theme_for_script(); @@ -170,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); @@ -197,11 +202,11 @@ public: virtual void add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) override; virtual void set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) override; - void update_toggle_scripts_button(); + 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; @@ -225,13 +230,15 @@ public: virtual void reload(bool p_soft) override; virtual Array get_breakpoints() override; + virtual void set_breakpoint(int p_line, bool p_enabled) override; + virtual void clear_breakpoints() override; virtual void add_callback(const String &p_function, PackedStringArray p_args) override; virtual void update_settings() override; virtual bool show_members_overview() override; - virtual void set_tooltip_request_func(String p_method, Object *p_obj) override; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) override; virtual void set_debugger_active(bool p_active) override; @@ -249,4 +256,51 @@ public: ~ScriptTextEditor(); }; +const int KIND_COUNT = 10; +// The order in which to sort code completion options. +const ScriptLanguage::CodeCompletionKind KIND_SORT_ORDER[KIND_COUNT] = { + ScriptLanguage::CODE_COMPLETION_KIND_VARIABLE, + ScriptLanguage::CODE_COMPLETION_KIND_MEMBER, + ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION, + ScriptLanguage::CODE_COMPLETION_KIND_ENUM, + ScriptLanguage::CODE_COMPLETION_KIND_SIGNAL, + ScriptLanguage::CODE_COMPLETION_KIND_CONSTANT, + ScriptLanguage::CODE_COMPLETION_KIND_CLASS, + ScriptLanguage::CODE_COMPLETION_KIND_NODE_PATH, + ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH, + ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT, +}; + +// The custom comparer which will sort completion options. +struct CodeCompletionOptionCompare { + _FORCE_INLINE_ bool operator()(const ScriptLanguage::CodeCompletionOption &l, const ScriptLanguage::CodeCompletionOption &r) const { + if (l.location == r.location) { + // If locations are same, sort on kind + if (l.kind == r.kind) { + // If kinds are same, sort alphanumeric + return l.display < r.display; + } + + // Sort kinds based on the const sorting array defined above. Lower index = higher priority. + int l_index = -1; + int r_index = -1; + for (int i = 0; i < KIND_COUNT; i++) { + const ScriptLanguage::CodeCompletionKind kind = KIND_SORT_ORDER[i]; + l_index = kind == l.kind ? i : l_index; + r_index = kind == r.kind ? i : r_index; + + if (l_index != -1 && r_index != -1) { + return l_index < r_index; + } + } + + // This return should never be hit unless something goes wrong. + // l and r should always have a Kind which is in the sort order array. + return l.display < r.display; + } + + return l.location < r.location; + } +}; + #endif // SCRIPT_TEXT_EDITOR_H diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 22ca5592bd..d70c50f72a 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,37 +34,151 @@ #include "core/io/resource_saver.h" #include "core/os/keyboard.h" #include "core/os/os.h" +#include "core/version_generated.gen.h" #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/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) { + switch (p_what) { + case NOTIFICATION_THEME_CHANGED: { + if (is_visible_in_tree()) { + _load_theme_settings(); + if (warnings.size() > 0 && last_compile_result == OK) { + warnings_panel->clear(); + _update_warning_panel(); + } + } + } break; + } +} + Ref<Shader> ShaderTextEditor::get_edited_shader() const { return shader; } +Ref<ShaderInclude> ShaderTextEditor::get_edited_shader_include() const { + return shader_inc; +} + void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { + set_edited_shader(p_shader, p_shader->get_code()); +} + +void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader, const String &p_code) { if (shader == p_shader) { return; } + if (shader.is_valid()) { + shader->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } shader = p_shader; + shader_inc = Ref<ShaderInclude>(); + set_edited_code(p_code); + + if (shader.is_valid()) { + shader->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc) { + set_edited_shader_include(p_shader_inc, p_shader_inc->get_code()); +} + +void ShaderTextEditor::_shader_changed() { + // This function is used for dependencies (include changing changes main shader and forces it to revalidate) + if (block_shader_changed) { + return; + } + dependencies_version++; + _validate_script(); +} + +void ShaderTextEditor::set_edited_shader_include(const Ref<ShaderInclude> &p_shader_inc, const String &p_code) { + if (shader_inc == p_shader_inc) { + return; + } + if (shader_inc.is_valid()) { + shader_inc->disconnect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + set_edited_code(p_code); + + if (shader_inc.is_valid()) { + shader_inc->connect(SNAME("changed"), callable_mp(this, &ShaderTextEditor::_shader_changed)); + } +} + +void ShaderTextEditor::set_edited_code(const String &p_code) { _load_theme_settings(); - get_text_editor()->set_text(p_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(); @@ -113,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); @@ -126,26 +241,66 @@ 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()) { - for (const Map<StringName, ShaderLanguage::FunctionInfo>::Element *E = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())).front(); E; E = E->next()) { - for (const Map<StringName, ShaderLanguage::BuiltInInfo>::Element *F = E->get().built_ins.front(); F; F = F->next()) { - built_ins.push_back(F->key()); + + 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); + } + } + + const Vector<ShaderLanguage::ModeInfo> &modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); - for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())).size(); i++) { - built_ins.push_back(ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()))[i]); + for (int i = 0; i < modes.size(); i++) { + const ShaderLanguage::ModeInfo &info = modes[i]; + + if (!info.options.is_empty()) { + for (int j = 0; j < info.options.size(); j++) { + built_ins.push_back(String(info.name) + "_" + String(info.options[j])); + } + } else { + built_ins.push_back(String(info.name)); + } } } - const Color member_variable_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); + const Color user_type_color = EDITOR_GET("text_editor/theme/highlighting/user_type_color"); for (const String &E : built_ins) { - syntax_highlighter->add_keyword_color(E, member_variable_color); + syntax_highlighter->add_keyword_color(E, user_type_color); } // Colorize comments. @@ -153,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); @@ -162,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"))); } @@ -178,75 +338,231 @@ void ShaderTextEditor::_check_shader_mode() { mode = Shader::MODE_CANVAS_ITEM; } else if (type == "particles") { mode = Shader::MODE_PARTICLES; + } else if (type == "sky") { + mode = Shader::MODE_SKY; + } else if (type == "fog") { + mode = Shader::MODE_FOG; } else { mode = Shader::MODE_SPATIAL; } if (shader->get_mode() != mode) { + set_block_shader_changed(true); shader->set_code(get_text_editor()->get_text()); + set_block_shader_changed(false); _load_theme_settings(); } } -static ShaderLanguage::DataType _get_global_variable_type(const StringName &p_variable) { - RS::GlobalVariableType gvt = RS::get_singleton()->global_variable_get_type(p_variable); - return RS::global_variable_type_get_shader_datatype(gvt); +static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { + RS::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 ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options) { - _check_shader_mode(); +static String complete_from_path; + +static void _complete_include_paths_search(EditorFileSystemDirectory *p_efsd, List<ScriptLanguage::CodeCompletionOption> *r_options) { + if (!p_efsd) { + return; + } + for (int i = 0; i < p_efsd->get_file_count(); i++) { + if (p_efsd->get_file_type(i) == SNAME("ShaderInclude")) { + String path = p_efsd->get_file_path(i); + if (path.begins_with(complete_from_path)) { + path = path.replace_first(complete_from_path, ""); + } + r_options->push_back(ScriptLanguage::CodeCompletionOption(path, ScriptLanguage::CODE_COMPLETION_KIND_FILE_PATH)); + } + } + for (int j = 0; j < p_efsd->get_subdir_count(); j++) { + _complete_include_paths_search(p_efsd->get_subdir(j), r_options); + } +} + +static void _complete_include_paths(List<ScriptLanguage::CodeCompletionOption> *r_options) { + _complete_include_paths_search(EditorFileSystem::get_singleton()->get_filesystem(), r_options); +} + +void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) { + List<ScriptLanguage::CodeCompletionOption> pp_options; + 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; - sl.complete(p_code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type, r_options, calltip); + 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(); + + 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 sl; + if (shader.is_valid()) { + _check_shader_mode(); + code = shader->get_code(); + } else { + code = shader_inc->get_code(); + } - sl.enable_warning_checking(saved_warnings_enabled); - sl.set_warning_flags(saved_warning_flags); + 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); - Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type); + for (int i = 0; i < get_text_editor()->get_line_count(); i++) { + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); + } + + syntax_highlighter->clear_disabled_branch_regions(); + for (const ShaderPreprocessor::Region ®ion : regions) { + if (!region.enabled) { + if (filename != region.file) { + continue; + } + syntax_highlighter->add_disabled_branch_region(Point2i(region.from_line, region.to_line)); + } + } + + set_error(""); + set_error_count(0); + + if (last_compile_result != OK) { + //preprocessor error + ERR_FAIL_COND(err_positions.size() == 0); + + String 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); + } - if (err != OK) { - String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); set_error(error_text); - set_error_pos(sl.get_error_line() - 1, 0); + 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 || err != OK) { - warnings_panel->clear(); - } - warnings.clear(); - for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { - warnings.push_back(E->get()); - } - if (warnings.size() > 0 && err == OK) { - warnings.sort_custom<WarningsComparator>(); - _update_warning_panel(); - } else { - set_warning_count(0); + 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() { @@ -266,15 +582,20 @@ void ShaderTextEditor::_update_warning_panel() { } warning_count++; + int line = w.get_line(); // First cell. warnings_panel->push_cell(); - warnings_panel->push_meta(w.get_line() - 1); warnings_panel->push_color(warnings_panel->get_theme_color(SNAME("warning_color"), SNAME("Editor"))); - warnings_panel->add_text(TTR("Line") + " " + itos(w.get_line())); - warnings_panel->add_text(" (" + w.get_name() + "):"); + if (line != -1) { + warnings_panel->push_meta(line - 1); + warnings_panel->add_text(TTR("Line") + " " + itos(line)); + warnings_panel->add_text(" (" + w.get_name() + "):"); + warnings_panel->pop(); // Meta goto. + } else { + warnings_panel->add_text(w.get_name() + ":"); + } warnings_panel->pop(); // Color. - warnings_panel->pop(); // Meta goto. warnings_panel->pop(); // Cell. // Second cell. @@ -288,6 +609,7 @@ void ShaderTextEditor::_update_warning_panel() { } void ShaderTextEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("script_validated", PropertyInfo(Variant::BOOL, "valid"))); } ShaderTextEditor::ShaderTextEditor() { @@ -380,7 +702,7 @@ void ShaderEditor::_menu_option(int p_option) { shader_editor->remove_all_bookmarks(); } break; case HELP_DOCS: { - OS::get_singleton()->shell_open("https://docs.godotengine.org/en/latest/tutorials/shaders/shader_reference/index.html"); + OS::get_singleton()->shell_open(vformat("%s/tutorials/shaders/shader_reference/index.html", VERSION_DOCS_URL)); } break; } if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { @@ -389,8 +711,16 @@ void ShaderEditor::_menu_option(int p_option) { } void ShaderEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN) { - _check_for_external_edit(); + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + PopupMenu *popup = help_menu->get_popup(); + popup->set_item_icon(popup->get_item_index(HELP_DOCS), get_theme_icon(SNAME("ExternalLink"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { + _check_for_external_edit(); + } break; } } @@ -415,17 +745,11 @@ 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() { - /* - if (tab_container->get_child_count() && tab_container->get_current_tab()>=0) { - ShaderTextEditor *ste = Object::cast_to<ShaderTextEditor>(tab_container->get_child(tab_container->get_current_tab())); - if (!ste) - return; - Ref<Shader> shader = ste->get_edited_shader(); - get_scene()->get_root_node()->call("_resource_selected",shader); - }*/ } void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) { @@ -474,16 +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; } - // internal shader. - if (shader->get_path() == "" || shader->get_path().find("local://") != -1 || shader->get_path().find("::") != -1) { + if (shader.is_null() || shader->is_built_in()) { return; } - bool use_autoreload = bool(EDITOR_DEF("text_editor/behavior/files/auto_reload_scripts_on_external_change", false)); if (shader->get_last_modified_time() != FileAccess::get_modified_time(shader->get_path())) { if (use_autoreload) { _reload_shader_from_disk(); @@ -497,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; @@ -512,44 +864,91 @@ void ShaderEditor::edit(const Ref<Shader> &p_shader) { } shader = p_shader; + shader_inc = Ref<ShaderInclude>(); - shader_editor->set_edited_shader(p_shader); + shader_editor->set_edited_shader(shader); +} - //vertex_editor->set_edited_shader(shader,ShaderLanguage::SHADER_MATERIAL_VERTEX); - // see if already has it +void ShaderEditor::edit(const Ref<ShaderInclude> &p_shader_inc) { + if (p_shader_inc.is_null()) { + return; + } + + if (shader_inc == p_shader_inc) { + return; + } + + shader_inc = p_shader_inc; + shader = Ref<Shader>(); + + shader_editor->set_edited_shader_include(p_shader_inc); } void 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->get_path() != "" && shader->get_path().find("local://") == -1 && shader->get_path().find("::") == -1) { - //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) { Ref<InputEventMouseButton> mb = ev; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { CodeEdit *tx = shader_editor->get_text_editor(); Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); @@ -610,7 +1009,7 @@ void ShaderEditor::_update_bookmark_list() { } bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); - bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); + bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } @@ -641,12 +1040,12 @@ void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) { context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); context_menu->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_bookmark"), BOOKMARK_TOGGLE); - context_menu->set_position(get_global_transform().xform(p_position)); - context_menu->set_size(Vector2(1, 1)); + context_menu->set_position(get_screen_position() + p_position); + context_menu->reset_size(); context_menu->popup(); } -ShaderEditor::ShaderEditor(EditorNode *p_node) { +ShaderEditor::ShaderEditor() { GLOBAL_DEF("debug/shader_language/warnings/enable", true); GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false); for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) { @@ -655,9 +1054,12 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { _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)); @@ -735,7 +1137,7 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { help_menu = memnew(MenuButton); help_menu->set_text(TTR("Help")); help_menu->set_switch_on_hover(true); - help_menu->get_popup()->add_icon_item(p_node->get_gui_base()->get_theme_icon(SNAME("Instance"), SNAME("EditorIcons")), TTR("Online Docs"), HELP_DOCS); + help_menu->get_popup()->add_item(TTR("Online Docs"), HELP_DOCS); help_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); add_child(main_container); @@ -744,11 +1146,11 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { hbc->add_child(edit_menu); hbc->add_child(goto_menu); hbc->add_child(help_menu); - hbc->add_theme_style_override("panel", p_node->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); + hbc->add_theme_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_theme_stylebox(SNAME("ScriptEditorPanel"), SNAME("EditorStyles"))); VSplitContainer *editor_box = memnew(VSplitContainer); main_container->add_child(editor_box); - editor_box->set_anchors_and_offsets_preset(Control::PRESET_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); @@ -780,8 +1182,8 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { 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)); @@ -791,51 +1193,317 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { _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(); - editor->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()) { - editor->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. } -ShaderEditorPlugin::ShaderEditorPlugin(EditorNode *p_node) { - editor = p_node; - shader_editor = memnew(ShaderEditor(p_node)); +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() { + 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 = editor->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 77579754d3..0980cc4db2 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -40,7 +40,28 @@ #include "scene/gui/text_edit.h" #include "scene/main/timer.h" #include "scene/resources/shader.h" -#include "servers/rendering/shader_language.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,28 +72,46 @@ 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(); virtual void _load_theme_settings() override; - virtual void _code_complete_script(const String &p_code, List<ScriptCodeCompletionOption> *r_options) override; + virtual void _code_complete_script(const String &p_code, List<ScriptLanguage::CodeCompletionOption> *r_options) override; public: + void set_block_shader_changed(bool p_block) { block_shader_changed = p_block; } + uint32_t get_dependencies_version() const { return dependencies_version; } + virtual void _validate_script() override; void reload_text(); void set_warnings_panel(RichTextLabel *p_warnings_panel); Ref<Shader> get_edited_shader() const; + Ref<ShaderInclude> get_edited_shader_include() const; + void set_edited_shader(const Ref<Shader> &p_shader); + void set_edited_shader(const Ref<Shader> &p_shader, const String &p_code); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include); + void set_edited_shader_include(const Ref<ShaderInclude> &p_include, const String &p_code); + void set_edited_code(const String &p_code); + ShaderTextEditor(); }; @@ -106,78 +145,126 @@ class ShaderEditor : public PanelContainer { HELP_DOCS, }; - MenuButton *edit_menu; - MenuButton *search_menu; - PopupMenu *bookmarks_menu; - MenuButton *help_menu; - PopupMenu *context_menu; + MenuButton *edit_menu = nullptr; + MenuButton *search_menu = nullptr; + PopupMenu *bookmarks_menu = nullptr; + MenuButton *help_menu = nullptr; + PopupMenu *context_menu = nullptr; RichTextLabel *warnings_panel = nullptr; - uint64_t idle; + uint64_t idle = 0; - GotoLineDialog *goto_line_dialog; - ConfirmationDialog *erase_tab_confirm; - ConfirmationDialog *disk_changed; + GotoLineDialog *goto_line_dialog = nullptr; + ConfirmationDialog *erase_tab_confirm = nullptr; + ConfirmationDialog *disk_changed = nullptr; - ShaderTextEditor *shader_editor; + 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(EditorNode *p_node); + ShaderEditor(); }; class ShaderEditorPlugin : public EditorPlugin { GDCLASS(ShaderEditorPlugin, EditorPlugin); - bool _2d; - ShaderEditor *shader_editor; - EditorNode *editor; - Button *button; + 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; - ShaderEditorPlugin(EditorNode *p_node); + ShaderEditorPlugin(); ~ShaderEditorPlugin(); }; -#endif +#endif // SHADER_EDITOR_PLUGIN_H diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp index 1e62261244..4874944d33 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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" @@ -97,7 +96,7 @@ void ShaderFileEditor::_version_selected(int p_option) { error_text->push_font(get_theme_font(SNAME("source"), SNAME("EditorFonts"))); - if (error == String()) { + if (error.is_empty()) { error_text->add_text(TTR("Shader stage compiled without errors.")); } else { error_text->add_text(error); @@ -107,7 +106,7 @@ void ShaderFileEditor::_version_selected(int p_option) { void ShaderFileEditor::_update_options() { ERR_FAIL_COND(shader_file.is_null()); - if (shader_file->get_base_error() != String()) { + if (!shader_file->get_base_error().is_empty()) { stage_hb->hide(); versions->hide(); error_text->clear(); @@ -136,7 +135,7 @@ void ShaderFileEditor::_update_options() { for (int i = 0; i < version_list.size(); i++) { String title = version_list[i]; - if (title == "") { + if (title.is_empty()) { title = "default"; } @@ -148,7 +147,7 @@ void ShaderFileEditor::_update_options() { bool failed = false; for (int j = 0; j < RD::SHADER_STAGE_MAX; j++) { String error = bytecode->get_stage_compile_error(RD::ShaderStage(j)); - if (error != String()) { + if (!error.is_empty()) { failed = true; } } @@ -182,7 +181,7 @@ void ShaderFileEditor::_update_options() { for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) { Vector<uint8_t> bc = bytecode->get_stage_bytecode(RD::ShaderStage(i)); String error = bytecode->get_stage_compile_error(RD::ShaderStage(i)); - bool disable = error == String() && bc.is_empty(); + bool disable = error.is_empty() && bc.is_empty(); stages[i]->set_disabled(disable); if (!disable) { if (stages[i]->is_pressed()) { @@ -200,10 +199,12 @@ void ShaderFileEditor::_update_options() { } void ShaderFileEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_WM_WINDOW_FOCUS_IN) { - if (is_visible_in_tree() && shader_file.is_valid()) { - _update_options(); - } + switch (p_what) { + case NOTIFICATION_WM_WINDOW_FOCUS_IN: { + if (is_visible_in_tree() && shader_file.is_valid()) { + _update_options(); + } + } break; } } @@ -245,7 +246,7 @@ void ShaderFileEditor::_shader_changed() { ShaderFileEditor *ShaderFileEditor::singleton = nullptr; -ShaderFileEditor::ShaderFileEditor(EditorNode *p_node) { +ShaderFileEditor::ShaderFileEditor() { singleton = this; HSplitContainer *main_hs = memnew(HSplitContainer); @@ -280,7 +281,7 @@ ShaderFileEditor::ShaderFileEditor(EditorNode *p_node) { stage_hb->add_child(button); stages[i] = button; button->set_button_group(bg); - button->connect("pressed", callable_mp(this, &ShaderFileEditor::_version_selected), varray(i)); + button->connect("pressed", callable_mp(this, &ShaderFileEditor::_version_selected).bind(i)); } error_text = memnew(RichTextLabel); @@ -301,22 +302,21 @@ bool ShaderFileEditorPlugin::handles(Object *p_object) const { void ShaderFileEditorPlugin::make_visible(bool p_visible) { if (p_visible) { button->show(); - editor->make_bottom_panel_item_visible(shader_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(shader_editor); } else { button->hide(); if (shader_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } } } -ShaderFileEditorPlugin::ShaderFileEditorPlugin(EditorNode *p_node) { - editor = p_node; - shader_editor = memnew(ShaderFileEditor(p_node)); +ShaderFileEditorPlugin::ShaderFileEditorPlugin() { + shader_editor = memnew(ShaderFileEditor); shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("ShaderFile"), shader_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("ShaderFile"), shader_editor); button->hide(); } diff --git a/editor/plugins/shader_file_editor_plugin.h b/editor/plugins/shader_file_editor_plugin.h index 7d6e503b6c..1ebd644282 100644 --- a/editor/plugins/shader_file_editor_plugin.h +++ b/editor/plugins/shader_file_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -41,15 +41,17 @@ #include "scene/main/timer.h" #include "servers/rendering/rendering_device_binds.h" +class ItemList; + class ShaderFileEditor : public PanelContainer { GDCLASS(ShaderFileEditor, PanelContainer); Ref<RDShaderFile> shader_file; - HBoxContainer *stage_hb; - ItemList *versions; + HBoxContainer *stage_hb = nullptr; + ItemList *versions = nullptr; Button *stages[RD::SHADER_STAGE_MAX]; - RichTextLabel *error_text; + RichTextLabel *error_text = nullptr; void _update_version(const StringName &p_version_txt, const RenderingDevice::ShaderStage p_stage); void _version_selected(int p_stage); @@ -66,15 +68,14 @@ public: static ShaderFileEditor *singleton; void edit(const Ref<RDShaderFile> &p_shader); - ShaderFileEditor(EditorNode *p_node); + ShaderFileEditor(); }; class ShaderFileEditorPlugin : public EditorPlugin { GDCLASS(ShaderFileEditorPlugin, EditorPlugin); - ShaderFileEditor *shader_editor; - EditorNode *editor; - Button *button; + ShaderFileEditor *shader_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "ShaderFile"; } @@ -85,7 +86,7 @@ public: ShaderFileEditor *get_shader_editor() const { return shader_editor; } - ShaderFileEditorPlugin(EditorNode *p_node); + ShaderFileEditorPlugin(); ~ShaderFileEditorPlugin(); }; diff --git a/editor/plugins/skeleton_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp index 7ef680d7ef..5a1505c232 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.cpp +++ b/editor/plugins/skeleton_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,6 +31,7 @@ #include "skeleton_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/gui/box_container.h" #include "thirdparty/misc/clipper.hpp" @@ -52,34 +53,34 @@ void Skeleton2DEditor::_menu_option(int p_option) { } switch (p_option) { - case MENU_OPTION_MAKE_REST: { + case MENU_OPTION_SET_REST: { if (node->get_bone_count() == 0) { err_dialog->set_text(TTR("This skeleton has no bones, create some children Bone2D nodes.")); err_dialog->popup_centered(); return; } UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Create Rest Pose from Bones")); + 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); - ur->add_do_method(bone, "set_rest", bone->get_transform()); - ur->add_undo_method(bone, "set_rest", bone->get_rest()); + ur->add_do_method(bone, "set_transform", bone->get_rest()); + ur->add_undo_method(bone, "set_transform", bone->get_transform()); } ur->commit_action(); } break; - case MENU_OPTION_SET_REST: { + case MENU_OPTION_MAKE_REST: { if (node->get_bone_count() == 0) { err_dialog->set_text(TTR("This skeleton has no bones, create some children Bone2D nodes.")); err_dialog->popup_centered(); return; } UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Set Rest Pose to Bones")); + 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); - ur->add_do_method(bone, "set_transform", bone->get_rest()); - ur->add_undo_method(bone, "set_transform", bone->get_transform()); + ur->add_do_method(bone, "set_rest", bone->get_transform()); + ur->add_undo_method(bone, "set_rest", bone->get_rest()); } ur->commit_action(); @@ -98,9 +99,10 @@ Skeleton2DEditor::Skeleton2DEditor() { options->set_text(TTR("Skeleton2D")); options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton2D"), SNAME("EditorIcons"))); - options->get_popup()->add_item(TTR("Make Rest Pose (From Bones)"), MENU_OPTION_MAKE_REST); + options->get_popup()->add_item(TTR("Reset to Rest Pose"), MENU_OPTION_SET_REST); options->get_popup()->add_separator(); - options->get_popup()->add_item(TTR("Set Bones to Rest Pose"), MENU_OPTION_SET_REST); + // Use the "Overwrite" word to highlight that this is a destructive operation. + options->get_popup()->add_item(TTR("Overwrite Rest Pose"), MENU_OPTION_MAKE_REST); options->set_switch_on_hover(true); options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton2DEditor::_menu_option)); @@ -126,10 +128,9 @@ void Skeleton2DEditorPlugin::make_visible(bool p_visible) { } } -Skeleton2DEditorPlugin::Skeleton2DEditorPlugin(EditorNode *p_node) { - editor = p_node; +Skeleton2DEditorPlugin::Skeleton2DEditorPlugin() { sprite_editor = memnew(Skeleton2DEditor); - editor->get_main_control()->add_child(sprite_editor); + EditorNode::get_singleton()->get_main_control()->add_child(sprite_editor); make_visible(false); //sprite_editor->options->hide(); diff --git a/editor/plugins/skeleton_2d_editor_plugin.h b/editor/plugins/skeleton_2d_editor_plugin.h index dacd8fe43f..295725b751 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.h +++ b/editor/plugins/skeleton_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef SKELETON_2D_EDITOR_PLUGIN_H #define SKELETON_2D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/skeleton_2d.h" #include "scene/gui/spin_box.h" @@ -40,14 +39,14 @@ class Skeleton2DEditor : public Control { GDCLASS(Skeleton2DEditor, Control); enum Menu { - MENU_OPTION_MAKE_REST, MENU_OPTION_SET_REST, + MENU_OPTION_MAKE_REST, }; - Skeleton2D *node; + Skeleton2D *node = nullptr; - MenuButton *options; - AcceptDialog *err_dialog; + MenuButton *options = nullptr; + AcceptDialog *err_dialog = nullptr; void _menu_option(int p_option); @@ -66,8 +65,7 @@ public: class Skeleton2DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton2DEditorPlugin, EditorPlugin); - Skeleton2DEditor *sprite_editor; - EditorNode *editor; + Skeleton2DEditor *sprite_editor = nullptr; public: virtual String get_name() const override { return "Skeleton2D"; } @@ -76,7 +74,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Skeleton2DEditorPlugin(EditorNode *p_node); + Skeleton2DEditorPlugin(); ~Skeleton2DEditorPlugin(); }; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 309821b3dc..c453ed26aa 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,309 +32,337 @@ #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/plugins/animation_player_editor_plugin.h" #include "node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" +#include "scene/3d/joint_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" -#include "scene/3d/physics_joint_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" void BoneTransformEditor::create_editors() { const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); section = memnew(EditorInspectorSection); section->setup("trf_properties", label, this, section_color, true); + section->unfold(); add_child(section); - key_button = memnew(Button); - key_button->set_text(TTR("Key Transform")); - key_button->set_visible(keyable); - key_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); - key_button->set_flat(true); - section->get_vbox()->add_child(key_button); - - enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled"))); - enabled_checkbox->set_flat(true); - enabled_checkbox->set_visible(toggle_enabled); + enabled_checkbox = memnew(EditorPropertyCheck()); + enabled_checkbox->set_label("Pose Enabled"); + enabled_checkbox->set_selectable(false); + enabled_checkbox->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(enabled_checkbox); - // Translation property - translation_property = memnew(EditorPropertyVector3()); - translation_property->setup(-10000, 10000, 0.001f, true); - translation_property->set_label("Translation"); - translation_property->set_use_folding(true); - translation_property->set_read_only(false); - translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); - section->get_vbox()->add_child(translation_property); - - // Rotation property - rotation_property = memnew(EditorPropertyVector3()); + // Position property. + position_property = memnew(EditorPropertyVector3()); + position_property->setup(-10000, 10000, 0.001f, true); + position_property->set_label("Position"); + position_property->set_selectable(false); + position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); + position_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed)); + section->get_vbox()->add_child(position_property); + + // Rotation property. + rotation_property = memnew(EditorPropertyQuaternion()); rotation_property->setup(-10000, 10000, 0.001f, true); - rotation_property->set_label("Rotation Degrees"); - rotation_property->set_use_folding(true); - rotation_property->set_read_only(false); - rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + rotation_property->set_label("Rotation"); + rotation_property->set_selectable(false); + rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); + rotation_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed)); section->get_vbox()->add_child(rotation_property); - // Scale property + // Scale property. scale_property = memnew(EditorPropertyVector3()); scale_property->setup(-10000, 10000, 0.001f, true); scale_property->set_label("Scale"); - scale_property->set_use_folding(true); - scale_property->set_read_only(false); - scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + scale_property->set_selectable(false); + scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); + scale_property->connect("property_keyed", callable_mp(this, &BoneTransformEditor::_property_keyed)); section->get_vbox()->add_child(scale_property); - // Transform/Matrix section - transform_section = memnew(EditorInspectorSection); - transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true); - section->get_vbox()->add_child(transform_section); - - // Transform/Matrix property - transform_property = memnew(EditorPropertyTransform3D()); - transform_property->setup(-10000, 10000, 0.001f, true); - transform_property->set_label("Transform"); - transform_property->set_use_folding(true); - transform_property->set_read_only(false); - transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform)); - transform_section->get_vbox()->add_child(transform_property); + // Transform/Matrix section. + rest_section = memnew(EditorInspectorSection); + rest_section->setup("trf_properties_transform", "Rest", this, section_color, true); + section->get_vbox()->add_child(rest_section); + + // Transform/Matrix property. + rest_matrix = memnew(EditorPropertyTransform3D()); + rest_matrix->setup(-10000, 10000, 0.001f, true); + rest_matrix->set_label("Transform"); + rest_matrix->set_selectable(false); + rest_section->get_vbox()->add_child(rest_matrix); } void BoneTransformEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { create_editors(); - key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed)); - enabled_checkbox->connect("toggled", callable_mp(this, &BoneTransformEditor::_checkbox_toggled)); - [[fallthrough]]; - } - case NOTIFICATION_SORT_CHILDREN: { - const Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); - - Point2 buffer; - buffer.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - buffer.y += font->get_height(font_size); - buffer.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - - const float vector_height = translation_property->get_size().y; - const float transform_height = transform_property->get_size().y; - const float button_height = key_button->get_size().y; - - const float width = get_size().x - get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - Vector<Rect2> input_rects; - if (keyable && section->get_vbox()->is_visible()) { - input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height))); - } else { - input_rects.push_back(Rect2(0, 0, 0, 0)); - } - - if (section->get_vbox()->is_visible()) { - input_rects.push_back(Rect2(translation_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(rotation_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(scale_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(transform_property->get_position() + buffer, Size2(width, transform_height))); - } else { - const int32_t start = input_rects.size(); - const int32_t empty_input_rect_elements = 4; - const int32_t end = start + empty_input_rect_elements; - for (int i = start; i < end; ++i) { - input_rects.push_back(Rect2(0, 0, 0, 0)); - } - } - - for (int32_t i = 0; i < input_rects.size(); i++) { - background_rects[i] = input_rects[i]; - } - - update(); - break; - } - case NOTIFICATION_DRAW: { - const Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); - - for (int i = 0; i < 5; ++i) { - draw_rect(background_rects[i], dark_color); - } - - break; - } + } break; } } -void BoneTransformEditor::_value_changed(const double p_value) { +void BoneTransformEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { if (updating) { return; } + if (skeleton) { + undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property)); + undo_redo->add_do_property(skeleton, p_property, p_value); + undo_redo->commit_action(); + } +} - Transform3D tform = compute_transform_from_vector3s(); - _change_transform(tform); +BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : + skeleton(p_skeleton) { + undo_redo = EditorNode::get_undo_redo(); } -void BoneTransformEditor::_value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) { - return; - } - Transform3D tform = compute_transform_from_vector3s(); - _change_transform(tform); +void BoneTransformEditor::set_keyable(const bool p_keyable) { + position_property->set_keying(p_keyable); + rotation_property->set_keying(p_keyable); + scale_property->set_keying(p_keyable); } -Transform3D BoneTransformEditor::compute_transform_from_vector3s() const { - // Convert rotation from degrees to radians. - Vector3 prop_rotation = rotation_property->get_vector(); - prop_rotation.x = Math::deg2rad(prop_rotation.x); - prop_rotation.y = Math::deg2rad(prop_rotation.y); - prop_rotation.z = Math::deg2rad(prop_rotation.z); +void BoneTransformEditor::set_target(const String &p_prop) { + enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled"); + enabled_checkbox->update_property(); - return Transform3D( - Basis(prop_rotation, scale_property->get_vector()), - translation_property->get_vector()); -} + position_property->set_object_and_property(skeleton, p_prop + "position"); + position_property->update_property(); -void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) { - return; - } - _change_transform(p_transform); -} + rotation_property->set_object_and_property(skeleton, p_prop + "rotation"); + rotation_property->update_property(); -void BoneTransformEditor::_change_transform(Transform3D p_new_transform) { - if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { - undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS); - undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int())); - undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), p_new_transform); - undo_redo->commit_action(); - } else if (property.get_slicec('/', 0) == "bones") { - undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); - undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); - undo_redo->add_do_property(skeleton, property, p_new_transform); - undo_redo->commit_action(); - } -} + scale_property->set_object_and_property(skeleton, p_prop + "scale"); + scale_property->update_property(); -void BoneTransformEditor::update_enabled_checkbox() { - if (enabled_checkbox) { - const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; - const bool is_enabled = skeleton->get(path); - enabled_checkbox->set_pressed(is_enabled); - } + rest_matrix->set_object_and_property(skeleton, p_prop + "rest"); + rest_matrix->update_property(); } -void BoneTransformEditor::_update_properties() { - if (updating) { +void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) { + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + if (!te->has_keying()) { return; } - - if (skeleton == nullptr) { - return; + PackedStringArray split = p_path.split("/"); + if (split.size() == 3 && split[0] == "bones") { + int bone_idx = split[1].to_int(); + if (split[2] == "position") { + te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, (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)); + } + if (split[2] == "scale") { + te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_SCALE_3D, skeleton->get(p_path)); + } } - - updating = true; - - Transform3D tform = skeleton->get(property); - _update_transform_properties(tform); } -void BoneTransformEditor::_update_custom_pose_properties() { - if (updating) { +void BoneTransformEditor::_update_properties() { + if (!skeleton) { return; } - - if (skeleton == nullptr) { - return; + int selected = Skeleton3DEditor::get_singleton()->get_selected_bone(); + List<PropertyInfo> props; + skeleton->get_property_list(&props); + for (const PropertyInfo &E : props) { + PackedStringArray split = E.name.split("/"); + if (split.size() == 3 && split[0] == "bones") { + if (split[1].to_int() == selected) { + if (split[2] == "enabled") { + enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + enabled_checkbox->update_property(); + enabled_checkbox->update(); + } + if (split[2] == "position") { + position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + position_property->update_property(); + position_property->update(); + } + if (split[2] == "rotation") { + rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rotation_property->update_property(); + rotation_property->update(); + } + if (split[2] == "scale") { + scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + scale_property->update_property(); + scale_property->update(); + } + if (split[2] == "rest") { + rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rest_matrix->update_property(); + rest_matrix->update(); + } + } + } } - - updating = true; - - Transform3D tform = skeleton->get_bone_custom_pose(property.to_int()); - _update_transform_properties(tform); } -void BoneTransformEditor::_update_transform_properties(Transform3D tform) { - Basis rotation_basis = tform.get_basis(); - Vector3 rotation_radians = rotation_basis.get_rotation_euler(); - Vector3 rotation_degrees = Vector3(Math::rad2deg(rotation_radians.x), Math::rad2deg(rotation_radians.y), Math::rad2deg(rotation_radians.z)); - Vector3 translation = tform.get_origin(); - Vector3 scale = tform.basis.get_scale(); +Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr; - translation_property->update_using_vector(translation); - rotation_property->update_using_vector(rotation_degrees); - scale_property->update_using_vector(scale); - transform_property->update_using_transform(tform); +void Skeleton3DEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + if (p_keyable) { + animation_hb->show(); + } else { + animation_hb->hide(); + } +}; - update_enabled_checkbox(); - updating = false; -} +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_SELECTED_POSES_TO_RESTS, !p_bone_options_enabled); +}; -BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : - skeleton(p_skeleton) { - undo_redo = EditorNode::get_undo_redo(); -} +void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { + if (!skeleton) { + return; + } -void BoneTransformEditor::set_target(const String &p_prop) { - property = p_prop; + switch (p_skeleton_option) { + case SKELETON_OPTION_INIT_ALL_POSES: { + init_pose(true); + break; + } + case SKELETON_OPTION_INIT_SELECTED_POSES: { + init_pose(false); + break; + } + case SKELETON_OPTION_ALL_POSES_TO_RESTS: { + pose_to_rest(true); + break; + } + case SKELETON_OPTION_SELECTED_POSES_TO_RESTS: { + pose_to_rest(false); + break; + } + case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: { + create_physical_skeleton(); + break; + } + case SKELETON_OPTION_EXPORT_SKELETON_PROFILE: { + export_skeleton_profile(); + break; + } + } } -void BoneTransformEditor::set_keyable(const bool p_keyable) { - keyable = p_keyable; - if (key_button) { - key_button->set_visible(p_keyable); +void Skeleton3DEditor::init_pose(const bool p_all_bones) { + if (!skeleton) { + return; + } + const int bone_len = skeleton->get_bone_count(); + if (!bone_len) { + return; } -} -void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { - toggle_enabled = p_enabled; - if (enabled_checkbox) { - enabled_checkbox->set_visible(p_enabled); + UndoRedo *ur = EditorNode::get_singleton()->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)); + } + } 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->commit_action(); } -void BoneTransformEditor::_key_button_pressed() { - if (skeleton == nullptr) { +void Skeleton3DEditor::insert_keys(const bool p_all_bones) { + if (!skeleton) { return; } - const BoneId bone_id = property.get_slicec('/', 1).to_int(); - const String name = skeleton->get_bone_name(bone_id); + bool pos_enabled = key_loc_button->is_pressed(); + bool rot_enabled = key_rot_button->is_pressed(); + bool scl_enabled = key_scale_button->is_pressed(); - if (name.is_empty()) { - return; - } + int bone_len = skeleton->get_bone_count(); + Node *root = EditorNode::get_singleton()->get_tree()->get_root(); + String path = root->get_path_to(skeleton); - // Need to normalize the basis before you key it - Transform3D tform = compute_transform_from_vector3s(); - tform.orthonormalize(); - AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); -} + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + te->make_insert_queue(); + for (int i = 0; i < bone_len; i++) { + const String name = skeleton->get_bone_name(i); -void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) { - if (enabled_checkbox) { - const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; - skeleton->set(path, p_toggled); + if (name.is_empty()) { + continue; + } + + 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) / 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)); + } + if (scl_enabled && (p_all_bones || te->has_track(skeleton, name, Animation::TYPE_SCALE_3D))) { + te->insert_transform_key(skeleton, name, Animation::TYPE_SCALE_3D, skeleton->get_bone_pose_scale(i)); + } } + te->commit_insert_queue(); } -void Skeleton3DEditor::_on_click_option(int p_option) { +void Skeleton3DEditor::pose_to_rest(const bool p_all_bones) { if (!skeleton) { return; } + const int bone_len = skeleton->get_bone_count(); + if (!bone_len) { + return; + } - switch (p_option) { - case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { - create_physical_skeleton(); - break; + UndoRedo *ur = EditorNode::get_singleton()->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++) { + ur->add_do_method(skeleton, "set_bone_rest", i, skeleton->get_bone_pose(i)); + ur->add_undo_method(skeleton, "set_bone_rest", i, skeleton->get_bone_rest(i)); + } + } else { + // Todo: Do method with multiple bone selection. + if (selected_bone == -1) { + ur->commit_action(); + return; } + ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone)); + ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone)); } + ur->commit_action(); } void Skeleton3DEditor::create_physical_skeleton() { UndoRedo *ur = EditorNode::get_singleton()->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(); @@ -345,37 +373,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); - - 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); + 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); - ur->create_action(TTR("Create physical bones")); - ur->add_do_method(skeleton, "add_child", bones_infos[parent].physical_bone); - ur->add_do_reference(bones_infos[parent].physical_bone); - ur->add_undo_method(skeleton, "remove_child", bones_infos[parent].physical_bone); - ur->commit_action(); - - 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 - - /// Create joint between parent of parent - if (-1 != parent_parent) { - bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN); + 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); + } + + ur->add_do_method(Node3DEditor::get_singleton(), "_request_gizmo", physical_bone); + ur->add_do_method(Node3DEditor::get_singleton(), "_request_gizmo", collision_shape); + + ur->add_do_reference(physical_bone); + ur->add_undo_method(skeleton, "remove_child", physical_bone); + } + } } } } + ur->commit_action(); } } @@ -391,13 +429,20 @@ 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)); bone_shape->set_transform(capsule_transform); + /// Get an up vector not collinear with child rest origin + Vector3 up = Vector3(0, 1, 0); + if (up.cross(child_rest.origin).is_equal_approx(Vector3())) { + up = Vector3(0, 0, 1); + } + Transform3D body_transform; - body_transform.basis = Basis::looking_at(child_rest.origin); + body_transform.basis = Basis::looking_at(child_rest.origin, up); body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height)); Transform3D joint_transform; @@ -411,6 +456,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(); @@ -483,7 +595,7 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se ERR_FAIL_NULL(skeleton); UndoRedo *ur = EditorNode::get_singleton()->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 the target is a child of ourselves, we move only *us* and not our children. if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx); const int bone_count = skeleton->get_bone_count(); @@ -505,49 +617,50 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se void Skeleton3DEditor::_joint_tree_selection_changed() { TreeItem *selected = joint_tree->get_selected(); - const String path = selected->get_metadata(0); - - if (path.begins_with("bones/")) { + if (selected) { + const String path = selected->get_metadata(0); + if (!path.begins_with("bones/")) { + return; + } const int b_idx = path.get_slicec('/', 1).to_int(); - const String bone_path = "bones/" + itos(b_idx) + "/"; - - pose_editor->set_target(bone_path + "pose"); - rest_editor->set_target(bone_path + "rest"); - custom_pose_editor->set_target(bone_path + "custom_pose"); - - _update_properties(); + selected_bone = b_idx; + if (pose_editor) { + const String bone_path = "bones/" + itos(b_idx) + "/"; + pose_editor->set_target(bone_path); + pose_editor->set_keyable(keyable); + } + } - pose_editor->set_visible(true); - rest_editor->set_visible(true); - custom_pose_editor->set_visible(true); + if (pose_editor && pose_editor->is_inside_tree()) { + pose_editor->set_visible(selected); } + set_bone_options_enabled(selected); + + _update_properties(); + _update_gizmo_visible(); } -void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { +// May be not used with single select mode. +void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button) { } void Skeleton3DEditor::_update_properties() { - if (rest_editor) { - rest_editor->_update_properties(); - } if (pose_editor) { pose_editor->_update_properties(); } - if (custom_pose_editor) { - custom_pose_editor->_update_custom_pose_properties(); - } + Node3DEditor::get_singleton()->update_transform_gizmo(); } void Skeleton3DEditor::update_joint_tree() { joint_tree->clear(); - if (skeleton == nullptr) { + if (!skeleton) { return; } TreeItem *root = joint_tree->create_item(); - Map<int, TreeItem *> items; + HashMap<int, TreeItem *> items; items.insert(-1, root); @@ -559,7 +672,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); @@ -569,7 +682,7 @@ void Skeleton3DEditor::update_joint_tree() { joint_item->set_selectable(0, true); joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); - // Add the bone's children to the list of bones to be processed + // Add the bone's children to the list of bones to be processed. Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); int child_bone_size = current_bone_child_bones.size(); for (int i = 0; i < child_bone_size; i++) { @@ -587,17 +700,102 @@ void Skeleton3DEditor::create_editors() { set_focus_mode(FOCUS_ALL); - // Create Top Menu Bar - options = memnew(MenuButton); - Node3DEditor::get_singleton()->add_control_to_menu_panel(options); - - options->set_text(TTR("Skeleton3D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); - - options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); - - options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option)); + 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); + + // Create Skeleton Option in Top Menu Bar. + skeleton_options = memnew(MenuButton); + ne->add_control_to_menu_panel(skeleton_options); + + skeleton_options->set_text(TTR("Skeleton3D")); + skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + + // Skeleton options. + PopupMenu *p = skeleton_options->get_popup(); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/init_all_poses", TTR("Init all Poses")), SKELETON_OPTION_INIT_ALL_POSES); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/init_selected_poses", TTR("Init selected Poses")), SKELETON_OPTION_INIT_SELECTED_POSES); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/all_poses_to_rests", TTR("Apply all poses to rests")), SKELETON_OPTION_ALL_POSES_TO_RESTS); + p->add_shortcut(ED_SHORTCUT("skeleton_3d_editor/selected_poses_to_rests", TTR("Apply selected poses to rests")), SKELETON_OPTION_SELECTED_POSES_TO_RESTS); + p->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); + p->add_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); + + Vector<Variant> button_binds; + button_binds.resize(1); + + edit_mode_button = memnew(Button); + ne->add_control_to_menu_panel(edit_mode_button); + edit_mode_button->set_flat(true); + edit_mode_button->set_toggle_mode(true); + edit_mode_button->set_focus_mode(FOCUS_NONE); + edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints.")); + edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); + + edit_mode = false; + + if (skeleton) { + skeleton->add_child(handles_mesh_instance); + handles_mesh_instance->set_skeleton_path(NodePath("")); + } + // Keying buttons. + animation_hb = memnew(HBoxContainer); + ne->add_control_to_menu_panel(animation_hb); + animation_hb->add_child(memnew(VSeparator)); + animation_hb->hide(); + + key_loc_button = memnew(Button); + key_loc_button->set_flat(true); + key_loc_button->set_toggle_mode(true); + key_loc_button->set_pressed(false); + key_loc_button->set_focus_mode(FOCUS_NONE); + key_loc_button->set_tooltip(TTR("Translation mask for inserting keys.")); + animation_hb->add_child(key_loc_button); + + key_rot_button = memnew(Button); + key_rot_button->set_flat(true); + key_rot_button->set_toggle_mode(true); + key_rot_button->set_pressed(true); + key_rot_button->set_focus_mode(FOCUS_NONE); + key_rot_button->set_tooltip(TTR("Rotation mask for inserting keys.")); + animation_hb->add_child(key_rot_button); + + key_scale_button = memnew(Button); + key_scale_button->set_flat(true); + key_scale_button->set_toggle_mode(true); + key_scale_button->set_pressed(false); + key_scale_button->set_focus_mode(FOCUS_NONE); + 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, &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); + + 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).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); + + // Bone tree. const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); EditorInspectorSection *bones_section = memnew(EditorInspectorSection); @@ -612,7 +810,7 @@ void Skeleton3DEditor::create_editors() { joint_tree = memnew(Tree); joint_tree->set_columns(1); - joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE); + joint_tree->set_focus_mode(Control::FOCUS_NONE); joint_tree->set_select_mode(Tree::SELECT_SINGLE); joint_tree->set_hide_root(true); joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); @@ -622,37 +820,37 @@ void Skeleton3DEditor::create_editors() { s_con->add_child(joint_tree); pose_editor = memnew(BoneTransformEditor(skeleton)); - pose_editor->set_label(TTR("Bone Pose")); - pose_editor->set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying()); - pose_editor->set_toggle_enabled(true); + pose_editor->set_label(TTR("Bone Transform")); pose_editor->set_visible(false); add_child(pose_editor); - rest_editor = memnew(BoneTransformEditor(skeleton)); - rest_editor->set_label(TTR("Bone Rest")); - rest_editor->set_visible(false); - add_child(rest_editor); - - custom_pose_editor = memnew(BoneTransformEditor(skeleton)); - custom_pose_editor->set_label(TTR("Bone Custom Pose")); - custom_pose_editor->set_visible(false); - add_child(custom_pose_editor); + set_keyable(te->has_keying()); } void Skeleton3DEditor::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { + edit_mode_button->set_icon(get_theme_icon(SNAME("ToolBoneSelect"), SNAME("EditorIcons"))); + key_loc_button->set_icon(get_theme_icon(SNAME("KeyPosition"), SNAME("EditorIcons"))); + key_rot_button->set_icon(get_theme_icon(SNAME("KeyRotation"), SNAME("EditorIcons"))); + key_scale_button->set_icon(get_theme_icon(SNAME("KeyScale"), SNAME("EditorIcons"))); + key_insert_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); + key_insert_all_button->set_icon(get_theme_icon(SNAME("NewKey"), SNAME("EditorIcons"))); + get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Object::CONNECT_ONESHOT); + break; + } case NOTIFICATION_ENTER_TREE: { create_editors(); update_joint_tree(); update_editors(); - - get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); 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)); -#endif // TOOLS_ENABLED - + skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); + skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); +#endif break; } } @@ -661,7 +859,7 @@ void Skeleton3DEditor::_notification(int p_what) { void Skeleton3DEditor::_node_removed(Node *p_node) { if (skeleton && p_node == skeleton) { skeleton = nullptr; - options->hide(); + skeleton_options->hide(); } _update_properties(); @@ -672,23 +870,236 @@ void Skeleton3DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed); ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select); ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties); - ClassDB::bind_method(D_METHOD("_on_click_option"), &Skeleton3DEditor::_on_click_option); + ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw); - ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Skeleton3DEditor::drop_data_fw); ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone); + + ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo); } -Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) : - editor(p_editor), +void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { + edit_mode = pressed; + _update_gizmo_visible(); +} + +Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skeleton3D *p_skeleton) : editor_plugin(e_plugin), skeleton(p_skeleton) { + singleton = this; + + // Handle. + handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + handle_shader = Ref<Shader>(memnew(Shader)); + handle_shader->set_code(R"( +// Skeleton 3D gizmo handle shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled, depth_draw_always; +uniform sampler2D texture_albedo : source_color; +uniform float point_size : hint_range(0,128) = 32; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); + POSITION.z = mix(POSITION.z, 0, 0.999); + POINT_SIZE = point_size; +} +void fragment() { + vec4 albedo_tex = texture(texture_albedo,POINT_COORD); + vec3 col = albedo_tex.rgb + COLOR.rgb; + col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0)); + ALBEDO = col; + if (albedo_tex.a < 0.5) { discard; } + ALPHA = albedo_tex.a; +} +)"); + 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_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); + handles_mesh.instantiate(); + handles_mesh_instance->set_mesh(handles_mesh); +} + +void Skeleton3DEditor::update_bone_original() { + if (!skeleton) { + return; + } + if (skeleton->get_bone_count() == 0 || selected_bone == -1) { + return; + } + bone_original_position = skeleton->get_bone_pose_position(selected_bone); + bone_original_rotation = skeleton->get_bone_pose_rotation(selected_bone); + bone_original_scale = skeleton->get_bone_pose_scale(selected_bone); +} + +void Skeleton3DEditor::_hide_handles() { + handles_mesh_instance->hide(); +} + +void Skeleton3DEditor::_draw_gizmo() { + if (!skeleton) { + return; + } + + // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode, + // the skeleton update will be done first and + // the drawing surface will be interrupted once and an error will occur. + skeleton->force_update_all_dirty_bones(); + + // Handles. + if (edit_mode) { + _draw_handles(); + } else { + _hide_handles(); + } +} + +void Skeleton3DEditor::_draw_handles() { + handles_mesh_instance->show(); + + const int bone_len = skeleton->get_bone_count(); + handles_mesh->clear_surfaces(); + handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); + + for (int i = 0; i < bone_len; i++) { + Color c; + if (i == selected_bone) { + c = Color(1, 1, 0); + } else { + c = Color(0.1, 0.25, 0.8); + } + Vector3 point = skeleton->get_bone_global_pose(i).origin; + handles_mesh->surface_set_color(c); + handles_mesh->surface_add_vertex(point); + } + handles_mesh->surface_end(); + handles_mesh->surface_set_material(0, handle_material); +} + +TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { + if (!p_node) { + return nullptr; + } + + NodePath np = p_node->get_metadata(0); + if (np == p_path) { + return p_node; + } + + TreeItem *children = p_node->get_first_child(); + while (children) { + TreeItem *n = _find(children, p_path); + if (n) { + return n; + } + children = children->get_next(); + } + + return nullptr; +} + +void Skeleton3DEditor::_subgizmo_selection_change() { + if (!skeleton) { + return; + } + + // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree. + if (!edit_mode) { + skeleton->clear_subgizmo_selection(); + return; + } + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + if (selected >= 0) { + Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos(); + for (int i = 0; i < gizmos.size(); i++) { + Ref<EditorNode3DGizmo> gizmo = gizmos[i]; + if (!gizmo.is_valid()) { + continue; + } + Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin(); + if (!plugin.is_valid()) { + continue; + } + skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected)); + break; + } + } else { + skeleton->clear_subgizmo_selection(); + } +} + +void Skeleton3DEditor::select_bone(int p_idx) { + if (p_idx >= 0) { + TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx)); + if (ti) { + // Make visible when it's collapsed. + TreeItem *node = ti->get_parent(); + while (node && node != joint_tree->get_root()) { + node->set_collapsed(false); + node = node->get_parent(); + } + ti->select(0); + joint_tree->scroll_to_item(ti); + } + } else { + selected_bone = -1; + joint_tree->deselect_all(); + _joint_tree_selection_changed(); + } } Skeleton3DEditor::~Skeleton3DEditor() { - if (options) { - Node3DEditor::get_singleton()->remove_control_from_menu_panel(options); + if (skeleton) { + select_bone(-1); +#ifdef TOOLS_ENABLED + skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); + skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); + skeleton->set_transform_gizmo_visible(true); +#endif + handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); + } + edit_mode_toggled(false); + + handles_mesh_instance->queue_delete(); + + Node3DEditor *ne = Node3DEditor::get_singleton(); + + if (animation_hb) { + ne->remove_control_from_menu_panel(animation_hb); + memdelete(animation_hb); + } + + if (separator) { + ne->remove_control_from_menu_panel(separator); + memdelete(separator); + } + + if (skeleton_options) { + ne->remove_control_from_menu_panel(skeleton_options); + memdelete(skeleton_options); + } + + if (edit_mode_button) { + ne->remove_control_from_menu_panel(edit_mode_button); + memdelete(edit_mode_button); } } @@ -700,16 +1111,418 @@ void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object); ERR_FAIL_COND(!skeleton); - Skeleton3DEditor *skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); + skel_editor = memnew(Skeleton3DEditor(this, skeleton)); add_custom_control(skel_editor); } -Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { - editor = p_node; - - Ref<EditorInspectorPluginSkeleton> skeleton_plugin; - skeleton_plugin.instantiate(); - skeleton_plugin->editor = editor; +Skeleton3DEditorPlugin::Skeleton3DEditorPlugin() { + skeleton_plugin = memnew(EditorInspectorPluginSkeleton); EditorInspector::add_inspector_plugin(skeleton_plugin); + + Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)); + Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); +} + +EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); + if (se->is_edit_mode()) { + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + if (!ne->is_gizmo_visible()) { + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + if (mb->is_pressed()) { + se->update_bone_original(); + } + } + return EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } + return EditorPlugin::AFTER_GUI_INPUT_PASS; +} + +bool Skeleton3DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("Skeleton3D"); +} + +void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) { + _update_gizmo_visible(); +} + +void Skeleton3DEditor::_update_gizmo_visible() { + _subgizmo_selection_change(); + if (edit_mode) { + if (selected_bone == -1) { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(false); +#endif + } else { +#ifdef TOOLS_ENABLED + if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) { + skeleton->set_transform_gizmo_visible(true); + } else { + skeleton->set_transform_gizmo_visible(false); + } +#endif + } + } else { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(true); +#endif + } + _draw_gizmo(); +} + +int Skeleton3DEditor::get_selected_bone() const { + return selected_bone; +} + +Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { + unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + + selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + selected_sh = Ref<Shader>(memnew(Shader)); + selected_sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * vec4(VERTEX.xyz, 1.0); + POSITION.z = mix(POSITION.z, 0, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selected_mat->set_shader(selected_sh); + + // Register properties in editor settings. + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0)); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron")); +} + +bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; +} + +String Skeleton3DGizmoPlugin::get_gizmo_name() const { + return "Skeleton3D"; +} + +int Skeleton3DGizmoPlugin::get_priority() const { + return -1; +} + +int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, -1); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + + if (!se->is_edit_mode()) { + return -1; + } + + if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + return -1; + } + + // Select bone. + real_t grab_threshold = 4 * EDSCALE; + Vector3 ray_from = p_camera->get_global_transform().origin; + Transform3D gt = skeleton->get_global_transform(); + int closest_idx = -1; + real_t closest_dist = 1e10; + const int bone_len = skeleton->get_bone_count(); + for (int i = 0; i < bone_len; i++) { + Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); + Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); + real_t dist_3d = ray_from.distance_to(joint_pos_3d); + real_t dist_2d = p_point.distance_to(joint_pos_2d); + if (dist_2d < grab_threshold && dist_3d < closest_dist) { + closest_dist = dist_3d; + closest_idx = i; + } + } + + if (closest_idx >= 0) { + WARN_PRINT("ray:"); + WARN_PRINT(itos(closest_idx)); + se->select_bone(closest_idx); + return closest_idx; + } + + se->select_bone(-1); + return -1; +} + +Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, Transform3D()); + + return skeleton->get_bone_global_pose(p_id); +} + +void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + // Prepare for global to local. + Transform3D original_to_local = Transform3D(); + int parent_idx = skeleton->get_bone_parent(p_id); + if (parent_idx >= 0) { + original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); + } + Basis to_local = original_to_local.get_basis().inverse(); + + // Prepare transform. + Transform3D t = Transform3D(); + + // Basis. + t.basis = to_local * p_transform.get_basis(); + + // Origin. + Vector3 orig = skeleton->get_bone_pose(p_id).origin; + Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin; + t.origin = orig + to_local.xform(sub); + + // Apply transform. + skeleton->set_bone_pose_position(p_id, t.origin); + skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion()); + skeleton->set_bone_pose_scale(p_id, t.basis.get_scale()); +} + +void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); + + UndoRedo *ur = EditorNode::get_singleton()->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++) { + ur->add_do_method(skeleton, "set_bone_pose_position", p_ids[i], skeleton->get_bone_pose_position(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_position", p_ids[i], se->get_bone_original_position()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { + for (int i = 0; i < p_ids.size(); i++) { + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { + for (int i = 0; i < p_ids.size(); i++) { + // If the axis is swapped by scaling, the rotation can be changed. + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + ur->add_do_method(skeleton, "set_bone_pose_scale", p_ids[i], skeleton->get_bone_pose_scale(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale()); + } + } + ur->commit_action(); +} + +void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + p_gizmo->clear(); + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton"); + Color selected_bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/selected_bone"); + real_t bone_axis_length = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_axis_length"); + int bone_shape = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_shape"); + + LocalVector<Color> axis_colors; + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); + + Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_gizmo->is_selected()) { + surface_tool->set_material(selected_mat); + } else { + unselected_mat->set_albedo(bone_color); + surface_tool->set_material(unselected_mat); + } + + LocalVector<int> bones; + LocalVector<float> weights; + bones.resize(4); + weights.resize(4); + for (int i = 0; i < 4; i++) { + bones[i] = 0; + weights[i] = 0; + } + weights[0] = 1; + + int current_bone_index = 0; + Vector<int> bones_to_process = skeleton->get_parentless_bones(); + + while (bones_to_process.size() > current_bone_index) { + int current_bone_idx = bones_to_process[current_bone_index]; + current_bone_index++; + + Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; + + Vector<int> child_bones_vector; + child_bones_vector = skeleton->get_bone_children(current_bone_idx); + int child_bones_size = child_bones_vector.size(); + + for (int i = 0; i < child_bones_size; i++) { + // Something wrong. + if (child_bones_vector[i] < 0) { + continue; + } + + int child_bone_idx = child_bones_vector[i]; + + Vector3 v0 = skeleton->get_bone_global_rest(current_bone_idx).origin; + Vector3 v1 = skeleton->get_bone_global_rest(child_bone_idx).origin; + Vector3 d = (v1 - v0).normalized(); + real_t dist = v0.distance_to(v1); + + // Find closest axis. + int closest = -1; + real_t closest_d = 0.0; + for (int j = 0; j < 3; j++) { + real_t dp = Math::abs(skeleton->get_bone_global_rest(current_bone_idx).basis[j].normalized().dot(d)); + if (j == 0 || dp > closest_d) { + closest = j; + } + } + + // Draw bone. + switch (bone_shape) { + case 0: { // Wire shape. + surface_tool->set_color(current_bone_color); + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + } break; + + case 1: { // Octahedron shape. + Vector3 first; + Vector3 points[6]; + int point_idx = 0; + for (int j = 0; j < 3; j++) { + Vector3 axis; + if (first == Vector3()) { + axis = d.cross(d.cross(skeleton->get_bone_global_rest(current_bone_idx).basis[j])).normalized(); + first = axis; + } else { + axis = d.cross(first).normalized(); + } + + surface_tool->set_color(current_bone_color); + for (int k = 0; k < 2; k++) { + if (k == 1) { + axis = -axis; + } + Vector3 point = v0 + d * dist * 0.2; + point += axis * dist * 0.1; + + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + points[point_idx++] = point; + } + } + surface_tool->set_color(current_bone_color); + SWAP(points[1], points[2]); + bones[0] = current_bone_idx; + for (int j = 0; j < 6; j++) { + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[(j + 1) % 6]); + } + } break; + } + + // Axis as root of the bone. + for (int j = 0; j < 3; j++) { + bones[0] = current_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0 + (skeleton->get_bone_global_rest(current_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + + // Axis at the end of the bone children. + if (i == child_bones_size - 1) { + for (int j = 0; j < 3; j++) { + bones[0] = child_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1 + (skeleton->get_bone_global_rest(child_bone_idx).basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + } + + // Add the bone's children to the list of bones to be processed. + bones_to_process.push_back(child_bones_vector[i]); + } + } + + Ref<ArrayMesh> m = surface_tool->commit(); + p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 9de52c6fa8..975b54fa77 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,41 +31,40 @@ #ifndef SKELETON_3D_EDITOR_PLUGIN_H #define SKELETON_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/editor_properties.h" +#include "node_3d_editor_plugin.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/skeleton_3d.h" +#include "scene/resources/immediate_mesh.h" class EditorInspectorPluginSkeleton; class Joint; class PhysicalBone3D; class Skeleton3DEditorPlugin; class Button; -class CheckBox; -class EditorPropertyTransform3D; -class EditorPropertyVector3; class BoneTransformEditor : public VBoxContainer { GDCLASS(BoneTransformEditor, VBoxContainer); EditorInspectorSection *section = nullptr; - EditorPropertyVector3 *translation_property = nullptr; - EditorPropertyVector3 *rotation_property = nullptr; + EditorPropertyCheck *enabled_checkbox = nullptr; + EditorPropertyVector3 *position_property = nullptr; + EditorPropertyQuaternion *rotation_property = nullptr; EditorPropertyVector3 *scale_property = nullptr; - EditorInspectorSection *transform_section = nullptr; - EditorPropertyTransform3D *transform_property = nullptr; - Rect2 background_rects[5]; + EditorInspectorSection *rest_section = nullptr; + EditorPropertyTransform3D *rest_matrix = nullptr; - Skeleton3D *skeleton; - String property; + Rect2 background_rects[5]; - UndoRedo *undo_redo; + Skeleton3D *skeleton = nullptr; + // String property; - Button *key_button = nullptr; - CheckBox *enabled_checkbox = nullptr; + UndoRedo *undo_redo = nullptr; - bool keyable = false; bool toggle_enabled = false; bool updating = false; @@ -73,18 +72,9 @@ class BoneTransformEditor : public VBoxContainer { void create_editors(); - // Called when one of the EditorSpinSliders are changed. - void _value_changed(const double p_value); - // Called when the one of the EditorPropertyVector3 are updated. - void _value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean); - // Called when the transform_property is updated. - void _value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean); - // Changes the transform to the given transform and updates the UI accordingly. - void _change_transform(Transform3D p_new_transform); - // Creates a Transform using the EditorPropertyVector3 properties. - Transform3D compute_transform_from_vector3s() const; + void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); - void update_enabled_checkbox(); + void _property_keyed(const String &p_path, bool p_advance); protected: void _notification(int p_what); @@ -92,28 +82,12 @@ protected: public: BoneTransformEditor(Skeleton3D *p_skeleton); - // Which transform target to modify + // Which transform target to modify. void set_target(const String &p_prop); void set_label(const String &p_label) { label = p_label; } - - void _update_properties(); - void _update_custom_pose_properties(); - void _update_transform_properties(Transform3D p_transform); - - // Can/cannot modify the spinner values for the Transform - void set_read_only(const bool p_read_only); - - // Transform can be keyed, whether or not to show the button void set_keyable(const bool p_keyable); - // Bone can be toggled enabled or disabled, whether or not to show the checkbox - void set_toggle_enabled(const bool p_enabled); - - // Key Transform Button pressed - void _key_button_pressed(); - - // Bone Enabled Checkbox toggled - void _checkbox_toggled(const bool p_toggled); + void _update_properties(); }; class Skeleton3DEditor : public VBoxContainer { @@ -121,32 +95,51 @@ class Skeleton3DEditor : public VBoxContainer { friend class Skeleton3DEditorPlugin; - enum Menu { - MENU_OPTION_CREATE_PHYSICAL_SKELETON + enum SkeletonOption { + SKELETON_OPTION_INIT_ALL_POSES, + SKELETON_OPTION_INIT_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 { PhysicalBone3D *physical_bone = nullptr; - Transform3D relative_rest; // Relative to skeleton node + Transform3D relative_rest; // Relative to skeleton node. }; - EditorNode *editor; - EditorInspectorPluginSkeleton *editor_plugin; + EditorInspectorPluginSkeleton *editor_plugin = nullptr; - Skeleton3D *skeleton; + Skeleton3D *skeleton = nullptr; Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; - BoneTransformEditor *custom_pose_editor = nullptr; - MenuButton *options = nullptr; + VSeparator *separator = nullptr; + MenuButton *skeleton_options = nullptr; + Button *edit_mode_button = nullptr; + + bool edit_mode = false; + + HBoxContainer *animation_hb = nullptr; + Button *key_loc_button = nullptr; + Button *key_rot_button = nullptr; + Button *key_scale_button = nullptr; + Button *key_insert_button = nullptr; + Button *key_insert_all_button = nullptr; + EditorFileDialog *file_dialog = nullptr; - UndoRedo *undo_redo = nullptr; + bool keyable = false; + + static Skeleton3DEditor *singleton; - void _on_click_option(int p_option); + void _on_click_skeleton_option(int p_skeleton_option); void _file_selected(const String &p_file); + TreeItem *_find(TreeItem *p_node, const NodePath &p_path); + void edit_mode_toggled(const bool pressed); EditorFileDialog *file_export_lib = nullptr; @@ -155,29 +148,73 @@ class Skeleton3DEditor : public VBoxContainer { void create_editors(); + void init_pose(const bool p_all_bones); + void pose_to_rest(const bool p_all_bones); + + void insert_keys(const bool p_all_bones); + 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); + void set_keyable(const bool p_keyable); + void set_bone_options_enabled(const bool p_bone_options_enabled); + + // Handle. + MeshInstance3D *handles_mesh_instance = nullptr; + Ref<ImmediateMesh> handles_mesh; + Ref<ShaderMaterial> handle_material; + Ref<Shader> handle_shader; + + Vector3 bone_original_position; + Quaternion bone_original_rotation; + Vector3 bone_original_scale; + + void _update_gizmo_visible(); + void _bone_enabled_changed(const int p_bone_id); + + void _hide_handles(); + + void _draw_gizmo(); + void _draw_handles(); + + void _joint_tree_selection_changed(); + void _joint_tree_rmb_select(const Vector2 &p_pos, MouseButton p_button); + void _update_properties(); + + void _subgizmo_selection_change(); + + int selected_bone = -1; + protected: void _notification(int p_what); void _node_removed(Node *p_node); static void _bind_methods(); public: + static Skeleton3DEditor *get_singleton() { return singleton; } + + void select_bone(int p_idx); + + int get_selected_bone() const; + void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx); Skeleton3D *get_skeleton() const { return skeleton; }; - void _joint_tree_selection_changed(); - void _joint_tree_rmb_select(const Vector2 &p_pos); + bool is_edit_mode() const { return edit_mode; } - void _update_properties(); + void update_bone_original(); + Vector3 get_bone_original_position() const { return bone_original_position; }; + Quaternion get_bone_original_rotation() const { return bone_original_rotation; }; + Vector3 get_bone_original_scale() const { return bone_original_scale; }; - Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton); + Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, Skeleton3D *skeleton); ~Skeleton3DEditor(); }; @@ -186,7 +223,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { friend class Skeleton3DEditorPlugin; - EditorNode *editor; + Skeleton3DEditor *skel_editor = nullptr; public: virtual bool can_handle(Object *p_object) override; @@ -196,12 +233,39 @@ public: class Skeleton3DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton3DEditorPlugin, EditorPlugin); - EditorNode *editor; + EditorInspectorPluginSkeleton *skeleton_plugin = nullptr; public: - Skeleton3DEditorPlugin(EditorNode *p_node); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + + bool has_main_screen() const override { return false; } + virtual bool handles(Object *p_object) const override; virtual String get_name() const override { return "Skeleton3D"; } + + Skeleton3DEditorPlugin(); +}; + +class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); + + Ref<StandardMaterial3D> unselected_mat; + Ref<ShaderMaterial> selected_mat; + Ref<Shader> selected_sh; + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override; + Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override; + void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override; + void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + Skeleton3DGizmoPlugin(); }; #endif // SKELETON_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/skeleton_ik_3d_editor_plugin.cpp b/editor/plugins/skeleton_ik_3d_editor_plugin.cpp index 85632cf481..7dc34a0874 100644 --- a/editor/plugins/skeleton_ik_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_ik_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,7 @@ #include "skeleton_ik_3d_editor_plugin.h" +#include "editor/editor_node.h" #include "scene/3d/skeleton_ik_3d.h" void SkeletonIK3DEditorPlugin::_play() { @@ -80,10 +81,9 @@ void SkeletonIK3DEditorPlugin::make_visible(bool p_visible) { void SkeletonIK3DEditorPlugin::_bind_methods() { } -SkeletonIK3DEditorPlugin::SkeletonIK3DEditorPlugin(EditorNode *p_node) { - editor = p_node; +SkeletonIK3DEditorPlugin::SkeletonIK3DEditorPlugin() { play_btn = memnew(Button); - play_btn->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); + play_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Play"), SNAME("EditorIcons"))); play_btn->set_text(TTR("Play IK")); play_btn->set_toggle_mode(true); play_btn->hide(); diff --git a/editor/plugins/skeleton_ik_3d_editor_plugin.h b/editor/plugins/skeleton_ik_3d_editor_plugin.h index b0d2138115..26aead6d67 100644 --- a/editor/plugins/skeleton_ik_3d_editor_plugin.h +++ b/editor/plugins/skeleton_ik_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef SKELETON_IK_3D_EDITOR_PLUGIN_H #define SKELETON_IK_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" class SkeletonIK3D; @@ -39,10 +38,9 @@ class SkeletonIK3D; class SkeletonIK3DEditorPlugin : public EditorPlugin { GDCLASS(SkeletonIK3DEditorPlugin, EditorPlugin); - SkeletonIK3D *skeleton_ik; + SkeletonIK3D *skeleton_ik = nullptr; - Button *play_btn; - EditorNode *editor; + Button *play_btn = nullptr; void _play(); @@ -56,7 +54,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - SkeletonIK3DEditorPlugin(EditorNode *p_node); + SkeletonIK3DEditorPlugin(); ~SkeletonIK3DEditorPlugin(); }; diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 0f889ce33d..7d350fd46f 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,7 +32,9 @@ #include "canvas_item_editor_plugin.h" #include "core/math/geometry_2d.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/scene_tree_dock.h" #include "scene/2d/collision_polygon_2d.h" #include "scene/2d/light_occluder_2d.h" #include "scene/2d/mesh_instance_2d.h" @@ -120,8 +122,8 @@ void Sprite2DEditor::_menu_option(int p_option) { switch (p_option) { case MENU_OPTION_CONVERT_TO_MESH_2D: { - debug_uv_dialog->get_ok_button()->set_text(TTR("Create Mesh2D")); - debug_uv_dialog->set_title(TTR("Mesh2D Preview")); + debug_uv_dialog->set_ok_button_text(TTR("Create MeshInstance2D")); + debug_uv_dialog->set_title(TTR("MeshInstance2D Preview")); _update_mesh_data(); debug_uv_dialog->popup_centered(); @@ -129,7 +131,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(); @@ -137,7 +139,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(); @@ -146,7 +148,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(); @@ -158,6 +160,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!")); @@ -182,7 +189,7 @@ void Sprite2DEditor::_update_mesh_data() { if (node->is_region_enabled()) { rect = node->get_region_rect(); } else { - rect.size = Size2(image->get_width(), image->get_height()); + rect.size = image->get_size(); } Ref<BitMap> bm; @@ -209,7 +216,7 @@ void Sprite2DEditor::_update_mesh_data() { computed_uv.clear(); computed_indices.clear(); - Size2 img_size = Vector2(image->get_width(), image->get_height()); + Size2 img_size = image->get_size(); for (int i = 0; i < lines.size(); i++) { lines.write[i] = expand(lines[i], rect, epsilon); } @@ -336,10 +343,10 @@ void Sprite2DEditor::_convert_to_mesh_2d_node() { mesh_instance->set_mesh(mesh); UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Convert to Mesh2D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", node, mesh_instance, true, false); + ur->create_action(TTR("Convert to MeshInstance2D")); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, mesh_instance, true, false); ur->add_do_reference(mesh_instance); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", mesh_instance, node, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", mesh_instance, node, false, false); ur->add_undo_reference(node); ur->commit_action(); } @@ -395,9 +402,9 @@ void Sprite2DEditor::_convert_to_polygon_2d_node() { UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Convert to Polygon2D")); - ur->add_do_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", node, polygon_2d_instance, true, false); + ur->add_do_method(SceneTreeDock::get_singleton(), "replace_node", node, polygon_2d_instance, true, false); ur->add_do_reference(polygon_2d_instance); - ur->add_undo_method(EditorNode::get_singleton()->get_scene_tree_dock(), "replace_node", polygon_2d_instance, node, false, false); + ur->add_undo_method(SceneTreeDock::get_singleton(), "replace_node", polygon_2d_instance, node, false, false); ur->add_undo_reference(node); ur->commit_action(); } @@ -496,6 +503,20 @@ void Sprite2DEditor::_debug_uv_draw() { } } +void Sprite2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + options->set_icon(get_theme_icon(SNAME("Sprite2D"), SNAME("EditorIcons"))); + + options->get_popup()->set_item_icon(MENU_OPTION_CONVERT_TO_MESH_2D, get_theme_icon(SNAME("MeshInstance2D"), SNAME("EditorIcons"))); + options->get_popup()->set_item_icon(MENU_OPTION_CONVERT_TO_POLYGON_2D, get_theme_icon(SNAME("Polygon2D"), SNAME("EditorIcons"))); + options->get_popup()->set_item_icon(MENU_OPTION_CREATE_COLLISION_POLY_2D, get_theme_icon(SNAME("CollisionPolygon2D"), SNAME("EditorIcons"))); + options->get_popup()->set_item_icon(MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D, get_theme_icon(SNAME("LightOccluder2D"), SNAME("EditorIcons"))); + } break; + } +} + void Sprite2DEditor::_bind_methods() { ClassDB::bind_method("_add_as_sibling_or_child", &Sprite2DEditor::_add_as_sibling_or_child); } @@ -506,9 +527,8 @@ Sprite2DEditor::Sprite2DEditor() { CanvasItemEditor::get_singleton()->add_control_to_menu_panel(options); options->set_text(TTR("Sprite2D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Sprite2D"), SNAME("EditorIcons"))); - options->get_popup()->add_item(TTR("Convert to Mesh2D"), MENU_OPTION_CONVERT_TO_MESH_2D); + options->get_popup()->add_item(TTR("Convert to MeshInstance2D"), MENU_OPTION_CONVERT_TO_MESH_2D); options->get_popup()->add_item(TTR("Convert to Polygon2D"), MENU_OPTION_CONVERT_TO_POLYGON_2D); options->get_popup()->add_item(TTR("Create CollisionPolygon2D Sibling"), MENU_OPTION_CREATE_COLLISION_POLY_2D); options->get_popup()->add_item(TTR("Create LightOccluder2D Sibling"), MENU_OPTION_CREATE_LIGHT_OCCLUDER_2D); @@ -520,14 +540,10 @@ Sprite2DEditor::Sprite2DEditor() { add_child(err_dialog); debug_uv_dialog = memnew(ConfirmationDialog); - debug_uv_dialog->get_ok_button()->set_text(TTR("Create Mesh2D")); - debug_uv_dialog->set_title(TTR("Mesh 2D Preview")); VBoxContainer *vb = memnew(VBoxContainer); debug_uv_dialog->add_child(vb); ScrollContainer *scroll = memnew(ScrollContainer); scroll->set_custom_minimum_size(Size2(800, 500) * EDSCALE); - scroll->set_enable_h_scroll(true); - scroll->set_enable_v_scroll(true); vb->add_margin_child(TTR("Preview:"), scroll, true); debug_uv = memnew(Control); debug_uv->connect("draw", callable_mp(this, &Sprite2DEditor::_debug_uv_draw)); @@ -535,7 +551,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); @@ -543,7 +559,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); @@ -551,7 +567,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); @@ -585,10 +601,9 @@ void Sprite2DEditorPlugin::make_visible(bool p_visible) { } } -Sprite2DEditorPlugin::Sprite2DEditorPlugin(EditorNode *p_node) { - editor = p_node; +Sprite2DEditorPlugin::Sprite2DEditorPlugin() { sprite_editor = memnew(Sprite2DEditor); - editor->get_main_control()->add_child(sprite_editor); + EditorNode::get_singleton()->get_main_control()->add_child(sprite_editor); make_visible(false); //sprite_editor->options->hide(); diff --git a/editor/plugins/sprite_2d_editor_plugin.h b/editor/plugins/sprite_2d_editor_plugin.h index d4a1ef4312..b87f108bd2 100644 --- a/editor/plugins/sprite_2d_editor_plugin.h +++ b/editor/plugins/sprite_2d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,10 +28,9 @@ /* 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_node.h" #include "editor/editor_plugin.h" #include "scene/2d/sprite_2d.h" #include "scene/gui/spin_box.h" @@ -48,16 +47,16 @@ class Sprite2DEditor : public Control { Menu selected_menu_item; - Sprite2D *node; + Sprite2D *node = nullptr; - MenuButton *options; + MenuButton *options = nullptr; - ConfirmationDialog *outline_dialog; + ConfirmationDialog *outline_dialog = nullptr; - AcceptDialog *err_dialog; + AcceptDialog *err_dialog = nullptr; - ConfirmationDialog *debug_uv_dialog; - Control *debug_uv; + ConfirmationDialog *debug_uv_dialog = nullptr; + Control *debug_uv = nullptr; Vector<Vector2> uv_lines; Vector<Vector<Vector2>> outline_lines; Vector<Vector<Vector2>> computed_outline_lines; @@ -65,10 +64,10 @@ class Sprite2DEditor : public Control { Vector<Vector2> computed_uv; Vector<int> computed_indices; - SpinBox *simplification; - SpinBox *grow_pixels; - SpinBox *shrink_pixels; - Button *update_preview; + SpinBox *simplification = nullptr; + SpinBox *grow_pixels = nullptr; + SpinBox *shrink_pixels = nullptr; + Button *update_preview = nullptr; void _menu_option(int p_option); @@ -88,6 +87,7 @@ class Sprite2DEditor : public Control { protected: void _node_removed(Node *p_node); + void _notification(int p_what); static void _bind_methods(); public: @@ -98,8 +98,7 @@ public: class Sprite2DEditorPlugin : public EditorPlugin { GDCLASS(Sprite2DEditorPlugin, EditorPlugin); - Sprite2DEditor *sprite_editor; - EditorNode *editor; + Sprite2DEditor *sprite_editor = nullptr; public: virtual String get_name() const override { return "Sprite2D"; } @@ -108,8 +107,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - Sprite2DEditorPlugin(EditorNode *p_node); + Sprite2DEditorPlugin(); ~Sprite2DEditorPlugin(); }; -#endif // SPRITE_EDITOR_PLUGIN_H +#endif // SPRITE_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 400f9f560f..a39d24a167 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,14 +33,19 @@ #include "core/config/project_settings.h" #include "core/io/resource_loader.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/scene_tree_dock.h" #include "scene/3d/sprite_3d.h" #include "scene/gui/center_container.h" #include "scene/gui/margin_container.h" #include "scene/gui/panel_container.h" -void SpriteFramesEditor::gui_input(const Ref<InputEvent> &p_event) { +static void _draw_shadowed_line(Control *p_control, const Point2 &p_from, const Size2 &p_size, const Size2 &p_shadow_offset, Color p_color, Color p_shadow_color) { + p_control->draw_line(p_from, p_from + p_size, p_color); + p_control->draw_line(p_from + p_shadow_offset, p_from + p_size + p_shadow_offset, p_shadow_color); } void SpriteFramesEditor::_open_sprite_sheet() { @@ -54,114 +59,142 @@ void SpriteFramesEditor::_open_sprite_sheet() { file_split_sheet->popup_file_dialog(); } +int SpriteFramesEditor::_sheet_preview_position_to_frame_index(const Point2 &p_position) { + const Size2i offset = _get_offset(); + const Size2i frame_size = _get_frame_size(); + const Size2i separation = _get_separation(); + const Size2i block_size = frame_size + separation; + const Point2i position = p_position / sheet_zoom - offset; + + if (position.x % block_size.x > frame_size.x || position.y % block_size.y > frame_size.y) { + return -1; // Gap between frames. + } + + const Point2i frame = position / block_size; + const Size2i frame_count = _get_frame_count(); + if (frame.x < 0 || frame.y < 0 || frame.x >= frame_count.x || frame.y >= frame_count.y) { + return -1; // Out of bound. + } + + return frame_count.x * frame.y + frame.x; +} + void SpriteFramesEditor::_sheet_preview_draw() { - Size2i size = split_sheet_preview->get_size(); - int h = split_sheet_h->get_value(); - int v = split_sheet_v->get_value(); - int width = size.width / h; - int height = size.height / v; - const float a = 0.3; - for (int i = 1; i < h; i++) { - int x = i * width; - split_sheet_preview->draw_line(Point2(x, 0), Point2(x, size.height), Color(1, 1, 1, a)); - split_sheet_preview->draw_line(Point2(x + 1, 0), Point2(x + 1, size.height), Color(0, 0, 0, a)); - } - for (int i = 1; i < v; i++) { - int y = i * height; - split_sheet_preview->draw_line(Point2(0, y), Point2(size.width, y), Color(1, 1, 1, a)); - split_sheet_preview->draw_line(Point2(0, y + 1), Point2(size.width, y + 1), Color(0, 0, 0, a)); + const Size2i frame_count = _get_frame_count(); + const Size2i separation = _get_separation(); + + const Size2 draw_offset = Size2(_get_offset()) * sheet_zoom; + const Size2 draw_sep = Size2(separation) * sheet_zoom; + const Size2 draw_frame_size = Size2(_get_frame_size()) * sheet_zoom; + const Size2 draw_size = draw_frame_size * frame_count + draw_sep * (frame_count - Size2i(1, 1)); + + const Color line_color = Color(1, 1, 1, 0.3); + const Color shadow_color = Color(0, 0, 0, 0.3); + + // Vertical lines. + _draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color); + for (int i = 0; i < frame_count.x - 1; i++) { + const Point2 start = draw_offset + Vector2(i * draw_sep.x + (i + 1) * draw_frame_size.x, 0); + if (separation.x == 0) { + _draw_shadowed_line(split_sheet_preview, start, Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color); + } else { + const Size2 size = Size2(draw_sep.x, draw_size.y); + split_sheet_preview->draw_rect(Rect2(start, size), line_color); + } } + _draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(draw_size.x, 0), Vector2(0, draw_size.y), Vector2(1, 0), line_color, shadow_color); + + // Horizontal lines. + _draw_shadowed_line(split_sheet_preview, draw_offset, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color); + for (int i = 0; i < frame_count.y - 1; i++) { + const Point2 start = draw_offset + Vector2(0, i * draw_sep.y + (i + 1) * draw_frame_size.y); + if (separation.y == 0) { + _draw_shadowed_line(split_sheet_preview, start, Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color); + } else { + const Size2 size = Size2(draw_size.x, draw_sep.y); + split_sheet_preview->draw_rect(Rect2(start, size), line_color); + } + } + _draw_shadowed_line(split_sheet_preview, draw_offset + Vector2(0, draw_size.y), Vector2(draw_size.x, 0), Vector2(0, 1), line_color, shadow_color); if (frames_selected.size() == 0) { split_sheet_dialog->get_ok_button()->set_disabled(true); - split_sheet_dialog->get_ok_button()->set_text(TTR("No Frames Selected")); + split_sheet_dialog->set_ok_button_text(TTR("No Frames Selected")); return; } - Color accent = get_theme_color(SNAME("accent_color"), SNAME("Editor")); - - for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { - int idx = E->get(); - int xp = idx % h; - int yp = (idx - xp) / h; - int x = xp * width; - int y = yp * height; + Color accent = get_theme_color("accent_color", "Editor"); - split_sheet_preview->draw_rect(Rect2(x + 5, y + 5, width - 10, height - 10), Color(0, 0, 0, 0.35), true); - split_sheet_preview->draw_rect(Rect2(x + 0, y + 0, width - 0, height - 0), Color(0, 0, 0, 1), false); - split_sheet_preview->draw_rect(Rect2(x + 1, y + 1, width - 2, height - 2), Color(0, 0, 0, 1), false); - split_sheet_preview->draw_rect(Rect2(x + 2, y + 2, width - 4, height - 4), accent, false); - split_sheet_preview->draw_rect(Rect2(x + 3, y + 3, width - 6, height - 6), accent, false); - split_sheet_preview->draw_rect(Rect2(x + 4, y + 4, width - 8, height - 8), Color(0, 0, 0, 1), false); - split_sheet_preview->draw_rect(Rect2(x + 5, y + 5, width - 10, height - 10), Color(0, 0, 0, 1), false); + for (const int &E : frames_selected) { + const int idx = E; + const int x = idx % frame_count.x; + const int y = idx / frame_count.x; + const Point2 pos = draw_offset + Point2(x, y) * (draw_frame_size + draw_sep); + split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 0.35), true); + split_sheet_preview->draw_rect(Rect2(pos, draw_frame_size), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(1, 1), draw_frame_size - Size2(2, 2)), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(2, 2), draw_frame_size - Size2(4, 4)), accent, false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(3, 3), draw_frame_size - Size2(6, 6)), accent, false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(4, 4), draw_frame_size - Size2(8, 8)), Color(0, 0, 0, 1), false); + split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 1), false); } split_sheet_dialog->get_ok_button()->set_disabled(false); - split_sheet_dialog->get_ok_button()->set_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size())); + split_sheet_dialog->set_ok_button_text(vformat(TTR("Add %d Frame(s)"), frames_selected.size())); } void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { const Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - const Size2i size = split_sheet_preview->get_size(); - const int h = split_sheet_h->get_value(); - const int v = split_sheet_v->get_value(); - - const int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1); - const int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1); - - const int idx = h * y + x; - - if (mb->is_shift_pressed() && last_frame_selected >= 0) { - //select multiple - int from = idx; - int to = last_frame_selected; - if (from > to) { - SWAP(from, to); - } + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { + const int idx = _sheet_preview_position_to_frame_index(mb->get_position()); + + if (idx != -1) { + if (mb->is_shift_pressed() && last_frame_selected >= 0) { + //select multiple + int from = idx; + int to = last_frame_selected; + if (from > to) { + SWAP(from, to); + } + + for (int i = from; i <= to; i++) { + // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held. + frames_toggled_by_mouse_hover.insert(idx); - for (int i = from; i <= to; i++) { + if (mb->is_ctrl_pressed()) { + frames_selected.erase(i); + } else { + frames_selected.insert(i); + } + } + } else { // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held. frames_toggled_by_mouse_hover.insert(idx); - if (mb->is_ctrl_pressed()) { - frames_selected.erase(i); + if (frames_selected.has(idx)) { + frames_selected.erase(idx); } else { - frames_selected.insert(i); + frames_selected.insert(idx); } } - } else { - // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held. - frames_toggled_by_mouse_hover.insert(idx); - - if (frames_selected.has(idx)) { - frames_selected.erase(idx); - } else { - frames_selected.insert(idx); - } } - last_frame_selected = idx; - split_sheet_preview->update(); + if (last_frame_selected != idx || idx != -1) { + last_frame_selected = idx; + split_sheet_preview->update(); + } } - if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { frames_toggled_by_mouse_hover.clear(); } const Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { // Select by holding down the mouse button on frames. - const Size2i size = split_sheet_preview->get_size(); - const int h = split_sheet_h->get_value(); - const int v = split_sheet_v->get_value(); - - const int x = CLAMP(int(mm->get_position().x) * h / size.width, 0, h - 1); - const int y = CLAMP(int(mm->get_position().y) * v / size.height, 0, v - 1); - - const int idx = h * y + x; + const int idx = _sheet_preview_position_to_frame_index(mm->get_position()); - if (!frames_toggled_by_mouse_hover.has(idx)) { + if (idx != -1 && !frames_toggled_by_mouse_hover.has(idx)) { // Only allow toggling each tile once per mouse hold. // Otherwise, the selection would constantly "flicker" in and out when moving the mouse cursor. // The mouse button must be released before it can be toggled again. @@ -186,50 +219,43 @@ void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) { // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer // to allow performing this action anywhere, even if the cursor isn't // hovering the texture in the workspace. - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { - _sheet_zoom_in(); + if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { + _sheet_zoom_on_position(scale_ratio, mb->get_position()); // Don't scroll up after zooming in. - accept_event(); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) { - _sheet_zoom_out(); + split_sheet_scroll->accept_event(); + } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) { + _sheet_zoom_on_position(1 / scale_ratio, mb->get_position()); // Don't scroll down after zooming out. - accept_event(); + split_sheet_scroll->accept_event(); } } + + const Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE) { + const Vector2 dragged = Input::get_singleton()->warp_mouse_motion(mm, split_sheet_scroll->get_global_rect()); + split_sheet_scroll->set_h_scroll(split_sheet_scroll->get_h_scroll() - dragged.x); + split_sheet_scroll->set_v_scroll(split_sheet_scroll->get_v_scroll() - dragged.y); + } } void SpriteFramesEditor::_sheet_add_frames() { - Size2i size = split_sheet_preview->get_texture()->get_size(); - int frame_count_x = split_sheet_h->get_value(); - int frame_count_y = split_sheet_v->get_value(); - Size2 frame_size(size.width / frame_count_x, size.height / frame_count_y); + const Size2i frame_count = _get_frame_count(); + const Size2i frame_size = _get_frame_size(); + const Size2i offset = _get_offset(); + const Size2i separation = _get_separation(); undo_redo->create_action(TTR("Add Frame")); int fc = frames->get_frame_count(edited_anim); - Point2 src_origin; - Rect2 src_region(Point2(), size); - - AtlasTexture *src_atlas = Object::cast_to<AtlasTexture>(*split_sheet_preview->get_texture()); - if (src_atlas && src_atlas->get_atlas().is_valid()) { - src_origin = src_atlas->get_region().position - src_atlas->get_margin().position; - src_region = src_atlas->get_region(); - } - - for (Set<int>::Element *E = frames_selected.front(); E; E = E->next()) { - int idx = E->get(); - Point2 frame_coords(idx % frame_count_x, idx / frame_count_x); - - Rect2 frame(frame_coords * frame_size + src_origin, frame_size); - Rect2 region = frame.intersection(src_region); - Rect2 margin(region == Rect2() ? Point2() : region.position - frame.position, frame.size - region.size); + for (const int &E : frames_selected) { + int idx = E; + const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x); Ref<AtlasTexture> at; at.instantiate(); at->set_atlas(split_sheet_preview->get_texture()); - at->set_region(region); - at->set_margin(margin); + at->set_region(Rect2(offset + frame_coords * (frame_size + separation), frame_size)); undo_redo->add_do_method(frames, "add_frame", edited_anim, at, -1); undo_redo->add_undo_method(frames, "remove_frame", edited_anim, fc); @@ -240,20 +266,25 @@ void SpriteFramesEditor::_sheet_add_frames() { undo_redo->commit_action(); } +void SpriteFramesEditor::_sheet_zoom_on_position(float p_zoom, const Vector2 &p_position) { + const float old_zoom = sheet_zoom; + sheet_zoom = CLAMP(sheet_zoom * p_zoom, min_sheet_zoom, max_sheet_zoom); + + const Size2 texture_size = split_sheet_preview->get_texture()->get_size(); + split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); + + Vector2 offset = Vector2(split_sheet_scroll->get_h_scroll(), split_sheet_scroll->get_v_scroll()); + offset = (offset + p_position) / old_zoom * sheet_zoom - p_position; + split_sheet_scroll->set_h_scroll(offset.x); + split_sheet_scroll->set_v_scroll(offset.y); +} + void SpriteFramesEditor::_sheet_zoom_in() { - if (sheet_zoom < max_sheet_zoom) { - sheet_zoom *= scale_ratio; - Size2 texture_size = split_sheet_preview->get_texture()->get_size(); - split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); - } + _sheet_zoom_on_position(scale_ratio, Vector2()); } void SpriteFramesEditor::_sheet_zoom_out() { - if (sheet_zoom > min_sheet_zoom) { - sheet_zoom /= scale_ratio; - Size2 texture_size = split_sheet_preview->get_texture()->get_size(); - split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); - } + _sheet_zoom_on_position(1 / scale_ratio, Vector2()); } void SpriteFramesEditor::_sheet_zoom_reset() { @@ -278,14 +309,64 @@ void SpriteFramesEditor::_sheet_select_clear_all_frames() { split_sheet_preview->update(); } -void SpriteFramesEditor::_sheet_spin_changed(double) { +void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_param) { + if (updating_split_settings) { + return; + } + updating_split_settings = true; + + if (p_dominant_param != PARAM_USE_CURRENT) { + dominant_param = p_dominant_param; + } + + const Size2i texture_size = split_sheet_preview->get_texture()->get_size(); + const Size2i size = texture_size - _get_offset(); + + switch (dominant_param) { + case PARAM_SIZE: { + const Size2i frame_size = _get_frame_size(); + + const Size2i offset_max = texture_size - frame_size; + split_sheet_offset_x->set_max(offset_max.x); + split_sheet_offset_y->set_max(offset_max.y); + + const Size2i sep_max = size - frame_size * 2; + split_sheet_sep_x->set_max(sep_max.x); + split_sheet_sep_y->set_max(sep_max.y); + + const Size2i separation = _get_separation(); + const Size2i count = (size + separation) / (frame_size + separation); + split_sheet_h->set_value(count.x); + split_sheet_v->set_value(count.y); + } break; + + case PARAM_FRAME_COUNT: { + const Size2i count = _get_frame_count(); + + const Size2i offset_max = texture_size - count; + split_sheet_offset_x->set_max(offset_max.x); + split_sheet_offset_y->set_max(offset_max.y); + + const Size2i gap_count = count - Size2i(1, 1); + split_sheet_sep_x->set_max(gap_count.x == 0 ? size.x : (size.x - count.x) / gap_count.x); + split_sheet_sep_y->set_max(gap_count.y == 0 ? size.y : (size.y - count.y) / gap_count.y); + + const Size2i separation = _get_separation(); + const Size2i frame_size = (size - separation * gap_count) / count; + split_sheet_size_x->set_value(frame_size.x); + split_sheet_size_y->set_value(frame_size.y); + } break; + } + + updating_split_settings = false; + frames_selected.clear(); last_frame_selected = -1; split_sheet_preview->update(); } void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { - Ref<Resource> texture = ResourceLoader::load(p_file); + Ref<Texture2D> texture = ResourceLoader::load(p_file); if (!texture.is_valid()) { EditorNode::get_singleton()->show_warning(TTR("Unable to load images")); ERR_FAIL_COND(!texture.is_valid()); @@ -296,10 +377,29 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { bool new_texture = texture != split_sheet_preview->get_texture(); split_sheet_preview->set_texture(texture); if (new_texture) { - //different texture, reset to 4x4 + // Reset spin max. + const Size2i size = texture->get_size(); + split_sheet_size_x->set_max(size.x); + split_sheet_size_y->set_max(size.y); + split_sheet_sep_x->set_max(size.x); + split_sheet_sep_y->set_max(size.y); + split_sheet_offset_x->set_max(size.x); + split_sheet_offset_y->set_max(size.y); + + // Different texture, reset to 4x4. + dominant_param = PARAM_FRAME_COUNT; + updating_split_settings = true; split_sheet_h->set_value(4); split_sheet_v->set_value(4); - //reset zoom + split_sheet_size_x->set_value(size.x / 4); + split_sheet_size_y->set_value(size.y / 4); + split_sheet_sep_x->set_value(0); + split_sheet_sep_y->set_value(0); + split_sheet_offset_x->set_value(0); + split_sheet_offset_y->set_value(0); + updating_split_settings = false; + + // Reset zoom. _sheet_zoom_reset(); } split_sheet_dialog->popup_centered_ratio(0.65); @@ -307,7 +407,8 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { void SpriteFramesEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { load->set_icon(get_theme_icon(SNAME("Load"), SNAME("EditorIcons"))); load_sheet->set_icon(get_theme_icon(SNAME("SpriteSheet"), SNAME("EditorIcons"))); copy->set_icon(get_theme_icon(SNAME("ActionCopy"), SNAME("EditorIcons"))); @@ -322,14 +423,13 @@ 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"))); - [[fallthrough]]; - } - case NOTIFICATION_THEME_CHANGED: { split_sheet_scroll->add_theme_style_override("bg", get_theme_stylebox(SNAME("bg"), SNAME("Tree"))); } break; + case NOTIFICATION_READY: { add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up. } break; @@ -350,7 +450,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 } @@ -378,6 +478,22 @@ void SpriteFramesEditor::_file_load_request(const Vector<String> &p_path, int p_ undo_redo->commit_action(); } +Size2i SpriteFramesEditor::_get_frame_count() const { + return Size2i(split_sheet_h->get_value(), split_sheet_v->get_value()); +} + +Size2i SpriteFramesEditor::_get_frame_size() const { + return Size2i(split_sheet_size_x->get_value(), split_sheet_size_y->get_value()); +} + +Size2i SpriteFramesEditor::_get_offset() const { + return Size2i(split_sheet_offset_x->get_value(), split_sheet_offset_y->get_value()); +} + +Size2i SpriteFramesEditor::_get_separation() const { + return Size2i(split_sheet_sep_x->get_value(), split_sheet_sep_y->get_value()); +} + void SpriteFramesEditor::_load_pressed() { ERR_FAIL_COND(!frames->has_animation(edited_anim)); loading_scene = false; @@ -401,7 +517,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 } @@ -635,7 +751,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(); } @@ -701,6 +817,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; @@ -732,11 +852,11 @@ void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) { const Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { _zoom_in(); // Don't scroll up after zooming in. accept_event(); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) { + } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) { _zoom_out(); // Don't scroll down after zooming out. accept_event(); @@ -785,14 +905,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); @@ -821,19 +946,30 @@ void SpriteFramesEditor::_update_library(bool p_skip_selector) { for (int i = 0; i < frames->get_frame_count(edited_anim); i++) { String name; - Ref<Texture2D> icon; + Ref<Texture2D> frame = frames->get_frame(edited_anim, i); - if (frames->get_frame(edited_anim, i).is_null()) { + if (frame.is_null()) { name = itos(i) + ": " + TTR("(empty)"); - } else { - name = itos(i) + ": " + frames->get_frame(edited_anim, i)->get_name(); - icon = frames->get_frame(edited_anim, i); + name = itos(i) + ": " + frame->get_name(); } - tree->add_item(name, icon); - if (frames->get_frame(edited_anim, i).is_valid()) { - tree->set_item_tooltip(tree->get_item_count() - 1, frames->get_frame(edited_anim, i)->get_path()); + tree->add_item(name, frame); + if (frame.is_valid()) { + String tooltip = frame->get_path(); + + // Frame is often saved as an AtlasTexture subresource within a scene/resource file, + // thus its path might be not what the user is looking for. So we're also showing + // subsequent source texture paths. + String prefix = String::utf8("┖╴"); + Ref<AtlasTexture> at = frame; + while (at.is_valid() && at->get_atlas().is_valid()) { + tooltip += "\n" + prefix + at->get_atlas()->get_path(); + prefix = " " + prefix; + at = at->get_atlas(); + } + + tree->set_item_tooltip(-1, tooltip); } if (sel == i) { tree->select(tree->get_item_count() - 1); @@ -844,7 +980,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) { @@ -886,7 +1021,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(); @@ -910,7 +1045,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; @@ -954,7 +1089,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; @@ -992,7 +1127,7 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da if (String(d["type"]) == "files") { Vector<String> files = d["files"]; - if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { _prepare_sprite_sheet(files[0]); } else { _file_load_request(files, at_pos); @@ -1031,6 +1166,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); @@ -1164,7 +1306,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; @@ -1185,23 +1327,66 @@ SpriteFramesEditor::SpriteFramesEditor() { HBoxContainer *split_sheet_hb = memnew(HBoxContainer); - Label *ss_label = memnew(Label(TTR("Horizontal:"))); - split_sheet_hb->add_child(ss_label); + split_sheet_hb->add_child(memnew(Label(TTR("Horizontal:")))); split_sheet_h = memnew(SpinBox); split_sheet_h->set_min(1); split_sheet_h->set_max(128); split_sheet_h->set_step(1); split_sheet_hb->add_child(split_sheet_h); - split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed)); + split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT)); - ss_label = memnew(Label(TTR("Vertical:"))); - split_sheet_hb->add_child(ss_label); + split_sheet_hb->add_child(memnew(Label(TTR("Vertical:")))); split_sheet_v = memnew(SpinBox); split_sheet_v->set_min(1); split_sheet_v->set_max(128); split_sheet_v->set_step(1); split_sheet_hb->add_child(split_sheet_v); - split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed)); + split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT)); + + split_sheet_hb->add_child(memnew(VSeparator)); + split_sheet_hb->add_child(memnew(Label(TTR("Size:")))); + split_sheet_size_x = memnew(SpinBox); + split_sheet_size_x->set_min(1); + split_sheet_size_x->set_step(1); + split_sheet_size_x->set_suffix("px"); + split_sheet_size_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE)); + split_sheet_hb->add_child(split_sheet_size_x); + split_sheet_size_y = memnew(SpinBox); + split_sheet_size_y->set_min(1); + split_sheet_size_y->set_step(1); + split_sheet_size_y->set_suffix("px"); + split_sheet_size_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE)); + split_sheet_hb->add_child(split_sheet_size_y); + + split_sheet_hb->add_child(memnew(VSeparator)); + split_sheet_hb->add_child(memnew(Label(TTR("Separation:")))); + split_sheet_sep_x = memnew(SpinBox); + split_sheet_sep_x->set_min(0); + split_sheet_sep_x->set_step(1); + split_sheet_sep_x->set_suffix("px"); + split_sheet_sep_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_sep_x); + split_sheet_sep_y = memnew(SpinBox); + split_sheet_sep_y->set_min(0); + split_sheet_sep_y->set_step(1); + split_sheet_sep_y->set_suffix("px"); + split_sheet_sep_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_sep_y); + + split_sheet_hb->add_child(memnew(VSeparator)); + split_sheet_hb->add_child(memnew(Label(TTR("Offset:")))); + split_sheet_offset_x = memnew(SpinBox); + split_sheet_offset_x->set_min(0); + split_sheet_offset_x->set_step(1); + split_sheet_offset_x->set_suffix("px"); + split_sheet_offset_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_offset_x); + split_sheet_offset_y = memnew(SpinBox); + split_sheet_offset_y->set_min(0); + split_sheet_offset_y->set_step(1); + split_sheet_offset_y->set_suffix("px"); + split_sheet_offset_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT)); + split_sheet_hb->add_child(split_sheet_offset_y); split_sheet_hb->add_spacer(); @@ -1218,14 +1403,12 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_vb->add_child(split_sheet_panel); split_sheet_preview = memnew(TextureRect); - split_sheet_preview->set_expand(true); + split_sheet_preview->set_ignore_texture_size(true); split_sheet_preview->set_mouse_filter(MOUSE_FILTER_PASS); split_sheet_preview->connect("draw", callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw)); split_sheet_preview->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_preview_input)); split_sheet_scroll = memnew(ScrollContainer); - split_sheet_scroll->set_enable_h_scroll(true); - split_sheet_scroll->set_enable_v_scroll(true); split_sheet_scroll->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input)); split_sheet_panel->add_child(split_sheet_scroll); CenterContainer *cc = memnew(CenterContainer); @@ -1281,6 +1464,10 @@ 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) { @@ -1317,20 +1504,19 @@ bool SpriteFramesEditorPlugin::handles(Object *p_object) const { void SpriteFramesEditorPlugin::make_visible(bool p_visible) { if (p_visible) { button->show(); - editor->make_bottom_panel_item_visible(frames_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(frames_editor); } else { button->hide(); if (frames_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } } } -SpriteFramesEditorPlugin::SpriteFramesEditorPlugin(EditorNode *p_node) { - editor = p_node; +SpriteFramesEditorPlugin::SpriteFramesEditorPlugin() { frames_editor = memnew(SpriteFramesEditor); frames_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("SpriteFrames"), frames_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("SpriteFrames"), frames_editor); button->hide(); } diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 17e30f0cab..6352259b73 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,64 +31,83 @@ #ifndef SPRITE_FRAMES_EDITOR_PLUGIN_H #define SPRITE_FRAMES_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/animated_sprite_2d.h" +#include "scene/gui/button.h" +#include "scene/gui/check_button.h" #include "scene/gui/dialogs.h" -#include "scene/gui/file_dialog.h" +#include "scene/gui/item_list.h" +#include "scene/gui/line_edit.h" #include "scene/gui/scroll_container.h" +#include "scene/gui/spin_box.h" #include "scene/gui/split_container.h" #include "scene/gui/texture_rect.h" #include "scene/gui/tree.h" +class EditorFileDialog; + class SpriteFramesEditor : public HSplitContainer { GDCLASS(SpriteFramesEditor, HSplitContainer); - Button *load; - Button *load_sheet; - Button *_delete; - Button *copy; - Button *paste; - Button *empty; - Button *empty2; - Button *move_up; - Button *move_down; - Button *zoom_out; - Button *zoom_reset; - Button *zoom_in; - ItemList *tree; + enum { + PARAM_USE_CURRENT, // Used in callbacks to indicate `dominant_param` should be not updated. + PARAM_FRAME_COUNT, // Keep "Horizontal" & "Vertical" values. + PARAM_SIZE, // Keep "Size" values. + }; + int dominant_param = PARAM_FRAME_COUNT; + + Button *load = nullptr; + Button *load_sheet = nullptr; + Button *_delete = nullptr; + Button *copy = nullptr; + Button *paste = nullptr; + Button *empty = nullptr; + Button *empty2 = nullptr; + Button *move_up = nullptr; + Button *move_down = nullptr; + Button *zoom_out = nullptr; + Button *zoom_reset = nullptr; + Button *zoom_in = nullptr; + ItemList *tree = nullptr; bool loading_scene; int sel; - Button *new_anim; - Button *remove_anim; + Button *new_anim = nullptr; + Button *remove_anim = nullptr; + LineEdit *anim_search_box = nullptr; - Tree *animations; - SpinBox *anim_speed; - CheckButton *anim_loop; + Tree *animations = nullptr; + SpinBox *anim_speed = nullptr; + CheckButton *anim_loop = nullptr; - EditorFileDialog *file; + EditorFileDialog *file = nullptr; - AcceptDialog *dialog; + AcceptDialog *dialog = nullptr; - SpriteFrames *frames; + SpriteFrames *frames = nullptr; StringName edited_anim; - ConfirmationDialog *delete_dialog; - - ConfirmationDialog *split_sheet_dialog; - ScrollContainer *split_sheet_scroll; - TextureRect *split_sheet_preview; - SpinBox *split_sheet_h; - SpinBox *split_sheet_v; - Button *split_sheet_zoom_out; - Button *split_sheet_zoom_reset; - Button *split_sheet_zoom_in; - EditorFileDialog *file_split_sheet; - Set<int> frames_selected; - Set<int> frames_toggled_by_mouse_hover; - int last_frame_selected; + ConfirmationDialog *delete_dialog = nullptr; + + ConfirmationDialog *split_sheet_dialog = nullptr; + ScrollContainer *split_sheet_scroll = nullptr; + TextureRect *split_sheet_preview = nullptr; + SpinBox *split_sheet_h = nullptr; + SpinBox *split_sheet_v = nullptr; + SpinBox *split_sheet_size_x = nullptr; + SpinBox *split_sheet_size_y = nullptr; + SpinBox *split_sheet_sep_x = nullptr; + SpinBox *split_sheet_sep_y = nullptr; + SpinBox *split_sheet_offset_x = nullptr; + SpinBox *split_sheet_offset_y = nullptr; + Button *split_sheet_zoom_out = nullptr; + Button *split_sheet_zoom_reset = nullptr; + Button *split_sheet_zoom_in = nullptr; + EditorFileDialog *file_split_sheet = nullptr; + HashSet<int> frames_selected; + HashSet<int> frames_toggled_by_mouse_hover; + int last_frame_selected = 0; float scale_ratio; int thumbnail_default_size; @@ -99,8 +118,12 @@ class SpriteFramesEditor : public HSplitContainer { float max_sheet_zoom; float min_sheet_zoom; + Size2i _get_frame_count() const; + Size2i _get_frame_size() const; + Size2i _get_offset() const; + Size2i _get_separation() const; + void _load_pressed(); - void _load_scene_pressed(); void _file_load_request(const Vector<String> &p_path, int p_at_pos = -1); void _copy_pressed(); void _paste_pressed(); @@ -116,6 +139,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); @@ -125,21 +149,23 @@ class SpriteFramesEditor : public HSplitContainer { void _zoom_reset(); bool updating; + bool updating_split_settings = false; // Skip SpinBox/Range callback when setting value by code. - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; - bool _is_drop_valid(const Dictionary &p_drag_data, const Dictionary &p_item_data) const; 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); void _open_sprite_sheet(); void _prepare_sprite_sheet(const String &p_file); + int _sheet_preview_position_to_frame_index(const Vector2 &p_position); void _sheet_preview_draw(); - void _sheet_spin_changed(double); + void _sheet_spin_changed(double p_value, int p_dominant_param); void _sheet_preview_input(const Ref<InputEvent> &p_event); void _sheet_scroll_input(const Ref<InputEvent> &p_event); void _sheet_add_frames(); + void _sheet_zoom_on_position(float p_zoom, const Vector2 &p_position); void _sheet_zoom_in(); void _sheet_zoom_out(); void _sheet_zoom_reset(); @@ -147,7 +173,6 @@ class SpriteFramesEditor : public HSplitContainer { protected: void _notification(int p_what); - virtual void gui_input(const Ref<InputEvent> &p_event) override; static void _bind_methods(); public: @@ -160,9 +185,8 @@ public: class SpriteFramesEditorPlugin : public EditorPlugin { GDCLASS(SpriteFramesEditorPlugin, EditorPlugin); - SpriteFramesEditor *frames_editor; - EditorNode *editor; - Button *button; + SpriteFramesEditor *frames_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "SpriteFrames"; } @@ -171,7 +195,7 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - SpriteFramesEditorPlugin(EditorNode *p_node); + SpriteFramesEditorPlugin(); ~SpriteFramesEditorPlugin(); }; diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp index 91c5e96f08..d4baff34e2 100644 --- a/editor/plugins/style_box_editor_plugin.cpp +++ b/editor/plugins/style_box_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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; } @@ -44,13 +51,6 @@ void EditorInspectorPluginStyleBox::parse_begin(Object *p_object) { add_custom_control(preview); } -bool EditorInspectorPluginStyleBox::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, bool p_wide) { - return false; //do not want -} - -void EditorInspectorPluginStyleBox::parse_end() { -} - void StyleBoxPreview::edit(const Ref<StyleBox> &p_stylebox) { if (stylebox.is_valid()) { stylebox->disconnect("changed", callable_mp(this, &StyleBoxPreview::_sb_changed)); @@ -60,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(); } @@ -67,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); @@ -77,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); + } + } } } @@ -84,14 +123,26 @@ void StyleBoxPreview::_bind_methods() { } StyleBoxPreview::StyleBoxPreview() { + checkerboard = memnew(TextureRect); + checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); + checkerboard->set_texture_repeat(CanvasItem::TEXTURE_REPEAT_ENABLED); + checkerboard->set_custom_minimum_size(Size2(0.0, 150.0) * EDSCALE); + preview = memnew(Control); - preview->set_custom_minimum_size(Size2(0, 150 * EDSCALE)); preview->set_clip_contents(true); preview->connect("draw", callable_mp(this, &StyleBoxPreview::_redraw)); - add_margin_child(TTR("Preview:"), preview); + checkerboard->add_child(preview); + preview->set_anchors_and_offsets_preset(PRESET_FULL_RECT); + + add_margin_child(TTR("Preview:"), checkerboard); + grid_preview = memnew(TextureButton); + preview->add_child(grid_preview); + grid_preview->set_toggle_mode(true); + grid_preview->connect("toggled", callable_mp(this, &StyleBoxPreview::_grid_preview_toggled)); + grid_preview->set_pressed(grid_preview_enabled); } -StyleBoxEditorPlugin::StyleBoxEditorPlugin(EditorNode *p_node) { +StyleBoxEditorPlugin::StyleBoxEditorPlugin() { Ref<EditorInspectorPluginStyleBox> inspector_plugin; inspector_plugin.instantiate(); add_inspector_plugin(inspector_plugin); diff --git a/editor/plugins/style_box_editor_plugin.h b/editor/plugins/style_box_editor_plugin.h index 8ca348bd80..a072745d8f 100644 --- a/editor/plugins/style_box_editor_plugin.h +++ b/editor/plugins/style_box_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,7 +32,7 @@ #define STYLE_BOX_EDITOR_PLUGIN_H #include "editor/editor_inspector.h" -#include "editor/editor_node.h" +#include "editor/editor_plugin.h" #include "scene/gui/option_button.h" #include "scene/gui/texture_rect.h" #include "scene/resources/style_box.h" @@ -40,11 +40,16 @@ class StyleBoxPreview : public VBoxContainer { GDCLASS(StyleBoxPreview, VBoxContainer); - Control *preview; + TextureRect *checkerboard = nullptr; + TextureButton *grid_preview = nullptr; + Control *preview = nullptr; Ref<StyleBox> stylebox; void _sb_changed(); void _redraw(); + void _notification(int p_what); + static bool grid_preview_enabled; + void _grid_preview_toggled(bool p_active); protected: static void _bind_methods(); @@ -61,8 +66,6 @@ class EditorInspectorPluginStyleBox : public 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; - virtual void parse_end() override; }; class StyleBoxEditorPlugin : public EditorPlugin { @@ -71,7 +74,7 @@ class StyleBoxEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "StyleBox"; } - StyleBoxEditorPlugin(EditorNode *p_node); + StyleBoxEditorPlugin(); }; #endif // STYLE_BOX_EDITOR_PLUGIN_H diff --git a/editor/plugins/sub_viewport_preview_editor_plugin.cpp b/editor/plugins/sub_viewport_preview_editor_plugin.cpp index 75c47bda2e..c8bb0cd56f 100644 --- a/editor/plugins/sub_viewport_preview_editor_plugin.cpp +++ b/editor/plugins/sub_viewport_preview_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -43,7 +43,7 @@ void EditorInspectorPluginSubViewportPreview::parse_begin(Object *p_object) { add_custom_control(sub_viewport_preview); } -SubViewportPreviewEditorPlugin::SubViewportPreviewEditorPlugin(EditorNode *p_node) { +SubViewportPreviewEditorPlugin::SubViewportPreviewEditorPlugin() { Ref<EditorInspectorPluginSubViewportPreview> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/sub_viewport_preview_editor_plugin.h b/editor/plugins/sub_viewport_preview_editor_plugin.h index 03b8b678d1..269553ffb1 100644 --- a/editor/plugins/sub_viewport_preview_editor_plugin.h +++ b/editor/plugins/sub_viewport_preview_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H #define SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/plugins/texture_editor_plugin.h" #include "scene/main/viewport.h" @@ -50,7 +49,7 @@ class SubViewportPreviewEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "SubViewportPreview"; } - SubViewportPreviewEditorPlugin(EditorNode *p_node); + SubViewportPreviewEditorPlugin(); }; #endif // SUB_VIEWPORT_PREVIEW_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 32bcc1a4e6..196d87da36 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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(); @@ -65,18 +66,21 @@ void TextEditor::_load_theme_settings() { String TextEditor::get_name() { String name; - if (text_file->get_path().find("local://") == -1 && text_file->get_path().find("::") == -1) { - name = text_file->get_path().get_file(); - if (is_unsaved()) { - if (text_file->get_path().is_empty()) { - name = TTR("[unsaved]"); - } - name += "(*)"; + name = text_file->get_path().get_file(); + if (name.is_empty()) { + // This appears for newly created built-in text_files before saving the scene. + name = TTR("[unsaved]"); + } else if (text_file->is_built_in()) { + const String &text_file_name = text_file->get_name(); + if (!text_file_name.is_empty()) { + // If the built-in text_file has a custom resource name defined, + // display the built-in text_file name as follows: `ResourceName (scene_file.tscn)` + name = vformat("%s (%s)", text_file_name, name.get_slice("::", 0)); } - } else if (text_file->get_name() != "") { - name = text_file->get_name(); - } else { - name = text_file->get_class() + "(" + itos(text_file->get_instance_id()) + ")"; + } + + if (is_unsaved()) { + name += "(*)"; } return name; @@ -86,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()); @@ -176,7 +180,7 @@ void TextEditor::_update_bookmark_list() { } bookmarks_menu->add_item(String::num((int)bookmark_list[i] + 1) + " - \"" + line + "\""); - bookmarks_menu->set_item_metadata(bookmarks_menu->get_item_count() - 1, bookmark_list[i]); + bookmarks_menu->set_item_metadata(-1, bookmark_list[i]); } } @@ -269,8 +273,10 @@ void TextEditor::update_settings() { code_editor->update_editor_settings(); } -void TextEditor::set_tooltip_request_func(String p_method, Object *p_obj) { - code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this); +void TextEditor::set_tooltip_request_func(const Callable &p_toolip_callback) { + Variant args[1] = { this }; + const Variant *argp[] = { &args[0] }; + code_editor->get_text_editor()->set_tooltip_request_func(p_toolip_callback.bindp(argp, 1)); } Control *TextEditor::get_edit_menu() { @@ -407,11 +413,7 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } -void TextEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &TextEditor::add_syntax_highlighter); -} - -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); } @@ -426,7 +428,7 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { Ref<InputEventMouseButton> mb = ev; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::RIGHT) { CodeEdit *tx = code_editor->get_text_editor(); Point2i pos = tx->get_line_column_at_pos(mb->get_global_position() - tx->get_global_position()); @@ -508,19 +510,24 @@ void TextEditor::_make_context_menu(bool p_selection, bool p_can_fold, bool p_is context_menu->set_item_disabled(context_menu->get_item_index(EDIT_UNDO), !tx->has_undo()); context_menu->set_item_disabled(context_menu->get_item_index(EDIT_REDO), !tx->has_redo()); - context_menu->set_position(get_global_transform().xform(p_position)); - context_menu->set_size(Vector2(1, 1)); + context_menu->set_position(get_screen_position() + p_position); + context_menu->reset_size(); context_menu->popup(); } +void TextEditor::update_toggle_scripts_button() { + code_editor->update_toggle_scripts_button(); +} + TextEditor::TextEditor() { code_editor = memnew(CodeTextEditor); add_child(code_editor); 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(); update_settings(); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 839e1c5f7a..4f0121da52 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -87,14 +87,12 @@ private: }; protected: - static void _bind_methods(); - void _edit_option(int p_op); void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position); 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(); @@ -111,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; @@ -121,6 +119,8 @@ public: virtual void set_edit_state(const Variant &p_state) override; virtual Vector<String> get_functions() override; virtual Array 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; void goto_line_selection(int p_line, int p_begin, int p_end); virtual void set_executing_line(int p_line) override; @@ -135,8 +135,9 @@ public: virtual bool show_members_overview() override; virtual bool can_lose_focus_on_node_selection() override { return true; } virtual void set_debugger_active(bool p_active) override; - virtual void set_tooltip_request_func(String p_method, Object *p_obj) override; + virtual void set_tooltip_request_func(const Callable &p_toolip_callback) override; virtual void add_callback(const String &p_function, PackedStringArray p_args) override; + void update_toggle_scripts_button() override; virtual Control *get_edit_menu() override; virtual void clear_edit_menu() override; diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp index bd1923f4ab..64cafa17f3 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,27 +30,22 @@ #include "texture_3d_editor_plugin.h" -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "editor/editor_settings.h" - void Texture3DEditor::_texture_rect_draw() { texture_rect->draw_rect(Rect2(Point2(), texture_rect->get_size()), Color(1, 1, 1, 1)); } void Texture3DEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); - } - if (p_what == NOTIFICATION_RESIZED) { - _texture_rect_update_area(); - } + switch (p_what) { + case NOTIFICATION_RESIZED: { + _texture_rect_update_area(); + } break; - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - Size2 size = get_size(); + case NOTIFICATION_DRAW: { + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); + Size2 size = get_size(); - draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + } break; } } @@ -62,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()); @@ -173,7 +168,7 @@ Texture3DEditor::Texture3DEditor() { info->set_v_grow_direction(GROW_DIRECTION_BEGIN); info->add_theme_color_override("font_color", Color(1, 1, 1, 1)); info->add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.5)); - info->add_theme_constant_override("shadow_as_outline", 1); + info->add_theme_constant_override("shadow_outline_size", 1); info->add_theme_constant_override("shadow_offset_x", 2); info->add_theme_constant_override("shadow_offset_y", 2); @@ -204,7 +199,7 @@ void EditorInspectorPlugin3DTexture::parse_begin(Object *p_object) { add_custom_control(editor); } -Texture3DEditorPlugin::Texture3DEditorPlugin(EditorNode *p_node) { +Texture3DEditorPlugin::Texture3DEditorPlugin() { Ref<EditorInspectorPlugin3DTexture> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/texture_3d_editor_plugin.h b/editor/plugins/texture_3d_editor_plugin.h index 855194e644..357bdb0845 100644 --- a/editor/plugins/texture_3d_editor_plugin.h +++ b/editor/plugins/texture_3d_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,22 +31,22 @@ #ifndef TEXTURE_3D_EDITOR_PLUGIN_H #define TEXTURE_3D_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "scene/gui/spin_box.h" #include "scene/resources/shader.h" #include "scene/resources/texture.h" class Texture3DEditor : public Control { GDCLASS(Texture3DEditor, Control); - SpinBox *layer; - Label *info; + SpinBox *layer = nullptr; + Label *info = nullptr; Ref<Texture3D> texture; Ref<Shader> shader; Ref<ShaderMaterial> material; - Control *texture_rect; + Control *texture_rect = nullptr; void _make_shaders(); @@ -88,7 +88,7 @@ class Texture3DEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Texture3D"; } - Texture3DEditorPlugin(EditorNode *p_node); + Texture3DEditorPlugin(); }; -#endif // TEXTURE_EDITOR_PLUGIN_H +#endif // TEXTURE_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 44db06bcfd..be382759f5 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -58,6 +58,63 @@ void TexturePreview::_notification(int p_what) { } } +void TexturePreview::_update_metadata_label_text() { + const Ref<Texture2D> texture = texture_display->get_texture(); + + String format; + if (Object::cast_to<ImageTexture>(*texture)) { + format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format()); + } else if (Object::cast_to<CompressedTexture2D>(*texture)) { + format = Image::get_format_name(Object::cast_to<CompressedTexture2D>(*texture)->get_format()); + } else { + format = texture->get_class(); + } + + 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) { checkerboard = memnew(TextureRect); checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); @@ -67,34 +124,24 @@ 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_expand(true); + texture_display->set_ignore_texture_size(true); add_child(texture_display); if (p_show_metadata) { metadata_label = memnew(Label); - String format; - if (Object::cast_to<ImageTexture>(*p_texture)) { - format = Image::get_format_name(Object::cast_to<ImageTexture>(*p_texture)->get_format()); - } else if (Object::cast_to<StreamTexture2D>(*p_texture)) { - format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*p_texture)->get_format()); - } else { - format = p_texture->get_class(); - } - - metadata_label->set_text(itos(p_texture->get_width()) + "x" + itos(p_texture->get_height()) + " " + format); + _update_metadata_label_text(); + p_texture->connect("changed", callable_mp(this, &TexturePreview::_update_metadata_label_text)); // 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_as_outline", 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); @@ -103,7 +150,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { } bool EditorInspectorPluginTexture::can_handle(Object *p_object) { - return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<StreamTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr; + return Object::cast_to<ImageTexture>(p_object) != nullptr || Object::cast_to<AtlasTexture>(p_object) != nullptr || Object::cast_to<CompressedTexture2D>(p_object) != nullptr || Object::cast_to<AnimatedTexture>(p_object) != nullptr; } void EditorInspectorPluginTexture::parse_begin(Object *p_object) { @@ -112,7 +159,7 @@ void EditorInspectorPluginTexture::parse_begin(Object *p_object) { add_custom_control(memnew(TexturePreview(texture, true))); } -TextureEditorPlugin::TextureEditorPlugin(EditorNode *p_node) { +TextureEditorPlugin::TextureEditorPlugin() { Ref<EditorInspectorPluginTexture> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 36a5513ea6..9beada556c 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,7 +31,6 @@ #ifndef TEXTURE_EDITOR_PLUGIN_H #define TEXTURE_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/resources/texture.h" @@ -44,6 +43,8 @@ private: TextureRect *checkerboard = nullptr; Label *metadata_label = nullptr; + void _update_metadata_label_text(); + protected: void _notification(int p_what); @@ -66,7 +67,7 @@ class TextureEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "Texture2D"; } - TextureEditorPlugin(EditorNode *p_node); + TextureEditorPlugin(); }; #endif // TEXTURE_EDITOR_PLUGIN_H diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 424e018a47..2c6f70463d 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,15 +30,11 @@ #include "texture_layered_editor_plugin.h" -#include "core/config/project_settings.h" -#include "core/io/resource_loader.h" -#include "editor/editor_settings.h" - void TextureLayeredEditor::gui_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + if (mm.is_valid() && (mm->get_button_mask() & MouseButton::MASK_LEFT) != MouseButton::NONE) { y_rot += -mm->get_relative().x * 0.01; x_rot += mm->get_relative().y * 0.01; _update_material(); @@ -50,18 +46,17 @@ void TextureLayeredEditor::_texture_rect_draw() { } void TextureLayeredEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - //get_scene()->connect("node_removed",this,"_node_removed"); - } - if (p_what == NOTIFICATION_RESIZED) { - _texture_rect_update_area(); - } + switch (p_what) { + case NOTIFICATION_RESIZED: { + _texture_rect_update_area(); + } break; - if (p_what == NOTIFICATION_DRAW) { - Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); - Size2 size = get_size(); + case NOTIFICATION_DRAW: { + Ref<Texture2D> checkerboard = get_theme_icon(SNAME("Checkerboard"), SNAME("EditorIcons")); + Size2 size = get_size(); - draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + draw_texture_rect(checkerboard, Rect2(Point2(), size), true); + } break; } } @@ -73,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(); @@ -84,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()); @@ -249,7 +244,7 @@ TextureLayeredEditor::TextureLayeredEditor() { info->set_v_grow_direction(GROW_DIRECTION_BEGIN); info->add_theme_color_override("font_color", Color(1, 1, 1, 1)); info->add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.5)); - info->add_theme_constant_override("shadow_as_outline", 1); + info->add_theme_constant_override("shadow_outline_size", 1); info->add_theme_constant_override("shadow_offset_x", 2); info->add_theme_constant_override("shadow_offset_y", 2); @@ -277,7 +272,7 @@ void EditorInspectorPluginLayeredTexture::parse_begin(Object *p_object) { add_custom_control(editor); } -TextureLayeredEditorPlugin::TextureLayeredEditorPlugin(EditorNode *p_node) { +TextureLayeredEditorPlugin::TextureLayeredEditorPlugin() { Ref<EditorInspectorPluginLayeredTexture> plugin; plugin.instantiate(); add_inspector_plugin(plugin); diff --git a/editor/plugins/texture_layered_editor_plugin.h b/editor/plugins/texture_layered_editor_plugin.h index a7fe4b94e9..f49aa83eb2 100644 --- a/editor/plugins/texture_layered_editor_plugin.h +++ b/editor/plugins/texture_layered_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,16 +31,16 @@ #ifndef TEXTURE_LAYERED_EDITOR_PLUGIN_H #define TEXTURE_LAYERED_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "scene/gui/spin_box.h" #include "scene/resources/shader.h" #include "scene/resources/texture.h" class TextureLayeredEditor : public Control { GDCLASS(TextureLayeredEditor, Control); - SpinBox *layer; - Label *info; + SpinBox *layer = nullptr; + Label *info = nullptr; Ref<TextureLayered> texture; Ref<Shader> shaders[3]; @@ -48,7 +48,7 @@ class TextureLayeredEditor : public Control { float x_rot = 0; float y_rot = 0; - Control *texture_rect; + Control *texture_rect = nullptr; void _make_shaders(); @@ -90,7 +90,7 @@ class TextureLayeredEditorPlugin : public EditorPlugin { public: virtual String get_name() const override { return "TextureLayered"; } - TextureLayeredEditorPlugin(EditorNode *p_node); + TextureLayeredEditorPlugin(); }; -#endif // TEXTURE_EDITOR_PLUGIN_H +#endif // TEXTURE_LAYERED_EDITOR_PLUGIN_H diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 1a6eb7b63b..0bd8a8a484 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,12 +33,13 @@ #include "core/core_string_names.h" #include "core/input/input.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" +#include "editor/editor_settings.h" #include "scene/gui/check_box.h" - -/** - @author Mariano Suligoy -*/ +#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; @@ -50,7 +51,7 @@ void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) { EditorNode::get_singleton()->get_theme_base()->get_theme_color(SNAME("mono_color"), SNAME("Editor")).inverted() * Color(1, 1, 1, 0.5), Math::round(2 * EDSCALE)); - while ((to - from).length_squared() > 200) { + while (from.distance_squared_to(to) > 200) { edit_draw->draw_line( from, from + line, @@ -63,16 +64,16 @@ void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) { void TextureRegionEditor::_region_draw() { Ref<Texture2D> base_tex = nullptr; - if (node_sprite) { - base_tex = node_sprite->get_texture(); + if (atlas_tex.is_valid()) { + base_tex = atlas_tex->get_atlas(); + } else if (node_sprite_2d) { + base_tex = node_sprite_2d->get_texture(); } else if (node_sprite_3d) { base_tex = node_sprite_3d->get_texture(); } else if (node_ninepatch) { base_tex = node_ninepatch->get_texture(); } else if (obj_styleBox.is_valid()) { base_tex = obj_styleBox->get_texture(); - } else if (atlas_tex.is_valid()) { - base_tex = atlas_tex->get_atlas(); } if (base_tex.is_null()) { @@ -80,15 +81,18 @@ void TextureRegionEditor::_region_draw() { } Transform2D mtx; - mtx.elements[2] = -draw_ofs * draw_zoom; + mtx.columns[2] = -draw_ofs * draw_zoom; mtx.scale_basis(Vector2(draw_zoom, draw_zoom)); RS::get_singleton()->canvas_item_add_set_transform(edit_draw->get_canvas_item(), mtx); + edit_draw->draw_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()); + const Color color = get_theme_color(SNAME("mono_color"), SNAME("Editor")); + if (snap_mode == SNAP_GRID) { - Color grid_color = Color(1.0, 1.0, 1.0, 0.15); + const Color grid_color = Color(color.r, color.g, color.b, color.a * 0.15); Size2 s = edit_draw->get_size(); int last_cell = 0; @@ -145,7 +149,7 @@ void TextureRegionEditor::_region_draw() { } } else if (snap_mode == SNAP_AUTOSLICE) { for (const Rect2 &r : autoslice_cache) { - Vector2 endpoints[4] = { + const Vector2 endpoints[4] = { mtx.basis_xform(r.position), mtx.basis_xform(r.position + Vector2(r.size.x, 0)), mtx.basis_xform(r.position + r.size), @@ -174,7 +178,6 @@ void TextureRegionEditor::_region_draw() { mtx.basis_xform(raw_endpoints[2]), mtx.basis_xform(raw_endpoints[3]) }; - Color color = get_theme_color(SNAME("mono_color"), SNAME("Editor")); for (int i = 0; i < 4; i++) { int prev = (i + 3) % 4; int next = (i + 1) % 4; @@ -229,11 +232,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) { @@ -263,8 +274,12 @@ void TextureRegionEditor::_region_draw() { } void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { + if (panner->gui_input(p_input)) { + return; + } + Transform2D mtx; - mtx.elements[2] = -draw_ofs * draw_zoom; + mtx.columns[2] = -draw_ofs * draw_zoom; mtx.scale_basis(Vector2(draw_zoom, draw_zoom)); const real_t handle_radius = 8 * EDSCALE; @@ -284,8 +299,8 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { Ref<InputEventMouseButton> mb = p_input; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (mb->is_pressed()) { + if (mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed() && !panner->is_panning()) { if (node_ninepatch || obj_styleBox.is_valid()) { edited_margin = -1; float margins[4] = { 0 }; @@ -321,35 +336,38 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { prev_margin = margins[3]; } if (edited_margin >= 0) { - drag_from = Vector2(mb->get_position().x, mb->get_position().y); + drag_from = mb->get_position(); drag = true; } } if (edited_margin < 0 && snap_mode == SNAP_AUTOSLICE) { - Vector2 point = mtx.affine_inverse().xform(Vector2(mb->get_position().x, mb->get_position().y)); + Vector2 point = mtx.affine_inverse().xform(mb->get_position()); for (const Rect2 &E : autoslice_cache) { if (E.has_point(point)) { rect = E; - if (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !(Input::get_singleton()->is_key_pressed(Key(KEY_SHIFT | KEY_ALT)))) { + if (Input::get_singleton()->is_key_pressed(Key::CTRL) && !(Input::get_singleton()->is_key_pressed(Key(Key::SHIFT | Key::ALT)))) { Rect2 r; - if (node_sprite) { - r = node_sprite->get_region_rect(); + if (atlas_tex.is_valid()) { + r = atlas_tex->get_region(); + } else if (node_sprite_2d) { + r = node_sprite_2d->get_region_rect(); } else if (node_sprite_3d) { r = node_sprite_3d->get_region_rect(); } else if (node_ninepatch) { r = node_ninepatch->get_region_rect(); } else if (obj_styleBox.is_valid()) { r = obj_styleBox->get_region_rect(); - } else if (atlas_tex.is_valid()) { - r = atlas_tex->get_region(); } rect.expand_to(r.position); - rect.expand_to(r.position + r.size); + rect.expand_to(r.get_end()); } undo_redo->create_action(TTR("Set Region Rect")); - if (node_sprite) { - undo_redo->add_do_method(node_sprite, "set_region_rect", rect); - undo_redo->add_undo_method(node_sprite, "set_region_rect", node_sprite->get_region_rect()); + if (atlas_tex.is_valid()) { + undo_redo->add_do_method(atlas_tex.ptr(), "set_region", rect); + undo_redo->add_undo_method(atlas_tex.ptr(), "set_region", atlas_tex->get_region()); + } else if (node_sprite_2d) { + undo_redo->add_do_method(node_sprite_2d, "set_region_rect", rect); + undo_redo->add_undo_method(node_sprite_2d, "set_region_rect", node_sprite_2d->get_region_rect()); } else if (node_sprite_3d) { undo_redo->add_do_method(node_sprite_3d, "set_region_rect", rect); undo_redo->add_undo_method(node_sprite_3d, "set_region_rect", node_sprite_3d->get_region_rect()); @@ -359,9 +377,6 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } else if (obj_styleBox.is_valid()) { undo_redo->add_do_method(obj_styleBox.ptr(), "set_region_rect", rect); undo_redo->add_undo_method(obj_styleBox.ptr(), "set_region_rect", obj_styleBox->get_region_rect()); - } else if (atlas_tex.is_valid()) { - undo_redo->add_do_method(atlas_tex.ptr(), "set_region", rect); - undo_redo->add_undo_method(atlas_tex.ptr(), "set_region", atlas_tex->get_region()); } undo_redo->add_do_method(this, "_update_rect"); undo_redo->add_undo_method(this, "_update_rect"); @@ -372,28 +387,28 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } } } else if (edited_margin < 0) { - drag_from = mtx.affine_inverse().xform(Vector2(mb->get_position().x, mb->get_position().y)); + drag_from = mtx.affine_inverse().xform(mb->get_position()); if (snap_mode == SNAP_PIXEL) { drag_from = drag_from.snapped(Vector2(1, 1)); } else if (snap_mode == SNAP_GRID) { drag_from = snap_point(drag_from); } drag = true; - if (node_sprite) { - rect_prev = node_sprite->get_region_rect(); + if (atlas_tex.is_valid()) { + rect_prev = atlas_tex->get_region(); + } else if (node_sprite_2d) { + rect_prev = node_sprite_2d->get_region_rect(); } else if (node_sprite_3d) { rect_prev = node_sprite_3d->get_region_rect(); } else if (node_ninepatch) { rect_prev = node_ninepatch->get_region_rect(); } else if (obj_styleBox.is_valid()) { rect_prev = obj_styleBox->get_region_rect(); - } else if (atlas_tex.is_valid()) { - rect_prev = atlas_tex->get_region(); } for (int i = 0; i < 8; i++) { Vector2 tuv = endpoints[i]; - if (tuv.distance_to(Vector2(mb->get_position().x, mb->get_position().y)) < handle_radius) { + if (tuv.distance_to(mb->get_position()) < handle_radius) { drag_index = i; } } @@ -404,7 +419,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } } - } else if (drag) { + } else if (!mb->is_pressed() && drag) { if (edited_margin >= 0) { undo_redo->create_action(TTR("Set Margin")); static Side side[4] = { SIDE_TOP, SIDE_BOTTOM, SIDE_LEFT, SIDE_RIGHT }; @@ -419,15 +434,15 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { edited_margin = -1; } else { undo_redo->create_action(TTR("Set Region Rect")); - if (node_sprite) { - undo_redo->add_do_method(node_sprite, "set_region_rect", node_sprite->get_region_rect()); - undo_redo->add_undo_method(node_sprite, "set_region_rect", rect_prev); + if (atlas_tex.is_valid()) { + undo_redo->add_do_method(atlas_tex.ptr(), "set_region", atlas_tex->get_region()); + undo_redo->add_undo_method(atlas_tex.ptr(), "set_region", rect_prev); + } else if (node_sprite_2d) { + undo_redo->add_do_method(node_sprite_2d, "set_region_rect", node_sprite_2d->get_region_rect()); + undo_redo->add_undo_method(node_sprite_2d, "set_region_rect", rect_prev); } else if (node_sprite_3d) { undo_redo->add_do_method(node_sprite_3d, "set_region_rect", node_sprite_3d->get_region_rect()); undo_redo->add_undo_method(node_sprite_3d, "set_region_rect", rect_prev); - } else if (atlas_tex.is_valid()) { - undo_redo->add_do_method(atlas_tex.ptr(), "set_region", atlas_tex->get_region()); - undo_redo->add_undo_method(atlas_tex.ptr(), "set_region", rect_prev); } else if (node_ninepatch) { undo_redo->add_do_method(node_ninepatch, "set_region_rect", node_ninepatch->get_region_rect()); undo_redo->add_undo_method(node_ninepatch, "set_region_rect", rect_prev); @@ -446,7 +461,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { creating = false; } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed()) { + } else if (mb->get_button_index() == MouseButton::RIGHT && mb->is_pressed()) { if (drag) { drag = false; if (edited_margin >= 0) { @@ -465,21 +480,13 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { drag_index = -1; } } - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed()) { - _zoom_on_position(draw_zoom * ((0.95 + (0.05 * mb->get_factor())) / 0.95), mb->get_position()); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { - _zoom_on_position(draw_zoom * (1 - (0.05 * mb->get_factor())), mb->get_position()); } } Ref<InputEventMouseMotion> mm = p_input; if (mm.is_valid()) { - if (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { - Vector2 dragged(mm->get_relative().x / draw_zoom, mm->get_relative().y / draw_zoom); - hscroll->set_value(hscroll->get_value() - dragged.x); - vscroll->set_value(vscroll->get_value() - dragged.y); - } else if (drag) { + if (drag) { if (edited_margin >= 0) { float new_margin = 0; @@ -544,7 +551,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { switch (drag_index) { case 0: { - Vector2 p = rect_prev.position + rect_prev.size; + Vector2 p = rect_prev.get_end(); rect = Rect2(p, Size2()); rect.expand_to(new_pos); apply_rect(rect); @@ -609,6 +616,24 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { } } +void TextureRegionEditor::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { + _pan_callback(-p_scroll_vec * 32); +} + +void TextureRegionEditor::_pan_callback(Vector2 p_scroll_vec) { + p_scroll_vec /= draw_zoom; + hscroll->set_value(hscroll->get_value() - p_scroll_vec.x); + vscroll->set_value(vscroll->get_value() - p_scroll_vec.y); +} + +void TextureRegionEditor::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { + if (p_scroll_vec.y < 0) { + _zoom_on_position(draw_zoom * ((0.95 + (0.05 * Math::abs(p_scroll_vec.y))) / 0.95), p_origin); + } else { + _zoom_on_position(draw_zoom * (1 - (0.05 * Math::abs(p_scroll_vec.y))), p_origin); + } +} + void TextureRegionEditor::_scroll_changed(float) { if (updating_scroll) { return; @@ -674,8 +699,7 @@ void TextureRegionEditor::_zoom_on_position(float p_zoom, Point2 p_position) { draw_zoom = p_zoom; Point2 ofs = p_position; ofs = ofs / prev_zoom - ofs / draw_zoom; - draw_ofs.x = Math::round(draw_ofs.x + ofs.x); - draw_ofs.y = Math::round(draw_ofs.y + ofs.y); + draw_ofs = (draw_ofs + ofs).round(); edit_draw->update(); } @@ -693,22 +717,24 @@ void TextureRegionEditor::_zoom_out() { } void TextureRegionEditor::apply_rect(const Rect2 &p_rect) { - if (node_sprite) { - node_sprite->set_region_rect(p_rect); + if (atlas_tex.is_valid()) { + atlas_tex->set_region(p_rect); + } else if (node_sprite_2d) { + node_sprite_2d->set_region_rect(p_rect); } else if (node_sprite_3d) { node_sprite_3d->set_region_rect(p_rect); } else if (node_ninepatch) { node_ninepatch->set_region_rect(p_rect); } else if (obj_styleBox.is_valid()) { obj_styleBox->set_region_rect(p_rect); - } else if (atlas_tex.is_valid()) { - atlas_tex->set_region(p_rect); } } void TextureRegionEditor::_update_rect() { - if (node_sprite) { - rect = node_sprite->get_region_rect(); + if (atlas_tex.is_valid()) { + rect = atlas_tex->get_region(); + } else if (node_sprite_2d) { + rect = node_sprite_2d->get_region_rect(); } else if (node_sprite_3d) { rect = node_sprite_3d->get_region_rect(); } else if (node_ninepatch) { @@ -718,8 +744,6 @@ void TextureRegionEditor::_update_rect() { } } else if (obj_styleBox.is_valid()) { rect = obj_styleBox->get_region_rect(); - } else if (atlas_tex.is_valid()) { - rect = atlas_tex->get_region(); } } @@ -728,16 +752,16 @@ void TextureRegionEditor::_update_autoslice() { autoslice_cache.clear(); Ref<Texture2D> texture = nullptr; - if (node_sprite) { - texture = node_sprite->get_texture(); + if (atlas_tex.is_valid()) { + texture = atlas_tex->get_atlas(); + } else if (node_sprite_2d) { + texture = node_sprite_2d->get_texture(); } else if (node_sprite_3d) { texture = node_sprite_3d->get_texture(); } else if (node_ninepatch) { texture = node_ninepatch->get_texture(); } else if (obj_styleBox.is_valid()) { texture = obj_styleBox->get_texture(); - } else if (atlas_tex.is_valid()) { - texture = atlas_tex->get_atlas(); } if (texture.is_null()) { @@ -805,8 +829,12 @@ 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: { + 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"))); } break; case NOTIFICATION_VISIBILITY_CHANGED: { if (snap_mode == SNAP_AUTOSLICE && is_visible() && autoslice_is_dirty) { @@ -823,8 +851,8 @@ void TextureRegionEditor::_notification(int p_what) { } void TextureRegionEditor::_node_removed(Object *p_obj) { - if (p_obj == node_sprite || p_obj == node_sprite_3d || p_obj == node_ninepatch || p_obj == obj_styleBox.ptr() || p_obj == atlas_tex.ptr()) { - node_sprite = nullptr; + if (p_obj == node_sprite_2d || p_obj == node_sprite_3d || p_obj == node_ninepatch || p_obj == obj_styleBox.ptr() || p_obj == atlas_tex.ptr()) { + node_sprite_2d = nullptr; node_sprite_3d = nullptr; node_ninepatch = nullptr; obj_styleBox = Ref<StyleBox>(nullptr); @@ -852,17 +880,17 @@ bool TextureRegionEditor::is_ninepatch() { return node_ninepatch != nullptr; } -Sprite3D *TextureRegionEditor::get_sprite_3d() { - return node_sprite_3d; +Sprite2D *TextureRegionEditor::get_sprite_2d() { + return node_sprite_2d; } -Sprite2D *TextureRegionEditor::get_sprite() { - return node_sprite; +Sprite3D *TextureRegionEditor::get_sprite_3d() { + return node_sprite_3d; } void TextureRegionEditor::edit(Object *p_obj) { - if (node_sprite) { - node_sprite->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + if (node_sprite_2d) { + node_sprite_2d->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } if (node_sprite_3d) { node_sprite_3d->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); @@ -877,7 +905,7 @@ void TextureRegionEditor::edit(Object *p_obj) { atlas_tex->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } if (p_obj) { - node_sprite = Object::cast_to<Sprite2D>(p_obj); + node_sprite_2d = Object::cast_to<Sprite2D>(p_obj); node_sprite_3d = Object::cast_to<Sprite3D>(p_obj); node_ninepatch = Object::cast_to<NinePatchRect>(p_obj); @@ -898,19 +926,15 @@ void TextureRegionEditor::edit(Object *p_obj) { } _edit_region(); } else { - node_sprite = nullptr; + node_sprite_2d = nullptr; node_sprite_3d = nullptr; node_ninepatch = nullptr; obj_styleBox = Ref<StyleBoxTexture>(nullptr); atlas_tex = Ref<AtlasTexture>(nullptr); } edit_draw->update(); - if ((node_sprite && !node_sprite->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() { @@ -922,16 +946,16 @@ void TextureRegionEditor::_texture_changed() { void TextureRegionEditor::_edit_region() { Ref<Texture2D> texture = nullptr; - if (node_sprite) { - texture = node_sprite->get_texture(); + if (atlas_tex.is_valid()) { + texture = atlas_tex->get_atlas(); + } else if (node_sprite_2d) { + texture = node_sprite_2d->get_texture(); } else if (node_sprite_3d) { texture = node_sprite_3d->get_texture(); } else if (node_ninepatch) { texture = node_ninepatch->get_texture(); } else if (obj_styleBox.is_valid()) { texture = obj_styleBox->get_texture(); - } else if (atlas_tex.is_valid()) { - texture = atlas_tex->get_atlas(); } if (texture.is_null()) { @@ -966,14 +990,16 @@ Vector2 TextureRegionEditor::snap_point(Vector2 p_target) const { return p_target; } -TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { - node_sprite = nullptr; +TextureRegionEditor::TextureRegionEditor() { + set_ok_button_text(TTR("Close")); + VBoxContainer *vb = memnew(VBoxContainer); + add_child(vb); + node_sprite_2d = nullptr; node_sprite_3d = nullptr; node_ninepatch = nullptr; obj_styleBox = Ref<StyleBoxTexture>(nullptr); atlas_tex = Ref<AtlasTexture>(nullptr); - editor = p_editor; - undo_redo = editor->get_undo_redo(); + undo_redo = EditorNode::get_singleton()->get_undo_redo(); snap_step = Vector2(10, 10); snap_separation = Vector2(0, 0); @@ -983,7 +1009,7 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { drag = false; HBoxContainer *hb_tools = memnew(HBoxContainer); - add_child(hb_tools); + vb->add_child(hb_tools); hb_tools->add_child(memnew(Label(TTR("Snap Mode:")))); snap_mode_button = memnew(OptionButton); @@ -1063,11 +1089,16 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { hb_grid->hide(); + panner.instantiate(); + 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(Control::FOCUS_CLICK); draw_zoom = 1.0; edit_draw->set_clip_contents(true); @@ -1105,87 +1136,39 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { updating_scroll = false; autoslice_is_dirty = true; -} -void TextureRegionEditorPlugin::edit(Object *p_object) { - region_editor->edit(p_object); + set_title(TTR("Region Editor")); } -bool TextureRegionEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("Sprite2D") || p_object->is_class("Sprite3D") || p_object->is_class("NinePatchRect") || p_object->is_class("StyleBoxTexture") || p_object->is_class("AtlasTexture"); -} - -void TextureRegionEditorPlugin::_editor_visiblity_changed() { - manually_hidden = !region_editor->is_visible_in_tree(); -} +//////////////////////// -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() || (region_editor->get_sprite() && region_editor->get_sprite()->is_region_enabled()) || (region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region_enabled()); - if ((is_node_configured && !manually_hidden) || texture_region_button->is_pressed()) { - editor->make_bottom_panel_item_visible(region_editor); - } - } else { - if (region_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); - manually_hidden = false; - } - texture_region_button->hide(); - region_editor->edit(nullptr); - } +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); } -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 EditorInspectorPluginTextureRegion::_region_edit(Object *p_object) { + texture_region_editor->edit(p_object); } -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(EditorNode *p_node) { - manually_hidden = false; - editor = p_node; - - region_editor = memnew(TextureRegionEditor(p_node)); - region_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); - region_editor->hide(); - region_editor->connect("visibility_changed", callable_mp(this, &TextureRegionEditorPlugin::_editor_visiblity_changed)); - - texture_region_button = p_node->add_bottom_panel_item(TTR("TextureRegion"), region_editor); - texture_region_button->hide(); +TextureRegionEditorPlugin::TextureRegionEditorPlugin() { + Ref<EditorInspectorPluginTextureRegion> inspector_plugin; + inspector_plugin.instantiate(); + add_inspector_plugin(inspector_plugin); } diff --git a/editor/plugins/texture_region_editor_plugin.h b/editor/plugins/texture_region_editor_plugin.h index d3db0a08a9..a18c87f153 100644 --- a/editor/plugins/texture_region_editor_plugin.h +++ b/editor/plugins/texture_region_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,7 +32,6 @@ #define TEXTURE_REGION_EDITOR_PLUGIN_H #include "canvas_item_editor_plugin.h" -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/2d/sprite_2d.h" #include "scene/3d/sprite_3d.h" @@ -40,12 +39,10 @@ #include "scene/resources/style_box.h" #include "scene/resources/texture.h" -/** - @author Mariano Suligoy -*/ +class ViewPanner; -class TextureRegionEditor : public VBoxContainer { - GDCLASS(TextureRegionEditor, VBoxContainer); +class TextureRegionEditor : public AcceptDialog { + GDCLASS(TextureRegionEditor, AcceptDialog); enum SnapMode { SNAP_NONE, @@ -55,52 +52,57 @@ class TextureRegionEditor : public VBoxContainer { }; friend class TextureRegionEditorPlugin; - OptionButton *snap_mode_button; - Button *zoom_in; - Button *zoom_reset; - Button *zoom_out; - HBoxContainer *hb_grid; //For showing/hiding the grid controls when changing the SnapMode - SpinBox *sb_step_y; - SpinBox *sb_step_x; - SpinBox *sb_off_y; - SpinBox *sb_off_x; - SpinBox *sb_sep_y; - SpinBox *sb_sep_x; - Panel *edit_draw; - - VScrollBar *vscroll; - HScrollBar *hscroll; - - EditorNode *editor; - UndoRedo *undo_redo; + OptionButton *snap_mode_button = nullptr; + Button *zoom_in = nullptr; + Button *zoom_reset = nullptr; + Button *zoom_out = nullptr; + HBoxContainer *hb_grid = nullptr; //For showing/hiding the grid controls when changing the SnapMode + SpinBox *sb_step_y = nullptr; + SpinBox *sb_step_x = nullptr; + SpinBox *sb_off_y = nullptr; + SpinBox *sb_off_x = nullptr; + SpinBox *sb_sep_y = nullptr; + SpinBox *sb_sep_x = nullptr; + Panel *edit_draw = nullptr; + + VScrollBar *vscroll = nullptr; + HScrollBar *hscroll = nullptr; + + UndoRedo *undo_redo = nullptr; Vector2 draw_ofs; - float draw_zoom; - bool updating_scroll; + float draw_zoom = 0.0; + bool updating_scroll = false; - int snap_mode; + int snap_mode = 0; Vector2 snap_offset; Vector2 snap_step; Vector2 snap_separation; - Sprite2D *node_sprite; - Sprite3D *node_sprite_3d; - NinePatchRect *node_ninepatch; + Sprite2D *node_sprite_2d = nullptr; + Sprite3D *node_sprite_3d = nullptr; + NinePatchRect *node_ninepatch = nullptr; Ref<StyleBoxTexture> obj_styleBox; Ref<AtlasTexture> atlas_tex; 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); + void _pan_callback(Vector2 p_scroll_vec); + void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); void _set_snap_mode(int p_mode); void _set_snap_off_x(float p_val); @@ -134,36 +136,36 @@ public: bool is_stylebox(); bool is_atlas_texture(); bool is_ninepatch(); + Sprite2D *get_sprite_2d(); Sprite3D *get_sprite_3d(); - Sprite2D *get_sprite(); void edit(Object *p_obj); - TextureRegionEditor(EditorNode *p_editor); + TextureRegionEditor(); }; -class TextureRegionEditorPlugin : public EditorPlugin { - GDCLASS(TextureRegionEditorPlugin, EditorPlugin); +// - bool manually_hidden; - Button *texture_region_button; - TextureRegionEditor *region_editor; - EditorNode *editor; +class EditorInspectorPluginTextureRegion : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginTextureRegion, EditorInspectorPlugin); -protected: - static void _bind_methods(); + TextureRegionEditor *texture_region_editor = nullptr; - void _editor_visiblity_changed(); + void _region_edit(Object *p_object); + +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(EditorNode *p_node); + + TextureRegionEditorPlugin(); }; #endif // TEXTURE_REGION_EDITOR_PLUGIN_H diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 165a381407..bbc22c8622 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,12 @@ #include "theme_editor_plugin.h" #include "core/os/keyboard.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_resource_picker.h" #include "editor/editor_scale.h" #include "editor/progress_dialog.h" +#include "scene/gui/color_picker.h" void ThemeItemImportTree::_update_items_tree() { import_items_tree->clear(); @@ -68,9 +71,17 @@ void ThemeItemImportTree::_update_items_tree() { for (const StringName &E : types) { String type_name = (String)E; + Ref<Texture2D> type_icon; + if (E == "") { + type_icon = get_theme_icon(SNAME("NodeDisabled"), SNAME("EditorIcons")); + } else { + type_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled"); + } + TreeItem *type_node = import_items_tree->create_item(root); type_node->set_meta("_can_be_imported", false); type_node->set_collapsed(true); + type_node->set_icon(0, type_icon); type_node->set_text(0, type_name); type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK); type_node->set_checked(IMPORT_ITEM, false); @@ -81,8 +92,6 @@ void ThemeItemImportTree::_update_items_tree() { bool is_matching_filter = (filter_text.is_empty() || type_name.findn(filter_text) > -1); bool has_filtered_items = false; - bool any_checked = false; - bool any_checked_with_data = false; for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) { Theme::DataType dt = (Theme::DataType)i; @@ -178,9 +187,6 @@ void ThemeItemImportTree::_update_items_tree() { break; // Can't happen, but silences warning. } - bool data_type_any_checked = false; - bool data_type_any_checked_with_data = false; - filtered_names.sort_custom<StringName::AlphCompare>(); for (const StringName &F : filtered_names) { TreeItem *item_node = import_items_tree->create_item(data_type_node); @@ -194,20 +200,11 @@ void ThemeItemImportTree::_update_items_tree() { item_node->set_editable(IMPORT_ITEM_DATA, true); _restore_selected_item(item_node); - if (item_node->is_checked(IMPORT_ITEM)) { - data_type_any_checked = true; - any_checked = true; - } - if (item_node->is_checked(IMPORT_ITEM_DATA)) { - data_type_any_checked_with_data = true; - any_checked_with_data = true; - } + item_node->propagate_check(IMPORT_ITEM, false); + item_node->propagate_check(IMPORT_ITEM_DATA, false); item_list->push_back(item_node); } - - data_type_node->set_checked(IMPORT_ITEM, data_type_any_checked); - data_type_node->set_checked(IMPORT_ITEM_DATA, data_type_any_checked && data_type_any_checked_with_data); } // Remove the item if it doesn't match the filter in any way. @@ -221,15 +218,12 @@ void ThemeItemImportTree::_update_items_tree() { if (!filter_text.is_empty() && has_filtered_items) { type_node->set_collapsed(false); } - - type_node->set_checked(IMPORT_ITEM, any_checked); - type_node->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); } if (color_amount > 0) { Array arr; arr.push_back(color_amount); - select_colors_label->set_text(TTRN("One color", "{num} colors", color_amount).format(arr, "{num}")); + select_colors_label->set_text(TTRN("1 color", "{num} colors", color_amount).format(arr, "{num}")); select_all_colors_button->set_visible(true); select_full_colors_button->set_visible(true); deselect_all_colors_button->set_visible(true); @@ -243,7 +237,7 @@ void ThemeItemImportTree::_update_items_tree() { if (constant_amount > 0) { Array arr; arr.push_back(constant_amount); - select_constants_label->set_text(TTRN("One constant", "{num} constants", constant_amount).format(arr, "{num}")); + select_constants_label->set_text(TTRN("1 constant", "{num} constants", constant_amount).format(arr, "{num}")); select_all_constants_button->set_visible(true); select_full_constants_button->set_visible(true); deselect_all_constants_button->set_visible(true); @@ -257,7 +251,7 @@ void ThemeItemImportTree::_update_items_tree() { if (font_amount > 0) { Array arr; arr.push_back(font_amount); - select_fonts_label->set_text(TTRN("One font", "{num} fonts", font_amount).format(arr, "{num}")); + select_fonts_label->set_text(TTRN("1 font", "{num} fonts", font_amount).format(arr, "{num}")); select_all_fonts_button->set_visible(true); select_full_fonts_button->set_visible(true); deselect_all_fonts_button->set_visible(true); @@ -271,7 +265,7 @@ void ThemeItemImportTree::_update_items_tree() { if (font_size_amount > 0) { Array arr; arr.push_back(font_size_amount); - select_font_sizes_label->set_text(TTRN("One font size", "{num} font sizes", font_size_amount).format(arr, "{num}")); + select_font_sizes_label->set_text(TTRN("1 font size", "{num} font sizes", font_size_amount).format(arr, "{num}")); select_all_font_sizes_button->set_visible(true); select_full_font_sizes_button->set_visible(true); deselect_all_font_sizes_button->set_visible(true); @@ -285,7 +279,7 @@ void ThemeItemImportTree::_update_items_tree() { if (icon_amount > 0) { Array arr; arr.push_back(icon_amount); - select_icons_label->set_text(TTRN("One icon", "{num} icons", icon_amount).format(arr, "{num}")); + select_icons_label->set_text(TTRN("1 icon", "{num} icons", icon_amount).format(arr, "{num}")); select_all_icons_button->set_visible(true); select_full_icons_button->set_visible(true); deselect_all_icons_button->set_visible(true); @@ -301,7 +295,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); @@ -437,8 +431,8 @@ void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) { } int count = 0; - for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) { - ThemeItem ti = E->key(); + for (const KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) { + ThemeItem ti = E.key; if (ti.data_type == p_data_type) { count++; } @@ -471,23 +465,26 @@ void ThemeItemImportTree::_tree_item_edited() { if (is_checked) { if (edited_column == IMPORT_ITEM_DATA) { edited_item->set_checked(IMPORT_ITEM, true); + edited_item->propagate_check(IMPORT_ITEM); } - - _select_all_subitems(edited_item, (edited_column == IMPORT_ITEM_DATA)); } else { if (edited_column == IMPORT_ITEM) { edited_item->set_checked(IMPORT_ITEM_DATA, false); + edited_item->propagate_check(IMPORT_ITEM_DATA); } - - _deselect_all_subitems(edited_item, (edited_column == IMPORT_ITEM)); } - - _update_parent_items(edited_item); - _store_selected_item(edited_item); - + edited_item->propagate_check(edited_column); updating_tree = false; } +void ThemeItemImportTree::_check_propagated_to_tree_item(Object *p_obj, int p_column) { + TreeItem *item = Object::cast_to<TreeItem>(p_obj); + // Skip "category" tree items by checking for children. + if (item && !item->get_first_child()) { + _store_selected_item(item); + } +} + void ThemeItemImportTree::_select_all_subitems(TreeItem *p_root_item, bool p_select_with_data) { TreeItem *child_item = p_root_item->get_first_child(); while (child_item) { @@ -516,32 +513,6 @@ void ThemeItemImportTree::_deselect_all_subitems(TreeItem *p_root_item, bool p_d } } -void ThemeItemImportTree::_update_parent_items(TreeItem *p_root_item) { - TreeItem *parent_item = p_root_item->get_parent(); - if (!parent_item) { - return; - } - - bool any_checked = false; - bool any_checked_with_data = false; - - TreeItem *child_item = parent_item->get_first_child(); - while (child_item) { - if (child_item->is_checked(IMPORT_ITEM)) { - any_checked = true; - } - if (child_item->is_checked(IMPORT_ITEM_DATA)) { - any_checked_with_data = true; - } - - child_item = child_item->get_next(); - } - - parent_item->set_checked(IMPORT_ITEM, any_checked); - parent_item->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); - _update_parent_items(parent_item); -} - void ThemeItemImportTree::_select_all_items_pressed() { if (updating_tree) { return; @@ -629,7 +600,7 @@ void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) { } child_item->set_checked(IMPORT_ITEM, true); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); _store_selected_item(child_item); } @@ -685,7 +656,8 @@ void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) { child_item->set_checked(IMPORT_ITEM, true); child_item->set_checked(IMPORT_ITEM_DATA, true); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); + child_item->propagate_check(IMPORT_ITEM_DATA, false); _store_selected_item(child_item); } @@ -741,7 +713,8 @@ void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) { child_item->set_checked(IMPORT_ITEM, false); child_item->set_checked(IMPORT_ITEM_DATA, false); - _update_parent_items(child_item); + child_item->propagate_check(IMPORT_ITEM, false); + child_item->propagate_check(IMPORT_ITEM_DATA, false); _store_selected_item(child_item); } @@ -754,12 +727,13 @@ void ThemeItemImportTree::_import_selected() { return; } - // Prevent changes from immediately being reported while the operation is still ongoing. - edited_theme->_freeze_change_propagation(); + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); + ProgressDialog::get_singleton()->add_task("import_theme_items", TTR("Importing Theme Items"), selected_items.size() + 2); int idx = 0; - for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) { + for (KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) { // Arbitrary number of items to skip from reporting. // Reduces the number of UI updates that this causes when copying large themes. if (idx % 10 == 0) { @@ -769,8 +743,8 @@ void ThemeItemImportTree::_import_selected() { ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Importing items {n}/{n}").format(arr, "{n}"), idx); } - ItemCheckedState cs = E->get(); - ThemeItem ti = E->key(); + ItemCheckedState cs = E.value; + ThemeItem ti = E.key; if (cs == SELECT_IMPORT_DEFINITION || cs == SELECT_IMPORT_FULL) { Variant item_value = Variant(); @@ -808,7 +782,7 @@ void ThemeItemImportTree::_import_selected() { } } - edited_theme->set_theme_item(ti.data_type, ti.item_name, ti.type_name, item_value); + new_snapshot->set_theme_item(ti.data_type, ti.item_name, ti.type_name, item_value); } idx++; @@ -816,12 +790,24 @@ void ThemeItemImportTree::_import_selected() { // Allow changes to be reported now that the operation is finished. ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Updating the editor"), idx++); - edited_theme->_unfreeze_and_propagate_changes(); + // Make sure the task is not ended before the editor freezes to update the Inspector. ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Finalizing"), idx++); ProgressDialog::get_singleton()->end_task("import_theme_items"); - emit_signal(SNAME("items_imported")); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Import Theme Items")); + + ur->add_do_method(*edited_theme, "clear"); + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + ur->add_undo_method(*edited_theme, "clear"); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(this, "emit_signal", SNAME("items_imported")); + ur->add_undo_method(this, "emit_signal", SNAME("items_imported")); + + ur->commit_action(); } void ThemeItemImportTree::set_edited_theme(const Ref<Theme> &p_theme) { @@ -857,6 +843,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"))); @@ -904,15 +892,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); @@ -924,6 +907,7 @@ ThemeItemImportTree::ThemeItemImportTree() { import_items_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); import_main_hb->add_child(import_items_tree); import_items_tree->connect("item_edited", callable_mp(this, &ThemeItemImportTree::_tree_item_edited)); + import_items_tree->connect("check_propagated_to_item", callable_mp(this, &ThemeItemImportTree::_check_propagated_to_tree_item)); import_items_tree->set_columns(3); import_items_tree->set_column_titles_visible(true); @@ -941,7 +925,7 @@ ThemeItemImportTree::ThemeItemImportTree() { ScrollContainer *import_bulk_sc = memnew(ScrollContainer); import_bulk_sc->set_custom_minimum_size(Size2(260.0, 0.0) * EDSCALE); - import_bulk_sc->set_enable_h_scroll(false); + import_bulk_sc->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); import_main_hb->add_child(import_bulk_sc); VBoxContainer *import_bulk_vb = memnew(VBoxContainer); import_bulk_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -1115,22 +1099,22 @@ ThemeItemImportTree::ThemeItemImportTree() { label_set->add_child(select_items_label); HBoxContainer *button_set = memnew(HBoxContainer); - button_set->set_alignment(BoxContainer::ALIGN_END); + button_set->set_alignment(BoxContainer::ALIGNMENT_END); all_set->add_child(button_set); select_all_items_button->set_flat(true); select_all_items_button->set_tooltip(select_all_items_tooltip); 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_align(Label::ALIGN_RIGHT); + total_selected_items_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); total_selected_items_label->hide(); import_bulk_vb->add_child(total_selected_items_label); @@ -1144,7 +1128,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); } @@ -1159,12 +1143,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)); @@ -1195,6 +1179,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?")); @@ -1235,7 +1221,8 @@ void ThemeItemEditorDialog::_update_edit_types() { bool item_reselected = false; edit_type_list->clear(); - int e_idx = 0; + TreeItem *list_root = edit_type_list->create_item(); + for (const StringName &E : theme_types) { Ref<Texture2D> item_icon; if (E == "") { @@ -1243,19 +1230,21 @@ void ThemeItemEditorDialog::_update_edit_types() { } else { item_icon = EditorNode::get_singleton()->get_class_icon(E, "NodeDisabled"); } - edit_type_list->add_item(E, item_icon); + TreeItem *list_item = edit_type_list->create_item(list_root); + list_item->set_text(0, E); + list_item->set_icon(0, item_icon); + list_item->add_button(0, get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TYPES_TREE_REMOVE_ITEM, false, TTR("Remove Type")); if (E == edited_item_type) { - edit_type_list->select(e_idx); + list_item->select(0); item_reselected = true; } - e_idx++; } if (!item_reselected) { edited_item_type = ""; - if (edit_type_list->get_item_count() > 0) { - edit_type_list->select(0); + if (list_root->get_child_count() > 0) { + list_root->get_child(0)->select(0); } } @@ -1264,9 +1253,9 @@ void ThemeItemEditorDialog::_update_edit_types() { default_types.sort_custom<StringName::AlphCompare>(); String selected_type = ""; - Vector<int> selected_ids = edit_type_list->get_selected_items(); - if (selected_ids.size() > 0) { - selected_type = edit_type_list->get_item_text(selected_ids[0]); + TreeItem *selected_item = edit_type_list->get_selected(); + if (selected_item) { + selected_type = selected_item->get_text(0); edit_items_add_color->set_disabled(false); edit_items_add_constant->set_disabled(false); @@ -1296,14 +1285,34 @@ void ThemeItemEditorDialog::_update_edit_types() { edit_items_message->set_text(TTR("Select a theme type from the list to edit its items.\nYou can add a custom type or import a type with its items from another theme.")); edit_items_message->show(); } + _update_edit_item_tree(selected_type); } -void ThemeItemEditorDialog::_edited_type_selected(int p_item_idx) { - String selected_type = edit_type_list->get_item_text(p_item_idx); +void ThemeItemEditorDialog::_edited_type_selected() { + TreeItem *selected_item = edit_type_list->get_selected(); + String selected_type = selected_item->get_text(0); _update_edit_item_tree(selected_type); } +void ThemeItemEditorDialog::_edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) { + if (p_button != MouseButton::LEFT) { + return; + } + + TreeItem *item = Object::cast_to<TreeItem>(p_item); + if (!item) { + return; + } + + switch (p_id) { + case TYPES_TREE_REMOVE_ITEM: { + String type_name = item->get_text(0); + _remove_theme_type(type_name); + } break; + } +} + void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { edited_item_type = p_item_type; @@ -1452,8 +1461,8 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } // If some type is selected, but it doesn't seem to have any items, show a guiding message. - Vector<int> selected_ids = edit_type_list->get_selected_items(); - if (selected_ids.size() > 0) { + TreeItem *selected_item = edit_type_list->get_selected(); + if (selected_item) { if (!has_any_items) { edit_items_message->set_text(TTR("This theme type is empty.\nAdd more items to it manually or by importing from another theme.")); edit_items_message->show(); @@ -1464,7 +1473,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; @@ -1475,82 +1488,142 @@ 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); _open_rename_theme_item_dialog((Theme::DataType)data_type, item_name); + _update_edit_item_tree(edited_item_type); } break; case ITEMS_TREE_REMOVE_ITEM: { String item_name = item->get_text(0); int data_type = item->get_parent()->get_metadata(0); - edited_theme->clear_theme_item((Theme::DataType)data_type, item_name, edited_item_type); + + UndoRedo *ur = EditorNode::get_singleton()->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)); + ur->add_do_method(this, "_update_edit_item_tree", edited_item_type); + ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type); + ur->commit_action(); } break; case ITEMS_TREE_REMOVE_DATA_TYPE: { int data_type = item->get_metadata(0); _remove_data_type_items((Theme::DataType)data_type, edited_item_type); } break; } - - _update_edit_item_tree(edited_item_type); } void ThemeItemEditorDialog::_add_theme_type(const String &p_new_text) { const String new_type = edit_add_type_value->get_text().strip_edges(); edit_add_type_value->clear(); - edited_theme->add_icon_type(new_type); - edited_theme->add_stylebox_type(new_type); - edited_theme->add_font_type(new_type); - edited_theme->add_font_size_type(new_type); - edited_theme->add_color_type(new_type); - edited_theme->add_constant_type(new_type); - _update_edit_types(); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Add Theme Type")); - // Force emit a change so that other parts of the editor can update. - edited_theme->emit_changed(); + ur->add_do_method(*edited_theme, "add_type", new_type); + ur->add_undo_method(*edited_theme, "remove_type", new_type); + ur->add_do_method(this, "_update_edit_types"); + ur->add_undo_method(this, "_update_edit_types"); + + ur->commit_action(); } void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type) { + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Create Theme Item")); + switch (p_data_type) { case Theme::DATA_TYPE_ICON: - edited_theme->set_icon(p_item_name, p_item_type, Ref<Texture2D>()); + ur->add_do_method(*edited_theme, "set_icon", p_item_name, p_item_type, Ref<Texture2D>()); + ur->add_undo_method(*edited_theme, "clear_icon", p_item_name, p_item_type); break; case Theme::DATA_TYPE_STYLEBOX: - edited_theme->set_stylebox(p_item_name, p_item_type, Ref<StyleBox>()); + ur->add_do_method(*edited_theme, "set_stylebox", p_item_name, p_item_type, Ref<StyleBox>()); + ur->add_undo_method(*edited_theme, "clear_stylebox", p_item_name, p_item_type); + + if (theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(p_item_name, p_item_type))) { + ur->add_undo_method(theme_type_editor, "_unpin_leading_stylebox"); + } break; case Theme::DATA_TYPE_FONT: - edited_theme->set_font(p_item_name, p_item_type, Ref<Font>()); + ur->add_do_method(*edited_theme, "set_font", p_item_name, p_item_type, Ref<Font>()); + ur->add_undo_method(*edited_theme, "clear_font", p_item_name, p_item_type); break; case Theme::DATA_TYPE_FONT_SIZE: - edited_theme->set_font_size(p_item_name, p_item_type, -1); + ur->add_do_method(*edited_theme, "set_font_size", p_item_name, p_item_type, -1); + ur->add_undo_method(*edited_theme, "clear_font_size", p_item_name, p_item_type); break; case Theme::DATA_TYPE_COLOR: - edited_theme->set_color(p_item_name, p_item_type, Color()); + ur->add_do_method(*edited_theme, "set_color", p_item_name, p_item_type, Color()); + ur->add_undo_method(*edited_theme, "clear_color", p_item_name, p_item_type); break; case Theme::DATA_TYPE_CONSTANT: - edited_theme->set_constant(p_item_name, p_item_type, 0); + ur->add_do_method(*edited_theme, "set_constant", p_item_name, p_item_type, 0); + ur->add_undo_method(*edited_theme, "clear_constant", p_item_name, p_item_type); break; case Theme::DATA_TYPE_MAX: break; // Can't happen, but silences warning. } + + ur->add_do_method(this, "_update_edit_item_tree", edited_item_type); + ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type); + ur->commit_action(); +} + +void ThemeItemEditorDialog::_remove_theme_type(const String &p_theme_type) { + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Theme Type")); + + new_snapshot->remove_type(p_theme_type); + + ur->add_do_method(*edited_theme, "clear"); + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + // If the type was empty, it cannot be restored with merge, but thankfully we can fake it. + ur->add_undo_method(*edited_theme, "add_type", p_theme_type); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(this, "_update_edit_types"); + ur->add_undo_method(this, "_update_edit_types"); + + ur->commit_action(); } void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type, String p_item_type) { List<StringName> names; - // Prevent changes from immediately being reported while the operation is still ongoing. - edited_theme->_freeze_change_propagation(); + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); - edited_theme->get_theme_item_list(p_data_type, p_item_type, &names); + UndoRedo *ur = EditorNode::get_singleton()->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); for (const StringName &E : names) { - edited_theme->clear_theme_item(p_data_type, E, p_item_type); + new_snapshot->clear_theme_item(p_data_type, E, edited_item_type); + + if (p_data_type == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, p_item_type))) { + ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox"); + ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, p_item_type)); + } } - // Allow changes to be reported now that the operation is finished. - edited_theme->_unfreeze_and_propagate_changes(); + ur->add_do_method(*edited_theme, "clear"); + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(theme_type_editor, "_update_edit_item_tree", edited_item_type); + ur->add_undo_method(theme_type_editor, "_update_edit_item_tree", edited_item_type); + + ur->commit_action(); } void ThemeItemEditorDialog::_remove_class_items() { List<StringName> names; - // Prevent changes from immediately being reported while the operation is still ongoing. - edited_theme->_freeze_change_propagation(); + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Class Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { Theme::DataType data_type = (Theme::DataType)dt; @@ -1558,62 +1631,95 @@ void ThemeItemEditorDialog::_remove_class_items() { names.clear(); Theme::get_default()->get_theme_item_list(data_type, edited_item_type, &names); for (const StringName &E : names) { - if (edited_theme->has_theme_item_nocheck(data_type, E, edited_item_type)) { - edited_theme->clear_theme_item(data_type, E, edited_item_type); + if (new_snapshot->has_theme_item_nocheck(data_type, E, edited_item_type)) { + new_snapshot->clear_theme_item(data_type, E, edited_item_type); + + if (dt == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, edited_item_type))) { + ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox"); + ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, edited_item_type)); + } } } } - // Allow changes to be reported now that the operation is finished. - edited_theme->_unfreeze_and_propagate_changes(); + ur->add_do_method(*edited_theme, "clear"); + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(this, "_update_edit_item_tree", edited_item_type); + ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type); - _update_edit_item_tree(edited_item_type); + ur->commit_action(); } void ThemeItemEditorDialog::_remove_custom_items() { List<StringName> names; - // Prevent changes from immediately being reported while the operation is still ongoing. - edited_theme->_freeze_change_propagation(); + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Custom Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { Theme::DataType data_type = (Theme::DataType)dt; names.clear(); - edited_theme->get_theme_item_list(data_type, edited_item_type, &names); + new_snapshot->get_theme_item_list(data_type, edited_item_type, &names); for (const StringName &E : names) { if (!Theme::get_default()->has_theme_item_nocheck(data_type, E, edited_item_type)) { - edited_theme->clear_theme_item(data_type, E, edited_item_type); + new_snapshot->clear_theme_item(data_type, E, edited_item_type); + + if (dt == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, edited_item_type))) { + ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox"); + ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, edited_item_type)); + } } } } - // Allow changes to be reported now that the operation is finished. - edited_theme->_unfreeze_and_propagate_changes(); + ur->add_do_method(*edited_theme, "clear"); + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(this, "_update_edit_item_tree", edited_item_type); + ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type); - _update_edit_item_tree(edited_item_type); + ur->commit_action(); } void ThemeItemEditorDialog::_remove_all_items() { List<StringName> names; - // Prevent changes from immediately being reported while the operation is still ongoing. - edited_theme->_freeze_change_propagation(); + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove All Items From Theme")); for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { Theme::DataType data_type = (Theme::DataType)dt; names.clear(); - edited_theme->get_theme_item_list(data_type, edited_item_type, &names); + new_snapshot->get_theme_item_list(data_type, edited_item_type, &names); for (const StringName &E : names) { - edited_theme->clear_theme_item(data_type, E, edited_item_type); + new_snapshot->clear_theme_item(data_type, E, edited_item_type); + + if (dt == Theme::DATA_TYPE_STYLEBOX && theme_type_editor->is_stylebox_pinned(edited_theme->get_stylebox(E, edited_item_type))) { + ur->add_do_method(theme_type_editor, "_unpin_leading_stylebox"); + ur->add_undo_method(theme_type_editor, "_pin_leading_stylebox", E, edited_theme->get_stylebox(E, edited_item_type)); + } } } - // Allow changes to be reported now that the operation is finished. - edited_theme->_unfreeze_and_propagate_changes(); + ur->add_do_method(*edited_theme, "clear"); + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(this, "_update_edit_item_tree", edited_item_type); + ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type); - _update_edit_item_tree(edited_item_type); + ur->commit_action(); } void ThemeItemEditorDialog::_open_add_theme_item_dialog(int p_data_type) { @@ -1692,14 +1798,21 @@ 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) { - edited_theme->rename_theme_item(edit_item_data_type, edit_item_old_name, theme_item_name->get_text(), edited_item_type); + UndoRedo *ur = EditorNode::get_singleton()->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); + ur->add_undo_method(*edited_theme, "rename_theme_item", edit_item_data_type, theme_item_name->get_text(), edit_item_old_name, edited_item_type); + + ur->add_do_method(this, "_update_edit_item_tree", edited_item_type); + ur->add_undo_method(this, "_update_edit_item_tree", edited_item_type); + + ur->commit_action(); } item_popup_mode = ITEM_POPUP_MODE_MAX; edit_item_data_type = Theme::DATA_TYPE_MAX; edit_item_old_name = ""; - - _update_edit_item_tree(edited_item_type); } void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_event) { @@ -1711,13 +1824,13 @@ void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_ } switch (k->get_keycode()) { - case KEY_KP_ENTER: - case KEY_ENTER: { + case Key::KP_ENTER: + case Key::ENTER: { _confirm_edit_theme_item(); edit_theme_item_dialog->hide(); edit_theme_item_dialog->set_input_as_handled(); } break; - case KEY_ESCAPE: { + case Key::ESCAPE: { edit_theme_item_dialog->hide(); edit_theme_item_dialog->set_input_as_handled(); } break; @@ -1765,26 +1878,32 @@ 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; } } +void ThemeItemEditorDialog::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_edit_types"), &ThemeItemEditorDialog::_update_edit_types); + ClassDB::bind_method(D_METHOD("_update_edit_item_tree"), &ThemeItemEditorDialog::_update_edit_item_tree); +} + void ThemeItemEditorDialog::set_edited_theme(const Ref<Theme> &p_theme) { edited_theme = p_theme; } -ThemeItemEditorDialog::ThemeItemEditorDialog() { +ThemeItemEditorDialog::ThemeItemEditorDialog(ThemeTypeEditor *p_theme_type_editor) { set_title(TTR("Manage Theme Items")); - get_ok_button()->set_text(TTR("Close")); + set_ok_button_text(TTR("Close")); set_hide_on_ok(false); // Closing may require a confirmation in some cases. + theme_type_editor = p_theme_type_editor; + tc = memnew(TabContainer); - tc->set_tab_align(TabContainer::TabAlign::ALIGN_LEFT); add_child(tc); + tc->set_theme_type_variation("TabContainerOdd"); // Edit Items tab. HSplitContainer *edit_dialog_hs = memnew(HSplitContainer); @@ -1799,10 +1918,14 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { edit_type_label->set_text(TTR("Types:")); edit_dialog_side_vb->add_child(edit_type_label); - edit_type_list = memnew(ItemList); + edit_type_list = memnew(Tree); + edit_type_list->set_hide_root(true); + edit_type_list->set_hide_folding(true); + edit_type_list->set_columns(1); edit_type_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); edit_dialog_side_vb->add_child(edit_type_list); edit_type_list->connect("item_selected", callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected)); + edit_type_list->connect("button_clicked", callable_mp(this, &ThemeItemEditorDialog::_edited_type_button_pressed)); Label *edit_add_type_label = memnew(Label); edit_add_type_label->set_text(TTR("Add Type:")); @@ -1814,10 +1937,9 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { 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); @@ -1835,42 +1957,42 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { 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)); @@ -1904,14 +2026,14 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { 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_align(Label::ALIGN_CENTER); - edit_items_message->set_valign(Label::VALIGN_CENTER); - edit_items_message->set_autowrap_mode(Label::AUTOWRAP_WORD); + edit_items_message->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + edit_items_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); + edit_items_message->set_autowrap_mode(TextServer::AUTOWRAP_WORD); edit_items_tree->add_child(edit_items_message); edit_theme_item_dialog = memnew(ConfirmationDialog); @@ -1938,6 +2060,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { // Import Items tab. TabContainer *import_tc = memnew(TabContainer); + import_tc->set_tab_alignment(TabBar::ALIGNMENT_CENTER); tc->add_child(import_tc); tc->set_tab_title(1, TTR("Import Items")); @@ -1969,7 +2092,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { List<String> ext; ResourceLoader::get_recognized_extensions_for_type("Theme", &ext); for (const String &E : ext) { - import_another_theme_dialog->add_filter("*." + E + "; Theme Resource"); + import_another_theme_dialog->add_filter("*." + E, TTR("Theme Resource")); } import_another_file_hb->add_child(import_another_theme_dialog); import_another_theme_dialog->connect("file_selected", callable_mp(this, &ThemeItemEditorDialog::_select_another_theme_cbk)); @@ -1988,6 +2111,8 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { 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(); @@ -1996,7 +2121,7 @@ void ThemeTypeDialog::_dialog_about_to_show() { } void ThemeTypeDialog::ok_pressed() { - emit_signal(SNAME("type_selected"), add_type_filter->get_text().strip_edges()); + _add_type_selected(add_type_filter->get_text().strip_edges()); } void ThemeTypeDialog::_update_add_type_options(const String &p_filter) { @@ -2012,7 +2137,7 @@ void ThemeTypeDialog::_update_add_type_options(const String &p_filter) { Vector<StringName> unique_names; for (const StringName &E : names) { // Filter out undesired values. - if (!p_filter.is_subsequence_ofi(String(E))) { + if (!p_filter.is_subsequence_ofn(String(E))) { continue; } @@ -2042,12 +2167,25 @@ void ThemeTypeDialog::_add_type_options_cbk(int p_index) { } void ThemeTypeDialog::_add_type_dialog_entered(const String &p_value) { - emit_signal(SNAME("type_selected"), p_value.strip_edges()); - hide(); + _add_type_selected(p_value.strip_edges()); } void ThemeTypeDialog::_add_type_dialog_activated(int p_index) { - emit_signal(SNAME("type_selected"), add_type_options->get_item_text(p_index)); + _add_type_selected(add_type_options->get_item_text(p_index)); +} + +void ThemeTypeDialog::_add_type_selected(const String &p_type_name) { + pre_submitted_value = p_type_name; + if (p_type_name.is_empty()) { + add_type_confirmation->popup_centered(); + return; + } + + _add_type_confirmed(); +} + +void ThemeTypeDialog::_add_type_confirmed() { + emit_signal(SNAME("type_selected"), pre_submitted_value); hide(); } @@ -2082,11 +2220,13 @@ void ThemeTypeDialog::set_include_own_types(bool p_enable) { } ThemeTypeDialog::ThemeTypeDialog() { + set_hide_on_ok(false); + VBoxContainer *add_type_vb = memnew(VBoxContainer); add_child(add_type_vb); Label *add_type_filter_label = memnew(Label); - add_type_filter_label->set_text(TTR("Name:")); + add_type_filter_label->set_text(TTR("Filter the list of types or create a new custom type:")); add_type_vb->add_child(add_type_filter_label); add_type_filter = memnew(LineEdit); @@ -2095,7 +2235,7 @@ ThemeTypeDialog::ThemeTypeDialog() { add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_entered)); Label *add_type_options_label = memnew(Label); - add_type_options_label->set_text(TTR("Node Types:")); + add_type_options_label->set_text(TTR("Available Node-based types:")); add_type_vb->add_child(add_type_options_label); add_type_options = memnew(ItemList); @@ -2103,8 +2243,16 @@ ThemeTypeDialog::ThemeTypeDialog() { add_type_vb->add_child(add_type_options); add_type_options->connect("item_selected", callable_mp(this, &ThemeTypeDialog::_add_type_options_cbk)); add_type_options->connect("item_activated", callable_mp(this, &ThemeTypeDialog::_add_type_dialog_activated)); + + add_type_confirmation = memnew(ConfirmationDialog); + add_type_confirmation->set_title(TTR("Type name is empty!")); + add_type_confirmation->set_text(TTR("Are you sure you want to create an empty type?")); + add_type_confirmation->connect("confirmed", callable_mp(this, &ThemeTypeDialog::_add_type_confirmed)); + 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); @@ -2113,7 +2261,7 @@ VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) { ScrollContainer *items_sc = memnew(ScrollContainer); items_sc->set_v_size_flags(SIZE_EXPAND_FILL); - items_sc->set_enable_h_scroll(false); + items_sc->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); items_tab->add_child(items_sc); VBoxContainer *items_list = memnew(VBoxContainer); items_list->set_h_size_flags(SIZE_EXPAND_FILL); @@ -2124,11 +2272,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; } @@ -2141,7 +2289,7 @@ void ThemeTypeEditor::_update_type_list() { } updating = true; - Control *focused = get_focus_owner(); + Control *focused = get_viewport()->gui_get_focus_owner(); if (focused) { if (focusables.has(focused)) { // If focus is currently on one of the internal property editors, don't update. @@ -2210,8 +2358,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) { @@ -2238,12 +2386,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]; } @@ -2271,7 +2419,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); @@ -2279,21 +2427,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); @@ -2301,7 +2449,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"))); @@ -2311,7 +2459,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; @@ -2335,18 +2483,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); } @@ -2363,9 +2512,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); @@ -2375,11 +2524,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); } @@ -2396,27 +2545,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); } @@ -2434,9 +2583,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); @@ -2446,11 +2595,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); } @@ -2467,27 +2616,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); } @@ -2519,44 +2668,42 @@ void ThemeTypeEditor::_update_type_items() { pin_leader_button->set_icon(get_theme_icon(SNAME("Pin"), SNAME("EditorIcons"))); pin_leader_button->set_tooltip(TTR("Unpin this StyleBox as a main style.")); item_control->add_child(pin_leader_button); - pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_unpin_leading_stylebox)); + pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_on_unpin_leader_button_pressed)); item_control->add_child(item_editor); - if (leading_stylebox.stylebox.is_valid()) { + 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()) { - Ref<StyleBox> stylebox_value; - if (edited_theme->has_stylebox(E.key(), edited_type)) { - stylebox_value = edited_theme->get_stylebox(E.key(), edited_type); - item_editor->set_edited_resource(stylebox_value); + 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); @@ -2564,12 +2711,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::_pin_leading_stylebox), 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); } @@ -2581,11 +2728,11 @@ void ThemeTypeEditor::_update_type_items() { } // Various type settings. - if (ClassDB::class_exists(edited_type)) { + if (edited_type.is_empty() || ClassDB::class_exists(edited_type)) { type_variation_edit->set_editable(false); type_variation_edit->set_text(""); type_variation_button->hide(); - type_variation_locked->show(); + type_variation_locked->set_visible(!edited_type.is_empty()); } else { type_variation_edit->set_editable(true); type_variation_edit->set_text(edited_theme->get_type_variation_base(edited_type)); @@ -2603,6 +2750,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->set_ok_button_text(TTR("Add Type")); add_type_dialog->set_include_own_types(false); add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); } @@ -2614,16 +2762,17 @@ void ThemeTypeEditor::_add_default_type_items() { default_type = edited_theme->get_type_variation_base(edited_type); } + Ref<Theme> old_snapshot = edited_theme->duplicate(); + Ref<Theme> new_snapshot = edited_theme->duplicate(); + updating = true; - // Prevent changes from immediately being reported while the operation is still ongoing. - edited_theme->_freeze_change_propagation(); { names.clear(); Theme::get_default()->get_icon_list(default_type, &names); for (const StringName &E : names) { - if (!edited_theme->has_icon(E, edited_type)) { - edited_theme->set_icon(E, edited_type, Ref<Texture2D>()); + if (!new_snapshot->has_icon(E, edited_type)) { + new_snapshot->set_icon(E, edited_type, Theme::get_default()->get_icon(E, edited_type)); } } } @@ -2631,8 +2780,8 @@ void ThemeTypeEditor::_add_default_type_items() { names.clear(); Theme::get_default()->get_stylebox_list(default_type, &names); for (const StringName &E : names) { - if (!edited_theme->has_stylebox(E, edited_type)) { - edited_theme->set_stylebox(E, edited_type, Ref<StyleBox>()); + if (!new_snapshot->has_stylebox(E, edited_type)) { + new_snapshot->set_stylebox(E, edited_type, Theme::get_default()->get_stylebox(E, edited_type)); } } } @@ -2640,8 +2789,8 @@ void ThemeTypeEditor::_add_default_type_items() { names.clear(); Theme::get_default()->get_font_list(default_type, &names); for (const StringName &E : names) { - if (!edited_theme->has_font(E, edited_type)) { - edited_theme->set_font(E, edited_type, Ref<Font>()); + if (!new_snapshot->has_font(E, edited_type)) { + new_snapshot->set_font(E, edited_type, Theme::get_default()->get_font(E, edited_type)); } } } @@ -2649,8 +2798,8 @@ void ThemeTypeEditor::_add_default_type_items() { names.clear(); Theme::get_default()->get_font_size_list(default_type, &names); for (const StringName &E : names) { - if (!edited_theme->has_font_size(E, edited_type)) { - edited_theme->set_font_size(E, edited_type, Theme::get_default()->get_font_size(E, default_type)); + if (!new_snapshot->has_font_size(E, edited_type)) { + new_snapshot->set_font_size(E, edited_type, Theme::get_default()->get_font_size(E, edited_type)); } } } @@ -2658,8 +2807,8 @@ void ThemeTypeEditor::_add_default_type_items() { names.clear(); Theme::get_default()->get_color_list(default_type, &names); for (const StringName &E : names) { - if (!edited_theme->has_color(E, edited_type)) { - edited_theme->set_color(E, edited_type, Theme::get_default()->get_color(E, default_type)); + if (!new_snapshot->has_color(E, edited_type)) { + new_snapshot->set_color(E, edited_type, Theme::get_default()->get_color(E, edited_type)); } } } @@ -2667,17 +2816,25 @@ void ThemeTypeEditor::_add_default_type_items() { names.clear(); Theme::get_default()->get_constant_list(default_type, &names); for (const StringName &E : names) { - if (!edited_theme->has_constant(E, edited_type)) { - edited_theme->set_constant(E, edited_type, Theme::get_default()->get_constant(E, default_type)); + if (!new_snapshot->has_constant(E, edited_type)) { + new_snapshot->set_constant(E, edited_type, Theme::get_default()->get_constant(E, edited_type)); } } } - // Allow changes to be reported now that the operation is finished. - edited_theme->_unfreeze_and_propagate_changes(); updating = false; - _update_type_items(); + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Override All Default Theme Items")); + + ur->add_do_method(*edited_theme, "merge_with", new_snapshot); + ur->add_undo_method(*edited_theme, "clear"); + ur->add_undo_method(*edited_theme, "merge_with", old_snapshot); + + ur->add_do_method(this, "_update_type_items"); + ur->add_undo_method(this, "_update_type_items"); + + ur->commit_action(); } void ThemeTypeEditor::_item_add_cbk(int p_data_type, Control *p_control) { @@ -2687,27 +2844,43 @@ 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(); + ur->create_action(TTR("Add Theme Item")); + switch (p_data_type) { case Theme::DATA_TYPE_COLOR: { - edited_theme->set_color(item_name, edited_type, Color()); + ur->add_do_method(*edited_theme, "set_color", item_name, edited_type, Color()); + ur->add_undo_method(*edited_theme, "clear_color", item_name, edited_type); } break; case Theme::DATA_TYPE_CONSTANT: { - edited_theme->set_constant(item_name, edited_type, 0); + ur->add_do_method(*edited_theme, "set_constant", item_name, edited_type, 0); + ur->add_undo_method(*edited_theme, "clear_constant", item_name, edited_type); } break; case Theme::DATA_TYPE_FONT: { - edited_theme->set_font(item_name, edited_type, Ref<Font>()); + ur->add_do_method(*edited_theme, "set_font", item_name, edited_type, Ref<Font>()); + ur->add_undo_method(*edited_theme, "clear_font", item_name, edited_type); } break; case Theme::DATA_TYPE_FONT_SIZE: { - edited_theme->set_font_size(item_name, edited_type, -1); + ur->add_do_method(*edited_theme, "set_font_size", item_name, edited_type, -1); + ur->add_undo_method(*edited_theme, "clear_font_size", item_name, edited_type); } break; case Theme::DATA_TYPE_ICON: { - edited_theme->set_icon(item_name, edited_type, Ref<Texture2D>()); + ur->add_do_method(*edited_theme, "set_icon", item_name, edited_type, Ref<Texture2D>()); + ur->add_undo_method(*edited_theme, "clear_icon", item_name, edited_type); } break; case Theme::DATA_TYPE_STYLEBOX: { - edited_theme->set_stylebox(item_name, edited_type, Ref<StyleBox>()); + Ref<StyleBox> sb; + ur->add_do_method(*edited_theme, "set_stylebox", item_name, edited_type, sb); + ur->add_undo_method(*edited_theme, "clear_stylebox", item_name, edited_type); + + if (is_stylebox_pinned(sb)) { + ur->add_undo_method(this, "_unpin_leading_stylebox"); + } } break; } + ur->commit_action(); + le->set_text(""); } @@ -2716,53 +2889,90 @@ 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(); + ur->create_action(TTR("Override Theme Item")); + switch (p_data_type) { case Theme::DATA_TYPE_COLOR: { - edited_theme->set_color(p_item_name, edited_type, Theme::get_default()->get_color(p_item_name, edited_type)); + ur->add_do_method(*edited_theme, "set_color", p_item_name, edited_type, Theme::get_default()->get_color(p_item_name, edited_type)); + ur->add_undo_method(*edited_theme, "clear_color", p_item_name, edited_type); } break; case Theme::DATA_TYPE_CONSTANT: { - edited_theme->set_constant(p_item_name, edited_type, Theme::get_default()->get_constant(p_item_name, edited_type)); + ur->add_do_method(*edited_theme, "set_constant", p_item_name, edited_type, Theme::get_default()->get_constant(p_item_name, edited_type)); + ur->add_undo_method(*edited_theme, "clear_constant", p_item_name, edited_type); } break; case Theme::DATA_TYPE_FONT: { - edited_theme->set_font(p_item_name, edited_type, Ref<Font>()); + ur->add_do_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>()); + ur->add_undo_method(*edited_theme, "clear_font", p_item_name, edited_type); } break; case Theme::DATA_TYPE_FONT_SIZE: { - edited_theme->set_font_size(p_item_name, edited_type, Theme::get_default()->get_font_size(p_item_name, edited_type)); + ur->add_do_method(*edited_theme, "set_font_size", p_item_name, edited_type, Theme::get_default()->get_font_size(p_item_name, edited_type)); + ur->add_undo_method(*edited_theme, "clear_font_size", p_item_name, edited_type); } break; case Theme::DATA_TYPE_ICON: { - edited_theme->set_icon(p_item_name, edited_type, Ref<Texture2D>()); + ur->add_do_method(*edited_theme, "set_icon", p_item_name, edited_type, Ref<Texture2D>()); + ur->add_undo_method(*edited_theme, "clear_icon", p_item_name, edited_type); } break; case Theme::DATA_TYPE_STYLEBOX: { - edited_theme->set_stylebox(p_item_name, edited_type, Ref<StyleBox>()); + Ref<StyleBox> sb; + ur->add_do_method(*edited_theme, "set_stylebox", p_item_name, edited_type, sb); + ur->add_undo_method(*edited_theme, "clear_stylebox", p_item_name, edited_type); + + if (is_stylebox_pinned(sb)) { + ur->add_undo_method(this, "_unpin_leading_stylebox"); + } } break; } + + ur->commit_action(); } void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) { + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Remove Theme Item")); + switch (p_data_type) { case Theme::DATA_TYPE_COLOR: { - edited_theme->clear_color(p_item_name, edited_type); + ur->add_do_method(*edited_theme, "clear_color", p_item_name, edited_type); + ur->add_undo_method(*edited_theme, "set_color", p_item_name, edited_type, edited_theme->get_color(p_item_name, edited_type)); } break; case Theme::DATA_TYPE_CONSTANT: { - edited_theme->clear_constant(p_item_name, edited_type); + ur->add_do_method(*edited_theme, "clear_constant", p_item_name, edited_type); + ur->add_undo_method(*edited_theme, "set_constant", p_item_name, edited_type, edited_theme->get_constant(p_item_name, edited_type)); } break; case Theme::DATA_TYPE_FONT: { - edited_theme->clear_font(p_item_name, edited_type); - } break; - case Theme::DATA_TYPE_FONT_SIZE: { - edited_theme->clear_font_size(p_item_name, edited_type); + ur->add_do_method(*edited_theme, "clear_font", p_item_name, edited_type); + if (edited_theme->has_font(p_item_name, edited_type)) { + ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, edited_theme->get_font(p_item_name, edited_type)); + } else { + ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>()); + } } break; case Theme::DATA_TYPE_ICON: { - edited_theme->clear_icon(p_item_name, edited_type); + ur->add_do_method(*edited_theme, "clear_icon", p_item_name, edited_type); + if (edited_theme->has_icon(p_item_name, edited_type)) { + ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, edited_theme->get_icon(p_item_name, edited_type)); + } else { + ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, Ref<Texture2D>()); + } } break; case Theme::DATA_TYPE_STYLEBOX: { - edited_theme->clear_stylebox(p_item_name, edited_type); + Ref<StyleBox> sb = edited_theme->get_stylebox(p_item_name, edited_type); + ur->add_do_method(*edited_theme, "clear_stylebox", p_item_name, edited_type); + if (edited_theme->has_stylebox(p_item_name, edited_type)) { + ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, sb); + } else { + ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, Ref<StyleBox>()); + } - if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) { - _unpin_leading_stylebox(); + if (is_stylebox_pinned(sb)) { + ur->add_do_method(this, "_unpin_leading_stylebox"); + ur->add_undo_method(this, "_pin_leading_stylebox", p_item_name, sb); } } break; } + + ur->commit_action(); } void ThemeTypeEditor::_item_rename_cbk(int p_data_type, String p_item_name, Control *p_control) { @@ -2792,30 +3002,41 @@ void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name return; } + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Rename Theme Item")); + switch (p_data_type) { case Theme::DATA_TYPE_COLOR: { - edited_theme->rename_color(p_item_name, new_name, edited_type); + ur->add_do_method(*edited_theme, "rename_color", p_item_name, new_name, edited_type); + ur->add_undo_method(*edited_theme, "rename_color", new_name, p_item_name, edited_type); } break; case Theme::DATA_TYPE_CONSTANT: { - edited_theme->rename_constant(p_item_name, new_name, edited_type); + ur->add_do_method(*edited_theme, "rename_constant", p_item_name, new_name, edited_type); + ur->add_undo_method(*edited_theme, "rename_constant", new_name, p_item_name, edited_type); } break; case Theme::DATA_TYPE_FONT: { - edited_theme->rename_font(p_item_name, new_name, edited_type); + ur->add_do_method(*edited_theme, "rename_font", p_item_name, new_name, edited_type); + ur->add_undo_method(*edited_theme, "rename_font", new_name, p_item_name, edited_type); } break; case Theme::DATA_TYPE_FONT_SIZE: { - edited_theme->rename_font_size(p_item_name, new_name, edited_type); + ur->add_do_method(*edited_theme, "rename_font_size", p_item_name, new_name, edited_type); + ur->add_undo_method(*edited_theme, "rename_font_size", new_name, p_item_name, edited_type); } break; case Theme::DATA_TYPE_ICON: { - edited_theme->rename_icon(p_item_name, new_name, edited_type); + ur->add_do_method(*edited_theme, "rename_icon", p_item_name, new_name, edited_type); + ur->add_undo_method(*edited_theme, "rename_icon", new_name, p_item_name, edited_type); } break; case Theme::DATA_TYPE_STYLEBOX: { - edited_theme->rename_stylebox(p_item_name, new_name, edited_type); + ur->add_do_method(*edited_theme, "rename_stylebox", p_item_name, new_name, edited_type); + ur->add_undo_method(*edited_theme, "rename_stylebox", new_name, p_item_name, edited_type); if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) { leading_stylebox.item_name = new_name; } } break; } + + ur->commit_action(); } void ThemeTypeEditor::_item_rename_entered(String p_value, int p_data_type, String p_item_name, Control *p_control) { @@ -2837,69 +3058,151 @@ 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) { - edited_theme->set_color(p_item_name, edited_type, p_value); + UndoRedo *ur = EditorNode::get_singleton()->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)); + ur->commit_action(); } void ThemeTypeEditor::_constant_item_changed(float p_value, String p_item_name) { - edited_theme->set_constant(p_item_name, edited_type, int(p_value)); + UndoRedo *ur = EditorNode::get_singleton()->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)); + ur->commit_action(); } void ThemeTypeEditor::_font_size_item_changed(float p_value, String p_item_name) { - edited_theme->set_font_size(p_item_name, edited_type, int(p_value)); + UndoRedo *ur = EditorNode::get_singleton()->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) { +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) { - edited_theme->set_font(p_item_name, edited_type, p_value); + UndoRedo *ur = EditorNode::get_singleton()->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>()); + if (edited_theme->has_font(p_item_name, edited_type)) { + ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, edited_theme->get_font(p_item_name, edited_type)); + } else { + ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>()); + } + + ur->add_do_method(this, "call_deferred", "_update_type_items"); + ur->add_undo_method(this, "call_deferred", "_update_type_items"); + + ur->commit_action(); } void ThemeTypeEditor::_icon_item_changed(Ref<Texture2D> p_value, String p_item_name) { - edited_theme->set_icon(p_item_name, edited_type, p_value); + UndoRedo *ur = EditorNode::get_singleton()->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>()); + if (edited_theme->has_icon(p_item_name, edited_type)) { + ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, edited_theme->get_icon(p_item_name, edited_type)); + } else { + ur->add_undo_method(*edited_theme, "set_icon", p_item_name, edited_type, Ref<Texture2D>()); + } + + ur->add_do_method(this, "call_deferred", "_update_type_items"); + ur->add_undo_method(this, "call_deferred", "_update_type_items"); + + ur->commit_action(); } void ThemeTypeEditor::_stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name) { - edited_theme->set_stylebox(p_item_name, edited_type, p_value); + UndoRedo *ur = EditorNode::get_singleton()->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>()); + if (edited_theme->has_stylebox(p_item_name, edited_type)) { + ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, edited_theme->get_stylebox(p_item_name, edited_type)); + } else { + ur->add_undo_method(*edited_theme, "set_stylebox", p_item_name, edited_type, Ref<StyleBox>()); + } + + ur->add_do_method(this, "_change_pinned_stylebox"); + ur->add_undo_method(this, "_change_pinned_stylebox"); + + ur->add_do_method(this, "call_deferred", "_update_type_items"); + ur->add_undo_method(this, "call_deferred", "_update_type_items"); + + ur->commit_action(); +} - if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) { +void ThemeTypeEditor::_change_pinned_stylebox() { + if (leading_stylebox.pinned) { if (leading_stylebox.stylebox.is_valid()) { leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } - leading_stylebox.stylebox = p_value; - leading_stylebox.ref_stylebox = (p_value.is_valid() ? p_value->duplicate() : RES()); - if (p_value.is_valid()) { - leading_stylebox.stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); - } - } -} + 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() : Ref<Resource>()); -void ThemeTypeEditor::_pin_leading_stylebox(Control *p_editor, String p_item_name) { - if (leading_stylebox.stylebox.is_valid()) { + if (leading_stylebox.stylebox.is_valid()) { + new_stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + } + } else if (leading_stylebox.stylebox.is_valid()) { leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } +} +void ThemeTypeEditor::_on_pin_leader_button_pressed(Control *p_editor, String p_item_name) { Ref<StyleBox> stylebox; if (Object::cast_to<EditorResourcePicker>(p_editor)) { stylebox = Object::cast_to<EditorResourcePicker>(p_editor)->get_edited_resource(); } + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Pin Stylebox")); + ur->add_do_method(this, "_pin_leading_stylebox", p_item_name, stylebox); + + if (leading_stylebox.pinned) { + ur->add_undo_method(this, "_pin_leading_stylebox", leading_stylebox.item_name, leading_stylebox.stylebox); + } else { + ur->add_undo_method(this, "_unpin_leading_stylebox"); + } + + ur->commit_action(); +} + +void ThemeTypeEditor::_pin_leading_stylebox(String p_item_name, Ref<StyleBox> p_stylebox) { + if (leading_stylebox.stylebox.is_valid()) { + leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + } + LeadingStylebox leader; leader.pinned = true; leader.item_name = p_item_name; - leader.stylebox = stylebox; - leader.ref_stylebox = (stylebox.is_valid() ? stylebox->duplicate() : RES()); + leader.stylebox = p_stylebox; + leader.ref_stylebox = (p_stylebox.is_valid() ? p_stylebox->duplicate() : Ref<Resource>()); leading_stylebox = leader; - if (leading_stylebox.stylebox.is_valid()) { - leading_stylebox.stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + if (p_stylebox.is_valid()) { + p_stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } _update_type_items(); } +void ThemeTypeEditor::_on_unpin_leader_button_pressed() { + UndoRedo *ur = EditorNode::get_singleton()->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); + ur->commit_action(); +} + void ThemeTypeEditor::_unpin_leading_stylebox() { if (leading_stylebox.stylebox.is_valid()) { leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); @@ -2924,11 +3227,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); } @@ -2960,16 +3265,28 @@ void ThemeTypeEditor::_update_stylebox_from_leading() { } void ThemeTypeEditor::_type_variation_changed(const String p_value) { + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Theme Type Variation")); + if (p_value.is_empty()) { - edited_theme->clear_type_variation(edited_type); + ur->add_do_method(*edited_theme, "clear_type_variation", edited_type); + } else { + ur->add_do_method(*edited_theme, "set_type_variation", edited_type, StringName(p_value)); + } + + if (edited_theme->get_type_variation_base(edited_type) == "") { + ur->add_undo_method(*edited_theme, "clear_type_variation", edited_type); } else { - edited_theme->set_type_variation(edited_type, StringName(p_value)); + ur->add_undo_method(*edited_theme, "set_type_variation", edited_type, edited_theme->get_type_variation_base(edited_type)); } + + ur->commit_action(); } void ThemeTypeEditor::_add_type_variation_cbk() { add_type_mode = ADD_VARIATION_BASE; - add_type_dialog->set_title(TTR("Add Variation Base Type")); + add_type_dialog->set_title(TTR("Set Variation 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); } @@ -2979,7 +3296,6 @@ void ThemeTypeEditor::_add_type_dialog_selected(const String p_type_name) { select_type(p_type_name); } else if (add_type_mode == ADD_VARIATION_BASE) { _type_variation_changed(p_type_name); - _update_type_items(); } } @@ -2997,14 +3313,18 @@ 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; } } +void ThemeTypeEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_type_items"), &ThemeTypeEditor::_update_type_items); + ClassDB::bind_method(D_METHOD("_pin_leading_stylebox"), &ThemeTypeEditor::_pin_leading_stylebox); + ClassDB::bind_method(D_METHOD("_unpin_leading_stylebox"), &ThemeTypeEditor::_unpin_leading_stylebox); + ClassDB::bind_method(D_METHOD("_change_pinned_stylebox"), &ThemeTypeEditor::_change_pinned_stylebox); +} + void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) { if (edited_theme.is_valid()) { edited_theme->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); @@ -3044,6 +3364,10 @@ void ThemeTypeEditor::select_type(String p_type_name) { } } +bool ThemeTypeEditor::is_stylebox_pinned(Ref<StyleBox> p_stylebox) { + return leading_stylebox.pinned && leading_stylebox.stylebox == p_stylebox; +} + ThemeTypeEditor::ThemeTypeEditor() { VBoxContainer *main_vb = memnew(VBoxContainer); add_child(main_vb); @@ -3057,6 +3381,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)); @@ -3084,9 +3409,11 @@ ThemeTypeEditor::ThemeTypeEditor() { add_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_default_type_items)); data_type_tabs = memnew(TabContainer); + data_type_tabs->set_tab_alignment(TabBar::ALIGNMENT_CENTER); main_vb->add_child(data_type_tabs); data_type_tabs->set_v_size_flags(SIZE_EXPAND_FILL); data_type_tabs->set_use_hidden_tabs_for_min_size(true); + data_type_tabs->set_theme_type_variation("TabContainerOdd"); color_items_list = _create_item_list(Theme::DATA_TYPE_COLOR); constant_items_list = _create_item_list(Theme::DATA_TYPE_CONSTANT); @@ -3102,7 +3429,7 @@ ThemeTypeEditor::ThemeTypeEditor() { ScrollContainer *type_settings_sc = memnew(ScrollContainer); type_settings_sc->set_v_size_flags(SIZE_EXPAND_FILL); - type_settings_sc->set_enable_h_scroll(false); + type_settings_sc->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); type_settings_tab->add_child(type_settings_sc); VBoxContainer *type_settings_list = memnew(VBoxContainer); type_settings_list->set_h_size_flags(SIZE_EXPAND_FILL); @@ -3129,8 +3456,8 @@ ThemeTypeEditor::ThemeTypeEditor() { type_variation_locked = memnew(Label); type_variation_vb->add_child(type_variation_locked); - type_variation_locked->set_align(Label::ALIGN_CENTER); - type_variation_locked->set_autowrap_mode(Label::AUTOWRAP_WORD); + type_variation_locked->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + 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(); @@ -3145,6 +3472,8 @@ ThemeTypeEditor::ThemeTypeEditor() { add_child(update_debounce_timer); } +/////////////////////// + void ThemeEditor::edit(const Ref<Theme> &p_theme) { if (theme == p_theme) { return; @@ -3195,8 +3524,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) { @@ -3204,7 +3533,7 @@ void ThemeEditor::_add_preview_tab(ThemeEditorPreview *p_preview_tab, const Stri preview_tabs->add_tab(p_preview_name, p_icon); preview_tabs_content->add_child(p_preview_tab); - preview_tabs->set_tab_right_button(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("close"), SNAME("Tabs"))); + preview_tabs->set_tab_button_icon(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("close"), SNAME("TabBar"))); p_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked)); preview_tabs->set_current_tab(preview_tabs->get_tab_count() - 1); @@ -3269,7 +3598,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; @@ -3290,13 +3619,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)); @@ -3308,7 +3637,9 @@ ThemeEditor::ThemeEditor() { theme_edit_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_edit_button_cbk)); top_menu->add_child(theme_edit_button); - theme_edit_dialog = memnew(ThemeItemEditorDialog); + theme_type_editor = memnew(ThemeTypeEditor); + + theme_edit_dialog = memnew(ThemeItemEditorDialog(theme_type_editor)); theme_edit_dialog->hide(); top_menu->add_child(theme_edit_dialog); @@ -3328,12 +3659,11 @@ ThemeEditor::ThemeEditor() { preview_tabs_content->set_draw_behind_parent(true); preview_tabs_vb->add_child(preview_tabs_content); - preview_tabs = memnew(Tabs); - preview_tabs->set_tab_align(Tabs::ALIGN_LEFT); + preview_tabs = memnew(TabBar); preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL); preview_tabbar_hb->add_child(preview_tabs); preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab)); - preview_tabs->connect("right_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab)); + preview_tabs->connect("tab_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab)); HBoxContainer *add_preview_button_hb = memnew(HBoxContainer); preview_tabbar_hb->add_child(add_preview_button_hb); @@ -3353,16 +3683,17 @@ ThemeEditor::ThemeEditor() { List<String> ext; ResourceLoader::get_recognized_extensions_for_type("PackedScene", &ext); for (const String &E : ext) { - preview_scene_dialog->add_filter("*." + E + "; Scene"); + preview_scene_dialog->add_filter("*." + E, TTR("Scene")); } main_hs->add_child(preview_scene_dialog); preview_scene_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_preview_scene_dialog_cbk)); - theme_type_editor = memnew(ThemeTypeEditor); main_hs->add_child(theme_type_editor); 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)); @@ -3385,7 +3716,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)) { @@ -3447,21 +3778,20 @@ bool ThemeEditorPlugin::handles(Object *p_node) const { void ThemeEditorPlugin::make_visible(bool p_visible) { if (p_visible) { button->show(); - editor->make_bottom_panel_item_visible(theme_editor); + EditorNode::get_singleton()->make_bottom_panel_item_visible(theme_editor); } else { if (theme_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); + EditorNode::get_singleton()->hide_bottom_panel(); } button->hide(); } } -ThemeEditorPlugin::ThemeEditorPlugin(EditorNode *p_node) { - editor = p_node; +ThemeEditorPlugin::ThemeEditorPlugin() { theme_editor = memnew(ThemeEditor); theme_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); - button = editor->add_bottom_panel_item(TTR("Theme"), theme_editor); + button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Theme"), theme_editor); button->hide(); } diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index 5b0357e3f8..9f89a047cb 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,15 +31,20 @@ #ifndef THEME_EDITOR_PLUGIN_H #define THEME_EDITOR_PLUGIN_H +#include "editor/editor_plugin.h" +#include "editor/plugins/theme_editor_preview.h" +#include "scene/gui/check_button.h" +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" #include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/scroll_container.h" -#include "scene/gui/tabs.h" +#include "scene/gui/tab_bar.h" #include "scene/gui/texture_rect.h" +#include "scene/gui/tree.h" #include "scene/resources/theme.h" -#include "theme_editor_preview.h" -#include "editor/editor_node.h" +class EditorFileDialog; class ThemeItemImportTree : public VBoxContainer { GDCLASS(ThemeItemImportTree, VBoxContainer); @@ -68,11 +73,11 @@ class ThemeItemImportTree : public VBoxContainer { SELECT_IMPORT_FULL, }; - Map<ThemeItem, ItemCheckedState> selected_items; + RBMap<ThemeItem, ItemCheckedState> selected_items; - LineEdit *import_items_filter; + LineEdit *import_items_filter = nullptr; - Tree *import_items_tree; + Tree *import_items_tree = nullptr; List<TreeItem *> tree_color_items; List<TreeItem *> tree_constant_items; List<TreeItem *> tree_font_items; @@ -87,57 +92,57 @@ class ThemeItemImportTree : public VBoxContainer { IMPORT_ITEM_DATA = 2, }; - TextureRect *select_colors_icon; - Label *select_colors_label; - Button *select_all_colors_button; - Button *select_full_colors_button; - Button *deselect_all_colors_button; - Label *total_selected_colors_label; - - TextureRect *select_constants_icon; - Label *select_constants_label; - Button *select_all_constants_button; - Button *select_full_constants_button; - Button *deselect_all_constants_button; - Label *total_selected_constants_label; - - TextureRect *select_fonts_icon; - Label *select_fonts_label; - Button *select_all_fonts_button; - Button *select_full_fonts_button; - Button *deselect_all_fonts_button; - Label *total_selected_fonts_label; - - TextureRect *select_font_sizes_icon; - Label *select_font_sizes_label; - Button *select_all_font_sizes_button; - Button *select_full_font_sizes_button; - Button *deselect_all_font_sizes_button; - Label *total_selected_font_sizes_label; - - TextureRect *select_icons_icon; - Label *select_icons_label; - Button *select_all_icons_button; - Button *select_full_icons_button; - Button *deselect_all_icons_button; - Label *total_selected_icons_label; - - TextureRect *select_styleboxes_icon; - Label *select_styleboxes_label; - Button *select_all_styleboxes_button; - Button *select_full_styleboxes_button; - Button *deselect_all_styleboxes_button; - Label *total_selected_styleboxes_label; - - HBoxContainer *select_icons_warning_hb; - TextureRect *select_icons_warning_icon; - Label *select_icons_warning; - - Button *import_collapse_types_button; - Button *import_expand_types_button; - Button *import_select_all_button; - Button *import_select_full_button; - Button *import_deselect_all_button; + TextureRect *select_colors_icon = nullptr; + Label *select_colors_label = nullptr; + Button *select_all_colors_button = nullptr; + Button *select_full_colors_button = nullptr; + Button *deselect_all_colors_button = nullptr; + Label *total_selected_colors_label = nullptr; + + TextureRect *select_constants_icon = nullptr; + Label *select_constants_label = nullptr; + Button *select_all_constants_button = nullptr; + Button *select_full_constants_button = nullptr; + Button *deselect_all_constants_button = nullptr; + Label *total_selected_constants_label = nullptr; + + TextureRect *select_fonts_icon = nullptr; + Label *select_fonts_label = nullptr; + Button *select_all_fonts_button = nullptr; + Button *select_full_fonts_button = nullptr; + Button *deselect_all_fonts_button = nullptr; + Label *total_selected_fonts_label = nullptr; + + TextureRect *select_font_sizes_icon = nullptr; + Label *select_font_sizes_label = nullptr; + Button *select_all_font_sizes_button = nullptr; + Button *select_full_font_sizes_button = nullptr; + Button *deselect_all_font_sizes_button = nullptr; + Label *total_selected_font_sizes_label = nullptr; + + TextureRect *select_icons_icon = nullptr; + Label *select_icons_label = nullptr; + Button *select_all_icons_button = nullptr; + Button *select_full_icons_button = nullptr; + Button *deselect_all_icons_button = nullptr; + Label *total_selected_icons_label = nullptr; + + TextureRect *select_styleboxes_icon = nullptr; + Label *select_styleboxes_label = nullptr; + Button *select_all_styleboxes_button = nullptr; + Button *select_full_styleboxes_button = nullptr; + Button *deselect_all_styleboxes_button = nullptr; + Label *total_selected_styleboxes_label = nullptr; + + HBoxContainer *select_icons_warning_hb = nullptr; + TextureRect *select_icons_warning_icon = nullptr; + Label *select_icons_warning = nullptr; + + Button *import_collapse_types_button = nullptr; + Button *import_expand_types_button = nullptr; + Button *import_select_all_button = nullptr; + Button *import_select_full_button = nullptr; + Button *import_deselect_all_button = nullptr; void _update_items_tree(); void _toggle_type_items(bool p_collapse); @@ -148,9 +153,9 @@ class ThemeItemImportTree : public VBoxContainer { void _update_total_selected(Theme::DataType p_data_type); void _tree_item_edited(); + void _check_propagated_to_tree_item(Object *p_obj, int p_column); void _select_all_subitems(TreeItem *p_root_item, bool p_select_with_data); void _deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely); - void _update_parent_items(TreeItem *p_root_item); void _select_all_items_pressed(); void _select_full_items_pressed(); @@ -176,28 +181,37 @@ public: ThemeItemImportTree(); }; +class ThemeTypeEditor; + class ThemeItemEditorDialog : public AcceptDialog { GDCLASS(ThemeItemEditorDialog, AcceptDialog); + ThemeTypeEditor *theme_type_editor = nullptr; + Ref<Theme> edited_theme; - TabContainer *tc; + TabContainer *tc = nullptr; + + enum TypesTreeAction { + TYPES_TREE_REMOVE_ITEM, + }; - ItemList *edit_type_list; - LineEdit *edit_add_type_value; + Tree *edit_type_list = nullptr; + LineEdit *edit_add_type_value = nullptr; + Button *edit_add_type_button = nullptr; String edited_item_type; - Button *edit_items_add_color; - Button *edit_items_add_constant; - Button *edit_items_add_font; - Button *edit_items_add_font_size; - Button *edit_items_add_icon; - Button *edit_items_add_stylebox; - Button *edit_items_remove_class; - Button *edit_items_remove_custom; - Button *edit_items_remove_all; - Tree *edit_items_tree; - Label *edit_items_message; + Button *edit_items_add_color = nullptr; + Button *edit_items_add_constant = nullptr; + Button *edit_items_add_font = nullptr; + Button *edit_items_add_font_size = nullptr; + Button *edit_items_add_icon = nullptr; + Button *edit_items_add_stylebox = nullptr; + Button *edit_items_remove_class = nullptr; + Button *edit_items_remove_custom = nullptr; + Button *edit_items_remove_all = nullptr; + Tree *edit_items_tree = nullptr; + Label *edit_items_message = nullptr; enum ItemsTreeAction { ITEMS_TREE_RENAME_ITEM, @@ -205,10 +219,10 @@ class ThemeItemEditorDialog : public AcceptDialog { ITEMS_TREE_REMOVE_DATA_TYPE, }; - ConfirmationDialog *edit_theme_item_dialog; - VBoxContainer *edit_theme_item_old_vb; - Label *theme_item_old_name; - LineEdit *theme_item_name; + ConfirmationDialog *edit_theme_item_dialog = nullptr; + VBoxContainer *edit_theme_item_old_vb = nullptr; + Label *theme_item_old_name = nullptr; + LineEdit *theme_item_name = nullptr; enum ItemPopupMode { CREATE_THEME_ITEM, @@ -220,28 +234,30 @@ class ThemeItemEditorDialog : public AcceptDialog { String edit_item_old_name; Theme::DataType edit_item_data_type = Theme::DATA_TYPE_MAX; - ThemeItemImportTree *import_default_theme_items; - ThemeItemImportTree *import_editor_theme_items; - ThemeItemImportTree *import_other_theme_items; + ThemeItemImportTree *import_default_theme_items = nullptr; + ThemeItemImportTree *import_editor_theme_items = nullptr; + ThemeItemImportTree *import_other_theme_items = nullptr; - LineEdit *import_another_theme_value; - Button *import_another_theme_button; - EditorFileDialog *import_another_theme_dialog; + LineEdit *import_another_theme_value = nullptr; + Button *import_another_theme_button = nullptr; + EditorFileDialog *import_another_theme_dialog = nullptr; - ConfirmationDialog *confirm_closing_dialog; + ConfirmationDialog *confirm_closing_dialog = nullptr; void ok_pressed() override; void _close_dialog(); void _dialog_about_to_show(); void _update_edit_types(); - void _edited_type_selected(int p_item_idx); + void _edited_type_selected(); + void _edited_type_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _update_edit_item_tree(String p_item_type); - void _item_tree_button_pressed(Object *p_item, int p_column, int p_id); + void _item_tree_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button); void _add_theme_type(const String &p_new_text); void _add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type); + void _remove_theme_type(const String &p_theme_type); void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type); void _remove_class_items(); void _remove_custom_items(); @@ -257,11 +273,12 @@ class ThemeItemEditorDialog : public AcceptDialog { protected: void _notification(int p_what); + static void _bind_methods(); public: void set_edited_theme(const Ref<Theme> &p_theme); - ThemeItemEditorDialog(); + ThemeItemEditorDialog(ThemeTypeEditor *p_theme_editor); }; class ThemeTypeDialog : public ConfirmationDialog { @@ -270,8 +287,11 @@ class ThemeTypeDialog : public ConfirmationDialog { Ref<Theme> edited_theme; bool include_own_types = false; - LineEdit *add_type_filter; - ItemList *add_type_options; + String pre_submitted_value; + + LineEdit *add_type_filter = nullptr; + ItemList *add_type_options = nullptr; + ConfirmationDialog *add_type_confirmation = nullptr; void _dialog_about_to_show(); void ok_pressed() override; @@ -283,6 +303,9 @@ class ThemeTypeDialog : public ConfirmationDialog { void _add_type_dialog_entered(const String &p_value); void _add_type_dialog_activated(int p_index); + void _add_type_selected(const String &p_type_name); + void _add_type_confirmed(); + protected: void _notification(int p_what); static void _bind_methods(); @@ -310,22 +333,22 @@ class ThemeTypeEditor : public MarginContainer { LeadingStylebox leading_stylebox; - OptionButton *theme_type_list; - Button *add_type_button; + OptionButton *theme_type_list = nullptr; + Button *add_type_button = nullptr; - CheckButton *show_default_items_button; + CheckButton *show_default_items_button = nullptr; - TabContainer *data_type_tabs; - VBoxContainer *color_items_list; - VBoxContainer *constant_items_list; - VBoxContainer *font_items_list; - VBoxContainer *font_size_items_list; - VBoxContainer *icon_items_list; - VBoxContainer *stylebox_items_list; + TabContainer *data_type_tabs = nullptr; + VBoxContainer *color_items_list = nullptr; + VBoxContainer *constant_items_list = nullptr; + VBoxContainer *font_items_list = nullptr; + VBoxContainer *font_size_items_list = nullptr; + VBoxContainer *icon_items_list = nullptr; + VBoxContainer *stylebox_items_list = nullptr; - LineEdit *type_variation_edit; - Button *type_variation_button; - Label *type_variation_locked; + LineEdit *type_variation_edit = nullptr; + Button *type_variation_button = nullptr; + Label *type_variation_locked = nullptr; enum TypeDialogMode { ADD_THEME_TYPE, @@ -333,21 +356,20 @@ class ThemeTypeEditor : public MarginContainer { }; TypeDialogMode add_type_mode = ADD_THEME_TYPE; - ThemeTypeDialog *add_type_dialog; + ThemeTypeDialog *add_type_dialog = nullptr; Vector<Control *> focusables; - Timer *update_debounce_timer; + Timer *update_debounce_timer = nullptr; VBoxContainer *_create_item_list(Theme::DataType p_data_type); void _update_type_list(); void _update_type_list_debounced(); - OrderedHashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default); + HashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default); HBoxContainer *_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable); void _add_focusable(Control *p_control); void _update_type_items(); void _list_type_selected(int p_index); - void _select_type(String p_type_name); void _add_type_button_cbk(); void _add_default_type_items(); @@ -363,11 +385,14 @@ 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); + 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); - void _pin_leading_stylebox(Control *p_editor, String p_item_name); + void _change_pinned_stylebox(); + void _on_pin_leader_button_pressed(Control *p_editor, String p_item_name); + void _pin_leading_stylebox(String p_item_name, Ref<StyleBox> p_stylebox); + void _on_unpin_leader_button_pressed(); void _unpin_leading_stylebox(); void _update_stylebox_from_leading(); @@ -378,10 +403,12 @@ class ThemeTypeEditor : public MarginContainer { protected: void _notification(int p_what); + static void _bind_methods(); public: void set_edited_theme(const Ref<Theme> &p_theme); void select_type(String p_type_name); + bool is_stylebox_pinned(Ref<StyleBox> p_stylebox); ThemeTypeEditor(); }; @@ -391,15 +418,15 @@ class ThemeEditor : public VBoxContainer { Ref<Theme> theme; - Tabs *preview_tabs; - PanelContainer *preview_tabs_content; - Button *add_preview_button; - EditorFileDialog *preview_scene_dialog; + TabBar *preview_tabs = nullptr; + PanelContainer *preview_tabs_content = nullptr; + Button *add_preview_button = nullptr; + EditorFileDialog *preview_scene_dialog = nullptr; - ThemeTypeEditor *theme_type_editor; + ThemeTypeEditor *theme_type_editor = nullptr; - Label *theme_name; - ThemeItemEditorDialog *theme_edit_dialog; + Label *theme_name = nullptr; + ThemeItemEditorDialog *theme_edit_dialog = nullptr; void _theme_save_button_cbk(bool p_save_as); void _theme_edit_button_cbk(); @@ -426,9 +453,8 @@ public: class ThemeEditorPlugin : public EditorPlugin { GDCLASS(ThemeEditorPlugin, EditorPlugin); - ThemeEditor *theme_editor; - EditorNode *editor; - Button *button; + ThemeEditor *theme_editor = nullptr; + Button *button = nullptr; public: virtual String get_name() const override { return "Theme"; } @@ -437,7 +463,7 @@ public: virtual bool handles(Object *p_node) const override; virtual void make_visible(bool p_visible) override; - ThemeEditorPlugin(EditorNode *p_node); + ThemeEditorPlugin(); }; #endif // THEME_EDITOR_PLUGIN_H diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index 801ee0eac2..b5c6c6d651 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,11 +30,18 @@ #include "theme_editor_preview.h" +#include "core/config/project_settings.h" #include "core/input/input.h" #include "core/math/math_funcs.h" +#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" -#include "editor/editor_scale.h" +constexpr double REFRESH_TIMER = 1.5; void ThemeEditorPreview::set_preview_theme(const Ref<Theme> &p_theme) { preview_content->set_theme(p_theme); @@ -47,7 +54,7 @@ void ThemeEditorPreview::add_preview_overlay(Control *p_overlay) { void ThemeEditorPreview::_propagate_redraw(Control *p_at) { p_at->notification(NOTIFICATION_THEME_CHANGED); - p_at->minimum_size_changed(); + p_at->update_minimum_size(); p_at->update(); for (int i = 0; i < p_at->get_child_count(); i++) { Control *a = Object::cast_to<Control>(p_at->get_child(i)); @@ -66,11 +73,14 @@ void ThemeEditorPreview::_refresh_interval() { } void ThemeEditorPreview::_preview_visibility_changed() { - set_process(is_visible()); + set_process(is_visible_in_tree()); } void ThemeEditorPreview::_picker_button_cbk() { picker_overlay->set_visible(picker_button->is_pressed()); + if (picker_button->is_pressed()) { + _reset_picker_overlay(); + } } Control *ThemeEditorPreview::_find_hovered_control(Control *p_parent, Vector2 p_mouse_position) { @@ -117,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); + 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); @@ -126,14 +136,14 @@ void ThemeEditorPreview::_draw_picker_overlay() { highlight_label_rect.size.x += margin_left + margin_right; highlight_label_rect.size.y += margin_top + margin_bottom; - highlight_label_rect.position.x = CLAMP(highlight_label_rect.position.x, 0.0, picker_overlay->get_size().width); - highlight_label_rect.position.y = CLAMP(highlight_label_rect.position.y, 0.0, picker_overlay->get_size().height); + highlight_label_rect.position = highlight_label_rect.position.clamp(Vector2(), picker_overlay->get_size()); + picker_overlay->draw_style_box(theme_cache.preview_picker_label, highlight_label_rect); Point2 label_pos = highlight_label_rect.position; label_pos.y += highlight_label_rect.size.y - margin_bottom; label_pos.x += margin_left; - picker_overlay->draw_string(theme_cache.preview_picker_font, label_pos, highlight_name); + picker_overlay->draw_string(theme_cache.preview_picker_font, label_pos, highlight_name, HORIZONTAL_ALIGNMENT_LEFT, -1, theme_cache.font_size); } } @@ -144,7 +154,7 @@ void ThemeEditorPreview::_gui_input_picker_overlay(const Ref<InputEvent> &p_even Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { if (hovered_control) { StringName theme_type = hovered_control->get_theme_type_variation(); if (theme_type == StringName()) { @@ -154,6 +164,7 @@ void ThemeEditorPreview::_gui_input_picker_overlay(const Ref<InputEvent> &p_even emit_signal(SNAME("control_picked"), theme_type); picker_button->set_pressed(false); picker_overlay->set_visible(false); + return; } } @@ -164,6 +175,9 @@ void ThemeEditorPreview::_gui_input_picker_overlay(const Ref<InputEvent> &p_even hovered_control = _find_hovered_control(preview_content, mp); picker_overlay->update(); } + + // Forward input to the scroll container underneath to allow scrolling. + preview_container->gui_input(p_event); } void ThemeEditorPreview::_reset_picker_overlay() { @@ -188,11 +202,13 @@ void ThemeEditorPreview::_notification(int p_what) { theme_cache.preview_picker_overlay_color = get_theme_color(SNAME("preview_picker_overlay_color"), SNAME("ThemeEditor")); theme_cache.preview_picker_label = get_theme_stylebox(SNAME("preview_picker_label"), SNAME("ThemeEditor")); theme_cache.preview_picker_font = get_theme_font(SNAME("status_source"), SNAME("EditorFonts")); + theme_cache.font_size = get_theme_font_size(SNAME("font_size"), SNAME("EditorFonts")); } break; + case NOTIFICATION_PROCESS: { time_left -= get_process_delta_time(); if (time_left < 0) { - time_left = 1.5; + time_left = REFRESH_TIMER; _refresh_interval(); } } break; @@ -219,9 +235,7 @@ ThemeEditorPreview::ThemeEditorPreview() { preview_body->set_v_size_flags(SIZE_EXPAND_FILL); add_child(preview_body); - ScrollContainer *preview_container = memnew(ScrollContainer); - preview_container->set_enable_v_scroll(true); - preview_container->set_enable_h_scroll(true); + preview_container = memnew(ScrollContainer); preview_body->add_child(preview_container); MarginContainer *preview_root = memnew(MarginContainer); @@ -233,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); @@ -256,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); @@ -330,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); @@ -359,7 +383,7 @@ DefaultThemeEditorPreview::DefaultThemeEditorPreview() { vhb->add_child(memnew(VSeparator)); VBoxContainer *hvb = memnew(VBoxContainer); vhb->add_child(hvb); - hvb->set_alignment(BoxContainer::ALIGN_CENTER); + hvb->set_alignment(BoxContainer::ALIGNMENT_CENTER); hvb->set_h_size_flags(SIZE_EXPAND_FILL); hvb->add_child(memnew(HSlider)); HScrollBar *hsb = memnew(HScrollBar); diff --git a/editor/plugins/theme_editor_preview.h b/editor/plugins/theme_editor_preview.h index 4e1b149e70..4d209ac788 100644 --- a/editor/plugins/theme_editor_preview.h +++ b/editor/plugins/theme_editor_preview.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,32 +32,21 @@ #define THEME_EDITOR_PREVIEW_H #include "scene/gui/box_container.h" -#include "scene/gui/check_box.h" -#include "scene/gui/check_button.h" -#include "scene/gui/color_picker.h" +#include "scene/gui/button.h" #include "scene/gui/color_rect.h" -#include "scene/gui/label.h" #include "scene/gui/margin_container.h" -#include "scene/gui/menu_button.h" -#include "scene/gui/option_button.h" -#include "scene/gui/panel.h" -#include "scene/gui/progress_bar.h" #include "scene/gui/scroll_container.h" -#include "scene/gui/separator.h" -#include "scene/gui/spin_box.h" -#include "scene/gui/tab_container.h" -#include "scene/gui/text_edit.h" -#include "scene/gui/tree.h" #include "scene/resources/theme.h" -#include "editor/editor_node.h" +class ColorPickerButton; class ThemeEditorPreview : public VBoxContainer { GDCLASS(ThemeEditorPreview, VBoxContainer); - ColorRect *preview_bg; - MarginContainer *preview_overlay; - Control *picker_overlay; + ScrollContainer *preview_container = nullptr; + ColorRect *preview_bg = nullptr; + MarginContainer *preview_overlay = nullptr; + Control *picker_overlay = nullptr; Control *hovered_control = nullptr; struct ThemeCache { @@ -65,6 +54,7 @@ class ThemeEditorPreview : public VBoxContainer { Color preview_picker_overlay_color; Ref<StyleBox> preview_picker_label; Ref<Font> preview_picker_font; + int font_size = 16; } theme_cache; double time_left = 0; @@ -81,9 +71,9 @@ class ThemeEditorPreview : public VBoxContainer { void _reset_picker_overlay(); protected: - HBoxContainer *preview_toolbar; - MarginContainer *preview_content; - Button *picker_button; + HBoxContainer *preview_toolbar = nullptr; + MarginContainer *preview_content = nullptr; + Button *picker_button = nullptr; void add_preview_overlay(Control *p_overlay); @@ -99,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(); }; @@ -108,7 +103,7 @@ class SceneThemeEditorPreview : public ThemeEditorPreview { Ref<PackedScene> loaded_scene; - Button *reload_scene_button; + Button *reload_scene_button = nullptr; void _reload_scene(); diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index d54906c98c..3fe6778f48 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,8 @@ #include "atlas_merging_dialog.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/gui/control.h" @@ -60,7 +62,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; @@ -81,7 +83,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla } // Copy the properties. - TileData *original_tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + TileData *original_tile_data = atlas_source->get_tile_data(tile_id, alternative_id); List<PropertyInfo> properties; original_tile_data->get_property_list(&properties); for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { @@ -94,12 +96,14 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla } // Copy the texture. - Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id); - Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.size * new_texture_region_size); - if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) { - output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height())); + for (int frame = 0; frame < atlas_source->get_tile_animation_frames_count(tile_id); frame++) { + Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id, frame); + Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.size * new_texture_region_size); + if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) { + output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height())); + } + output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, dst_rect_wide.get_center() - src_rect.size / 2); } - output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, (dst_rect_wide.get_position() + dst_rect_wide.get_end()) / 2 - src_rect.size / 2); } // Compute the atlas offset. @@ -112,11 +116,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_texture(output_image_texture); + merged->set_name(p_atlas_sources[0]->get_name()); + merged->set_texture(ImageTexture::create_from_image(output_image)); merged->set_texture_region_size(new_texture_region_size); } } @@ -236,7 +237,7 @@ void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { if (texture.is_valid()) { String item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); atlas_merging_atlases_list->add_item(item_text, texture); - atlas_merging_atlases_list->set_item_metadata(atlas_merging_atlases_list->get_item_count() - 1, source_id); + atlas_merging_atlases_list->set_item_metadata(-1, source_id); } } } @@ -248,12 +249,14 @@ void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { } AtlasMergingDialog::AtlasMergingDialog() { + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + // Atlas merging window. set_title(TTR("Atlas Merging")); 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); @@ -298,7 +301,7 @@ AtlasMergingDialog::AtlasMergingDialog() { preview = memnew(TextureRect); preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); - preview->set_expand(true); + preview->set_ignore_texture_size(true); preview->hide(); preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); atlas_merging_right_panel->add_child(preview); @@ -306,8 +309,8 @@ AtlasMergingDialog::AtlasMergingDialog() { select_2_atlases_label = memnew(Label); select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); - select_2_atlases_label->set_align(Label::ALIGN_CENTER); - select_2_atlases_label->set_valign(Label::VALIGN_CENTER); + select_2_atlases_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + select_2_atlases_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); select_2_atlases_label->set_text(TTR("Please select two atlases or more.")); atlas_merging_right_panel->add_child(select_2_atlases_label); diff --git a/editor/plugins/tiles/atlas_merging_dialog.h b/editor/plugins/tiles/atlas_merging_dialog.h index 7cb54bc17e..c54e259594 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.h +++ b/editor/plugins/tiles/atlas_merging_dialog.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,14 +31,14 @@ #ifndef ATLAS_MERGING_DIALOG_H #define ATLAS_MERGING_DIALOG_H -#include "editor/editor_node.h" #include "editor/editor_properties.h" - #include "scene/gui/dialogs.h" #include "scene/gui/item_list.h" #include "scene/gui/texture_rect.h" #include "scene/resources/tile_set.h" +class EditorFileDialog; + class AtlasMergingDialog : public ConfirmationDialog { GDCLASS(AtlasMergingDialog, ConfirmationDialog); @@ -46,22 +46,22 @@ private: int commited_actions_count = 0; bool delete_original_atlases = true; Ref<TileSetAtlasSource> merged; - LocalVector<Map<Vector2i, Vector2i>> merged_mapping; + LocalVector<HashMap<Vector2i, Vector2i>> merged_mapping; Ref<TileSet> tile_set; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + UndoRedo *undo_redo = nullptr; // Settings. int next_line_after_column = 30; // GUI. - ItemList *atlas_merging_atlases_list; - EditorPropertyVector2i *texture_region_size_editor_property; - EditorPropertyInteger *columns_editor_property; - TextureRect *preview; - Label *select_2_atlases_label; - EditorFileDialog *editor_file_dialog; - Button *merge_button; + ItemList *atlas_merging_atlases_list = nullptr; + EditorPropertyVector2i *texture_region_size_editor_property = nullptr; + EditorPropertyInteger *columns_editor_property = nullptr; + TextureRect *preview = nullptr; + Label *select_2_atlases_label = nullptr; + EditorFileDialog *editor_file_dialog = nullptr; + Button *merge_button = nullptr; void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 0add83f64d..f119ada810 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,58 +36,31 @@ #include "scene/gui/box_container.h" #include "scene/gui/label.h" #include "scene/gui/panel.h" -#include "scene/gui/texture_rect.h" +#include "scene/gui/view_panner.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) { - Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid()) { - drag_type = DRAG_TYPE_NONE; - - Vector2i scroll_vec = Vector2((mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT) - (mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT), (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) - (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN)); - if (scroll_vec != Vector2()) { - if (mb->is_ctrl_pressed()) { - if (mb->is_shift_pressed()) { - panning.x += 32 * mb->get_factor() * scroll_vec.y; - panning.y += 32 * mb->get_factor() * scroll_vec.x; - } else { - panning.y += 32 * mb->get_factor() * scroll_vec.y; - panning.x += 32 * mb->get_factor() * scroll_vec.x; - } - - emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); - _update_zoom_and_panning(true); - accept_event(); + if (panner->gui_input(p_event)) { + accept_event(); + } +} - } else if (!mb->is_shift_pressed()) { - zoom_widget->set_zoom_by_increments(scroll_vec.y * 2); - emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); - _update_zoom_and_panning(true); - accept_event(); - } - } +void TileAtlasView::_scroll_callback(Vector2 p_scroll_vec, bool p_alt) { + _pan_callback(-p_scroll_vec * 32); +} - if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - if (mb->is_pressed()) { - drag_type = DRAG_TYPE_PAN; - } else { - drag_type = DRAG_TYPE_NONE; - } - accept_event(); - } - } +void TileAtlasView::_pan_callback(Vector2 p_scroll_vec) { + panning += p_scroll_vec; + emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); + _update_zoom_and_panning(true); +} - Ref<InputEventMouseMotion> mm = p_event; - if (mm.is_valid()) { - if (drag_type == DRAG_TYPE_PAN) { - panning += mm->get_relative(); - _update_zoom_and_panning(); - emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); - accept_event(); - } - } +void TileAtlasView::_zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt) { + zoom_widget->set_zoom_by_increments(-p_scroll_vec.y * 2); + emit_signal(SNAME("transform_changed"), zoom_widget->get_zoom(), panning); + _update_zoom_and_panning(true); } Size2i TileAtlasView::_compute_base_tiles_control_size() { @@ -97,15 +70,6 @@ Size2i TileAtlasView::_compute_base_tiles_control_size() { if (texture.is_valid()) { size = texture->get_size(); } - - // Extend the size to all existing tiles. - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { - Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); - grid_size = grid_size.max(tile_id + Vector2i(1, 1)); - } - size = size.max(grid_size * (tile_set_atlas_source->get_texture_region_size() + tile_set_atlas_source->get_separation()) + tile_set_atlas_source->get_margins()); - return size; } @@ -118,7 +82,7 @@ Size2i TileAtlasView::_compute_alternative_tiles_control_size() { Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size; for (int j = 1; j < alternatives_count; j++) { int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j); - bool transposed = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id))->get_transpose(); + bool transposed = tile_set_atlas_source->get_tile_data(tile_id, alternative_id)->get_transpose(); line_size.x += transposed ? texture_region_size.y : texture_region_size.x; line_size.y = MAX(line_size.y, transposed ? texture_region_size.x : texture_region_size.y); } @@ -130,6 +94,8 @@ Size2i TileAtlasView::_compute_alternative_tiles_control_size() { } void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) { + // Don't allow zoom to go below 1% or above 10000% + zoom_widget->set_zoom(CLAMP(zoom_widget->get_zoom(), 0.01f, 100.f)); float zoom = zoom_widget->get_zoom(); // Compute the minimum sizes. @@ -213,54 +179,89 @@ void TileAtlasView::_draw_base_tiles() { Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (texture.is_valid()) { Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - // Draw the texture, square by square. Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + + // Draw the texture where there is no tile. for (int x = 0; x < grid_size.x; x++) { for (int y = 0; y < grid_size.y; y++) { Vector2i coords = Vector2i(x, y); if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { - Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size); - base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation); + rect = rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (rect.size.x > 0 && rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + } } } } // Draw the texture around the grid. Rect2i rect; + // Top. rect.position = Vector2i(); rect.set_end(Vector2i(texture->get_size().x, margins.y)); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Bottom - int bottom_border = margins.y + (grid_size.y * texture_region_size.y); + int bottom_border = margins.y + (grid_size.y * (texture_region_size.y + separation.y)); if (bottom_border < texture->get_size().y) { rect.position = Vector2i(0, bottom_border); rect.set_end(texture->get_size()); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } + // Left rect.position = Vector2i(0, margins.y); - rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Right. - int right_border = margins.x + (grid_size.x * texture_region_size.x); + int right_border = margins.x + (grid_size.x * (texture_region_size.x + separation.x)); if (right_border < texture->get_size().x) { rect.position = Vector2i(right_border, margins.y); - rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } // Draw actual tiles, using their properties (modulation, etc...) for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i); - // Update the y to max value. - Vector2i offset_pos = (margins + (atlas_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0)); + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) { + // Update the y to max value. + Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame); + Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0); - // Draw the tile. - TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0); + // Draw the tile. + TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame); + + // Draw, the texture in the separation areas + if (separation.x > 0) { + Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y)); + right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect); + base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } + + if (separation.y > 0) { + Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y)); + bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect); + base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } + } } } } @@ -295,30 +296,6 @@ void TileAtlasView::_draw_base_tiles_texture_grid() { } } -void TileAtlasView::_draw_base_tiles_dark() { - Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); - if (texture.is_valid()) { - Vector2i margins = tile_set_atlas_source->get_margins(); - Vector2i separation = tile_set_atlas_source->get_separation(); - Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - - // Draw each tile texture region. - for (int x = 0; x < grid_size.x; x++) { - for (int y = 0; y < grid_size.y; y++) { - Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation)); - Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - - if (base_tile_coords == TileSetSource::INVALID_ATLAS_COORDS) { - // Draw the grid. - base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true); - } - } - } - } -} - void TileAtlasView::_draw_base_tiles_shape_grid() { // Draw the shapes. Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); @@ -326,11 +303,18 @@ void TileAtlasView::_draw_base_tiles_shape_grid() { for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_effective_texture_offset(tile_id, 0); - Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id); - Vector2 origin = texture_region.position + (texture_region.size - tile_shape_size) / 2 + in_tile_base_offset; - // Draw only if the tile shape fits in the texture region - tile_set->draw_tile_shape(base_tiles_shape_grid, Rect2(origin, tile_shape_size), grid_color); + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(tile_id); frame++) { + Color color = grid_color; + if (frame > 0) { + color.a *= 0.3; + } + Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id); + Transform2D tile_xform; + tile_xform.set_origin(texture_region.get_center() + in_tile_base_offset); + tile_xform.set_scale(tile_shape_size); + tile_set->draw_tile_shape(base_tiles_shape_grid, tile_xform, color); + } } } @@ -358,28 +342,28 @@ void TileAtlasView::_draw_alternatives() { Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i); current_pos.x = 0; int y_increment = 0; - Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(atlas_coords); + Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(atlas_coords).size; int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords); for (int j = 1; j < alternatives_count; j++) { int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j); - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id); bool transposed = tile_data->get_transpose(); // Update the y to max value. - Vector2i offset_pos = current_pos; + Vector2i offset_pos; if (transposed) { - offset_pos = (current_pos + Vector2(texture_region.size.y, texture_region.size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); - y_increment = MAX(y_increment, texture_region.size.x); + offset_pos = (current_pos + Vector2(texture_region_size.y, texture_region_size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); + y_increment = MAX(y_increment, texture_region_size.x); } else { - offset_pos = (current_pos + texture_region.size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); - y_increment = MAX(y_increment, texture_region.size.y); + offset_pos = (current_pos + texture_region_size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); + y_increment = MAX(y_increment, texture_region_size.y); } // Draw the tile. TileMap::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id); // Increment the x position. - current_pos.x += transposed ? texture_region.size.y : texture_region.size.x; + current_pos.x += transposed ? texture_region_size.y : texture_region_size.x; } if (alternatives_count > 1) { current_pos.y += y_increment; @@ -442,7 +426,6 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_ base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -491,13 +474,13 @@ void TileAtlasView::_update_alternative_tiles_rect_cache() { int line_height = 0; for (int j = 1; j < alternatives_count; j++) { int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j); - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(tile_id, alternative_id); bool transposed = tile_data->get_transpose(); current.size = transposed ? Vector2i(texture_region_size.y, texture_region_size.x) : texture_region_size; // Update the rect. if (!alternative_tiles_rect_cache.has(tile_id)) { - alternative_tiles_rect_cache[tile_id] = Map<int, Rect2i>(); + alternative_tiles_rect_cache[tile_id] = HashMap<int, Rect2i>(); } alternative_tiles_rect_cache[tile_id][alternative_id] = current; @@ -511,10 +494,10 @@ void TileAtlasView::_update_alternative_tiles_rect_cache() { } Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const { - for (Map<Vector2, Map<int, Rect2i>>::Element *E_coords = alternative_tiles_rect_cache.front(); E_coords; E_coords = E_coords->next()) { - for (Map<int, Rect2i>::Element *E_alternative = E_coords->value().front(); E_alternative; E_alternative = E_alternative->next()) { - if (E_alternative->value().has_point(p_pos)) { - return Vector3i(E_coords->key().x, E_coords->key().y, E_alternative->key()); + 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); } } } @@ -533,7 +516,6 @@ void TileAtlasView::update() { base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -541,9 +523,14 @@ void TileAtlasView::update() { void TileAtlasView::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: + case NOTIFICATION_ENTER_TREE: + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + panner->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(EditorSettings::get_singleton()->get("editors/panning/simple_panning"))); + } break; + + case NOTIFICATION_READY: { button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); - break; + } break; } } @@ -557,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); @@ -577,10 +564,16 @@ TileAtlasView::TileAtlasView() { button_center_view->set_tooltip(TTR("Center View")); add_child(button_center_view); + panner.instantiate(); + panner->set_callbacks(callable_mp(this, &TileAtlasView::_scroll_callback), callable_mp(this, &TileAtlasView::_pan_callback), callable_mp(this, &TileAtlasView::_zoom_callback)); + panner->set_enable_rmb(true); + center_container = memnew(CenterContainer); center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); center_container->set_anchors_preset(Control::PRESET_CENTER); center_container->connect("gui_input", callable_mp(this, &TileAtlasView::gui_input)); + center_container->connect("focus_exited", callable_mp(panner.ptr(), &ViewPanner::release_pan_key)); + center_container->set_focus_mode(FOCUS_CLICK); panel->add_child(center_container); missing_source_label = memnew(Label); @@ -609,7 +602,7 @@ TileAtlasView::TileAtlasView() { Label *base_tile_label = memnew(Label); base_tile_label->set_mouse_filter(Control::MOUSE_FILTER_PASS); base_tile_label->set_text(TTR("Base Tiles")); - base_tile_label->set_align(Label::ALIGN_CENTER); + base_tile_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); left_vbox->add_child(base_tile_label); base_tiles_root_control = memnew(Control); @@ -620,46 +613,40 @@ 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); - base_tiles_dark = memnew(Control); - base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark)); - base_tiles_drawing_root->add_child(base_tiles_dark); - // Alternative tiles. Label *alternative_tiles_label = memnew(Label); alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); alternative_tiles_label->set_text(TTR("Alternative Tiles")); - alternative_tiles_label->set_align(Label::ALIGN_CENTER); + alternative_tiles_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); right_vbox->add_child(alternative_tiles_label); alternative_tiles_root_control = memnew(Control); diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index 5b0df366ae..196a642283 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -37,16 +37,16 @@ #include "scene/gui/center_container.h" #include "scene/gui/label.h" #include "scene/gui/margin_container.h" -#include "scene/gui/scroll_container.h" -#include "scene/gui/texture_rect.h" #include "scene/resources/tile_set.h" +class ViewPanner; + class TileAtlasView : public Control { GDCLASS(TileAtlasView, Control); private: - TileSet *tile_set; - TileSetAtlasSource *tile_set_atlas_source; + TileSet *tile_set = nullptr; + TileSetAtlasSource *tile_set_atlas_source = nullptr; int source_id = TileSet::INVALID_SOURCE; enum DragType { @@ -55,56 +55,58 @@ private: }; DragType drag_type = DRAG_TYPE_NONE; float previous_zoom = 1.0; - EditorZoomWidget *zoom_widget; - Button *button_center_view; - CenterContainer *center_container; + EditorZoomWidget *zoom_widget = nullptr; + Button *button_center_view = nullptr; + CenterContainer *center_container = nullptr; Vector2 panning; void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false); void _zoom_widget_changed(); void _center_view(); virtual void gui_input(const Ref<InputEvent> &p_event) override; - Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache; + Ref<ViewPanner> panner; + void _scroll_callback(Vector2 p_scroll_vec, bool p_alt); + void _pan_callback(Vector2 p_scroll_vec); + void _zoom_callback(Vector2 p_scroll_vec, Vector2 p_origin, bool p_alt); + + HashMap<Vector2, HashMap<int, Rect2i>> alternative_tiles_rect_cache; void _update_alternative_tiles_rect_cache(); - MarginContainer *margin_container; + MarginContainer *margin_container = nullptr; int margin_container_paddings[4] = { 0, 0, 0, 0 }; - HBoxContainer *hbox; - Label *missing_source_label; + HBoxContainer *hbox = nullptr; + Label *missing_source_label = nullptr; // Background - Control *background_left; + Control *background_left = nullptr; void _draw_background_left(); - Control *background_right; + Control *background_right = nullptr; void _draw_background_right(); // Left side. - Control *base_tiles_root_control; + Control *base_tiles_root_control = nullptr; void _base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event); - Control *base_tiles_drawing_root; + Control *base_tiles_drawing_root = nullptr; - Control *base_tiles_draw; + Control *base_tiles_draw = nullptr; void _draw_base_tiles(); - Control *base_tiles_texture_grid; + Control *base_tiles_texture_grid = nullptr; void _draw_base_tiles_texture_grid(); - Control *base_tiles_shape_grid; + Control *base_tiles_shape_grid = nullptr; void _draw_base_tiles_shape_grid(); - Control *base_tiles_dark; - void _draw_base_tiles_dark(); - Size2i _compute_base_tiles_control_size(); // Right side. - Control *alternative_tiles_root_control; + Control *alternative_tiles_root_control = nullptr; void _alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event); - Control *alternative_tiles_drawing_root; + Control *alternative_tiles_drawing_root = nullptr; - Control *alternatives_draw; + Control *alternatives_draw = nullptr; void _draw_alternatives(); Size2i _compute_alternative_tiles_control_size(); @@ -124,7 +126,6 @@ public: // Left side. void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); }; - void set_dark_visible(bool p_visible) { base_tiles_dark->set_visible(p_visible); }; void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); }; Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const; @@ -157,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 d406c2514c..a00e1ed9e8 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -35,11 +35,20 @@ #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" +#include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" -void TileDataEditor::_call_tile_set_changed() { - _tile_set_changed(); +void TileDataEditor::_tile_set_changed_plan_update() { + _tile_set_changed_update_needed = true; + call_deferred(SNAME("_tile_set_changed_deferred_update")); +} + +void TileDataEditor::_tile_set_changed_deferred_update() { + if (_tile_set_changed_update_needed) { + _tile_set_changed(); + _tile_set_changed_update_needed = false; + } } TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { @@ -52,25 +61,27 @@ TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { if (atlas_source) { ERR_FAIL_COND_V(!atlas_source->has_tile(p_cell.get_atlas_coords()), nullptr); ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_cell.get_atlas_coords(), p_cell.alternative_tile), nullptr); - td = Object::cast_to<TileData>(atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile)); + td = atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile); } return td; } void TileDataEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileDataEditor::_tile_set_changed_deferred_update); + ADD_SIGNAL(MethodInfo("needs_redraw")); } void TileDataEditor::set_tile_set(Ref<TileSet> p_tile_set) { if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } tile_set = p_tile_set; if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->connect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } - _call_tile_set_changed(); + _tile_set_changed_plan_update(); } bool DummyObject::_set(const StringName &p_name, const Variant &p_value) { @@ -115,7 +126,14 @@ void GenericTilePolygonEditor::_base_control_draw() { Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorPathSharpHandle"), SNAME("EditorIcons")); const Ref<Texture2D> add_handle = get_theme_icon(SNAME("EditorHandleAdd"), SNAME("EditorIcons")); + const Ref<StyleBox> focus_stylebox = get_theme_stylebox(SNAME("Focus"), SNAME("EditorStyles")); + // Draw the focus rectangle. + if (base_control->has_focus()) { + base_control->draw_style_box(focus_stylebox, Rect2(Vector2(), base_control->get_size())); + } + + // Draw tile-related things. Size2 tile_size = tile_set->get_tile_size(); Transform2D xform; @@ -124,7 +142,9 @@ void GenericTilePolygonEditor::_base_control_draw() { base_control->draw_set_transform_matrix(xform); // Draw the tile shape filled. - tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), Color(1.0, 1.0, 1.0, 0.3), true); + Transform2D tile_xform; + tile_xform.set_scale(tile_size); + tile_set->draw_tile_shape(base_control, tile_xform, Color(1.0, 1.0, 1.0, 0.3), true); // Draw the background. if (background_texture.is_valid()) { @@ -198,8 +218,8 @@ 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); - base_control->draw_string(font, xform.xform(polygons[tinted_polygon_index][tinted_point_index]) - text_size * 0.5, text, HALIGN_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); + 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)); } if (drag_type == DRAG_TYPE_CREATE_POINT) { @@ -213,7 +233,7 @@ void GenericTilePolygonEditor::_base_control_draw() { // Draw the tile shape line. base_control->draw_set_transform_matrix(xform); - tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), grid_color, false); + tile_set->draw_tile_shape(base_control, tile_xform, grid_color, false); base_control->draw_set_transform_matrix(Transform2D()); } @@ -228,11 +248,16 @@ 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); switch (p_item_pressed) { - case RESET_TO_DEFAULT_TILE: - undo_redo->create_action(TTR("Edit Polygons")); + case RESET_TO_DEFAULT_TILE: { + undo_redo->create_action(TTR("Reset Polygons")); undo_redo->add_do_method(this, "clear_polygons"); - undo_redo->add_do_method(this, "add_polygon", tile_set->get_tile_shape_polygon()); + Vector<Vector2> polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } + undo_redo->add_do_method(this, "add_polygon", polygon); undo_redo->add_do_method(base_control, "update"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); undo_redo->add_undo_method(this, "clear_polygons"); @@ -242,9 +267,9 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; - case CLEAR_TILE: - undo_redo->create_action(TTR("Edit Polygons")); + } break; + case CLEAR_TILE: { + undo_redo->create_action(TTR("Clear Polygons")); undo_redo->add_do_method(this, "clear_polygons"); undo_redo->add_do_method(base_control, "update"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); @@ -255,10 +280,51 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; + } break; + case ROTATE_RIGHT: + case ROTATE_LEFT: + case FLIP_HORIZONTALLY: + case FLIP_VERTICALLY: { + undo_redo->create_action(TTR("Rotate Polygons Left")); + for (unsigned int i = 0; i < polygons.size(); i++) { + Vector<Point2> new_polygon; + for (int point_index = 0; point_index < polygons[i].size(); point_index++) { + Vector2 point = polygons[i][point_index]; + switch (p_item_pressed) { + case ROTATE_RIGHT: { + point = Vector2(-point.y, point.x); + } break; + case ROTATE_LEFT: { + point = Vector2(point.y, -point.x); + } break; + case FLIP_HORIZONTALLY: { + point = Vector2(-point.x, point.y); + } break; + case FLIP_VERTICALLY: { + point = Vector2(point.x, -point.y); + } break; + default: + break; + } + new_polygon.push_back(point); + } + undo_redo->add_do_method(this, "set_polygon", i, new_polygon); + } + undo_redo->add_do_method(base_control, "update"); + undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); + for (unsigned int i = 0; i < polygons.size(); i++) { + undo_redo->add_undo_method(this, "set_polygon", polygons[i]); + } + undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); + undo_redo->commit_action(true); + } break; default: break; } + if (!use_undo_redo) { + memdelete(undo_redo); + } } void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index) { @@ -306,6 +372,9 @@ void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_cur ERR_FAIL_COND(!tile_set.is_valid()); Vector<Point2> polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } Point2 snapped_point = r_point; // Snap to polygon vertices. @@ -340,6 +409,7 @@ 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); real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); hovered_polygon_index = -1; @@ -380,15 +450,15 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_ctrl_pressed()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_ctrl_pressed()) { editor_zoom_widget->set_zoom_by_increments(1); _zoom_changed(); accept_event(); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_ctrl_pressed()) { + } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_ctrl_pressed()) { editor_zoom_widget->set_zoom_by_increments(-1); _zoom_changed(); accept_event(); - } else if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + } else if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (tools_button_group->get_pressed_button() != button_create) { in_creation_polygon.clear(); @@ -447,7 +517,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); if (closest_polygon >= 0) { PackedVector2Array old_polygon = polygons[closest_polygon]; - polygons[closest_polygon].remove(closest_point); + polygons[closest_polygon].remove_at(closest_point); undo_redo->create_action(TTR("Edit Polygons")); if (polygons[closest_polygon].size() < 3) { remove_polygon(closest_polygon); @@ -485,7 +555,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) drag_point_index = -1; } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + } else if (mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { if (tools_button_group->get_pressed_button() == button_edit) { // Remove point or pan. @@ -494,7 +564,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); if (closest_polygon >= 0) { PackedVector2Array old_polygon = polygons[closest_polygon]; - polygons[closest_polygon].remove(closest_point); + polygons[closest_polygon].remove_at(closest_point); undo_redo->create_action(TTR("Edit Polygons")); if (polygons[closest_polygon].size() < 3) { remove_polygon(closest_polygon); @@ -519,7 +589,7 @@ void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) } else { drag_type = DRAG_TYPE_NONE; } - } else if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE) { + } else if (mb->get_button_index() == MouseButton::MIDDLE) { if (mb->is_pressed()) { drag_type = DRAG_TYPE_PAN; drag_last_pos = mb->get_position(); @@ -530,17 +600,47 @@ 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) { + use_undo_redo = p_use_undo_redo; } void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) { - if (tile_set != p_tile_set) { - // Set the default tile shape - clear_polygons(); - if (p_tile_set.is_valid()) { - add_polygon(p_tile_set->get_tile_shape_polygon()); + ERR_FAIL_COND(!p_tile_set.is_valid()); + if (tile_set == p_tile_set) { + return; + } + + // Set the default tile shape + clear_polygons(); + if (p_tile_set.is_valid()) { + Vector<Vector2> polygon = p_tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * p_tile_set->get_tile_size(); } + add_polygon(polygon); } + tile_set = p_tile_set; + + // Set the default zoom value. + int default_control_y_size = 200 * EDSCALE; + Vector2 zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size(); + while (zoomed_tile.y < default_control_y_size) { + editor_zoom_widget->set_zoom_by_increments(6, false); + zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size(); + } + while (zoomed_tile.y > default_control_y_size) { + editor_zoom_widget->set_zoom_by_increments(-6, false); + zoomed_tile = editor_zoom_widget->get_zoom() * tile_set->get_tile_size(); + } + editor_zoom_widget->set_zoom_by_increments(-6, false); + _zoom_changed(); } void GenericTilePolygonEditor::set_background(Ref<Texture2D> p_texture, Rect2 p_region, Vector2 p_offset, bool p_flip_h, bool p_flip_v, bool p_transpose, Color p_modulate) { @@ -577,7 +677,7 @@ int GenericTilePolygonEditor::add_polygon(Vector<Point2> p_polygon, int p_index) void GenericTilePolygonEditor::remove_polygon(int p_index) { ERR_FAIL_INDEX(p_index, (int)polygons.size()); - polygons.remove(p_index); + polygons.remove_at(p_index); if (polygons.size() == 0) { button_create->set_pressed(true); @@ -614,14 +714,21 @@ void GenericTilePolygonEditor::set_multiple_polygon_mode(bool p_multiple_polygon void GenericTilePolygonEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_READY: - button_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); - button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); - button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); - button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); - button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenu"), SNAME("EditorIcons"))); - break; + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + button_create->set_icon(get_theme_icon(SNAME("CurveCreate"), SNAME("EditorIcons"))); + button_edit->set_icon(get_theme_icon(SNAME("CurveEdit"), SNAME("EditorIcons"))); + button_delete->set_icon(get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); + button_center_view->set_icon(get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); + button_pixel_snap->set_icon(get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); + button_advanced_menu->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); + + PopupMenu *p = button_advanced_menu->get_popup(); + p->set_item_icon(p->get_item_index(ROTATE_RIGHT), get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons"))); + p->set_item_icon(p->get_item_index(ROTATE_LEFT), get_theme_icon(SNAME("RotateLeft"), SNAME("EditorIcons"))); + p->set_item_icon(p->get_item_index(FLIP_HORIZONTALLY), get_theme_icon(SNAME("MirrorX"), SNAME("EditorIcons"))); + p->set_item_icon(p->get_item_index(FLIP_VERTICALLY), get_theme_icon(SNAME("MirrorY"), SNAME("EditorIcons"))); + } break; } } @@ -637,6 +744,8 @@ void GenericTilePolygonEditor::_bind_methods() { } GenericTilePolygonEditor::GenericTilePolygonEditor() { + editor_undo_redo = EditorNode::get_undo_redo(); + toolbar = memnew(HBoxContainer); add_child(toolbar); @@ -647,26 +756,35 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { button_create->set_toggle_mode(true); button_create->set_button_group(tools_button_group); button_create->set_pressed(true); + button_create->set_tooltip(TTR("Add polygon tool")); toolbar->add_child(button_create); button_edit = memnew(Button); button_edit->set_flat(true); button_edit->set_toggle_mode(true); button_edit->set_button_group(tools_button_group); + button_edit->set_tooltip(TTR("Edit points tool")); toolbar->add_child(button_edit); button_delete = memnew(Button); button_delete->set_flat(true); button_delete->set_toggle_mode(true); button_delete->set_button_group(tools_button_group); + button_delete->set_tooltip(TTR("Delete points tool")); toolbar->add_child(button_delete); button_advanced_menu = memnew(MenuButton); button_advanced_menu->set_flat(true); button_advanced_menu->set_toggle_mode(true); - button_advanced_menu->get_popup()->add_item(TTR("Reset to default tile shape"), RESET_TO_DEFAULT_TILE); - button_advanced_menu->get_popup()->add_item(TTR("Clear"), CLEAR_TILE); + button_advanced_menu->get_popup()->add_item(TTR("Reset to default tile shape"), RESET_TO_DEFAULT_TILE, Key::F); + button_advanced_menu->get_popup()->add_item(TTR("Clear"), CLEAR_TILE, Key::C); + button_advanced_menu->get_popup()->add_separator(); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons")), TTR("Rotate Right"), ROTATE_RIGHT, 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); toolbar->add_child(memnew(VSeparator)); @@ -675,6 +793,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { button_pixel_snap->set_flat(true); button_pixel_snap->set_toggle_mode(true); button_pixel_snap->set_pressed(true); + button_pixel_snap->set_tooltip(TTR("Snap to half-pixel")); toolbar->add_child(button_pixel_snap); Control *root = memnew(Control); @@ -684,16 +803,17 @@ 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); + base_control->set_focus_mode(Control::FOCUS_CLICK); root->add_child(base_control); editor_zoom_widget = memnew(EditorZoomWidget); @@ -721,7 +841,7 @@ Variant TileDataDefaultEditor::_get_painted_value() { } void TileDataDefaultEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Variant value = tile_data->get(property); dummy_object->set(property, value); @@ -731,22 +851,22 @@ void TileDataDefaultEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_at } void TileDataDefaultEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); tile_data->set(property, p_value); } Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); return tile_data->get(property); } -void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - 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->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), 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); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E.key.alternative_tile, property), p_new_value); } } @@ -762,7 +882,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); @@ -777,8 +897,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()); @@ -813,7 +933,7 @@ void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_ti Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (picker_button->is_pressed()) { Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); @@ -904,7 +1024,7 @@ void TileDataDefaultEditor::forward_painting_alternatives_gui_input(TileAtlasVie Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (picker_button->is_pressed()) { Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); @@ -964,6 +1084,7 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2 p_canvas_item->draw_rect(rect, value); } else { Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); String text; switch (value.get_type()) { case Variant::INT: @@ -995,8 +1116,9 @@ void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2 } } - Vector2 string_size = font->get_string_size(text); - p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, 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); } } @@ -1026,11 +1148,12 @@ void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, p_type, p_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); property_editor->set_object_and_property(dummy_object, p_property); if (p_label.is_empty()) { - property_editor->set_label(p_property); + property_editor->set_label(EditorPropertyNameProcessor::get_singleton()->process_name(p_property, EditorPropertyNameProcessor::get_default_inspector_style())); } else { property_editor->set_label(p_label); } property_editor->connect("property_changed", callable_mp(this, &TileDataDefaultEditor::_property_value_changed).unbind(1)); + property_editor->set_tooltip(p_property); property_editor->update_property(); add_child(property_editor); } @@ -1038,19 +1161,20 @@ void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p void TileDataDefaultEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); tile_bool_checked = get_theme_icon(SNAME("TileChecked"), SNAME("EditorIcons")); tile_bool_unchecked = get_theme_icon(SNAME("TileUnchecked"), SNAME("EditorIcons")); - break; - default: - break; + } break; } } TileDataDefaultEditor::TileDataDefaultEditor() { + undo_redo = EditorNode::get_undo_redo(); + label = memnew(Label); label->set_text(TTR("Painting:")); + label->set_theme_type_variation("HeaderSmall"); add_child(label); toolbar->add_child(memnew(VSeparator)); @@ -1058,7 +1182,7 @@ TileDataDefaultEditor::TileDataDefaultEditor() { picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", Key::P)); toolbar->add_child(picker_button); } @@ -1072,14 +1196,15 @@ void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Tran ERR_FAIL_COND(!tile_data); Vector2i tile_set_tile_size = tile_set->get_tile_size(); - Rect2i rect = Rect2i(-tile_set_tile_size / 2, tile_set_tile_size); Color color = Color(1.0, 0.0, 0.0); if (p_selected) { 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); color = selection_color; } - tile_set->draw_tile_shape(p_canvas_item, p_transform.xform(rect), color); + Transform2D tile_xform; + tile_xform.set_scale(tile_set_tile_size); + tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, color); } void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { @@ -1150,7 +1275,7 @@ Variant TileDataOcclusionShapeEditor::_get_painted_value() { } void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer); @@ -1162,7 +1287,7 @@ void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile } void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Ref<OccluderPolygon2D> occluder_polygon = p_value; tile_data->set_occluder(occlusion_layer, occluder_polygon); @@ -1171,16 +1296,16 @@ void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atl } Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); return tile_data->get_occluder(occlusion_layer); } -void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - 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->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), 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); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, occlusion_layer), p_new_value); } } @@ -1190,15 +1315,15 @@ void TileDataOcclusionShapeEditor::_tile_set_changed() { void TileDataOcclusionShapeEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); - break; - default: - break; + } break; } } TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() { + undo_redo = EditorNode::get_undo_redo(); + polygon_editor = memnew(GenericTilePolygonEditor); add_child(polygon_editor); } @@ -1207,6 +1332,15 @@ void TileDataCollisionEditor::_property_value_changed(StringName p_property, Var dummy_object->set(p_property, p_value); } +void TileDataCollisionEditor::_property_selected(StringName p_path, int p_focusable) { + // Deselect all other properties + for (KeyValue<StringName, EditorProperty *> &editor : property_editors) { + if (editor.key != p_path) { + editor.value->deselect(); + } + } +} + void TileDataCollisionEditor::_polygons_changed() { // Update the dummy object properties and their editors. for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { @@ -1228,6 +1362,8 @@ void TileDataCollisionEditor::_polygons_changed() { one_way_property_editor->set_object_and_property(dummy_object, one_way_property); one_way_property_editor->set_label(one_way_property); one_way_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + one_way_property_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + one_way_property_editor->set_tooltip(one_way_property_editor->get_edited_property()); one_way_property_editor->update_property(); add_child(one_way_property_editor); property_editors[one_way_property] = one_way_property_editor; @@ -1238,6 +1374,8 @@ void TileDataCollisionEditor::_polygons_changed() { one_way_margin_property_editor->set_object_and_property(dummy_object, one_way_margin_property); one_way_margin_property_editor->set_label(one_way_margin_property); one_way_margin_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + one_way_margin_property_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + one_way_margin_property_editor->set_tooltip(one_way_margin_property_editor->get_edited_property()); one_way_margin_property_editor->update_property(); add_child(one_way_margin_property_editor); property_editors[one_way_margin_property] = one_way_margin_property_editor; @@ -1262,21 +1400,25 @@ void TileDataCollisionEditor::_polygons_changed() { } Variant TileDataCollisionEditor::_get_painted_value() { + Dictionary dict; + dict["linear_velocity"] = dummy_object->get("linear_velocity"); + dict["angular_velocity"] = dummy_object->get("angular_velocity"); Array array; for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { ERR_FAIL_COND_V(polygon_editor->get_polygon(i).size() < 3, Variant()); - Dictionary dict; - dict["points"] = polygon_editor->get_polygon(i); - dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); - dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = polygon_editor->get_polygon(i); + polygon_dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); + polygon_dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); + array.push_back(polygon_dict); } + dict["polygons"] = array; - return array; + return dict; } void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); polygon_editor->clear_polygons(); @@ -1288,68 +1430,77 @@ void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_ } _polygons_changed(); + dummy_object->set("linear_velocity", tile_data->get_constant_linear_velocity(physics_layer)); + dummy_object->set("angular_velocity", tile_data->get_constant_angular_velocity(physics_layer)); for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { dummy_object->set(vformat("polygon_%d_one_way", i), tile_data->is_collision_polygon_one_way(physics_layer, i)); dummy_object->set(vformat("polygon_%d_one_way_margin", i), tile_data->get_collision_polygon_one_way_margin(physics_layer, i)); } - for (Map<StringName, EditorProperty *>::Element *E = property_editors.front(); E; E = E->next()) { - E->get()->update_property(); + for (const KeyValue<StringName, EditorProperty *> &E : property_editors) { + E.value->update_property(); } polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); - Array array = p_value; + Dictionary dict = p_value; + tile_data->set_constant_linear_velocity(physics_layer, dict["linear_velocity"]); + tile_data->set_constant_angular_velocity(physics_layer, dict["angular_velocity"]); + Array array = dict["polygons"]; tile_data->set_collision_polygons_count(physics_layer, array.size()); for (int i = 0; i < array.size(); i++) { - Dictionary dict = array[i]; - tile_data->set_collision_polygon_points(physics_layer, i, dict["points"]); - tile_data->set_collision_polygon_one_way(physics_layer, i, dict["one_way"]); - tile_data->set_collision_polygon_one_way_margin(physics_layer, i, dict["one_way_margin"]); + Dictionary polygon_dict = array[i]; + tile_data->set_collision_polygon_points(physics_layer, i, polygon_dict["points"]); + tile_data->set_collision_polygon_one_way(physics_layer, i, polygon_dict["one_way"]); + tile_data->set_collision_polygon_one_way_margin(physics_layer, i, polygon_dict["one_way_margin"]); } polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); } Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); + Dictionary dict; + dict["linear_velocity"] = tile_data->get_constant_linear_velocity(physics_layer); + dict["angular_velocity"] = tile_data->get_constant_angular_velocity(physics_layer); Array array; for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { - Dictionary dict; - dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); - dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); - dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); + polygon_dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); + polygon_dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); + array.push_back(polygon_dict); } - return array; + dict["polygons"] = array; + 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 (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Array old_array = E->get(); + for (KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Array old_array = E.value; - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), old_array.size()); + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), old_array.size()); for (int i = 0; i < old_array.size(); i++) { Dictionary dict = old_array[i]; - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); } - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), new_array.size()); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), new_array.size()); for (int i = 0; i < new_array.size(); i++) { Dictionary dict = new_array[i]; - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); } } } @@ -1361,20 +1512,45 @@ void TileDataCollisionEditor::_tile_set_changed() { void TileDataCollisionEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); - break; - default: - break; + } break; } } TileDataCollisionEditor::TileDataCollisionEditor() { + undo_redo = EditorNode::get_undo_redo(); + polygon_editor = memnew(GenericTilePolygonEditor); polygon_editor->set_multiple_polygon_mode(true); polygon_editor->connect("polygons_changed", callable_mp(this, &TileDataCollisionEditor::_polygons_changed)); add_child(polygon_editor); + dummy_object->add_dummy_property("linear_velocity"); + dummy_object->set("linear_velocity", Vector2()); + dummy_object->add_dummy_property("angular_velocity"); + dummy_object->set("angular_velocity", 0.0); + + EditorProperty *linear_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::VECTOR2, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + linear_velocity_editor->set_object_and_property(dummy_object, "linear_velocity"); + linear_velocity_editor->set_label("linear_velocity"); + linear_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + linear_velocity_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + linear_velocity_editor->set_tooltip(linear_velocity_editor->get_edited_property()); + linear_velocity_editor->update_property(); + add_child(linear_velocity_editor); + property_editors["linear_velocity"] = linear_velocity_editor; + + EditorProperty *angular_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::FLOAT, "angular_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + angular_velocity_editor->set_object_and_property(dummy_object, "angular_velocity"); + angular_velocity_editor->set_label("angular_velocity"); + angular_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + angular_velocity_editor->connect("selected", callable_mp(this, &TileDataCollisionEditor::_property_selected)); + angular_velocity_editor->set_tooltip(angular_velocity_editor->get_edited_property()); + angular_velocity_editor->update_property(); + add_child(angular_velocity_editor); + property_editors["angular_velocity"] = angular_velocity_editor; + _polygons_changed(); } @@ -1465,12 +1641,13 @@ void TileDataTerrainsEditor::_tile_set_changed() { ERR_FAIL_COND(!tile_set.is_valid()); // Fix if wrong values are selected. - if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) { + int terrain_set = int(dummy_object->get("terrain_set")); + if (terrain_set >= tile_set->get_terrain_sets_count()) { + terrain_set = -1; dummy_object->set("terrain_set", -1); } - int terrain_set = int(dummy_object->get("terrain")); if (terrain_set >= 0) { - if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) { + if (int(dummy_object->get("terrain")) >= tile_set->get_terrains_count(terrain_set)) { dummy_object->set("terrain", -1); } } @@ -1488,10 +1665,10 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas hovered_coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_pos); hovered_coords = p_tile_set_atlas_source->get_tile_at_coords(hovered_coords); if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(hovered_coords, 0); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(hovered_coords); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, 0); if (terrain_set >= 0 && terrain_set == int(dummy_object->get("terrain_set"))) { // Draw hovered bit. @@ -1501,10 +1678,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); @@ -1513,19 +1695,21 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas } } else { // Draw hovered tile. - Vector2i tile_size = tile_set->get_tile_size(); - Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size)); - tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true); + Transform2D tile_xform; + tile_xform.set_origin(position); + tile_xform.set_scale(tile_set->get_tile_size()); + tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } } } // Dim terrains with wrong terrain set. Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) { Vector2i coords = p_tile_set_atlas_source->get_tile_id(i); if (coords != hovered_coords) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { // Dimming p_canvas_item->draw_set_transform_matrix(p_transform); @@ -1535,7 +1719,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas // Text p_canvas_item->draw_set_transform_matrix(Transform2D()); Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); Color color = Color(1, 1, 1); String text; @@ -1544,8 +1728,9 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas } else { text = "-"; } - Vector2 string_size = font->get_string_size(text); - p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, 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); } } } @@ -1563,7 +1748,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); @@ -1578,8 +1763,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()); @@ -1593,13 +1778,13 @@ 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); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() == terrain_set) { TileMapCell cell; cell.source_id = 0; @@ -1623,16 +1808,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.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_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; } @@ -1660,10 +1854,10 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til hovered_coords = Vector2i(hovered.x, hovered.y); hovered_alternative = hovered.z; if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, hovered_alternative)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(hovered_coords, hovered_alternative); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(hovered_coords, hovered_alternative); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, hovered_alternative); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, hovered_alternative); if (terrain_set == int(dummy_object->get("terrain_set"))) { // Draw hovered bit. @@ -1673,10 +1867,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); @@ -1685,21 +1885,23 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til } } else { // Draw hovered tile. - Vector2i tile_size = tile_set->get_tile_size(); - Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size)); - tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true); + Transform2D tile_xform; + tile_xform.set_origin(position); + tile_xform.set_scale(tile_set->get_tile_size()); + tile_set->draw_tile_shape(p_canvas_item, p_transform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } } } // Dim terrains with wrong terrain set. Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font(SNAME("bold"), SNAME("EditorFonts")); + int font_size = TileSetEditor::get_singleton()->get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts")); for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) { Vector2i coords = p_tile_set_atlas_source->get_tile_id(i); for (int j = 1; j < p_tile_set_atlas_source->get_alternative_tiles_count(coords); j++) { int alternative_tile = p_tile_set_atlas_source->get_alternative_tile_id(coords, j); if (coords != hovered_coords || alternative_tile != hovered_alternative) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { // Dimming p_canvas_item->draw_set_transform_matrix(p_transform); @@ -1709,7 +1911,7 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til // Text p_canvas_item->draw_set_transform_matrix(Transform2D()); Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); Color color = Color(1, 1, 1); String text; @@ -1718,8 +1920,9 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til } else { text = "-"; } - Vector2 string_size = font->get_string_size(text); - p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, 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); } } } @@ -1743,14 +1946,15 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t cell.alternative_tile = 0; // Save the old terrain_set and terrains bits. - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -1773,16 +1977,17 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t cell.set_atlas_coords(coords); cell.alternative_tile = 0; - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() == terrain_set) { // Save the old terrain_set and terrains bits. if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -1790,13 +1995,18 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t // Set the terrains bits. Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_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); } } } @@ -1809,24 +2019,29 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (picker_button->is_pressed()) { Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_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)); } } } @@ -1839,7 +2054,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t coords = p_tile_set_atlas_source->get_tile_at_coords(coords); TileData *tile_data = nullptr; if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { - tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); } int terrain_set = int(dummy_object->get("terrain_set")); int terrain = int(dummy_object->get("terrain")); @@ -1865,10 +2080,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; @@ -1878,7 +2094,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t } drag_last_pos = mb->get_position(); } - } else if (tile_data && tile_data->get_terrain_set() == terrain_set) { + } else if (tile_data->get_terrain_set() == terrain_set) { if (mb->is_ctrl_pressed()) { // Paint terrain set with rect. drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS_RECT; @@ -1906,24 +2122,29 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t // Save the old terrain_set and terrains bits. Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; // Set the terrain bit. Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_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); } } } @@ -1939,7 +2160,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); @@ -1954,15 +2175,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(); - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), tile_data->get_terrain_set()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), drag_painted_value); + for (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.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)); } } } @@ -1970,16 +2192,17 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t drag_type = DRAG_TYPE_NONE; } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Terrain Set")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - 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"]); + 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_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)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + if (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]); } } } @@ -1990,17 +2213,19 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t int terrain_set = int(painted["terrain_set"]); int terrain = int(painted["terrain"]); undo_redo->create_action(TTR("Painting Terrain")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); + 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)) { - 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_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)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + if (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]); } } } @@ -2016,13 +2241,13 @@ 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); coords = p_tile_set_atlas_source->get_tile_at_coords(coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); if (tile_data->get_terrain_set() == terrain_set) { TileMapCell cell; cell.source_id = 0; @@ -2041,24 +2266,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(); - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + for (const TileMapCell &E : edited) { + Vector2i coords = E.get_atlas_coords(); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, 0); + + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = texture_region.get_center() + 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.position + texture_region.get_end()) / 2 + 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)); } } } @@ -2084,14 +2319,15 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi cell.source_id = 0; cell.set_atlas_coords(coords); cell.alternative_tile = alternative_tile; - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -2116,15 +2352,16 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi cell.alternative_tile = alternative_tile; // Save the old terrain_set and terrains bits. - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); if (tile_data->get_terrain_set() == terrain_set) { if (!drag_modified.has(cell)) { Dictionary dict; dict["terrain_set"] = tile_data->get_terrain_set(); + dict["terrain"] = tile_data->get_terrain(); Array array; for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); - array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + array.push_back(tile_data->is_valid_terrain_peering_bit(bit) ? tile_data->get_terrain_peering_bit(bit) : -1); } dict["terrain_peering_bits"] = array; drag_modified[cell] = dict; @@ -2132,13 +2369,19 @@ 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.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(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); } } } @@ -2150,7 +2393,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (picker_button->is_pressed()) { Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); @@ -2158,18 +2401,24 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi int alternative_tile = tile.z; if (coords != TileSetSource::INVALID_ATLAS_COORDS) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); int terrain_set = tile_data->get_terrain_set(); Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + 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)); } } } @@ -2185,7 +2434,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi Vector2i coords = Vector2i(tile.x, tile.y); int alternative_tile = tile.z; - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(coords, alternative_tile); if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; @@ -2201,14 +2450,14 @@ 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; tile_data->set_terrain_set(drag_painted_value); } drag_last_pos = mb->get_position(); - } else if (tile_data && tile_data->get_terrain_set() == terrain_set) { + } else if (tile_data->get_terrain_set() == terrain_set) { // Paint terrain bits. drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; drag_modified.clear(); @@ -2226,23 +2475,29 @@ 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; // Set the terrain bit. Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(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); } } } @@ -2253,14 +2508,15 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi } else { if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Tiles Property")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - 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); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain", coords.x, coords.y, E.key.alternative_tile), dict["terrain"]); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } undo_redo->commit_action(false); @@ -2270,17 +2526,19 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi int terrain_set = int(painted["terrain_set"]); int terrain = int(painted["terrain"]); undo_redo->create_action(TTR("Painting Terrain")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); + 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)) { - 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_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)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + if (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]); } } } @@ -2302,17 +2560,18 @@ void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform void TileDataTerrainsEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); - break; - default: - break; + } break; } } TileDataTerrainsEditor::TileDataTerrainsEditor() { + 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 @@ -2321,7 +2580,7 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", Key::P)); toolbar->add_child(picker_button); // Setup @@ -2335,6 +2594,7 @@ TileDataTerrainsEditor::TileDataTerrainsEditor() { terrain_set_property_editor->set_object_and_property(dummy_object, "terrain_set"); terrain_set_property_editor->set_label("Terrain Set"); terrain_set_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1)); + terrain_set_property_editor->set_tooltip(terrain_set_property_editor->get_edited_property()); add_child(terrain_set_property_editor); terrain_property_editor = memnew(EditorPropertyEnum); @@ -2363,7 +2623,7 @@ Variant TileDataNavigationEditor::_get_painted_value() { } void TileDataNavigationEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); @@ -2377,7 +2637,7 @@ void TileDataNavigationEditor::_set_painted_value(TileSetAtlasSource *p_tile_set } void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND(!tile_data); Ref<NavigationPolygon> navigation_polygon = p_value; tile_data->set_navigation_polygon(navigation_layer, navigation_polygon); @@ -2386,16 +2646,16 @@ void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_s } Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { - TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + TileData *tile_data = p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile); ERR_FAIL_COND_V(!tile_data, Variant()); return tile_data->get_navigation_polygon(navigation_layer); } -void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - 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->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), 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); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, navigation_layer), p_new_value); } } @@ -2405,15 +2665,15 @@ void TileDataNavigationEditor::_tile_set_changed() { void TileDataNavigationEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_ENTER_TREE: { polygon_editor->set_polygons_color(get_tree()->get_debug_navigation_color()); - break; - default: - break; + } break; } } TileDataNavigationEditor::TileDataNavigationEditor() { + undo_redo = EditorNode::get_undo_redo(); + polygon_editor = memnew(GenericTilePolygonEditor); polygon_editor->set_multiple_polygon_mode(true); add_child(polygon_editor); diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 99998dc779..f9b8948d0a 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,9 +33,7 @@ #include "tile_atlas_view.h" -#include "editor/editor_node.h" #include "editor/editor_properties.h" - #include "scene/2d/tile_map.h" #include "scene/gui/box_container.h" #include "scene/gui/control.h" @@ -45,7 +43,9 @@ class TileDataEditor : public VBoxContainer { GDCLASS(TileDataEditor, VBoxContainer); private: - void _call_tile_set_changed(); + bool _tile_set_changed_update_needed = false; + void _tile_set_changed_plan_update(); + void _tile_set_changed_deferred_update(); protected: Ref<TileSet> tile_set; @@ -71,7 +71,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); @@ -92,7 +92,8 @@ private: LocalVector<Vector<Point2>> polygons; bool multiple_polygon_mode = false; - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + bool use_undo_redo = true; + UndoRedo *editor_undo_redo = nullptr; // UI int hovered_polygon_index = -1; @@ -106,34 +107,34 @@ private: DRAG_TYPE_CREATE_POINT, DRAG_TYPE_PAN, }; - DragType drag_type; - int drag_polygon_index; - int drag_point_index; + DragType drag_type = DRAG_TYPE_NONE; + int drag_polygon_index = 0; + int drag_point_index = 0; Vector2 drag_last_pos; PackedVector2Array drag_old_polygon; - HBoxContainer *toolbar; + HBoxContainer *toolbar = nullptr; Ref<ButtonGroup> tools_button_group; - Button *button_create; - Button *button_edit; - Button *button_delete; - Button *button_pixel_snap; - MenuButton *button_advanced_menu; + Button *button_create = nullptr; + Button *button_edit = nullptr; + Button *button_delete = nullptr; + Button *button_pixel_snap = nullptr; + MenuButton *button_advanced_menu = nullptr; Vector<Point2> in_creation_polygon; - Panel *panel; - Control *base_control; - EditorZoomWidget *editor_zoom_widget; - Button *button_center_view; + Panel *panel = nullptr; + Control *base_control = nullptr; + EditorZoomWidget *editor_zoom_widget = nullptr; + Button *button_center_view = nullptr; Vector2 panning; Ref<Texture2D> background_texture; Rect2 background_region; Vector2 background_offset; - bool background_h_flip; - bool background_v_flip; - bool background_transpose; + bool background_h_flip = false; + bool background_v_flip = false; + bool background_transpose = false; Color background_modulate; Color polygon_color = Color(1.0, 0.0, 0.0); @@ -141,6 +142,10 @@ private: enum AdvancedMenuOption { RESET_TO_DEFAULT_TILE, CLEAR_TILE, + ROTATE_RIGHT, + ROTATE_LEFT, + FLIP_HORIZONTALLY, + FLIP_VERTICALLY, }; void _base_control_draw(); @@ -159,6 +164,8 @@ protected: static void _bind_methods(); public: + void set_use_undo_redo(bool p_use_undo_redo); + void set_tile_set(Ref<TileSet> p_tile_set); void set_background(Ref<Texture2D> p_texture, Rect2 p_region = Rect2(), Vector2 p_offset = Vector2(), bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, Color p_modulate = Color(1.0, 1.0, 1.0, 0.0)); @@ -181,12 +188,12 @@ class TileDataDefaultEditor : public TileDataEditor { private: // Toolbar HBoxContainer *toolbar = memnew(HBoxContainer); - Button *picker_button; + Button *picker_button = nullptr; // UI Ref<Texture2D> tile_bool_checked; Ref<Texture2D> tile_bool_unchecked; - Label *label; + Label *label = nullptr; EditorProperty *property_editor = nullptr; @@ -199,7 +206,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); @@ -207,7 +214,7 @@ private: protected: DummyObject *dummy_object = memnew(DummyObject); - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; StringName type; String property; @@ -217,7 +224,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; }; @@ -261,7 +268,7 @@ private: int occlusion_layer = -1; // UI - GenericTilePolygonEditor *polygon_editor; + GenericTilePolygonEditor *polygon_editor = nullptr; void _polygon_changed(PackedVector2Array p_polygon); @@ -269,10 +276,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 = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; virtual void _tile_set_changed() override; @@ -292,21 +299,22 @@ class TileDataCollisionEditor : public TileDataDefaultEditor { int physics_layer = -1; // UI - GenericTilePolygonEditor *polygon_editor; + GenericTilePolygonEditor *polygon_editor = nullptr; DummyObject *dummy_object = memnew(DummyObject); - Map<StringName, EditorProperty *> property_editors; + HashMap<StringName, EditorProperty *> property_editors; void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + void _property_selected(StringName p_path, int p_focusable); void _polygons_changed(); virtual Variant _get_painted_value() override; virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; - virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, HashMap<TileMapCell, Variant, TileMapCell> p_previous_values, Variant p_new_value) override; protected: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; virtual void _tile_set_changed() override; @@ -327,7 +335,7 @@ class TileDataTerrainsEditor : public TileDataEditor { private: // Toolbar HBoxContainer *toolbar = memnew(HBoxContainer); - Button *picker_button; + Button *picker_button = nullptr; // Painting state. enum DragType { @@ -340,11 +348,11 @@ private: DragType drag_type = DRAG_TYPE_NONE; Vector2 drag_start_pos; Vector2 drag_last_pos; - Map<TileMapCell, Variant> drag_modified; + HashMap<TileMapCell, Variant, TileMapCell> drag_modified; Variant drag_painted_value; // UI - Label *label; + Label *label = nullptr; DummyObject *dummy_object = memnew(DummyObject); EditorPropertyEnum *terrain_set_property_editor = nullptr; EditorPropertyEnum *terrain_property_editor = nullptr; @@ -358,7 +366,7 @@ protected: void _notification(int p_what); - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; public: virtual Control *get_toolbar() override { return toolbar; }; @@ -380,7 +388,7 @@ private: PackedVector2Array navigation_polygon; // UI - GenericTilePolygonEditor *polygon_editor; + GenericTilePolygonEditor *polygon_editor = nullptr; void _polygon_changed(PackedVector2Array p_polygon); @@ -388,10 +396,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 = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; 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 77084f551a..1bf24a7393 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -32,10 +32,12 @@ #include "tiles_editor_plugin.h" +#include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" #include "editor/plugins/canvas_item_editor_plugin.h" +#include "scene/2d/camera_2d.h" #include "scene/gui/center_container.h" #include "scene/gui/split_container.h" @@ -43,31 +45,11 @@ #include "core/math/geometry_2d.h" #include "core/os/keyboard.h" -void TileMapEditorTilesPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: - select_tool_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); - paint_tool_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - line_tool_button->set_icon(get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons"))); - rect_tool_button->set_icon(get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons"))); - bucket_tool_button->set_icon(get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons"))); - - picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); - erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); - - missing_atlas_texture_icon = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); - break; - case NOTIFICATION_VISIBILITY_CHANGED: - _stop_dragging(); - break; - } -} - void TileMapEditorTilesPlugin::tile_set_changed() { _update_fix_selected_and_hovered(); _update_tile_set_sources_list(); - _update_bottom_panel(); + _update_source_display(); + _update_patterns_list(); } void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) { @@ -94,7 +76,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) { @@ -102,7 +84,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) { @@ -110,7 +92,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) { @@ -118,20 +100,35 @@ void TileMapEditorTilesPlugin::_update_toolbar() { picker_button->show(); erase_button->show(); tools_settings_vsep_2->show(); - bucket_continuous_checkbox->show(); - random_tile_checkbox->show(); + bucket_contiguous_checkbox->show(); + random_tile_toggle->show(); scatter_label->show(); scatter_spinbox->show(); } } -Control *TileMapEditorTilesPlugin::get_toolbar() const { - return toolbar; +Vector<TileMapEditorPlugin::TabData> TileMapEditorTilesPlugin::get_tabs() const { + Vector<TileMapEditorPlugin::TabData> tabs; + tabs.push_back({ toolbar, tiles_bottom_panel }); + tabs.push_back({ toolbar, patterns_bottom_panel }); + return tabs; +} + +void TileMapEditorTilesPlugin::_tab_changed() { + if (tiles_bottom_panel->is_visible_in_tree()) { + _update_selection_pattern_from_tileset_tiles_selection(); + } else if (patterns_bottom_panel->is_visible_in_tree()) { + _update_selection_pattern_from_tileset_pattern_selection(); + } } void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { // Update the sources. int old_current = sources_list->get_current(); + int old_source = -1; + if (old_current > -1) { + old_source = sources_list->get_item_metadata(old_current); + } sources_list->clear(); TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); @@ -144,30 +141,42 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { return; } - for (int i = 0; i < tile_set->get_source_count(); i++) { - int source_id = tile_set->get_source_id(i); + if (!tile_set->has_source(old_source)) { + old_source = -1; + } + List<int> source_ids = TilesEditorPlugin::get_singleton()->get_sorted_sources(tile_set); + for (const int &source_id : source_ids) { TileSetSource *source = *tile_set->get_source(source_id); Ref<Texture2D> texture; String item_text; + // Common to all type of sources. + if (!source->get_name().is_empty()) { + item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + } + // Atlas source. TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { texture = atlas_source->get_texture(); - if (texture.is_valid()) { - item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); - } else { - item_text = vformat("No Texture Atlas Source (ID: %d)", source_id); + if (item_text.is_empty()) { + if (texture.is_valid()) { + item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); + } else { + item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); + } } } // Scene collection source. TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); if (scene_collection_source) { - texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")); - item_text = vformat(TTR("Scene Collection Source (ID: %d)"), source_id); + texture = tiles_bottom_panel->get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")); + if (item_text.is_empty()) { + item_text = vformat(TTR("Scene Collection Source (ID: %d)"), source_id); + } } // Use default if not valid. @@ -179,24 +188,29 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { } sources_list->add_item(item_text, texture); - sources_list->set_item_metadata(i, source_id); + sources_list->set_item_metadata(-1, source_id); } if (sources_list->get_item_count() > 0) { - if (old_current > 0) { - // Keep the current selected item if needed. - sources_list->set_current(CLAMP(old_current, 0, sources_list->get_item_count() - 1)); + if (old_source >= 0) { + for (int i = 0; i < sources_list->get_item_count(); i++) { + if ((int)sources_list->get_item_metadata(i) == old_source) { + sources_list->set_current(i); + sources_list->ensure_current_is_visible(); + break; + } + } } else { sources_list->set_current(0); } sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current()); } - // Synchronize - TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current()); + // Synchronize the lists. + TilesEditorPlugin::get_singleton()->set_sources_lists_current(sources_list->get_current()); } -void TileMapEditorTilesPlugin::_update_bottom_panel() { +void TileMapEditorTilesPlugin::_update_source_display() { // Update the atlas display. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -243,6 +257,81 @@ void TileMapEditorTilesPlugin::_update_bottom_panel() { } } +void TileMapEditorTilesPlugin::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + if (ED_IS_SHORTCUT("tiles_editor/paste", p_event) && p_event->is_pressed() && !p_event->is_echo()) { + select_last_pattern = true; + int new_pattern_index = tile_set->get_patterns_count(); + undo_redo->create_action(TTR("Add TileSet pattern")); + undo_redo->add_do_method(*tile_set, "add_pattern", tile_map_clipboard, new_pattern_index); + undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index); + undo_redo->commit_action(); + patterns_item_list->accept_event(); + } + + if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) { + Vector<int> selected = patterns_item_list->get_selected_items(); + undo_redo->create_action(TTR("Remove TileSet patterns")); + for (int i = 0; i < selected.size(); i++) { + int pattern_index = selected[i]; + undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index); + undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index); + } + undo_redo->commit_action(); + patterns_item_list->accept_event(); + } +} + +void TileMapEditorTilesPlugin::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) { + // TODO optimize ? + for (int i = 0; i < patterns_item_list->get_item_count(); i++) { + if (patterns_item_list->get_item_metadata(i) == p_pattern) { + patterns_item_list->set_item_icon(i, p_texture); + break; + } + } +} + +void TileMapEditorTilesPlugin::_update_patterns_list() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Recreate the items. + patterns_item_list->clear(); + for (int i = 0; i < tile_set->get_patterns_count(); i++) { + int id = patterns_item_list->add_item(""); + patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i)); + TilesEditorPlugin::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileMapEditorTilesPlugin::_pattern_preview_done)); + } + + // Update the label visibility. + patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0); + + // Added a new pattern, thus select the last one. + if (select_last_pattern) { + patterns_item_list->select(tile_set->get_patterns_count() - 1); + patterns_item_list->grab_focus(); + _update_selection_pattern_from_tileset_pattern_selection(); + } + select_last_pattern = false; +} + void TileMapEditorTilesPlugin::_update_atlas_view() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -260,7 +349,7 @@ void TileMapEditorTilesPlugin::_update_atlas_view() { ERR_FAIL_COND(!atlas_source); tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id); - TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view); + TilesEditorPlugin::get_singleton()->synchronize_atlas_view(tile_atlas_view); tile_atlas_control->update(); } @@ -295,7 +384,7 @@ void TileMapEditorTilesPlugin::_update_scenes_collection_view() { Variant udata = i; EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata); } else { - item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"))); + item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), tiles_bottom_panel->get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons"))); } scene_tiles_list->set_item_metadata(item_index, scene_id); @@ -339,7 +428,7 @@ void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_s TileMapCell selected = TileMapCell(source_id, Vector2i(), scene_id); // Clear the selection if shift is not pressed. - if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (!Input::get_singleton()->is_key_pressed(Key::SHIFT)) { tile_set_selection.clear(); } @@ -351,19 +440,38 @@ void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_s } } - _update_selection_pattern_from_tileset_selection(); + _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(); - selection_pattern->clear(); - _update_selection_pattern_from_tileset_selection(); + selection_pattern.instantiate(); + _update_selection_pattern_from_tileset_tiles_selection(); +} + +void TileMapEditorTilesPlugin::_update_theme() { + source_sort_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Sort"), SNAME("EditorIcons"))); + select_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); + paint_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + line_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons"))); + rect_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons"))); + bucket_tool_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons"))); + + picker_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); + erase_button->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); + random_tile_toggle->set_icon(tiles_bottom_panel->get_theme_icon(SNAME("RandomNumberGenerator"), SNAME("EditorIcons"))); + + missing_atlas_texture_icon = tiles_bottom_panel->get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); } bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { - if (!is_visible_in_tree()) { + if (!(tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree())) { // If the bottom editor is not visible, we ignore inputs. return false; } @@ -391,10 +499,10 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (ED_IS_SHORTCUT("tiles_editor/cut", p_event) || ED_IS_SHORTCUT("tiles_editor/copy", p_event)) { // Fill in the clipboard. if (!tile_map_selection.is_empty()) { - memdelete(tile_map_clipboard); + 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); } @@ -403,9 +511,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(); @@ -434,9 +542,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(); @@ -454,35 +562,37 @@ 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); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos, drag_erasing); + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } + _fix_invalid_tiles_in_tile_map_selection(); } break; case DRAG_TYPE_BUCKET: { Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); 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_continuous_checkbox->is_pressed()); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + HashMap<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } + _fix_invalid_tiles_in_tile_map_selection(); } break; default: break; @@ -499,19 +609,27 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { // Pressed + if (erase_button->is_pressed() || mb->get_button_index() == MouseButton::RIGHT) { + drag_erasing = true; + } + if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { - // Do nothing. + // Cancel tile pasting on right-click + if (mb->get_button_index() == MouseButton::RIGHT) { + drag_type = DRAG_TYPE_NONE; + } } else if (tool_buttons_group->get_pressed_button() == select_tool_button) { drag_start_mouse_pos = mpos; if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { // Move the selection + _update_selection_pattern_from_tilemap_selection(); // Make sure the pattern is up to date before moving. drag_type = DRAG_TYPE_MOVE; drag_modified.clear(); - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - Vector2i coords = E->get(); + for (const Vector2i &E : tile_map_selection) { + Vector2i coords = E; drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); tile_map->set_cell(tile_map_layer, coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } @@ -521,31 +639,32 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } else { // Check if we are picking a tile. - if (picker_button->is_pressed()) { + if (picker_button->is_pressed() || (Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { drag_type = DRAG_TYPE_PICK; drag_start_mouse_pos = mpos; } else { // Paint otherwise. - if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + 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)) { 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); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + 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; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + _fix_invalid_tiles_in_tile_map_selection(); + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CTRL))) { drag_type = DRAG_TYPE_LINE; drag_start_mouse_pos = mpos; drag_modified.clear(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { + } 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))) { drag_type = DRAG_TYPE_RECT; drag_start_mouse_pos = mpos; drag_modified.clear(); @@ -556,19 +675,20 @@ 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_continuous_checkbox->is_pressed()); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + HashMap<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } + _fix_invalid_tiles_in_tile_map_selection(); } } } @@ -576,6 +696,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } else { // Released _stop_dragging(); + drag_erasing = false; } CanvasItemEditor::get_singleton()->update_viewport(); @@ -612,9 +733,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Vector2i tile_shape_size = tile_set->get_tile_size(); // Draw the selection. - if (is_visible_in_tree() && tool_buttons_group->get_pressed_button() == select_tool_button) { + if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && tool_buttons_group->get_pressed_button() == select_tool_button) { // In select mode, we only draw the current selection if we are modifying it (pressing control or shift). - if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { + if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { // Do nothing } else { Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); @@ -624,20 +745,22 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over } // Handle the preview of the tiles to be placed. - if (is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered. - Map<Vector2i, TileMapCell> preview; + 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. + HashMap<Vector2i, TileMapCell> preview; Rect2i drawn_grid_rect; if (drag_type == DRAG_TYPE_PICK) { - // Draw the area being picvked. + // Draw the area being picked. Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); rect.size += Vector2i(1, 1); for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { - Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size)); - tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false); + Transform2D tile_xform; + tile_xform.set_origin(tile_map->map_to_world(coords)); + tile_xform.set_scale(tile_shape_size); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0), false); } } } @@ -645,7 +768,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); @@ -656,21 +779,23 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over } tile_map->draw_cells_outline(p_overlay, to_draw, Color(1.0, 1.0, 1.0), xform); } else if (drag_type == DRAG_TYPE_MOVE) { - // Preview when moving. - Vector2i top_left; - if (!tile_map_selection.is_empty()) { - top_left = tile_map_selection.front()->get(); - } - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - top_left = top_left.min(E->get()); - } - 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); + if (!(patterns_item_list->is_visible_in_tree() && patterns_item_list->has_point(patterns_item_list->get_local_mouse_position()))) { + // Preview when moving. + Vector2i top_left; + if (!tile_map_selection.is_empty()) { + top_left = tile_map_selection.front()->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); - TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); - for (int i = 0; i < selection_used_cells.size(); i++) { - Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern); - preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i])); + TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); + for (int i = 0; i < selection_used_cells.size(); i++) { + Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern); + preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i])); + } } } else if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { // Preview when pasting. @@ -680,36 +805,36 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard); preview[coords] = TileMapCell(tile_map_clipboard->get_cell_source_id(clipboard_used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(clipboard_used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(clipboard_used_cells[i])); } - } else if (!picker_button->is_pressed()) { + } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { bool expand_grid = false; if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. - preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); + preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos, erase_button->is_pressed()); expand_grid = true; - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || drag_type == DRAG_TYPE_LINE) { if (drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. - preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); + preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos, erase_button->is_pressed()); expand_grid = true; } else if (drag_type == DRAG_TYPE_LINE) { // Preview for a line pattern. - preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos); + preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos, drag_erasing); expand_grid = true; } - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button && drag_type == DRAG_TYPE_RECT) { - // Preview for a line pattern. - preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos)); + } else if (drag_type == DRAG_TYPE_RECT) { + // Preview for a rect pattern. + preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos), drag_erasing); expand_grid = true; } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { - // Preview for a line pattern. - preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed()); + // Preview for a fill pattern. + preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_contiguous_checkbox->is_pressed(), erase_button->is_pressed()); } // Expand the grid if needed if (expand_grid && !preview.is_empty()) { - drawn_grid_rect = Rect2i(preview.front()->key(), Vector2i(1, 1)); - for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { - drawn_grid_rect.expand_to(E->key()); + drawn_grid_rect = Rect2i(preview.begin()->key, Vector2i(1, 1)); + for (const KeyValue<Vector2i, TileMapCell> &E : preview) { + drawn_grid_rect.expand_to(E.key); } } } @@ -729,38 +854,43 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Fade out the border of the grid. float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); - float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)(pos_in_rect.x + 1)), 0.0f, 1.0f); float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); - float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)(pos_in_rect.y + 1)), 0.0f, 1.0f); float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); - Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size)); + Transform2D tile_xform; + tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y))); + tile_xform.set_scale(tile_shape_size); Color color = grid_color; color.a = color.a * opacity; - tile_set->draw_tile_shape(p_overlay, cell_region, color, false); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, false); } } } } // Draw the preview. - for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { - Vector2i size = tile_set->get_tile_size(); - Vector2 position = tile_map->map_to_world(E->key()) - size / 2; - Rect2 cell_region = xform.xform(Rect2(position, size)); - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { - tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true); + for (const KeyValue<Vector2i, TileMapCell> &E : preview) { + 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_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->get().source_id)) { - TileSetSource *source = *tile_set->get_source(E->get().source_id); + if (tile_set->has_source(E.value.source_id)) { + TileSetSource *source = *tile_set->get_source(E.value.source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Get tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(E.value.get_atlas_coords(), E.value.alternative_tile); + if (!tile_data) { + continue; + } // Compute the offset - Rect2i source_rect = atlas_source->get_tile_texture_region(E->get().get_atlas_coords()); - Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E->get().get_atlas_coords(), E->get().alternative_tile); + Rect2i source_rect = atlas_source->get_tile_texture_region(E.value.get_atlas_coords()); + Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E.value.get_atlas_coords(), E.value.alternative_tile); // Compute the destination rectangle in the CanvasItem. Rect2 dest_rect; @@ -768,9 +898,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over bool transpose = tile_data->get_transpose(); if (transpose) { - dest_rect.position = (tile_map->map_to_world(E->key()) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_world(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); } else { - dest_rect.position = (tile_map->map_to_world(E->key()) - dest_rect.size / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_world(E.key) - dest_rect.size / 2 - tile_offset); } dest_rect = xform.xform(dest_rect); @@ -786,15 +916,15 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over // Get the tile modulation. Color modulate = tile_data->get_modulate(); Color self_modulate = tile_map->get_self_modulate(); - modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g, modulate.b * self_modulate.b, modulate.a * self_modulate.a); + modulate *= self_modulate; // Draw the tile. p_overlay->draw_texture_rect_region(atlas_source->get_texture(), dest_rect, source_rect, modulate * Color(1.0, 1.0, 1.0, 0.5), transpose, tile_set->is_uv_clipping()); } else { - tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } } else { - tile_set->draw_tile_shape(p_overlay, cell_region, Color(0.0, 0.0, 0.0, 0.5), true); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(0.0, 0.0, 0.0, 0.5), true); } } } @@ -807,7 +937,7 @@ void TileMapEditorTilesPlugin::_mouse_exited_viewport() { CanvasItemEditor::get_singleton()->update_viewport(); } -TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_pattern) { +TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(Ref<TileMapPattern> p_pattern) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { return TileMapCell(); @@ -828,7 +958,7 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_ TileSetSource *source = *tile_set->get_source(source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(atlas_coords, alternative_tile); ERR_FAIL_COND_V(!tile_data, TileMapCell()); sum += tile_data->get_probability(); } else { @@ -847,7 +977,7 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_ TileSetSource *source = *tile_set->get_source(source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { - current += Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile))->get_probability(); + current += atlas_source->get_tile_data(atlas_coords, alternative_tile)->get_probability(); } else { current += 1.0; } @@ -859,26 +989,27 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_ return TileMapCell(); } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos) { +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. - TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + Ref<TileMapPattern> erase_pattern; + erase_pattern.instantiate(); + 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 (!erase_button->is_pressed() && 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++) { @@ -907,15 +1038,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) { +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. @@ -923,10 +1054,12 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start rect.size += Vector2i(1, 1); // Get or create the pattern. - TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; - Map<Vector2i, TileMapCell> err_output; + Ref<TileMapPattern> erase_pattern; + erase_pattern.instantiate(); + 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; + + 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. @@ -934,9 +1067,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 (!erase_button->is_pressed() && 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++) { @@ -964,51 +1097,52 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start return output; } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous) { +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. - TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + Ref<TileMapPattern> erase_pattern; + erase_pattern.instantiate(); + 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; if (!pattern->is_empty()) { - TileMapCell source = tile_map->get_cell(tile_map_layer, p_coords); + TileMapCell source_cell = tile_map->get_cell(tile_map_layer, p_coords); // If we are filling empty tiles, compute the tilemap boundaries. Rect2i boundaries; - if (source.source_id == TileSet::INVALID_SOURCE) { + if (source_cell.source_id == TileSet::INVALID_SOURCE) { boundaries = tile_map->get_used_rect(); } 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()) { Vector2i coords = to_check.back()->get(); to_check.pop_back(); if (!already_checked.has(coords)) { - if (source.source_id == tile_map->get_cell_source_id(tile_map_layer, coords) && - source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && - source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && - (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (source_cell.source_id == tile_map->get_cell_source_id(tile_map_layer, coords) && + 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_toggle->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1035,9 +1169,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i } else { // Replace all tiles like the source. TypedArray<Vector2i> to_check; - if (source.source_id == TileSet::INVALID_SOURCE) { + if (source_cell.source_id == TileSet::INVALID_SOURCE) { Rect2i rect = tile_map->get_used_rect(); - if (rect.size.x <= 0 || rect.size.y <= 0) { + if (rect.has_no_area()) { rect = Rect2i(p_coords, Vector2i(1, 1)); } for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) { @@ -1050,11 +1184,11 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i } for (int i = 0; i < to_check.size(); i++) { Vector2i coords = to_check[i]; - if (source.source_id == tile_map->get_cell_source_id(tile_map_layer, coords) && - source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && - source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && - (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (source_cell.source_id == tile_map->get_cell_source_id(tile_map_layer, coords) && + 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_toggle->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1103,14 +1237,14 @@ void TileMapEditorTilesPlugin::_stop_dragging() { undo_redo->create_action(TTR("Change selection")); undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); - if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT) && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (!Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CTRL)) { tile_map_selection.clear(); } Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); for (int x = rect.position.x; x <= rect.get_end().x; x++) { for (int y = rect.position.y; y <= rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (Input::get_singleton()->is_key_pressed(Key::CTRL)) { if (tile_map_selection.has(coords)) { tile_map_selection.erase(coords); } @@ -1128,114 +1262,153 @@ void TileMapEditorTilesPlugin::_stop_dragging() { _update_tileset_selection_from_selection_pattern(); } break; case DRAG_TYPE_MOVE: { - Vector2i top_left; - if (!tile_map_selection.is_empty()) { - top_left = tile_map_selection.front()->get(); - } - for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { - top_left = top_left.min(E->get()); - } + if (patterns_item_list->is_visible_in_tree() && patterns_item_list->has_point(patterns_item_list->get_local_mouse_position())) { + // Restore the cells. + for (KeyValue<Vector2i, TileMapCell> kv : drag_modified) { + tile_map->set_cell(tile_map_layer, kv.key, kv.value.source_id, kv.value.get_atlas_coords(), kv.value.alternative_tile); + } - Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left); - offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset); + // Creating a pattern in the pattern list. + select_last_pattern = true; + int new_pattern_index = tile_set->get_patterns_count(); + undo_redo->create_action(TTR("Add TileSet pattern")); + undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index); + undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index); + undo_redo->commit_action(); + } else { + // Get the top-left cell. + Vector2i top_left; + if (!tile_map_selection.is_empty()) { + top_left = tile_map_selection.front()->get(); + } + for (const Vector2i &E : tile_map_selection) { + top_left = top_left.min(E); + } - TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); + // Get the offset from the mouse. + Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left); + offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset); - Vector2i coords; - Map<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); - coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); - cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords)); - } + TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); - Map<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(); - } - for (int i = 0; i < selection_used_cells.size(); i++) { - coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); - cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i])); - } - undo_redo->create_action(TTR("Move tiles")); - // Move the 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 (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); - } + // Build the list of cells to undo. + Vector2i coords; + 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); + coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); + cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(tile_map_layer, coords), tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords)); + } - // Update the selection. - undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); - tile_map_selection.clear(); - for (int i = 0; i < selection_used_cells.size(); i++) { - coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); - tile_map_selection.insert(coords); + // Build the list of cells to 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(); + } + for (int i = 0; i < selection_used_cells.size(); i++) { + coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); + cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i])); + } + + // Move the tiles. + undo_redo->create_action(TTR("Move tiles")); + 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 (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. + undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + tile_map_selection.clear(); + for (int i = 0; i < selection_used_cells.size(); i++) { + coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); + tile_map_selection.insert(coords); + } + undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + undo_redo->commit_action(); } - undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection()); - undo_redo->commit_action(); } break; case DRAG_TYPE_PICK: { Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); rect.size += Vector2i(1, 1); - memdelete(selection_pattern); + + int picked_source = -1; TypedArray<Vector2i> coords_array; for (int x = rect.position.x; x < rect.get_end().x; x++) { for (int y = rect.position.y; y < rect.get_end().y; y++) { Vector2i coords = Vector2i(x, y); - if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { + + int source = tile_map->get_cell_source_id(tile_map_layer, coords); + if (source != TileSet::INVALID_SOURCE) { coords_array.push_back(coords); + if (picked_source == -1) { + picked_source = source; + } else if (picked_source != source) { + picked_source = -2; + } } } } - selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); - if (!selection_pattern->is_empty()) { + + if (picked_source >= 0) { + for (int i = 0; i < sources_list->get_item_count(); i++) { + if (int(sources_list->get_item_metadata(i)) == picked_source) { + sources_list->set_current(i); + break; + } + } + sources_list->ensure_current_is_visible(); + TilesEditorPlugin::get_singleton()->set_sources_lists_current(picked_source); + } + + Ref<TileMapPattern> new_selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); + if (!new_selection_pattern->is_empty()) { + selection_pattern = new_selection_pattern; _update_tileset_selection_from_selection_pattern(); - } else { - _update_selection_pattern_from_tileset_selection(); } picker_button->set_pressed(false); } break; case DRAG_TYPE_PAINT: { undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - 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 : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + 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); } 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); + 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 (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - 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); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); + 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); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); } 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)); + 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 (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - 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); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); + 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); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); } undo_redo->commit_action(); } break; case DRAG_TYPE_BUCKET: { undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - 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 : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + 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); } undo_redo->commit_action(false); } break; @@ -1263,8 +1436,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); + patterns_item_list->deselect_all(); tile_map_selection.clear(); - selection_pattern->clear(); + selection_pattern.instantiate(); return; } @@ -1274,8 +1448,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); + patterns_item_list->deselect_all(); tile_map_selection.clear(); - selection_pattern->clear(); + selection_pattern.instantiate(); return; } @@ -1285,8 +1460,9 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); + patterns_item_list->deselect_all(); tile_map_selection.clear(); - selection_pattern->clear(); + selection_pattern.instantiate(); return; } @@ -1303,7 +1479,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()) || @@ -1314,8 +1490,34 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { if (!tile_map_selection.is_empty()) { _update_selection_pattern_from_tilemap_selection(); + } else if (tiles_bottom_panel->is_visible_in_tree()) { + _update_selection_pattern_from_tileset_tiles_selection(); } else { - _update_selection_pattern_from_tileset_selection(); + _update_selection_pattern_from_tileset_pattern_selection(); + } +} + +void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + 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) { + to_remove.insert(selected); + } + } + + for (Vector2i cell : to_remove) { + 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(); } } @@ -1325,18 +1527,23 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection( return; } + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count()); - memdelete(selection_pattern); + 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); } -void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection() { +void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_tiles_selection() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { return; @@ -1351,26 +1558,26 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( tile_map_selection.clear(); // Clear the selected pattern. - selection_pattern->clear(); + 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; - for (Map<int, List<const TileMapCell *>>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) { + for (const KeyValue<int, List<const TileMapCell *>> &E_source : per_source) { // 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()); + TileSetSource *source = *tile_set->get_source(E_source.key); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Organize using coordinates. - for (const TileMapCell *current : E_source->get()) { + for (const TileMapCell *current : E_source.value) { if (current->alternative_tile == 0) { organized_pattern[current->get_atlas_coords()] = current; } else { @@ -1379,24 +1586,24 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( } // 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 { // Add everything unorganized. - for (const TileMapCell *cell : E_source->get()) { + for (const TileMapCell *cell : E_source.value) { unorganized.push_back(cell); } } // Now add everything to the output pattern. - for (Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); E_cell; E_cell = E_cell->next()) { - selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + for (const KeyValue<Vector2i, const TileMapCell *> &E_cell : organized_pattern) { + selection_pattern->set_cell(E_cell.key - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell.value->source_id, E_cell.value->get_atlas_coords(), E_cell.value->alternative_tile); } Vector2i organized_size = selection_pattern->get_size(); int unorganized_index = 0; @@ -1409,6 +1616,30 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( CanvasItemEditor::get_singleton()->update_viewport(); } +void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Clear the tilemap selection. + tile_map_selection.clear(); + + // Clear the selected pattern. + selection_pattern.instantiate(); + + if (patterns_item_list->get_selected_items().size() >= 1) { + selection_pattern = patterns_item_list->get_item_metadata(patterns_item_list->get_selected_items()[0]); + } + + CanvasItemEditor::get_singleton()->update_viewport(); +} + void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern() { tile_set_selection.clear(); TypedArray<Vector2i> used_cells = selection_pattern->get_used_cells(); @@ -1418,7 +1649,9 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords))); } } - _update_bottom_panel(); + _update_source_display(); + tile_atlas_control->update(); + alternative_tiles_control->update(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { @@ -1450,15 +1683,27 @@ 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) { - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords()), selection_color, false); + 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_atlas_coords(), frame), color, false); + } } } // Draw the hovered tile. if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) { - tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords()), Color(1.0, 1.0, 1.0), false); + for (int frame = 0; frame < atlas->get_tile_animation_frames_count(hovered_tile.get_atlas_coords()); frame++) { + Color color = Color(1.0, 1.0, 1.0); + if (frame > 0) { + color.a *= 0.3; + } + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords(), frame), color, false); + } } // Draw the selection rect. @@ -1469,7 +1714,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)); @@ -1479,8 +1724,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); } } } @@ -1539,7 +1784,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven } Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { // Pressed tile_set_dragging_selection = true; tile_set_drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position(); @@ -1554,7 +1799,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0)); } } - _update_selection_pattern_from_tileset_selection(); + _update_selection_pattern_from_tileset_tiles_selection(); } else { // Released if (tile_set_dragging_selection) { if (!mb->is_shift_pressed()) { @@ -1591,7 +1836,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEven } } } - _update_selection_pattern_from_tileset_selection(); + _update_selection_pattern_from_tileset_tiles_selection(); } tile_set_dragging_selection = false; } @@ -1626,9 +1871,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); } @@ -1697,7 +1942,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In } Ref<InputEventMouseButton> mb = p_event; - if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { // Pressed // Left click pressed. if (!mb->is_shift_pressed()) { @@ -1711,7 +1956,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<In tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)); } } - _update_selection_pattern_from_tileset_selection(); + _update_selection_pattern_from_tileset_tiles_selection(); } tile_atlas_control->update(); alternative_tiles_control->update(); @@ -1730,8 +1975,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; } @@ -1744,13 +1989,22 @@ void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer // Clear the selection. tile_set_selection.clear(); + patterns_item_list->deselect_all(); tile_map_selection.clear(); - selection_pattern->clear(); + selection_pattern.instantiate(); } tile_map_layer = p_tile_map_layer; } +void TileMapEditorTilesPlugin::_set_source_sort(int p_sort) { + for (int i = 0; i != TilesEditorPlugin::SOURCE_SORT_MAX; i++) { + source_sort_button->get_popup()->set_item_checked(i, (i == (int)p_sort)); + } + TilesEditorPlugin::get_singleton()->set_sorting_option(p_sort); + _update_tile_set_sources_list(); +} + void TileMapEditorTilesPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileMapEditorTilesPlugin::_scene_thumbnail_done); ClassDB::bind_method(D_METHOD("_set_tile_map_selection", "selection"), &TileMapEditorTilesPlugin::_set_tile_map_selection); @@ -1758,18 +2012,25 @@ void TileMapEditorTilesPlugin::_bind_methods() { } TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { - CanvasItemEditor::get_singleton()->get_viewport_control()->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_mouse_exited_viewport)); + undo_redo = EditorNode::get_undo_redo(); + + CanvasItemEditor::get_singleton() + ->get_viewport_control() + ->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_mouse_exited_viewport)); // --- Shortcuts --- - ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KEY_MASK_CMD | KEY_X); - ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KEY_MASK_CMD | KEY_C); - ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KEY_MASK_CMD | KEY_V); - ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), KEY_ESCAPE); - ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE); + ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KeyModifierMask::CMD | Key::X); + ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KeyModifierMask::CMD | Key::C); + ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KeyModifierMask::CMD | Key::V); + ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), Key::ESCAPE); + ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE); + + // --- Initialize references --- + tile_map_clipboard.instantiate(); + selection_pattern.instantiate(); // --- Toolbar --- toolbar = memnew(HBoxContainer); - toolbar->set_h_size_flags(SIZE_EXPAND_FILL); HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer); @@ -1779,7 +2040,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { select_tool_button->set_flat(true); select_tool_button->set_toggle_mode(true); select_tool_button->set_button_group(tool_buttons_group); - select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", "Selection", KEY_S)); + select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", "Selection", Key::S)); select_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(select_tool_button); @@ -1787,7 +2048,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { paint_tool_button->set_flat(true); paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_D)); + paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", Key::D)); + paint_tool_button->set_tooltip(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle.")); paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -1795,7 +2057,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { line_tool_button->set_flat(true); line_tool_button->set_toggle_mode(true); line_tool_button->set_button_group(tool_buttons_group); - line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", KEY_L)); + line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", Key::L)); line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(line_tool_button); @@ -1803,7 +2065,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { rect_tool_button->set_flat(true); rect_tool_button->set_toggle_mode(true); rect_tool_button->set_button_group(tool_buttons_group); - rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", KEY_R)); + rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", Key::R)); rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(rect_tool_button); @@ -1811,7 +2073,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { bucket_tool_button->set_flat(true); bucket_tool_button->set_toggle_mode(true); bucket_tool_button->set_button_group(tool_buttons_group); - bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", KEY_B)); + bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", Key::B)); bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(bucket_tool_button); toolbar->add_child(tilemap_tiles_tools_buttons); @@ -1827,7 +2089,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", Key::P)); + picker_button->set_tooltip(TTR("Alternatively hold Ctrl with other tools to pick tile.")); picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); @@ -1835,7 +2098,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { erase_button = memnew(Button); erase_button->set_flat(true); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", Key::E)); + erase_button->set_tooltip(TTR("Alternatively use RMB to erase tiles.")); erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); @@ -1844,17 +2108,19 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { tools_settings->add_child(tools_settings_vsep_2); // Continuous checkbox. - bucket_continuous_checkbox = memnew(CheckBox); - bucket_continuous_checkbox->set_flat(true); - bucket_continuous_checkbox->set_text(TTR("Contiguous")); - tools_settings->add_child(bucket_continuous_checkbox); + bucket_contiguous_checkbox = memnew(CheckBox); + bucket_contiguous_checkbox->set_flat(true); + bucket_contiguous_checkbox->set_text(TTR("Contiguous")); + bucket_contiguous_checkbox->set_pressed(true); + 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); @@ -1877,42 +2143,74 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { paint_tool_button->set_pressed(true); _update_toolbar(); - // --- Bottom panel --- - set_name("Tiles"); + // --- Bottom panel tiles --- + tiles_bottom_panel = memnew(VBoxContainer); + tiles_bottom_panel->connect("tree_entered", callable_mp(this, &TileMapEditorTilesPlugin::_update_theme)); + tiles_bottom_panel->connect("theme_changed", callable_mp(this, &TileMapEditorTilesPlugin::_update_theme)); + tiles_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_stop_dragging)); + tiles_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_tab_changed)); + tiles_bottom_panel->set_name(TTR("Tiles")); missing_source_label = memnew(Label); missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Edit the TileSet resource to add one.")); - missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL); - missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); - missing_source_label->set_align(Label::ALIGN_CENTER); - missing_source_label->set_valign(Label::VALIGN_CENTER); + missing_source_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + missing_source_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + missing_source_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + missing_source_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); missing_source_label->hide(); - add_child(missing_source_label); + tiles_bottom_panel->add_child(missing_source_label); atlas_sources_split_container = memnew(HSplitContainer); - atlas_sources_split_container->set_h_size_flags(SIZE_EXPAND_FILL); - atlas_sources_split_container->set_v_size_flags(SIZE_EXPAND_FILL); - add_child(atlas_sources_split_container); + atlas_sources_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_sources_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + tiles_bottom_panel->add_child(atlas_sources_split_container); + + VBoxContainer *split_container_left_side = memnew(VBoxContainer); + split_container_left_side->set_h_size_flags(Control::SIZE_EXPAND_FILL); + split_container_left_side->set_v_size_flags(Control::SIZE_EXPAND_FILL); + split_container_left_side->set_stretch_ratio(0.25); + split_container_left_side->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + atlas_sources_split_container->add_child(split_container_left_side); + + HBoxContainer *sources_bottom_actions = memnew(HBoxContainer); + sources_bottom_actions->set_alignment(HBoxContainer::ALIGNMENT_END); + + source_sort_button = memnew(MenuButton); + source_sort_button->set_flat(true); + source_sort_button->set_tooltip(TTR("Sort sources")); + + PopupMenu *p = source_sort_button->get_popup(); + p->connect("id_pressed", callable_mp(this, &TileMapEditorTilesPlugin::_set_source_sort)); + p->add_radio_check_item(TTR("Sort by ID (Ascending)"), TilesEditorPlugin::SOURCE_SORT_ID); + p->add_radio_check_item(TTR("Sort by ID (Descending)"), TilesEditorPlugin::SOURCE_SORT_ID_REVERSE); + p->add_radio_check_item(TTR("Sort by Name (Ascending)"), TilesEditorPlugin::SOURCE_SORT_NAME); + p->add_radio_check_item(TTR("Sort by Name (Descending)"), TilesEditorPlugin::SOURCE_SORT_NAME_REVERSE); + p->set_item_checked(TilesEditorPlugin::SOURCE_SORT_ID, true); + sources_bottom_actions->add_child(source_sort_button); sources_list = memnew(ItemList); sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); - sources_list->set_h_size_flags(SIZE_EXPAND_FILL); + sources_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + sources_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); sources_list->set_stretch_ratio(0.25); sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1)); - sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1)); - sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current)); - sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list)); - atlas_sources_split_container->add_child(sources_list); + 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).bind(sources_list, source_sort_button)); + sources_list->add_user_signal(MethodInfo("sort_request")); + sources_list->connect("sort_request", callable_mp(this, &TileMapEditorTilesPlugin::_update_tile_set_sources_list)); + split_container_left_side->add_child(sources_list); + split_container_left_side->add_child(sources_bottom_actions); // Tile atlas source. tile_atlas_view = memnew(TileAtlasView); - tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL); - tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view->set_h_size_flags(Control::SIZE_EXPAND_FILL); + tile_atlas_view->set_v_size_flags(Control::SIZE_EXPAND_FILL); tile_atlas_view->set_texture_grid_visible(false); tile_atlas_view->set_tile_shape_grid_visible(false); - tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform)); + tile_atlas_view->connect("transform_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::set_atlas_view_transform)); atlas_sources_split_container->add_child(tile_atlas_view); tile_atlas_control = memnew(Control); @@ -1929,42 +2227,54 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { // Scenes collection source. scene_tiles_list = memnew(ItemList); - scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL); - scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + scene_tiles_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); scene_tiles_list->set_drag_forwarding(this); scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI); scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected)); - scene_tiles_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_nothing_selected)); + scene_tiles_list->connect("empty_clicked", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_lmb_empty_clicked)); scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); atlas_sources_split_container->add_child(scene_tiles_list); // Invalid source label. invalid_source_label = memnew(Label); invalid_source_label->set_text(TTR("Invalid source selected.")); - invalid_source_label->set_h_size_flags(SIZE_EXPAND_FILL); - invalid_source_label->set_v_size_flags(SIZE_EXPAND_FILL); - invalid_source_label->set_align(Label::ALIGN_CENTER); - invalid_source_label->set_valign(Label::VALIGN_CENTER); + invalid_source_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + invalid_source_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + invalid_source_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + invalid_source_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); invalid_source_label->hide(); atlas_sources_split_container->add_child(invalid_source_label); - _update_bottom_panel(); + // --- Bottom panel patterns --- + patterns_bottom_panel = memnew(VBoxContainer); + patterns_bottom_panel->set_name(TTR("Patterns")); + patterns_bottom_panel->connect("visibility_changed", callable_mp(this, &TileMapEditorTilesPlugin::_tab_changed)); + + int thumbnail_size = 64; + patterns_item_list = memnew(ItemList); + patterns_item_list->set_max_columns(0); + patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP); + patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2); + patterns_item_list->set_max_text_lines(2); + patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); + patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + patterns_item_list->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_patterns_item_list_gui_input)); + patterns_item_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection).unbind(1)); + patterns_item_list->connect("item_activated", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection)); + patterns_item_list->connect("empty_clicked", callable_mp(this, &TileMapEditorTilesPlugin::patterns_item_list_empty_clicked)); + patterns_bottom_panel->add_child(patterns_item_list); + + patterns_help_label = memnew(Label); + patterns_help_label->set_text(TTR("Drag and drop or paste a TileMap selection here to store a pattern.")); + patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER); + patterns_item_list->add_child(patterns_help_label); + + // Update. + _update_source_display(); } TileMapEditorTilesPlugin::~TileMapEditorTilesPlugin() { - memdelete(selection_pattern); - memdelete(tile_map_clipboard); -} - -void TileMapEditorTerrainsPlugin::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: - paint_tool_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); - picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); - erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); - break; - } } void TileMapEditorTerrainsPlugin::tile_set_changed() { @@ -1984,681 +2294,333 @@ void TileMapEditorTerrainsPlugin::_update_toolbar() { tools_settings_vsep->show(); picker_button->show(); erase_button->show(); + tools_settings_vsep_2->hide(); + bucket_contiguous_checkbox->hide(); + } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + tools_settings_vsep_2->hide(); + bucket_contiguous_checkbox->hide(); + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + tools_settings_vsep_2->hide(); + bucket_contiguous_checkbox->hide(); + } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + tools_settings_vsep_2->show(); + bucket_contiguous_checkbox->show(); } } -Control *TileMapEditorTerrainsPlugin::get_toolbar() const { - return toolbar; -} - -Map<Vector2i, TileSet::CellNeighbor> TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const { - Map<Vector2i, TileSet::CellNeighbor> output; - Ref<TileSet> tile_set = tile_map->get_tileset(); - ERR_FAIL_COND_V(!tile_set.is_valid(), output); - - TileSet::TileShape shape = tile_set->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - switch (bit) { - case 0: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - break; - default: - ERR_FAIL_V(output); - } - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (bit) { - case 0: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - break; - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - // Half offset shapes. - TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (bit) { - case 0: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; - break; - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } else { - switch (bit) { - case 0: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 1: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; - break; - case 2: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; - break; - case 3: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; - break; - case 4: - output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; - output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; - break; - default: - ERR_FAIL_V(output); - } - } - } - return output; +Vector<TileMapEditorPlugin::TabData> TileMapEditorTerrainsPlugin::get_tabs() const { + Vector<TileMapEditorPlugin::TabData> tabs; + tabs.push_back({ toolbar, main_vbox_container }); + return tabs; } -TileMapEditorTerrainsPlugin::Constraint::Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) { - // The way we build the constraint make it easy to detect conflicting constraints. - tile_map = p_tile_map; - - Ref<TileSet> tile_set = tile_map->get_tileset(); - ERR_FAIL_COND(!tile_set.is_valid()); - - TileSet::TileShape shape = tile_set->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE || shape == TileSet::TILE_SHAPE_ISOMETRIC) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 0; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 0; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); - break; - default: - ERR_FAIL(); - break; - } - } else { - // Half-offset shapes - TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); - if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: - bit = 0; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_LEFT_SIDE: - bit = 0; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_CORNER: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } else { - switch (p_bit) { - case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: - bit = 0; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: - bit = 1; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: - bit = 3; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: - bit = 0; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: - bit = 4; - base_cell_coords = p_position; - break; - case TileSet::CELL_NEIGHBOR_LEFT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: - bit = 1; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: - bit = 0; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_SIDE: - bit = 3; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: - bit = 2; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); - break; - case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: - bit = 4; - base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - break; - default: - ERR_FAIL(); - break; - } - } - } - terrain = p_terrain; -} - -Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<Constraint> p_constraints) 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 Set<TerrainsTilePattern>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Set<TerrainsTilePattern>(); - } - - // Returns all tiles compatible with the given constraints. - Set<TerrainsTilePattern> compatible_terrain_tile_patterns; - for (Map<TerrainsTilePattern, Set<TileMapCell>>::Element *E = per_terrain_terrains_tile_patterns_tiles[p_terrain_set].front(); E; E = E->next()) { - int valid = true; - int in_pattern_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(p_terrain_set, bit)) { - // Check if the bit is compatible with the constraints. - Constraint terrain_bit_constraint = Constraint(tile_map, p_position, bit, E->key()[in_pattern_count]); - - Set<Constraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint); - if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { - valid = false; - break; - } - in_pattern_count++; - } - } - - if (valid) { - compatible_terrain_tile_patterns.insert(E->key()); - } + return HashMap<Vector2i, TileMapCell>(); } - return compatible_terrain_tile_patterns; -} - -Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const { - TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); - if (!tile_map) { - return Set<Constraint>(); + 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); } - Ref<TileSet> tile_set = tile_map->get_tileset(); - if (!tile_set.is_valid()) { - return Set<Constraint>(); + // Make the painted path a set for faster lookups + HashSet<Vector2i> painted_set; + for (Vector2i coords : p_to_paint) { + painted_set.insert(coords); } - ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<Constraint>()); - ERR_FAIL_INDEX_V(tile_map_layer, tile_map->get_layers_count(), Set<Constraint>()); - - // Build a set of dummy constraints get the constrained points. - Set<Constraint> dummy_constraints; - for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) { - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides. - TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) { - dummy_constraints.insert(Constraint(tile_map, E->get(), bit, -1)); - } - } - } - - // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it. - Set<Constraint> constraints; - for (Set<Constraint>::Element *E = dummy_constraints.front(); E; E = E->next()) { - Constraint c = E->get(); - - Map<int, int> terrain_count; - - // Count the number of occurrences per terrain. - Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); - for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_overlapping = overlapping_terrain_bits.front(); E_overlapping; E_overlapping = E_overlapping->next()) { - if (!p_to_replace.has(E_overlapping->key())) { - TileMapCell neighbor_cell = tile_map->get_cell(tile_map_layer, E_overlapping->key()); - TileData *neighbor_tile_data = nullptr; - if (terrain_tiles.has(neighbor_cell) && terrain_tiles[neighbor_cell]->get_terrain_set() == p_terrain_set) { - neighbor_tile_data = terrain_tiles[neighbor_cell]; - } - - int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping->get())) : -1; - if (terrain_count.has(terrain)) { - terrain_count[terrain] = 0; + 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; + } } - terrain_count[terrain] += 1; } - } - - // Get the terrain with the max number of occurrences. - int max = 0; - int max_terrain = -1; - for (Map<int, int>::Element *E_terrain_count = terrain_count.front(); E_terrain_count; E_terrain_count = E_terrain_count->next()) { - if (E_terrain_count->get() > max) { - max = E_terrain_count->get(); - max_terrain = E_terrain_count->key(); + if (!keep_old) { + output[E.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); } } - - // Set the adequate terrain. - if (max > 0) { - c.set_terrain(max_terrain); - constraints.insert(c); - } } - - return constraints; + return output; } -Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const { +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 Set<TileMapEditorTerrainsPlugin::Constraint>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Set<TileMapEditorTerrainsPlugin::Constraint>(); + return HashMap<Vector2i, TileMapCell>(); } - // Compute the constraints needed from the surrounding tiles. - Set<TileMapEditorTerrainsPlugin::Constraint> output; - int in_pattern_count = 0; - for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor side = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) { - Constraint c = Constraint(tile_map, p_position, side, p_terrains_tile_pattern[in_pattern_count]); - output.insert(c); - in_pattern_count++; - } + 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); + + // Make the painted path a set for faster lookups + HashSet<Vector2i> painted_set; + for (Vector2i coords : p_to_paint) { + painted_set.insert(coords); } + 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, TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const { +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, TerrainsTilePattern>(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern>(); + return HashMap<Vector2i, TileMapCell>(); } - // Copy the constraints set. - Set<TileMapEditorTerrainsPlugin::Constraint> constraints = p_constraints; - - // Compute all acceptable tiles for each cell. - Map<Vector2i, Set<TerrainsTilePattern>> per_cell_acceptable_tiles; - for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) { - per_cell_acceptable_tiles[E->get()] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, E->get(), constraints); - } - - // Output map. - Map<Vector2i, TerrainsTilePattern> output; - - // Add all positions to a set. - Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace); - while (!to_replace.is_empty()) { - // Compute the minimum number of tile possibilities for each cell. - int min_nb_possibilities = 100000000; - for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { - min_nb_possibilities = MIN(min_nb_possibilities, E->get().size()); - } - - // Get the set of possible cells to fill. - LocalVector<Vector2i> to_choose_from; - for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { - if (E->get().size() == min_nb_possibilities) { - to_choose_from.push_back(E->key()); - } - } - - // Randomly pick a tile out of the most constrained. - Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)]; - - // Randomly select a tile out of them the put it in the grid. - Set<TerrainsTilePattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace]; - if (valid_tiles.is_empty()) { - // No possibilities :/ - break; - } - int random_terrain_tile_pattern_index = Math::random(0, valid_tiles.size() - 1); - Set<TerrainsTilePattern>::Element *E = valid_tiles.front(); - for (int i = 0; i < random_terrain_tile_pattern_index; i++) { - E = E->next(); - } - TerrainsTilePattern selected_terrain_tile_pattern = E->get(); - - // Set the selected cell into the output. - output[selected_cell_to_replace] = selected_terrain_tile_pattern; - to_replace.erase(selected_cell_to_replace); - per_cell_acceptable_tiles.erase(selected_cell_to_replace); - - // Add the new constraints from the added tiles. - Set<TileMapEditorTerrainsPlugin::Constraint> new_constraints = _get_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern); - for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) { - constraints.insert(E_constraint->get()); + 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; } - // Compute valid tiles again for neighbors. - for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor side = TileSet::CellNeighbor(i); - if (tile_map->is_existing_neighbor(side)) { - Vector2i neighbor = tile_map->get_neighbor_cell(selected_cell_to_replace, side); - if (to_replace.has(neighbor)) { - per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, neighbor, constraints); - } - } - } + 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 output; } -TileMapCell TileMapEditorTerrainsPlugin::_get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const { +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 TileMapCell(); + return HashMap<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return TileMapCell(); + return HashMap<Vector2i, TileMapCell>(); } - // Count the sum of probabilities. - double sum = 0.0; - Set<TileMapCell> set = per_terrain_terrains_tile_patterns_tiles[p_terrain_set][p_terrain_tile_pattern]; - for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) { - if (E->get().source_id >= 0) { - Ref<TileSetSource> source = tile_set->get_source(E->get().source_id); + Rect2i rect; + rect.set_position(p_start_cell); + rect.set_end(p_end_cell); + rect = rect.abs(); - Ref<TileSetAtlasSource> atlas_source = source; - if (atlas_source.is_valid()) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); - sum += tile_data->get_probability(); - } else { - sum += 1.0; - } - } else { - sum += 1.0; + 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.append(Vector2i(x, y)); } } - // Generate a random number. - double count = 0.0; - double picked = Math::random(0.0, sum); - - // Pick the tile. - for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) { - if (E->get().source_id >= 0) { - Ref<TileSetSource> source = tile_set->get_source(E->get().source_id); - - Ref<TileSetAtlasSource> atlas_source = source; - if (atlas_source.is_valid()) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); - count += tile_data->get_probability(); - } else { - count += 1.0; - } + 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 { - count += 1.0; - } - - if (count >= picked) { - return E->get(); + terrains_pattern = selected_terrains_pattern; } + return _draw_terrain_pattern(to_draw, selected_terrain_set, terrains_pattern); } - - ERR_FAIL_V(TileMapCell()); } -Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const { +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 Map<Vector2i, TileMapCell>(); + return RBSet<Vector2i>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); + return RBSet<Vector2i>(); } - Map<Vector2i, TileMapCell> output; + TileMapCell source_cell = tile_map->get_cell(tile_map_layer, p_coords); - // Add the constraints from the added tiles. - Set<TileMapEditorTerrainsPlugin::Constraint> added_tiles_constraints_set; - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - Vector2i coords = E_to_paint->key(); - TerrainsTilePattern terrains_tile_pattern = E_to_paint->get(); + TileSet::TerrainsPattern source_pattern(*tile_set, selected_terrain_set); + if (source_cell.source_id != TileSet::INVALID_SOURCE) { + TileData *tile_data = nullptr; + Ref<TileSetSource> source = tile_set->get_source(source_cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(source_cell.get_atlas_coords(), source_cell.alternative_tile); + } + if (!tile_data) { + return RBSet<Vector2i>(); + } + source_pattern = tile_data->get_terrains_pattern(); + } + + // If we are filling empty tiles, compute the tilemap boundaries. + Rect2i boundaries; + if (source_cell.source_id == TileSet::INVALID_SOURCE) { + boundaries = tile_map->get_used_rect(); + } + + RBSet<Vector2i> output; + if (p_contiguous) { + // Replace continuous tiles like the source. + RBSet<Vector2i> already_checked; + List<Vector2i> to_check; + to_check.push_back(p_coords); + while (!to_check.is_empty()) { + Vector2i coords = to_check.back()->get(); + to_check.pop_back(); + if (!already_checked.has(coords)) { + // Get the candidate cell pattern. + TileSet::TerrainsPattern candidate_pattern(*tile_set, selected_terrain_set); + if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { + TileData *tile_data = nullptr; + Ref<TileSetSource> source = tile_set->get_source(tile_map->get_cell_source_id(tile_map_layer, coords)); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords)); + } + if (tile_data) { + candidate_pattern = tile_data->get_terrains_pattern(); + } + } - Set<TileMapEditorTerrainsPlugin::Constraint> cell_constraints = _get_constraints_from_added_tile(coords, p_terrain_set, terrains_tile_pattern); - for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = cell_constraints.front(); E; E = E->next()) { - added_tiles_constraints_set.insert(E->get()); - } - } + // Draw. + if (candidate_pattern == source_pattern && (!source_pattern.is_erase_pattern() || boundaries.has_point(coords))) { + output.insert(coords); - // Build the list of potential tiles to replace. - Set<Vector2i> potential_to_replace; - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - 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); + // Get surrounding tiles (handles different tile shapes). + TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + for (int i = 0; i < around.size(); i++) { + to_check.push_back(around[i]); + } } + already_checked.insert(coords); } } - } - - // Set of tiles to replace - Set<Vector2i> to_replace; - - // Add the central tiles to the one to replace. TODO: maybe change that. - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - to_replace.insert(E_to_paint->key()); - } - - // Add the constraints from the surroundings of the modified areas. - Set<TileMapEditorTerrainsPlugin::Constraint> removed_cells_constraints_set; - bool to_replace_modified = true; - while (to_replace_modified) { - // Get the constraints from the removed cells. - removed_cells_constraints_set = _get_constraints_from_removed_cells_list(to_replace, p_terrain_set); - - // Filter the sources to make sure they are in the potential_to_replace. - Map<Constraint, Set<Vector2i>> source_tiles_of_constraint; - for (Set<Constraint>::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 (Map<Vector2i, TileSet::CellNeighbor>::Element *E_source_tile_of_constraint = sources_of_constraint.front(); E_source_tile_of_constraint; E_source_tile_of_constraint = E_source_tile_of_constraint->next()) { - if (potential_to_replace.has(E_source_tile_of_constraint->key())) { - source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint->key()); + } else { + // Replace all tiles like the source. + TypedArray<Vector2i> to_check; + if (source_cell.source_id == TileSet::INVALID_SOURCE) { + Rect2i rect = tile_map->get_used_rect(); + if (rect.has_no_area()) { + rect = Rect2i(p_coords, Vector2i(1, 1)); + } + for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) { + for (int y = boundaries.position.y; y < boundaries.get_end().y; y++) { + to_check.append(Vector2i(x, y)); } } - } - - to_replace_modified = false; - for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { - Constraint 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 (source_tiles_of_constraint.has(c) && !source_tiles_of_constraint[c].is_empty()) { - // Remove it. - Vector2i to_add_to_remove = source_tiles_of_constraint[c].front()->get(); - potential_to_replace.erase(to_add_to_remove); - to_replace.insert(to_add_to_remove); - to_replace_modified = true; - for (Map<Constraint, Set<Vector2i>>::Element *E_source_tiles_of_constraint = source_tiles_of_constraint.front(); E_source_tiles_of_constraint; E_source_tiles_of_constraint = E_source_tiles_of_constraint->next()) { - E_source_tiles_of_constraint->get().erase(to_add_to_remove); - } - break; + } else { + to_check = tile_map->get_used_cells(tile_map_layer); + } + for (int i = 0; i < to_check.size(); i++) { + Vector2i coords = to_check[i]; + // Get the candidate cell pattern. + TileSet::TerrainsPattern candidate_pattern; + if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { + TileData *tile_data = nullptr; + Ref<TileSetSource> source = tile_set->get_source(tile_map->get_cell_source_id(tile_map_layer, coords)); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords)); + } + if (tile_data) { + candidate_pattern = tile_data->get_terrains_pattern(); } } + + // Draw. + if (candidate_pattern == source_pattern && (!source_pattern.is_erase_pattern() || boundaries.has_point(coords))) { + output.insert(coords); + } } } + return output; +} - // Combine all constraints together. - Set<TileMapEditorTerrainsPlugin::Constraint> constraints = removed_cells_constraints_set; - for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { - constraints.insert(E->get()); +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 HashMap<Vector2i, TileMapCell>(); } - // Run WFC to fill the holes with the constraints. - Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints); - - // Use the WFC run for the output. - for (Map<Vector2i, TerrainsTilePattern>::Element *E = wfc_output.front(); E; E = E->next()) { - output[E->key()] = _get_random_tile_from_pattern(p_terrain_set, E->get()); + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return HashMap<Vector2i, TileMapCell>(); } - // Override the WFC results to make sure at least the painted tiles are actually painted. - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - output[E_to_paint->key()] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint->get()); + 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); } - return output; + 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); + } } void TileMapEditorTerrainsPlugin::_stop_dragging() { @@ -2667,26 +2629,40 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { return; } + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position()); switch (drag_type) { case DRAG_TYPE_PICK: { Vector2i coords = tile_map->world_to_map(mpos); - TileMapCell tile = tile_map->get_cell(tile_map_layer, coords); + TileMapCell cell = tile_map->get_cell(tile_map_layer, coords); + TileData *tile_data = nullptr; - if (terrain_tiles.has(tile)) { - Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[tile]); + Ref<TileSetSource> source = tile_set->get_source(cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); + } + + if (tile_data) { + TileSet::TerrainsPattern terrains_pattern = tile_data->get_terrains_pattern(); // Find the tree item for the right terrain set. bool need_tree_item_switch = true; TreeItem *tree_item = terrains_tree->get_selected(); + int new_terrain_set = -1; if (tree_item) { Dictionary metadata_dict = tree_item->get_metadata(0); if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) { int terrain_set = metadata_dict["terrain_set"]; int terrain_id = metadata_dict["terrain_id"]; - if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) { + if (per_terrain_terrains_patterns[terrain_set][terrain_id].has(terrains_pattern)) { + new_terrain_set = terrain_set; need_tree_item_switch = false; } } @@ -2698,8 +2674,9 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) { int terrain_set = metadata_dict["terrain_set"]; int terrain_id = metadata_dict["terrain_id"]; - if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) { + if (per_terrain_terrains_patterns[terrain_set][terrain_id].has(terrains_pattern)) { // Found + new_terrain_set = terrain_set; tree_item->select(0); _update_tiles_list(); break; @@ -2712,18 +2689,14 @@ 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); - TerrainsTilePattern in_meta_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"]; - bool equals = true; - for (int j = 0; j < terrains_tile_pattern.size(); j++) { - if (terrains_tile_pattern[j] != in_meta_terrains_tile_pattern[j]) { - equals = false; + 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; } } - if (equals) { - terrains_tile_list->select(i); - break; - } } } else { ERR_PRINT("Terrain tile not found."); @@ -2733,20 +2706,101 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { } break; case DRAG_TYPE_PAINT: { undo_redo->create_action(TTR("Paint terrain")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - 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 : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + 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); + } + undo_redo->commit_action(false); + } break; + case DRAG_TYPE_LINE: { + 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) { + continue; + } + 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); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + } + undo_redo->commit_action(); + } break; + case DRAG_TYPE_RECT: { + 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) { + continue; + } + 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); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + } + undo_redo->commit_action(); + } break; + case DRAG_TYPE_BUCKET: { + undo_redo->create_action(TTR("Paint terrain")); + for (const KeyValue<Vector2i, TileMapCell> &E : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + 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); } undo_redo->commit_action(false); } break; + default: break; } drag_type = DRAG_TYPE_NONE; } +void TileMapEditorTerrainsPlugin::_mouse_exited_viewport() { + has_mouse = false; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void TileMapEditorTerrainsPlugin::_update_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Get the selected terrain. + 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 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]); + 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(); + } + } + } +} + bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { - if (!is_visible_in_tree()) { + if (!main_vbox_container->is_visible_in_tree()) { // If the bottom editor is not visible, we ignore inputs. return false; } @@ -2770,50 +2824,23 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> } ERR_FAIL_COND_V(tile_map_layer >= tile_map->get_layers_count(), false); - // Get the selected terrain. - TerrainsTilePattern selected_terrains_tile_pattern; - int selected_terrain_set = -1; - - 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 tile - if (erase_button->is_pressed()) { - selected_terrains_tile_pattern.clear(); - for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - TileSet::CellNeighbor side = TileSet::CellNeighbor(i); - if (tile_set->is_valid_peering_bit_terrain(selected_terrain_set, side)) { - selected_terrains_tile_pattern.push_back(-1); - } - } - } 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_tile_pattern = metadata_dict["terrains_tile_pattern"]; - } - } + _update_selection(); Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { + has_mouse = true; Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(mm->get_position()); switch (drag_type) { case DRAG_TYPE_PAINT: { if (selected_terrain_set >= 0) { - Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); - Map<Vector2i, TerrainsTilePattern> to_draw; - for (int i = 0; i < line.size(); i++) { - to_draw[line[i]] = selected_terrains_tile_pattern; - } - Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set); - for (Map<Vector2i, TileMapCell>::Element *E = modified.front(); E; E = E->next()) { - if (!drag_modified.has(E->key())) { - drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key()); + 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); } - tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } break; @@ -2828,35 +2855,79 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { + has_mouse = true; Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { // Pressed + if (erase_button->is_pressed() || mb->get_button_index() == MouseButton::RIGHT) { + drag_erasing = true; + } + if (picker_button->is_pressed()) { drag_type = DRAG_TYPE_PICK; } else { // Paint otherwise. - if (selected_terrain_set >= 0 && !selected_terrains_tile_pattern.is_empty() && tool_buttons_group->get_pressed_button() == paint_tool_button) { + 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_terrain < 0 || (selected_type == SELECTED_TYPE_PATTERN && !selected_terrains_pattern.is_valid())) { + return true; + } + drag_type = DRAG_TYPE_PAINT; drag_start_mouse_pos = mpos; drag_modified.clear(); - - Map<Vector2i, TerrainsTilePattern> terrains_to_draw; - terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern; - - Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key()); - tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + Vector2i cell = tile_map->world_to_map(mpos); + HashMap<Vector2i, TileMapCell> to_draw = _draw_line(cell, cell, drag_erasing); + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + drag_modified[E.key] = tile_map->get_cell(tile_map_layer, E.key); + tile_map->set_cell(tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); + } + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CTRL))) { + if (selected_terrain_set < 0 || selected_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_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_terrain < 0 || (selected_type == SELECTED_TYPE_PATTERN && !selected_terrains_pattern.is_valid())) { + return true; + } + drag_type = DRAG_TYPE_BUCKET; + drag_start_mouse_pos = mpos; + drag_modified.clear(); + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); + for (int i = 0; i < line.size(); i++) { + if (!drag_modified.has(line[i])) { + HashMap<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_contiguous_checkbox->is_pressed(), drag_erasing); + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { + continue; + } + Vector2i coords = E.key; + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); + } + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); + } + } } } } } else { // Released _stop_dragging(); + drag_erasing = false; } CanvasItemEditor::get_singleton()->update_viewport(); @@ -2869,24 +2940,133 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> return false; } -TileMapEditorTerrainsPlugin::TerrainsTilePattern TileMapEditorTerrainsPlugin::_build_terrains_tile_pattern(TileData *p_tile_data) { +void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return TerrainsTilePattern(); + return; } + if (tile_map_layer < 0) { + return; + } + ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count()); + Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return TerrainsTilePattern(); + return; + } + + if (!tile_map->is_visible_in_tree()) { + return; } - TerrainsTilePattern output; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (tile_set->is_valid_peering_bit_terrain(p_tile_data->get_terrain_set(), TileSet::CellNeighbor(i))) { - output.push_back(p_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(i))); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Vector2i tile_shape_size = tile_set->get_tile_size(); + + // 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. + RBSet<Vector2i> preview; + Rect2i drawn_grid_rect; + + if (drag_type == DRAG_TYPE_PICK) { + // Draw the area being picked. + Vector2i coords = tile_map->world_to_map(drag_last_mouse_pos); + if (tile_map->get_cell_source_id(tile_map_layer, coords) != TileSet::INVALID_SOURCE) { + Transform2D tile_xform; + tile_xform.set_origin(tile_map->map_to_world(coords)); + tile_xform.set_scale(tile_shape_size); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0), false); + } + } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(Key::CTRL) && !Input::get_singleton()->is_key_pressed(Key::SHIFT))) { + bool expand_grid = false; + if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) { + // Preview for a single tile. + preview.insert(tile_map->world_to_map(drag_last_mouse_pos)); + expand_grid = true; + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || drag_type == DRAG_TYPE_LINE) { + if (drag_type == DRAG_TYPE_NONE) { + // Preview for a single tile. + preview.insert(tile_map->world_to_map(drag_last_mouse_pos)); + } else if (drag_type == DRAG_TYPE_LINE) { + // Preview for a line. + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos)); + for (int i = 0; i < line.size(); i++) { + preview.insert(line[i]); + } + expand_grid = true; + } + } else if (drag_type == DRAG_TYPE_RECT) { + // Preview for a rect. + Rect2i rect; + rect.set_position(tile_map->world_to_map(drag_start_mouse_pos)); + rect.set_end(tile_map->world_to_map(drag_last_mouse_pos)); + rect = rect.abs(); + + 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)); + } + } + expand_grid = true; + } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { + // Preview for a fill. + preview = _get_cells_for_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_contiguous_checkbox->is_pressed()); + } + + // Expand the grid if needed + if (expand_grid && !preview.is_empty()) { + drawn_grid_rect = Rect2i(preview.front()->get(), Vector2i(1, 1)); + for (const Vector2i &E : preview) { + drawn_grid_rect.expand_to(E); + } + } + } + + if (!preview.is_empty()) { + const int fading = 5; + + // Draw the lines of the grid behind the preview. + bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"); + if (display_grid) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + if (drawn_grid_rect.size.x > 0 && drawn_grid_rect.size.y > 0) { + drawn_grid_rect = drawn_grid_rect.grow(fading); + for (int x = drawn_grid_rect.position.x; x < (drawn_grid_rect.position.x + drawn_grid_rect.size.x); x++) { + for (int y = drawn_grid_rect.position.y; y < (drawn_grid_rect.position.y + drawn_grid_rect.size.y); y++) { + Vector2i pos_in_rect = Vector2i(x, y) - drawn_grid_rect.position; + + // Fade out the border of the grid. + float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)(pos_in_rect.x + 1)), 0.0f, 1.0f); + float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)(pos_in_rect.y + 1)), 0.0f, 1.0f); + float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); + + Transform2D tile_xform; + tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y))); + tile_xform.set_scale(tile_shape_size); + Color color = grid_color; + color.a = color.a * opacity; + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, false); + } + } + } + } + + // Draw the preview. + for (const Vector2i &E : preview) { + Transform2D tile_xform; + tile_xform.set_origin(tile_map->map_to_world(E)); + tile_xform.set_scale(tile_set->get_tile_size()); + if (drag_erasing || erase_button->is_pressed()) { + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(0.0, 0.0, 0.0, 0.5), true); + } else { + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); + } + } } } - return output; } void TileMapEditorTerrainsPlugin::_update_terrains_cache() { @@ -2900,45 +3080,12 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { return; } - // Compute the tile sides. - tile_sides.clear(); - TileSet::TileShape shape = tile_set->get_tile_shape(); - if (shape == TileSet::TILE_SHAPE_SQUARE) { - tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE); - } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - } else { - if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { - tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - } else { - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE); - tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); - } - } - // Organizes tiles into structures. - per_terrain_terrains_tile_patterns_tiles.resize(tile_set->get_terrain_sets_count()); - per_terrain_terrains_tile_patterns.resize(tile_set->get_terrain_sets_count()); + per_terrain_terrains_patterns.resize(tile_set->get_terrain_sets_count()); for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) { - per_terrain_terrains_tile_patterns_tiles[i].clear(); - per_terrain_terrains_tile_patterns[i].resize(tile_set->get_terrains_count(i)); - for (int j = 0; j < (int)per_terrain_terrains_tile_patterns[i].size(); j++) { - per_terrain_terrains_tile_patterns[i][j].clear(); + per_terrain_terrains_patterns[i].resize(tile_set->get_terrains_count(i)); + for (int j = 0; j < (int)per_terrain_terrains_patterns[i].size(); j++) { + per_terrain_terrains_patterns[i][j].clear(); } } @@ -2953,25 +3100,32 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + TileData *tile_data = atlas_source->get_tile_data(tile_id, alternative_id); int terrain_set = tile_data->get_terrain_set(); if (terrain_set >= 0) { - ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_tile_patterns.size()); + ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_patterns.size()); TileMapCell cell; cell.source_id = source_id; cell.set_atlas_coords(tile_id); cell.alternative_tile = alternative_id; - TerrainsTilePattern terrains_tile_pattern = _build_terrains_tile_pattern(tile_data); + 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 < terrains_tile_pattern.size(); i++) { - int terrain = terrains_tile_pattern[i]; - if (terrain >= 0 && terrain < (int)per_terrain_terrains_tile_patterns[terrain_set].size()) { - per_terrain_terrains_tile_patterns[terrain_set][terrain].insert(terrains_tile_pattern); - terrain_tiles[cell] = tile_data; - per_terrain_terrains_tile_patterns_tiles[terrain_set][terrains_tile_pattern].insert(cell); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_terrain_peering_bit(terrain_set, bit)) { + 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); + } } } } @@ -2979,22 +3133,6 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { } } } - - // Add the empty cell in the possible patterns and cells. - for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) { - TerrainsTilePattern empty_pattern; - for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { - if (tile_set->is_valid_peering_bit_terrain(i, TileSet::CellNeighbor(j))) { - empty_pattern.push_back(-1); - } - } - - TileMapCell empty_cell; - empty_cell.source_id = TileSet::INVALID_SOURCE; - empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); - empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; - per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell); - } } void TileMapEditorTerrainsPlugin::_update_terrains_tree() { @@ -3018,13 +3156,13 @@ void TileMapEditorTerrainsPlugin::_update_terrains_tree() { TreeItem *terrain_set_tree_item = terrains_tree->create_item(); String matches; if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { - terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchCornersAndSides"), SNAME("EditorIcons"))); + terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchCornersAndSides"), SNAME("EditorIcons"))); matches = String(TTR("Matches Corners and Sides")); } else if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS) { - terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchCorners"), SNAME("EditorIcons"))); + terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchCorners"), SNAME("EditorIcons"))); matches = String(TTR("Matches Corners Only")); } else { - terrain_set_tree_item->set_icon(0, get_theme_icon(SNAME("TerrainMatchSides"), SNAME("EditorIcons"))); + terrain_set_tree_item->set_icon(0, main_vbox_container->get_theme_icon(SNAME("TerrainMatchSides"), SNAME("EditorIcons"))); matches = String(TTR("Matches Sides Only")); } terrain_set_tree_item->set_text(0, vformat("Terrain Set %d (%s)", terrain_set_index, matches)); @@ -3063,26 +3201,41 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { Dictionary metadata_dict = selected_tree_item->get_metadata(0); int selected_terrain_set = metadata_dict["terrain_set"]; int selected_terrain_id = metadata_dict["terrain_id"]; - ERR_FAIL_INDEX(selected_terrain_set, (int)per_terrain_terrains_tile_patterns.size()); - ERR_FAIL_INDEX(selected_terrain_id, (int)per_terrain_terrains_tile_patterns[selected_terrain_set].size()); + 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<TerrainsTilePattern>> sorted; - for (Set<TerrainsTilePattern>::Element *E = per_terrain_terrains_tile_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) { + RBMap<int, RBSet<TileSet::TerrainsPattern>> sorted; + + 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 < E->get().size(); i++) { - if (int(E->get()[i]) == selected_terrain_id) { + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + 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<TerrainsTilePattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) { - for (Set<TerrainsTilePattern>::Element *E = E_set->get().front(); E; E = E->next()) { - TerrainsTilePattern terrains_tile_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; @@ -3090,15 +3243,15 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { bool transpose = false; double max_probability = -1.0; - for (Set<TileMapCell>::Element *E_tile_map_cell = per_terrain_terrains_tile_patterns_tiles[selected_terrain_set][terrains_tile_pattern].front(); E_tile_map_cell; E_tile_map_cell = E_tile_map_cell->next()) { - Ref<TileSetSource> source = tile_set->get_source(E_tile_map_cell->get().source_id); + for (const TileMapCell &cell : tile_set->get_tiles_for_terrains_pattern(selected_terrain_set, terrains_pattern)) { + Ref<TileSetSource> source = tile_set->get_source(cell.source_id); Ref<TileSetAtlasSource> atlas_source = source; if (atlas_source.is_valid()) { - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E_tile_map_cell->get().get_atlas_coords(), E_tile_map_cell->get().alternative_tile)); + TileData *tile_data = atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile); if (tile_data->get_probability() > max_probability) { icon = atlas_source->get_texture(); - region = atlas_source->get_tile_texture_region(E_tile_map_cell->get().get_atlas_coords()); + region = atlas_source->get_tile_texture_region(cell.get_atlas_coords()); if (tile_data->get_flip_h()) { region.position.x += region.size.x; region.size.x = -region.size.x; @@ -3114,12 +3267,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_tile_pattern"] = terrains_tile_pattern; + 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); } } @@ -3129,6 +3283,18 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { } } +void TileMapEditorTerrainsPlugin::_update_theme() { + paint_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); + line_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("CurveLinear"), SNAME("EditorIcons"))); + rect_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Rectangle"), SNAME("EditorIcons"))); + bucket_tool_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Bucket"), SNAME("EditorIcons"))); + + picker_button->set_icon(main_vbox_container->get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); + erase_button->set_icon(main_vbox_container->get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); + + _update_tiles_list(); +} + void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) { _stop_dragging(); // Avoids staying in a wrong drag state. @@ -3141,15 +3307,20 @@ void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_la } TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { - set_name("Terrains"); + undo_redo = EditorNode::get_undo_redo(); + + main_vbox_container = memnew(VBoxContainer); + main_vbox_container->connect("tree_entered", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme)); + main_vbox_container->connect("theme_changed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_theme)); + main_vbox_container->set_name("Terrains"); HSplitContainer *tilemap_tab_terrains = memnew(HSplitContainer); - tilemap_tab_terrains->set_h_size_flags(SIZE_EXPAND_FILL); - tilemap_tab_terrains->set_v_size_flags(SIZE_EXPAND_FILL); - add_child(tilemap_tab_terrains); + tilemap_tab_terrains->set_h_size_flags(Control::SIZE_EXPAND_FILL); + tilemap_tab_terrains->set_v_size_flags(Control::SIZE_EXPAND_FILL); + main_vbox_container->add_child(tilemap_tab_terrains); terrains_tree = memnew(Tree); - terrains_tree->set_h_size_flags(SIZE_EXPAND_FILL); + terrains_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); terrains_tree->set_stretch_ratio(0.25); terrains_tree->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); terrains_tree->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); @@ -3158,10 +3329,10 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { tilemap_tab_terrains->add_child(terrains_tree); terrains_tile_list = memnew(ItemList); - terrains_tile_list->set_h_size_flags(SIZE_EXPAND_FILL); + 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); @@ -3177,10 +3348,34 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); paint_tool_button->set_pressed(true); - paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E)); + paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", Key::D)); paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); + line_tool_button = memnew(Button); + line_tool_button->set_flat(true); + line_tool_button->set_toggle_mode(true); + line_tool_button->set_button_group(tool_buttons_group); + line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", Key::L)); + line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(line_tool_button); + + rect_tool_button = memnew(Button); + rect_tool_button->set_flat(true); + rect_tool_button->set_toggle_mode(true); + rect_tool_button->set_button_group(tool_buttons_group); + rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", Key::R)); + rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(rect_tool_button); + + bucket_tool_button = memnew(Button); + bucket_tool_button->set_flat(true); + bucket_tool_button->set_toggle_mode(true); + bucket_tool_button->set_button_group(tool_buttons_group); + bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", Key::B)); + bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(bucket_tool_button); + toolbar->add_child(tilemap_tiles_tools_buttons); // -- TileMap tool settings -- @@ -3194,7 +3389,7 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { picker_button = memnew(Button); picker_button->set_flat(true); picker_button->set_toggle_mode(true); - picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", Key::P)); picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); @@ -3202,9 +3397,20 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { erase_button = memnew(Button); erase_button->set_flat(true); erase_button->set_toggle_mode(true); - erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", Key::E)); erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); + + // Separator 2. + tools_settings_vsep_2 = memnew(VSeparator); + tools_settings->add_child(tools_settings_vsep_2); + + // Continuous checkbox. + bucket_contiguous_checkbox = memnew(CheckBox); + bucket_contiguous_checkbox->set_flat(true); + bucket_contiguous_checkbox->set_text(TTR("Contiguous")); + bucket_contiguous_checkbox->set_pressed(true); + tools_settings->add_child(bucket_contiguous_checkbox); } TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() { @@ -3213,27 +3419,30 @@ TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() { void TileMapEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { missing_tile_texture = get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); warning_pattern_texture = get_theme_icon(SNAME("WarningPattern"), SNAME("EditorIcons")); advanced_menu_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); - toogle_highlight_selected_layer_button->set_icon(get_theme_icon(SNAME("TileMapHighlightSelected"), SNAME("EditorIcons"))); - break; - case NOTIFICATION_INTERNAL_PROCESS: + toggle_highlight_selected_layer_button->set_icon(get_theme_icon(SNAME("TileMapHighlightSelected"), SNAME("EditorIcons"))); + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (is_visible_in_tree() && tileset_changed_needs_update) { _update_bottom_panel(); _update_layers_selection(); - tile_map_editor_plugins[tabs->get_current_tab()]->tile_set_changed(); + tabs_plugins[tabs_bar->get_current_tab()]->tile_set_changed(); CanvasItemEditor::get_singleton()->update_viewport(); tileset_changed_needs_update = false; } - break; - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: + } break; + + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); - break; - case NOTIFICATION_VISIBILITY_CHANGED: + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (tile_map) { if (is_visible_in_tree()) { @@ -3242,68 +3451,22 @@ void TileMapEditor::_notification(int p_what) { tile_map->set_selected_layer(-1); } } - break; + } break; } } void TileMapEditor::_on_grid_toggled(bool p_pressed) { EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed); + CanvasItemEditor::get_singleton()->update_viewport(); } -void TileMapEditor::_layers_selection_button_draw() { - if (!has_theme_icon(SNAME("arrow"), SNAME("OptionButton"))) { +void TileMapEditor::_layers_selection_item_selected(int p_index) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map || tile_map->get_layers_count() <= 0) { return; } - RID ci = layers_selection_button->get_canvas_item(); - Ref<Texture2D> arrow = Control::get_theme_icon(SNAME("arrow"), SNAME("OptionButton")); - - Color clr = Color(1, 1, 1); - if (get_theme_constant(SNAME("modulate_arrow"))) { - switch (layers_selection_button->get_draw_mode()) { - case BaseButton::DRAW_PRESSED: - clr = get_theme_color(SNAME("font_pressed_color")); - break; - case BaseButton::DRAW_HOVER: - clr = get_theme_color(SNAME("font_hover_color")); - break; - case BaseButton::DRAW_DISABLED: - clr = get_theme_color(SNAME("font_disabled_color")); - break; - default: - 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(); } @@ -3349,14 +3512,11 @@ void TileMapEditor::_update_bottom_panel() { // Update the visibility of controls. missing_tileset_label->set_visible(!tile_set.is_valid()); - if (!tile_set.is_valid()) { - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - tile_map_editor_plugins[i]->hide(); - } - } else { - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab()); - } + for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { + tabs_data[tab_index].panel->hide(); + } + if (tile_set.is_valid()) { + tabs_data[tabs_bar->get_current_tab()].panel->show(); } } @@ -3439,27 +3599,25 @@ void TileMapEditor::_tile_map_changed() { void TileMapEditor::_tab_changed(int p_tab_id) { // Make the plugin edit the correct tilemap. - tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer); + tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer); // Update toolbar. - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - tile_map_editor_plugins[i]->get_toolbar()->set_visible(i == p_tab_id); + for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { + tabs_data[tab_index].toolbar->hide(); } + tabs_data[p_tab_id].toolbar->show(); // Update visible panel. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); - if (!tile_map || !tile_map->get_tileset().is_valid()) { - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - tile_map_editor_plugins[i]->hide(); - } - } else { - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab()); - } + for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { + tabs_data[tab_index].panel->hide(); + } + if (tile_map && tile_map->get_tileset().is_valid()) { + tabs_data[tabs_bar->get_current_tab()].panel->show(); } // Graphical update. - tile_map_editor_plugins[tabs->get_current_tab()]->update(); + tabs_data[tabs_bar->get_current_tab()].panel->update(); CanvasItemEditor::get_singleton()->update_viewport(); } @@ -3491,8 +3649,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; @@ -3519,60 +3675,97 @@ 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(); - tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer); + tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer); } -void TileMapEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { +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); TileMap *tile_map = Object::cast_to<TileMap>(p_edited); - if (tile_map) { - if (p_property == "layers_count") { - int new_layers_count = (int)p_new_value; - if (new_layers_count < tile_map->get_layers_count()) { - List<PropertyInfo> property_list; - tile_map->get_property_list(&property_list); - - for (PropertyInfo property_info : property_list) { - Vector<String> components = String(property_info.name).split("/", true, 2); - if (components.size() == 2 && components[0].begins_with("layer_") && components[0].trim_prefix("layer_").is_valid_int()) { - int index = components[0].trim_prefix("layer_").to_int(); - if (index >= new_layers_count) { - undo_redo->add_undo_property(tile_map, property_info.name, tile_map->get(property_info.name)); - } - } + if (!tile_map) { + return; + } + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "layer_") { + end = tile_map->get_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + if (p_from_index < 0) { + undo_redo->add_undo_method(tile_map, "remove_layer", p_to_pos < 0 ? tile_map->get_layers_count() : p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_undo_method(tile_map, "add_layer", p_from_index); + } + + List<PropertyInfo> properties; + tile_map->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (!is_digit(str[to_char_index])) { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_map, pi.name); } } } } +#undef ADD_UNDO + + if (p_from_index < 0) { + undo_redo->add_do_method(tile_map, "add_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_map, "remove_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_map, "move_layer", p_from_index, p_to_pos); + } } bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { @@ -3586,7 +3779,7 @@ bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return true; } - return tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_gui_input(p_event); + return tabs_plugins[tabs_bar->get_current_tab()]->forward_canvas_gui_input(p_event); } void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { @@ -3643,12 +3836,14 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { 0.8); // Draw the scaled tile. - Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size))); - tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture); + Transform2D tile_xform; + tile_xform.set_origin(tile_map->map_to_world(coords)); + tile_xform.set_scale(tile_shape_size); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, true, warning_pattern_texture); } // Draw the warning icon. - int min_axis = missing_tile_texture->get_size().min_axis(); + Vector2::Axis min_axis = missing_tile_texture->get_size().min_axis_index(); Vector2 icon_size; icon_size[min_axis] = tile_set->get_tile_size()[min_axis] / 3; icon_size[(min_axis + 1) % 2] = (icon_size[min_axis] * missing_tile_texture->get_size()[(min_axis + 1) % 2] / missing_tile_texture->get_size()[min_axis]); @@ -3695,15 +3890,17 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { // Fade out the border of the grid. float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); - float right_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.x, (float)(displayed_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.x, (float)(displayed_rect.size.x - fading), (float)(pos_in_rect.x + 1)), 0.0f, 1.0f); float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); - float bottom_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.y, (float)(displayed_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.y, (float)(displayed_rect.size.y - fading), (float)(pos_in_rect.y + 1)), 0.0f, 1.0f); float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); - Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size)); + Transform2D tile_xform; + tile_xform.set_origin(tile_map->map_to_world(Vector2(x, y))); + tile_xform.set_scale(tile_shape_size); Color color = grid_color; color.a = color.a * opacity; - tile_set->draw_tile_shape(p_overlay, cell_region, color, false); + tile_set->draw_tile_shape(p_overlay, xform * tile_xform, color, false); } } } @@ -3717,7 +3914,7 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { }*/ // Draw the plugins. - tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay); + tabs_plugins[tabs_bar->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay); } void TileMapEditor::edit(TileMap *p_tile_map) { @@ -3751,69 +3948,71 @@ void TileMapEditor::edit(TileMap *p_tile_map) { _update_layers_selection(); // Call the plugins. - tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id, tile_map_layer); + tabs_plugins[tabs_bar->get_current_tab()]->edit(tile_map_id, tile_map_layer); _tile_map_changed(); } TileMapEditor::TileMapEditor() { + undo_redo = EditorNode::get_undo_redo(); + set_process_internal(true); // Shortcuts. - ED_SHORTCUT("tiles_editor/select_next_layer", TTR("Select Next Tile Map Layer"), KEY_PAGEUP); - ED_SHORTCUT("tiles_editor/select_previous_layer", TTR("Select Previous Tile Map Layer"), KEY_PAGEDOWN); + ED_SHORTCUT("tiles_editor/select_next_layer", TTR("Select Next Tile Map Layer"), Key::PAGEUP); + ED_SHORTCUT("tiles_editor/select_previous_layer", TTR("Select Previous Tile Map Layer"), Key::PAGEDOWN); // TileMap editor plugins tile_map_editor_plugins.push_back(memnew(TileMapEditorTilesPlugin)); tile_map_editor_plugins.push_back(memnew(TileMapEditorTerrainsPlugin)); - // Tabs. - tabs = memnew(Tabs); - tabs->set_clip_tabs(false); - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - tabs->add_tab(tile_map_editor_plugins[i]->get_name()); + // TabBar. + tabs_bar = memnew(TabBar); + 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(); + for (int tab_index = 0; tab_index < tabs_vector.size(); tab_index++) { + tabs_bar->add_tab(tabs_vector[tab_index].panel->get_name()); + tabs_data.push_back(tabs_vector[tab_index]); + tabs_plugins.push_back(tile_map_editor_plugins[plugin_index]); + } } - tabs->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed)); + tabs_bar->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed)); // --- TileMap toolbar --- tile_map_toolbar = memnew(HBoxContainer); tile_map_toolbar->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(tile_map_toolbar); // Tabs. - tile_map_toolbar->add_child(tabs); + tile_map_toolbar->add_child(tabs_bar); // Tabs toolbars. - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - tile_map_editor_plugins[i]->get_toolbar()->hide(); - tile_map_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar()); + for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { + tabs_data[tab_index].toolbar->hide(); + if (!tabs_data[tab_index].toolbar->get_parent()) { + tile_map_toolbar->add_child(tabs_data[tab_index].toolbar); + } } // 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_close_on_parent_focus(false); - - layers_selection_button = memnew(Button); - layers_selection_button->set_toggle_mode(true); - layers_selection_button->connect("draw", callable_mp(this, &TileMapEditor::_layers_selection_button_draw)); - layers_selection_button->connect("pressed", callable_mp(this, &TileMapEditor::_layers_selection_button_pressed)); - layers_selection_button->connect("hidden", callable_mp((Window *)layers_selection_popup, &Popup::hide)); - layers_selection_button->set_tooltip(TTR("Tile Map Layer")); - layers_selection_button->add_child(layers_selection_popup); + layers_selection_button = memnew(OptionButton); + layers_selection_button->set_custom_minimum_size(Size2(200, 0)); + layers_selection_button->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS); + layers_selection_button->set_tooltip(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)); @@ -3836,23 +4035,26 @@ TileMapEditor::TileMapEditor() { missing_tileset_label->set_text(TTR("The edited TileMap node has no TileSet resource.")); missing_tileset_label->set_h_size_flags(SIZE_EXPAND_FILL); missing_tileset_label->set_v_size_flags(SIZE_EXPAND_FILL); - missing_tileset_label->set_align(Label::ALIGN_CENTER); - missing_tileset_label->set_valign(Label::VALIGN_CENTER); + missing_tileset_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + missing_tileset_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); missing_tileset_label->hide(); add_child(missing_tileset_label); - for (int i = 0; i < tile_map_editor_plugins.size(); i++) { - add_child(tile_map_editor_plugins[i]); - tile_map_editor_plugins[i]->set_h_size_flags(SIZE_EXPAND_FILL); - tile_map_editor_plugins[i]->set_v_size_flags(SIZE_EXPAND_FILL); - tile_map_editor_plugins[i]->set_visible(i == 0); + for (unsigned int tab_index = 0; tab_index < tabs_data.size(); tab_index++) { + add_child(tabs_data[tab_index].panel); + tabs_data[tab_index].panel->set_v_size_flags(SIZE_EXPAND_FILL); + tabs_data[tab_index].panel->set_visible(tab_index == 0); + tabs_data[tab_index].panel->set_h_size_flags(SIZE_EXPAND_FILL); } _tab_changed(0); // Registers UndoRedo inspector callback. - EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileMapEditor::_undo_redo_inspector_callback)); + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileMap"), callable_mp(this, &TileMapEditor::_move_tile_map_array_element)); } TileMapEditor::~TileMapEditor() { + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + memdelete(tile_map_editor_plugins[i]); + } } diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6e2f2ce2ba..605fbe4823 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,17 +33,33 @@ #include "tile_atlas_view.h" +#include "core/os/thread.h" #include "core/typedefs.h" -#include "editor/editor_node.h" #include "scene/2d/tile_map.h" #include "scene/gui/box_container.h" -#include "scene/gui/tabs.h" - -class TileMapEditorPlugin : public VBoxContainer { +#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 TileMapEditorPlugin : public Object { public: - virtual Control *get_toolbar() const { - return memnew(Control); + struct TabData { + Control *toolbar = nullptr; + Control *panel = nullptr; + }; + + virtual Vector<TabData> get_tabs() const { + return Vector<TabData>(); }; + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; }; virtual void forward_canvas_draw_over_viewport(Control *p_overlay){}; virtual void tile_set_changed(){}; @@ -54,32 +70,33 @@ class TileMapEditorTilesPlugin : public TileMapEditorPlugin { GDCLASS(TileMapEditorTilesPlugin, TileMapEditorPlugin); private: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; ObjectID tile_map_id; int tile_map_layer = -1; virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override; ///// Toolbar ///// - HBoxContainer *toolbar; + HBoxContainer *toolbar = nullptr; Ref<ButtonGroup> tool_buttons_group; - Button *select_tool_button; - Button *paint_tool_button; - Button *line_tool_button; - Button *rect_tool_button; - Button *bucket_tool_button; - Button *picker_button; - - HBoxContainer *tools_settings; - VSeparator *tools_settings_vsep; - Button *erase_button; - CheckBox *bucket_continuous_checkbox; - - VSeparator *tools_settings_vsep_2; - CheckBox *random_tile_checkbox; + Button *select_tool_button = nullptr; + Button *paint_tool_button = nullptr; + Button *line_tool_button = nullptr; + Button *rect_tool_button = nullptr; + Button *bucket_tool_button = nullptr; + + HBoxContainer *tools_settings = nullptr; + + VSeparator *tools_settings_vsep = nullptr; + Button *picker_button = nullptr; + Button *erase_button = nullptr; + + VSeparator *tools_settings_vsep_2 = nullptr; + CheckBox *bucket_contiguous_checkbox = nullptr; + Button *random_tile_toggle = nullptr; float scattering = 0.0; - Label *scatter_label; - SpinBox *scatter_spinbox; + Label *scatter_label = nullptr; + SpinBox *scatter_spinbox = nullptr; void _on_random_tile_checkbox_toggled(bool p_pressed); void _on_scattering_spinbox_changed(double p_value); @@ -101,78 +118,100 @@ private: DRAG_TYPE_CLIPBOARD_PASTE, }; DragType drag_type = DRAG_TYPE_NONE; + 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(const TileMapPattern *p_pattern); - Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos); - Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell); - Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous); + TileMapCell _pick_random_tile(Ref<TileMapPattern> p_pattern); + 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; - TileMapPattern *tile_map_clipboard = memnew(TileMapPattern); - TileMapPattern *selection_pattern = memnew(TileMapPattern); + 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_selection(); + void _update_selection_pattern_from_tileset_tiles_selection(); + void _update_selection_pattern_from_tileset_pattern_selection(); void _update_tileset_selection_from_selection_pattern(); 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. ////. - Label *missing_source_label; - Label *invalid_source_label; + ///// Bottom panel common //// + void _tab_changed(); - ItemList *sources_list; + ///// Bottom panel tiles //// + VBoxContainer *tiles_bottom_panel = nullptr; + Label *missing_source_label = nullptr; + Label *invalid_source_label = nullptr; + + ItemList *sources_list = nullptr; + MenuButton *source_sort_button = nullptr; Ref<Texture2D> missing_atlas_texture_icon; void _update_tile_set_sources_list(); - void _update_bottom_panel(); + void _update_source_display(); // Atlas sources. TileMapCell hovered_tile; - TileAtlasView *tile_atlas_view; - HSplitContainer *atlas_sources_split_container; + TileAtlasView *tile_atlas_view = nullptr; + HSplitContainer *atlas_sources_split_container = nullptr; bool tile_set_dragging_selection = false; Vector2i tile_set_drag_start_mouse_pos; - Control *tile_atlas_control; + Control *tile_atlas_control = nullptr; void _tile_atlas_control_mouse_exited(); void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event); void _tile_atlas_control_draw(); - Control *alternative_tiles_control; + Control *alternative_tiles_control = nullptr; void _tile_alternatives_control_draw(); void _tile_alternatives_control_mouse_exited(); void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event); void _update_atlas_view(); + void _set_source_sort(int p_sort); // Scenes collection sources. - ItemList *scene_tiles_list; + ItemList *scene_tiles_list = nullptr; void _update_scenes_collection_view(); void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud); void _scenes_list_multi_selected(int p_index, bool p_selected); - void _scenes_list_nothing_selected(); + void _scenes_list_lmb_empty_clicked(const Vector2 &p_pos, MouseButton p_mouse_button_index); + + ///// Bottom panel patterns //// + VBoxContainer *patterns_bottom_panel = nullptr; + ItemList *patterns_item_list = nullptr; + Label *patterns_help_label = nullptr; + void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event); + void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture); + bool select_last_pattern = false; + void _update_patterns_list(); + + // General + void _update_theme(); // Update callback virtual void tile_set_changed() override; protected: - void _notification(int p_what); static void _bind_methods(); public: - virtual Control *get_toolbar() const override; + virtual Vector<TabData> get_tabs() const override; virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override; virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override; @@ -184,112 +223,91 @@ class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin { GDCLASS(TileMapEditorTerrainsPlugin, TileMapEditorPlugin); private: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; ObjectID tile_map_id; int tile_map_layer = -1; virtual void edit(ObjectID p_tile_map_id, int p_tile_map_layer) override; // Toolbar. - HBoxContainer *toolbar; + HBoxContainer *toolbar = nullptr; Ref<ButtonGroup> tool_buttons_group; - Button *paint_tool_button; + Button *paint_tool_button = nullptr; + Button *line_tool_button = nullptr; + Button *rect_tool_button = nullptr; + Button *bucket_tool_button = nullptr; + + HBoxContainer *tools_settings = nullptr; - HBoxContainer *tools_settings; - VSeparator *tools_settings_vsep; - Button *picker_button; - Button *erase_button; + VSeparator *tools_settings_vsep = nullptr; + Button *picker_button = nullptr; + Button *erase_button = nullptr; + VSeparator *tools_settings_vsep_2 = nullptr; + CheckBox *bucket_contiguous_checkbox = nullptr; void _update_toolbar(); + // Main vbox. + VBoxContainer *main_vbox_container = nullptr; + // TileMap editing. + bool has_mouse = false; + void _mouse_exited_viewport(); + enum DragType { DRAG_TYPE_NONE = 0, DRAG_TYPE_PAINT, + DRAG_TYPE_LINE, + DRAG_TYPE_RECT, + DRAG_TYPE_BUCKET, DRAG_TYPE_PICK, }; DragType drag_type = DRAG_TYPE_NONE; + 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 - class Constraint { - private: - const TileMap *tile_map; - Vector2i base_cell_coords = Vector2i(); - int bit = -1; - int terrain = -1; - - public: - // TODO implement difference operator. - bool operator<(const Constraint &p_other) const { - if (base_cell_coords == p_other.base_cell_coords) { - return bit < p_other.bit; - } - return base_cell_coords < p_other.base_cell_coords; - } - - String to_string() const { - return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain); - } - - Vector2i get_base_cell_coords() const { - return base_cell_coords; - } - - Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; - - void set_terrain(int p_terrain) { - terrain = p_terrain; - } - - int get_terrain() const { - return terrain; - } - - Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); - Constraint() {} - }; - - typedef Array TerrainsTilePattern; - - Set<TerrainsTilePattern> _get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const; - Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const; - Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const; - Map<Vector2i, TerrainsTilePattern> _wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const; - TileMapCell _get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const; - Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const; + 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(); - // Cached data. - TerrainsTilePattern _build_terrains_tile_pattern(TileData *p_tile_data); - LocalVector<Map<TerrainsTilePattern, Set<TileMapCell>>> per_terrain_terrains_tile_patterns_tiles; - LocalVector<LocalVector<Set<TerrainsTilePattern>>> per_terrain_terrains_tile_patterns; - - Map<TileMapCell, TileData *> terrain_tiles; - LocalVector<TileSet::CellNeighbor> tile_sides; + enum SelectedType { + SELECTED_TYPE_CONNECT = 0, + SELECTED_TYPE_PATH, + SELECTED_TYPE_PATTERN, + }; + SelectedType selected_type; + int selected_terrain_set = -1; + int selected_terrain = -1; + TileSet::TerrainsPattern selected_terrains_pattern; + void _update_selection(); // Bottom panel. - Tree *terrains_tree; - ItemList *terrains_tile_list; + Tree *terrains_tree = nullptr; + ItemList *terrains_tile_list = nullptr; + + // Cache. + LocalVector<LocalVector<RBSet<TileSet::TerrainsPattern>>> per_terrain_terrains_patterns; // Update functions. void _update_terrains_cache(); void _update_terrains_tree(); void _update_tiles_list(); + void _update_theme(); // Update callback virtual void tile_set_changed() override; -protected: - void _notification(int p_what); - // static void _bind_methods(); - public: - virtual Control *get_toolbar() const override; + virtual Vector<TabData> get_tabs() const override; virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override; - //virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override; + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override; TileMapEditorTerrainsPlugin(); ~TileMapEditorTerrainsPlugin(); @@ -299,7 +317,7 @@ class TileMapEditor : public VBoxContainer { GDCLASS(TileMapEditor, VBoxContainer); private: - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; bool tileset_changed_needs_update = false; ObjectID tile_map_id; int tile_map_layer = -1; @@ -308,24 +326,23 @@ private: Vector<TileMapEditorPlugin *> tile_map_editor_plugins; // Toolbar. - HBoxContainer *tile_map_toolbar; + HBoxContainer *tile_map_toolbar = nullptr; - PopupMenu *layers_selection_popup; - Button *layers_selection_button; - Button *toogle_highlight_selected_layer_button; - void _layers_selection_button_draw(); - void _layers_selection_button_pressed(); - void _layers_selection_id_pressed(int p_id); + OptionButton *layers_selection_button = nullptr; + Button *toggle_highlight_selected_layer_button = nullptr; + void _layers_selection_item_selected(int p_index); - Button *toggle_grid_button; + Button *toggle_grid_button = nullptr; void _on_grid_toggled(bool p_pressed); - MenuButton *advanced_menu_button; + MenuButton *advanced_menu_button = nullptr; void _advanced_menu_button_id_pressed(int p_id); // Bottom panel. - Label *missing_tileset_label; - Tabs *tabs; + Label *missing_tileset_label = nullptr; + TabBar *tabs_bar = nullptr; + LocalVector<TileMapEditorPlugin::TabData> tabs_data; + LocalVector<TileMapEditorPlugin *> tabs_plugins; void _update_bottom_panel(); // TileMap. @@ -341,7 +358,7 @@ private: void _update_layers_selection(); // Inspector undo/redo callback. - void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); + void _move_tile_map_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); protected: void _notification(int p_what); @@ -352,7 +369,6 @@ public: void forward_canvas_draw_over_viewport(Control *p_overlay); void edit(TileMap *p_tile_map); - Control *get_toolbar() { return tile_map_toolbar; }; TileMapEditor(); ~TileMapEditor(); @@ -361,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 9e47a44b34..12e1615484 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,11 +30,16 @@ #include "tile_proxies_manager_dialog.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" -void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list) { +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; + } + ItemList *item_list = Object::cast_to<ItemList>(p_item_list); - popup_menu->set_size(Vector2(1, 1)); + popup_menu->reset_size(); popup_menu->set_position(get_position() + item_list->get_global_mouse_position()); popup_menu->popup(); } @@ -312,6 +317,8 @@ void TileProxiesManagerDialog::update_tile_set(Ref<TileSet> p_tile_set) { } TileProxiesManagerDialog::TileProxiesManagerDialog() { + undo_redo = EditorNode::get_singleton()->get_undo_redo(); + // Tile proxy management window. set_title(TTR("Tile Proxies Management")); set_process_unhandled_key_input(true); @@ -333,7 +340,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { source_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); source_level_list->set_select_mode(ItemList::SELECT_MULTI); source_level_list->set_allow_rmb_select(true); - source_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(source_level_list)); + source_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(source_level_list)); vbox_container->add_child(source_level_list); Label *coords_level_label = memnew(Label); @@ -344,7 +351,7 @@ TileProxiesManagerDialog::TileProxiesManagerDialog() { coords_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); coords_level_list->set_select_mode(ItemList::SELECT_MULTI); coords_level_list->set_allow_rmb_select(true); - coords_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(coords_level_list)); + coords_level_list->connect("item_clicked", callable_mp(this, &TileProxiesManagerDialog::_right_clicked).bind(coords_level_list)); vbox_container->add_child(coords_level_list); Label *alternative_level_label = memnew(Label); @@ -355,7 +362,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 6849be2cd6..44de708898 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.h +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,9 +31,7 @@ #ifndef TILE_PROXIES_MANAGER_DIALOG_H #define TILE_PROXIES_MANAGER_DIALOG_H -#include "editor/editor_node.h" #include "editor/editor_properties.h" - #include "scene/2d/tile_map.h" #include "scene/gui/dialogs.h" #include "scene/gui/item_list.h" @@ -45,25 +43,25 @@ private: int commited_actions_count = 0; Ref<TileSet> tile_set; - UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + UndoRedo *undo_redo = nullptr; TileMapCell from; TileMapCell to; // GUI - ItemList *source_level_list; - ItemList *coords_level_list; - ItemList *alternative_level_list; + ItemList *source_level_list = nullptr; + ItemList *coords_level_list = nullptr; + ItemList *alternative_level_list = nullptr; - EditorPropertyInteger *source_from_property_editor; - EditorPropertyVector2i *coords_from_property_editor; - EditorPropertyInteger *alternative_from_property_editor; - EditorPropertyInteger *source_to_property_editor; - EditorPropertyVector2i *coords_to_property_editor; - EditorPropertyInteger *alternative_to_property_editor; + EditorPropertyInteger *source_from_property_editor = nullptr; + EditorPropertyVector2i *coords_from_property_editor = nullptr; + EditorPropertyInteger *alternative_from_property_editor = nullptr; + EditorPropertyInteger *source_to_property_editor = nullptr; + EditorPropertyVector2i *coords_to_property_editor = nullptr; + EditorPropertyInteger *alternative_to_property_editor = nullptr; - PopupMenu *popup_menu; - void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list); + PopupMenu *popup_menu = nullptr; + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, 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 432f48fa85..6950f57a00 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -36,6 +36,7 @@ #include "editor/editor_scale.h" #include "editor/progress_dialog.h" +#include "editor/editor_node.h" #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/control.h" @@ -66,10 +67,15 @@ int TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id() { } bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + if (name == "name") { + // Use the resource_name property to store the source's name. + name = "resource_name"; + } bool valid = false; - tile_set_atlas_source->set(p_name, p_value, &valid); + tile_set_atlas_source->set(name, p_value, &valid); if (valid) { - emit_signal(SNAME("changed"), String(p_name).utf8().get_data()); + emit_signal(SNAME("changed"), String(name).utf8().get_data()); } return valid; } @@ -78,16 +84,23 @@ bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get(const StringN if (!tile_set_atlas_source) { return false; } + String name = p_name; + if (name == "name") { + // Use the resource_name property to store the source's name. + name = "resource_name"; + } bool valid = false; - r_ret = tile_set_atlas_source->get(p_name, &valid); + r_ret = tile_set_atlas_source->get(name, &valid); return valid; } 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, "tile_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, "")); } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { @@ -95,7 +108,7 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id); ClassDB::bind_method(D_METHOD("get_id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id); - ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_RANGE, "0," + itos(INT_MAX) + ",1"), "set_id", "get_id"); ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); } @@ -106,6 +119,10 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet> ERR_FAIL_COND(p_source_id < 0); ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); + if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == source_id) { + return; + } + // Disconnect to changes. if (tile_set_atlas_source) { tile_set_atlas_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); @@ -131,59 +148,134 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na return false; } + // ID and size related properties. if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; - if (alternative == 0 && p_name == "atlas_coords") { - Vector2i as_vector2i = Vector2i(p_value); - ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, as_vector2i), false); + if (alternative == 0) { + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "atlas_coords") { + Vector2i as_vector2i = Vector2i(p_value); + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(as_vector2i, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); + ERR_FAIL_COND_V(!has_room_for_tile, false); + + if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) { + tiles_set_atlas_source_editor->selection.clear(); + tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 }); + tiles_set_atlas_source_editor->_update_tile_id_label(); + } - if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) { - tiles_set_atlas_source_editor->selection.clear(); - tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 }); - tiles_set_atlas_source_editor->_update_tile_id_label(); + tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i); + tiles.clear(); + tiles.insert({ as_vector2i, 0 }); + emit_signal(SNAME("changed"), "atlas_coords"); + return true; + } else if (p_name == "size_in_atlas") { + Vector2i as_vector2i = Vector2i(p_value); + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, as_vector2i, tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); + ERR_FAIL_COND_V_EDMSG(!has_room_for_tile, false, "Invalid size or not enough room in the atlas for the tile."); + tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i); + emit_signal(SNAME("changed"), "size_in_atlas"); + return true; } + } else if (alternative > 0) { + if (p_name == "alternative_id") { + int as_int = int(p_value); + ERR_FAIL_COND_V(as_int < 0, false); + ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords)); + + if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) { + tiles_set_atlas_source_editor->selection.clear(); + tiles_set_atlas_source_editor->selection.insert({ coords, as_int }); + } - tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i); - tiles.clear(); - tiles.insert({ as_vector2i, 0 }); - emit_signal(SNAME("changed"), "atlas_coords"); - return true; - } else if (alternative == 0 && p_name == "size_in_atlas") { - Vector2i as_vector2i = Vector2i(p_value); - ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i), false); + int previous_alternative_tile = alternative; + tiles.clear(); + tiles.insert({ coords, as_int }); // tiles must be updated before. + tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int); - tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i); - emit_signal(SNAME("changed"), "size_in_atlas"); - return true; - } else if (alternative > 0 && p_name == "alternative_id") { - int as_int = int(p_value); - ERR_FAIL_COND_V(as_int < 0, false); - ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords)); - - if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) { - tiles_set_atlas_source_editor->selection.clear(); - tiles_set_atlas_source_editor->selection.insert({ coords, as_int }); + emit_signal(SNAME("changed"), "alternative_id"); + return true; } + } + } - int previous_alternative_tile = alternative; - tiles.clear(); - tiles.insert({ coords, as_int }); // tiles must be updated before. - tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int); + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } - emit_signal(SNAME("changed"), "alternative_id"); + if (all_alternatve_id_zero) { + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_separation(tile.tile), tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_columns(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_columns"); return true; + } else if (p_name == "animation_separation") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_separation(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (p_name == "animation_speed") { + for (TileSelection tile : tiles) { + tile_set_atlas_source->set_tile_animation_speed(tile.tile, p_value); + } + emit_signal(SNAME("changed"), "animation_speed"); + return true; + } else if (p_name == "animation_frames_count") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), p_value, tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, p_value); + } + } + notify_property_list_changed(); + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + for (TileSelection tile : tiles) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)) { + ERR_PRINT(vformat("No tile animation frame with index %d", frame)); + } else { + if (components[1] == "duration") { + tile_set_atlas_source->set_tile_animation_frame_duration(tile.tile, frame, p_value); + return true; + } + } + } } } + // Other properties. bool any_valid = false; - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; bool valid = false; - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND_V(!tile_data, false); tile_data->set(p_name, p_value, &valid); @@ -202,29 +294,73 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na return false; } + // ID and size related properties.s if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; - if (alternative == 0 && p_name == "atlas_coords") { - r_ret = coords; + if (alternative == 0) { + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "atlas_coords") { + r_ret = coords; + return true; + } else if (p_name == "size_in_atlas") { + r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords); + return true; + } + } else if (alternative > 0) { + if (p_name == "alternative_id") { + r_ret = alternative; + return true; + } + } + } + + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + const Vector2i &coords = tiles.front()->get().tile; + + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + r_ret = tile_set_atlas_source->get_tile_animation_columns(coords); + return true; + } else if (p_name == "animation_separation") { + r_ret = tile_set_atlas_source->get_tile_animation_separation(coords); return true; - } else if (alternative == 0 && p_name == "size_in_atlas") { - r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords); + } else if (p_name == "animation_speed") { + r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); return true; - } else if (alternative > 0 && p_name == "alternative_id") { - r_ret = alternative; + } else if (p_name == "animation_frames_count") { + r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords); return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (components[1] == "duration") { + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) { + return false; + } + r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame); + return true; + } } } - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { + for (const TileSelection &E : tiles) { // Return the first tile with a property matching the name. // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit. - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND_V(!tile_data, false); bool valid = false; @@ -242,6 +378,7 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro return; } + // ID and size related properties. if (tiles.size() == 1) { if (tiles.front()->get().alternative == 0) { p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, "")); @@ -251,6 +388,32 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + 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, "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, "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, "suffix:s")); + } + } + } + // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit). struct PropertyId { int occurence_id = 0; @@ -263,20 +426,20 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro int uses = 0; PropertyInfo property_info; }; - Map<PropertyId, PLData> usage; + RBMap<PropertyId, PLData> usage; List<PLData *> data_list; - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); ERR_FAIL_COND(!tile_data); List<PropertyInfo> list; tile_data->get_property_list(&list); - Map<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once) + HashMap<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once) for (List<PropertyInfo>::Element *E_property = list.front(); E_property; E_property = E_property->next()) { 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")) { @@ -310,21 +473,21 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro } } -void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles) { +void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, RBSet<TileSelection> p_tiles) { ERR_FAIL_COND(!p_tile_set_atlas_source); ERR_FAIL_COND(p_tiles.is_empty()); - for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) { - ERR_FAIL_COND(E->get().tile == TileSetSource::INVALID_ATLAS_COORDS); - ERR_FAIL_COND(E->get().alternative < 0); + for (const TileSelection &E : p_tiles) { + ERR_FAIL_COND(E.tile == TileSetSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(E.alternative < 0); } // Disconnect to changes. - for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; if (tile_set_atlas_source && tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) { - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); if (tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { tile_data->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); } @@ -332,15 +495,15 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_ } tile_set_atlas_source = p_tile_set_atlas_source; - tiles = Set<TileSelection>(p_tiles); + tiles = RBSet<TileSelection>(p_tiles); // Connect to changes. - for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) { - const Vector2i &coords = E->get().tile; - const int &alternative = E->get().alternative; + for (const TileSelection &E : p_tiles) { + const Vector2i &coords = E.tile; + const int &alternative = E.alternative; if (tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) { - TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); if (!tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { tile_data->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); } @@ -374,14 +537,11 @@ void TileSetAtlasSourceEditor::_update_tile_id_label() { void TileSetAtlasSourceEditor::_update_source_inspector() { // Update the proxy object. atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id); - - // Update the "clear outside texture" button. - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture()); } 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); @@ -450,8 +610,8 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { } // Theming. - tile_data_editors_tree->add_theme_constant_override("vseparation", 1); - tile_data_editors_tree->add_theme_constant_override("hseparation", 3); + tile_data_editors_tree->add_theme_constant_override("v_separation", 1); + tile_data_editors_tree->add_theme_constant_override("h_separation", 3); Color group_color = get_theme_color(SNAME("prop_category"), SNAME("Editor")); @@ -575,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; @@ -598,9 +758,9 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { #undef ADD_TILE_DATA_EDITOR // Add tile data editors as children. - for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { + for (KeyValue<String, TileDataEditor *> &E : tile_data_editors) { // Tile Data Editor. - TileDataEditor *tile_data_editor = E->get(); + TileDataEditor *tile_data_editor = E.value; if (!tile_data_editor->is_inside_tree()) { tile_data_painting_editor_container->add_child(tile_data_editor); } @@ -617,7 +777,11 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { // Update visibility. bool is_visible = tools_button_group->get_pressed_button() == tool_paint_button; tile_data_editor_dropdown_button->set_visible(is_visible); - tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + if (tile_data_editors_tree->get_selected()) { + tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0)); + } else { + tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + } tile_data_editors_label->set_visible(is_visible); } @@ -640,9 +804,9 @@ void TileSetAtlasSourceEditor::_update_current_tile_data_editor() { } // Hide all editors but the current one. - for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { - E->get()->hide(); - E->get()->get_toolbar()->hide(); + for (const KeyValue<String, TileDataEditor *> &E : tile_data_editors) { + E.value->hide(); + E.value->get_toolbar()->hide(); } if (tile_data_editors.has(property)) { current_tile_data_editor = tile_data_editors[property]; @@ -679,7 +843,11 @@ void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw() { clr = get_theme_color(SNAME("font_disabled_color")); break; default: - clr = get_theme_color(SNAME("font_color")); + if (tile_data_editor_dropdown_button->has_focus()) { + clr = get_theme_color(SNAME("font_focus_color")); + } else { + clr = get_theme_color(SNAME("font_color")); + } } } @@ -745,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); @@ -762,7 +930,7 @@ void TileSetAtlasSourceEditor::_update_atlas_view() { tile_atlas_view->update(); // Synchronize atlas view. - TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view); + TilesEditorPlugin::get_singleton()->synchronize_atlas_view(tile_atlas_view); } void TileSetAtlasSourceEditor::_update_toolbar() { @@ -813,7 +981,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven current_tile_data_editor->forward_painting_atlas_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); } // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; tile_atlas_control->update(); tile_atlas_control_unscaled->update(); @@ -846,15 +1014,15 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven CursorShape cursor_shape = CURSOR_ARROW; bool can_grow[4]; for (int i = 0; i < 4; i++) { - can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); + can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; } @@ -869,7 +1037,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); new_rect.size += Vector2i(1, 1); // Check if the new tile can fit in the new rect. - if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { + if (tile_set_atlas_source->has_room_for_tile(new_rect.position, new_rect.size, tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) { // Move and resize the tile. tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); drag_current_tile = new_rect.position; @@ -908,14 +1076,14 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset); coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); - if (drag_current_tile != coords && tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, coords)) { + if (drag_current_tile != coords && tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile), tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) { tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords); selection.clear(); selection.insert({ coords, 0 }); drag_current_tile = coords; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -948,14 +1116,14 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1))); } - if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { + if (tile_set_atlas_source->has_room_for_tile(new_rect.position, new_rect.size, tile_set_atlas_source->get_tile_animation_columns(drag_current_tile), tile_set_atlas_source->get_tile_animation_separation(drag_current_tile), tile_set_atlas_source->get_tile_animation_frames_count(drag_current_tile), drag_current_tile)) { tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); selection.clear(); selection.insert({ new_rect.position, 0 }); drag_current_tile = new_rect.position; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -975,7 +1143,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { // Left click pressed. if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) { @@ -1056,11 +1224,11 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven CursorShape cursor_shape = CURSOR_ARROW; bool can_grow[4]; for (int i = 0; i < 4; i++) { - can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); + can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2); drag_start_mouse_pos = mouse_local_pos; @@ -1069,7 +1237,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2); drag_start_mouse_pos = mouse_local_pos; @@ -1121,7 +1289,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven alternative_tiles_control_unscaled->update(); tile_atlas_view->update(); return; - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + } else if (mb->get_button_index() == MouseButton::RIGHT) { // Right click pressed. if (mb->is_pressed()) { drag_type = DRAG_TYPE_MAY_POPUP_MENU; @@ -1145,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; @@ -1160,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)) { @@ -1202,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)); @@ -1216,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)) { @@ -1260,7 +1428,7 @@ void TileSetAtlasSourceEditor::_end_dragging() { // Determine if we clear, then add or remove to the selection. bool add_to_selection = true; - if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + if (Input::get_singleton()->is_key_pressed(Key::SHIFT)) { Vector2i coords = tile_set_atlas_source->get_tile_at_coords(start_base_tiles_coords); if (coords != TileSetSource::INVALID_ATLAS_COORDS) { if (selection.has({ coords, 0 })) { @@ -1355,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) { @@ -1376,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); @@ -1401,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); @@ -1440,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); @@ -1454,9 +1622,6 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) { undo_redo->commit_action(); _update_tile_id_label(); } break; - case ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE: { - tile_set_atlas_source->clear_tiles_outside_texture(); - } break; case ADVANCED_AUTO_CREATE_TILES: { _auto_create_tiles(); } break; @@ -1493,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; } @@ -1507,41 +1672,49 @@ 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. - Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); - tile_atlas_control->draw_rect(region, selection_color, false); + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(selected.tile); frame++) { + Color color = selection_color; + if (frame > 0) { + color.a *= 0.3; + } + Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile, frame); + tile_atlas_control->draw_rect(region, color, false); + } } } if (selection.size() == 1) { // Draw the resize handles (only when it's possible to expand). TileSelection selected = selection.front()->get(); - Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); - Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); - Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); - Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); - Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; - Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; - bool can_grow[4]; - for (int i = 0; i < 4; i++) { - can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); - can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; - } - for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; - if (can_grow[i] && can_grow[(i + 3) % 4]) { - tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false); - } else { - tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false); + if (selected.alternative == 0) { + Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); + Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); + Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); + Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); + const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; + const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; + bool can_grow[4]; + for (int i = 0; i < 4; i++) { + can_grow[i] = tile_set_atlas_source->has_room_for_tile(selected.tile + directions[i], tile_set_atlas_source->get_tile_size_in_atlas(selected.tile), tile_set_atlas_source->get_tile_animation_columns(selected.tile), tile_set_atlas_source->get_tile_animation_separation(selected.tile), tile_set_atlas_source->get_tile_animation_frames_count(selected.tile), selected.tile); + can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; - if (can_grow[i]) { - tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false); - } else { - tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false); + for (int i = 0; i < 4; i++) { + Vector2 pos = rect.position + rect.size * coords[i]; + if (can_grow[i] && can_grow[(i + 3) % 4]) { + tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false); + } else { + tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false); + } + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; + if (can_grow[i]) { + tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false); + } else { + tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false); + } } } } @@ -1549,8 +1722,10 @@ 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()) { - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get()), 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) { // Draw tiles to be removed. @@ -1564,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)); @@ -1574,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) { @@ -1617,7 +1792,13 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords); if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) { // Draw existing hovered tile. - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile), Color(1.0, 1.0, 1.0), false); + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(hovered_tile); frame++) { + Color color = Color(1.0, 1.0, 1.0); + if (frame > 0) { + color.a *= 0.3; + } + tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile, frame), color, false); + } } else { // Draw empty tile, only in add/remove tiles mode. if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) { @@ -1638,10 +1819,10 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() { for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { Vector2i coords = tile_set_atlas_source->get_tile_id(i); Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(coords); - Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + 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; @@ -1656,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.position + texture_region.get_end()) / 2 + 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); } @@ -1705,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; @@ -1712,7 +1899,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In drag_type = DRAG_TYPE_NONE; Vector2 mouse_local_pos = alternative_tiles_control->get_local_mouse_position(); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { // Left click pressed. if (tools_button_group->get_pressed_button() == tool_select_button) { @@ -1728,9 +1915,12 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<In _update_tile_id_label(); } } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + } 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(); @@ -1781,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()) { @@ -1805,10 +1995,10 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { continue; } Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2 position = (rect.get_position() + rect.get_end()) / 2; + 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; @@ -1824,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); - Vector2 position = (rect.get_position() + rect.get_end()) / 2; + 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); } } @@ -1850,8 +2040,13 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { } } -void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() { - tile_set_atlas_source_changed_needs_update = true; +void TileSetAtlasSourceEditor::_tile_set_changed() { + tile_set_changed_needs_update = true; +} + +void TileSetAtlasSourceEditor::_tile_proxy_object_changed(String p_what) { + tile_set_changed_needs_update = false; // Avoid updating too many things. + _update_atlas_view(); } void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) { @@ -1866,32 +2061,69 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); ERR_FAIL_COND(!undo_redo); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); - AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); - if (tile_data) { + undo_redo->start_force_keep_in_merge_ends(); + AtlasTileProxyObject *tile_data_proxy = Object::cast_to<AtlasTileProxyObject>(p_edited); + if (tile_data_proxy) { 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(); int new_polygons_count = p_new_value; - int old_polygons_count = tile_data->get(vformat("physics_layer_%d/polygons_count", layer_index)); + int old_polygons_count = tile_data_proxy->get(vformat("physics_layer_%d/polygons_count", layer_index)); if (new_polygons_count < old_polygons_count) { - for (int i = new_polygons_count - 1; i < old_polygons_count; i++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); + for (int i = new_polygons_count; i < old_polygons_count; i++) { + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); } } } else if (p_property == "terrain_set") { - int current_terrain_set = tile_data->get("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)) { - ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); + 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])); } } } } + + TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to<TileSetAtlasSourceProxyObject>(p_edited); + if (atlas_source_proxy) { + TileSetAtlasSource *atlas_source = atlas_source_proxy->get_edited(); + ERR_FAIL_COND(!atlas_source); + + 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()); + } else if (p_property == "margins") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), p_new_value, atlas_source->get_separation(), atlas_source->get_texture_region_size()); + } else if (p_property == "separation") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), p_new_value, atlas_source->get_texture_region_size()); + } else if (p_property == "texture_region_size") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), atlas_source->get_separation(), p_new_value); + } + + if (!arr.is_empty()) { + // Get all properties assigned to a tile. + List<PropertyInfo> properties; + atlas_source->get_property_list(&properties); + + for (int i = 0; i < arr.size(); i++) { + Vector2i coords = arr[i]; + String prefix = vformat("%d:%d/", coords.x, coords.y); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(prefix)) { + ADD_UNDO(atlas_source, pi.name); + } + } + } + } + } + undo_redo->end_force_keep_in_merge_ends(); + #undef ADD_UNDO } @@ -1906,8 +2138,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource } // Remove listener for old objects. - if (tile_set_atlas_source) { - tile_set_atlas_source->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Clear the selection. @@ -1919,8 +2151,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource tile_set_atlas_source_id = p_source_id; // Add the listener again. - if (tile_set_atlas_source) { - tile_set_atlas_source->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Update everything. @@ -2000,7 +2232,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); @@ -2048,7 +2280,7 @@ void TileSetAtlasSourceEditor::_auto_remove_tiles() { void TileSetAtlasSourceEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { tool_setup_atlas_source_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); tool_select_button->set_icon(get_theme_icon(SNAME("ToolSelect"), SNAME("EditorIcons"))); tool_paint_button->set_icon(get_theme_icon(SNAME("CanvasItem"), SNAME("EditorIcons"))); @@ -2059,9 +2291,10 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { resize_handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons")); - break; - case NOTIFICATION_INTERNAL_PROCESS: - if (tile_set_atlas_source_changed_needs_update) { + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { + if (tile_set_changed_needs_update) { // Update everything. _update_source_inspector(); @@ -2074,11 +2307,9 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { _update_tile_data_editors(); _update_current_tile_data_editor(); - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; } - break; - default: - break; + } break; } } @@ -2090,6 +2321,8 @@ void TileSetAtlasSourceEditor::_bind_methods() { } TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { + undo_redo = EditorNode::get_undo_redo(); + set_process_unhandled_key_input(true); set_process_internal(true); @@ -2100,7 +2333,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { // Middle panel. ScrollContainer *middle_panel = memnew(ScrollContainer); - middle_panel->set_enable_h_scroll(false); + middle_panel->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE); split_container_right_side->add_child(middle_panel); @@ -2111,22 +2344,23 @@ 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)); - tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); + tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_proxy_object_changed)); tile_inspector = memnew(EditorInspector); tile_inspector->set_undo_redo(undo_redo); - tile_inspector->set_enable_v_scroll(false); + tile_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); tile_inspector->edit(tile_proxy_object); tile_inspector->set_use_folding(true); tile_inspector->connect("property_selected", callable_mp(this, &TileSetAtlasSourceEditor::_inspector_property_selected)); middle_vbox_container->add_child(tile_inspector); tile_inspector_no_tile_selected_label = memnew(Label); - tile_inspector_no_tile_selected_label->set_align(Label::ALIGN_CENTER); - tile_inspector_no_tile_selected_label->set_text(TTR("No tile selected.")); + tile_inspector_no_tile_selected_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + 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. @@ -2134,6 +2368,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); @@ -2144,7 +2379,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)); @@ -2157,6 +2392,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()); @@ -2164,7 +2400,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { atlas_source_inspector = memnew(EditorInspector); atlas_source_inspector->set_undo_redo(undo_redo); - atlas_source_inspector->set_enable_v_scroll(false); + atlas_source_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); atlas_source_inspector->edit(atlas_source_proxy_object); middle_vbox_container->add_child(atlas_source_inspector); @@ -2178,7 +2414,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); @@ -2233,14 +2469,12 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tools_settings_erase_button = memnew(Button); tools_settings_erase_button->set_flat(true); tools_settings_erase_button->set_toggle_mode(true); - tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", Key::E)); tools_settings_erase_button->set_shortcut_context(this); tool_settings->add_child(tools_settings_erase_button); tool_advanced_menu_buttom = memnew(MenuButton); tool_advanced_menu_buttom->set_flat(true); - tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE); - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); @@ -2262,12 +2496,12 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_atlas_view = memnew(TileAtlasView); tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL); tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL); - tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform)); + tile_atlas_view->connect("transform_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::set_atlas_view_transform)); tile_atlas_view->connect("transform_changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed).unbind(2)); right_panel->add_child(tile_atlas_view); base_tile_popup_menu = memnew(PopupMenu); - base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE), TILE_DELETE); + base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), Key::KEY_DELETE), TILE_DELETE); base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE); base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); tile_atlas_view->add_child(base_tile_popup_menu); @@ -2284,13 +2518,13 @@ 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); alternative_tile_popup_menu = memnew(PopupMenu); - alternative_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete_tile", TTR("Delete"), KEY_DELETE), TILE_DELETE); + alternative_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete_tile", TTR("Delete"), Key::KEY_DELETE), TILE_DELETE); alternative_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); tile_atlas_view->add_child(alternative_tile_popup_menu); @@ -2301,22 +2535,209 @@ 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); tile_atlas_view_missing_source_label = memnew(Label); tile_atlas_view_missing_source_label->set_text(TTR("Add or select an atlas texture to the left panel.")); - tile_atlas_view_missing_source_label->set_align(Label::ALIGN_CENTER); - tile_atlas_view_missing_source_label->set_valign(Label::VALIGN_CENTER); + tile_atlas_view_missing_source_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + tile_atlas_view_missing_source_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); tile_atlas_view_missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL); tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); tile_atlas_view_missing_source_label->hide(); right_panel->add_child(tile_atlas_view_missing_source_label); + + EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback)); + + // Inspector plugin. + Ref<EditorInspectorPluginTileData> tile_data_inspector_plugin; + tile_data_inspector_plugin.instantiate(); + EditorInspector::add_inspector_plugin(tile_data_inspector_plugin); } TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() { memdelete(tile_proxy_object); memdelete(atlas_source_proxy_object); } + +////// EditorPropertyTilePolygon ////// + +void EditorPropertyTilePolygon::_add_focusable_children(Node *p_node) { + Control *control = Object::cast_to<Control>(p_node); + if (control && control->get_focus_mode() != Control::FOCUS_NONE) { + add_focusable(control); + } + for (int i = 0; i < p_node->get_child_count(); i++) { + _add_focusable_children(p_node->get_child(i)); + } +} + +void EditorPropertyTilePolygon::_polygons_changed() { + if (String(count_property).is_empty()) { + if (base_type == "OccluderPolygon2D") { + // Single OccluderPolygon2D. + Ref<OccluderPolygon2D> occluder; + if (generic_tile_polygon_editor->get_polygon_count() >= 1) { + occluder.instantiate(); + occluder->set_polygon(generic_tile_polygon_editor->get_polygon(0)); + } + emit_changed(get_edited_property(), occluder); + } else if (base_type == "NavigationPolygon") { + Ref<NavigationPolygon> navigation_polygon; + if (generic_tile_polygon_editor->get_polygon_count() >= 1) { + navigation_polygon.instantiate(); + for (int i = 0; i < generic_tile_polygon_editor->get_polygon_count(); i++) { + Vector<Vector2> polygon = generic_tile_polygon_editor->get_polygon(i); + navigation_polygon->add_outline(polygon); + } + navigation_polygon->make_polygons_from_outlines(); + } + emit_changed(get_edited_property(), navigation_polygon); + } + } else { + if (base_type.is_empty()) { + // Multiple array of vertices. + Vector<String> changed_properties; + Array values; + int count = generic_tile_polygon_editor->get_polygon_count(); + changed_properties.push_back(count_property); + values.push_back(count); + for (int i = 0; i < count; i++) { + changed_properties.push_back(vformat(element_pattern, i)); + values.push_back(generic_tile_polygon_editor->get_polygon(i)); + } + emit_signal(SNAME("multiple_properties_changed"), changed_properties, values, false); + } + } +} + +void EditorPropertyTilePolygon::update_property() { + TileSetAtlasSourceEditor::AtlasTileProxyObject *atlas_tile_proxy_object = Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(get_edited_object()); + ERR_FAIL_COND(!atlas_tile_proxy_object); + ERR_FAIL_COND(atlas_tile_proxy_object->get_edited_tiles().is_empty()); + + TileSetAtlasSource *tile_set_atlas_source = atlas_tile_proxy_object->get_edited_tile_set_atlas_source(); + generic_tile_polygon_editor->set_tile_set(Ref<TileSet>(tile_set_atlas_source->get_tile_set())); + + // Set the background + Vector2i coords = atlas_tile_proxy_object->get_edited_tiles().front()->get().tile; + int alternative = atlas_tile_proxy_object->get_edited_tiles().front()->get().alternative; + TileData *tile_data = tile_set_atlas_source->get_tile_data(coords, alternative); + generic_tile_polygon_editor->set_background(tile_set_atlas_source->get_texture(), tile_set_atlas_source->get_tile_texture_region(coords), tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + + // Reset the polygons. + generic_tile_polygon_editor->clear_polygons(); + + if (String(count_property).is_empty()) { + if (base_type == "OccluderPolygon2D") { + // Single OccluderPolygon2D. + Ref<OccluderPolygon2D> occluder = get_edited_object()->get(get_edited_property()); + generic_tile_polygon_editor->clear_polygons(); + if (occluder.is_valid()) { + generic_tile_polygon_editor->add_polygon(occluder->get_polygon()); + } + } else if (base_type == "NavigationPolygon") { + // Single OccluderPolygon2D. + Ref<NavigationPolygon> navigation_polygon = get_edited_object()->get(get_edited_property()); + generic_tile_polygon_editor->clear_polygons(); + if (navigation_polygon.is_valid()) { + for (int i = 0; i < navigation_polygon->get_outline_count(); i++) { + generic_tile_polygon_editor->add_polygon(navigation_polygon->get_outline(i)); + } + } + } + } else { + int count = get_edited_object()->get(count_property); + if (base_type.is_empty()) { + // Multiple array of vertices. + generic_tile_polygon_editor->clear_polygons(); + for (int i = 0; i < count; i++) { + generic_tile_polygon_editor->add_polygon(get_edited_object()->get(vformat(element_pattern, i))); + } + } + } +} + +void EditorPropertyTilePolygon::setup_single_mode(const StringName &p_property, const String &p_base_type) { + set_object_and_property(nullptr, p_property); + base_type = p_base_type; + + generic_tile_polygon_editor->set_multiple_polygon_mode(false); +} + +void EditorPropertyTilePolygon::setup_multiple_mode(const StringName &p_property, const StringName &p_count_property, const String &p_element_pattern, const String &p_base_type) { + set_object_and_property(nullptr, p_property); + count_property = p_count_property; + element_pattern = p_element_pattern; + base_type = p_base_type; + + generic_tile_polygon_editor->set_multiple_polygon_mode(true); +} + +EditorPropertyTilePolygon::EditorPropertyTilePolygon() { + // Setup the polygon editor. + generic_tile_polygon_editor = memnew(GenericTilePolygonEditor); + generic_tile_polygon_editor->set_use_undo_redo(false); + generic_tile_polygon_editor->clear_polygons(); + add_child(generic_tile_polygon_editor); + generic_tile_polygon_editor->connect("polygons_changed", callable_mp(this, &EditorPropertyTilePolygon::_polygons_changed)); + + // Add all focussable children of generic_tile_polygon_editor as focussable. + _add_focusable_children(generic_tile_polygon_editor); +} + +////// EditorInspectorPluginTileData ////// + +bool EditorInspectorPluginTileData::can_handle(Object *p_object) { + return Object::cast_to<TileSetAtlasSourceEditor::AtlasTileProxyObject>(p_object) != nullptr; +} + +bool EditorInspectorPluginTileData::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { + Vector<String> components = String(p_path).split("/", true, 2); + if (components.size() == 2 && components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_int()) { + // Occlusion layers. + int layer_index = components[0].trim_prefix("occlusion_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components[1] == "polygon") { + EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon); + ep->setup_single_mode(p_path, "OccluderPolygon2D"); + add_property_editor(p_path, ep); + return true; + } + } else if (components.size() >= 2 && components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_int()) { + // Physics layers. + int layer_index = components[0].trim_prefix("physics_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components[1] == "polygons_count") { + EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon); + ep->setup_multiple_mode(vformat("physics_layer_%d/polygons", layer_index), vformat("physics_layer_%d/polygons_count", layer_index), vformat("physics_layer_%d/polygon_%%d/points", layer_index), ""); + Vector<String> properties; + properties.push_back(p_path); + int count = p_object->get(vformat("physics_layer_%d/polygons_count", layer_index)); + for (int i = 0; i < count; i++) { + properties.push_back(vformat(vformat("physics_layer_%d/polygon_%d/points", layer_index, i))); + } + add_property_editor_for_multiple_properties("Polygons", properties, ep); + return true; + } else if (components.size() == 3 && components[1].begins_with("polygon_") && components[1].trim_prefix("polygon_").is_valid_int()) { + int polygon_index = components[1].trim_prefix("polygon_").to_int(); + ERR_FAIL_COND_V(polygon_index < 0, false); + if (components[2] == "points") { + return true; + } + } + } else if (components.size() == 2 && components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_int()) { + // Navigation layers. + int layer_index = components[0].trim_prefix("navigation_layer_").to_int(); + ERR_FAIL_COND_V(layer_index < 0, false); + if (components[1] == "polygon") { + EditorPropertyTilePolygon *ep = memnew(EditorPropertyTilePolygon); + ep->setup_single_mode(p_path, "NavigationPolygon"); + add_property_editor(p_path, ep); + return true; + } + } + return false; +} diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 501416c340..738fe1044d 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -34,7 +34,6 @@ #include "tile_atlas_view.h" #include "tile_data_editors.h" -#include "editor/editor_node.h" #include "scene/gui/split_container.h" #include "scene/resources/tile_set.h" @@ -43,7 +42,7 @@ class TileSet; class TileSetAtlasSourceEditor : public HBoxContainer { GDCLASS(TileSetAtlasSourceEditor, HBoxContainer); -private: +public: // A class to store which tiles are selected. struct TileSelection { Vector2i tile = TileSetSource::INVALID_ATLAS_COORDS; @@ -78,6 +77,7 @@ private: int get_id(); void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + TileSetAtlasSource *get_edited() { return tile_set_atlas_source; }; }; // -- Proxy object for a tile, needed by the inspector -- @@ -85,10 +85,10 @@ private: GDCLASS(AtlasTileProxyObject, Object); private: - TileSetAtlasSourceEditor *tiles_set_atlas_source_editor; + TileSetAtlasSourceEditor *tiles_set_atlas_source_editor = nullptr; TileSetAtlasSource *tile_set_atlas_source = nullptr; - Set<TileSelection> tiles = Set<TileSelection>(); + RBSet<TileSelection> tiles = RBSet<TileSelection>(); protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -98,54 +98,58 @@ private: static void _bind_methods(); public: + TileSetAtlasSource *get_edited_tile_set_atlas_source() const { return tile_set_atlas_source; }; + 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; } }; +private: Ref<TileSet> tile_set; TileSetAtlasSource *tile_set_atlas_source = nullptr; int tile_set_atlas_source_id = TileSet::INVALID_SOURCE; - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; - bool tile_set_atlas_source_changed_needs_update = false; + bool tile_set_changed_needs_update = false; // -- Properties painting -- - VBoxContainer *tile_data_painting_editor_container; - Label *tile_data_editors_label; - Button *tile_data_editor_dropdown_button; - Popup *tile_data_editors_popup; - Tree *tile_data_editors_tree; + VBoxContainer *tile_data_painting_editor_container = nullptr; + Label *tile_data_editors_label = nullptr; + Button *tile_data_editor_dropdown_button = nullptr; + Popup *tile_data_editors_popup = nullptr; + Tree *tile_data_editors_tree = nullptr; void _tile_data_editor_dropdown_button_draw(); void _tile_data_editor_dropdown_button_pressed(); // -- Tile data editors -- String current_property; Control *current_tile_data_editor_toolbar = nullptr; - Map<String, TileDataEditor *> tile_data_editors; + HashMap<String, TileDataEditor *> tile_data_editors; TileDataEditor *current_tile_data_editor = nullptr; void _tile_data_editors_tree_selected(); // -- Inspector -- - AtlasTileProxyObject *tile_proxy_object; - Label *tile_inspector_label; - EditorInspector *tile_inspector; - Label *tile_inspector_no_tile_selected_label; + AtlasTileProxyObject *tile_proxy_object = nullptr; + Label *tile_inspector_label = nullptr; + EditorInspector *tile_inspector = nullptr; + Label *tile_inspector_no_tile_selected_label = nullptr; String selected_property; void _inspector_property_selected(String p_property); - TileSetAtlasSourceProxyObject *atlas_source_proxy_object; - Label *atlas_source_inspector_label; - EditorInspector *atlas_source_inspector; + TileSetAtlasSourceProxyObject *atlas_source_proxy_object = nullptr; + Label *atlas_source_inspector_label = nullptr; + EditorInspector *atlas_source_inspector = nullptr; // -- Atlas view -- - HBoxContainer *toolbox; - Label *tile_atlas_view_missing_source_label; - TileAtlasView *tile_atlas_view; + HBoxContainer *toolbox = nullptr; + Label *tile_atlas_view_missing_source_label = nullptr; + TileAtlasView *tile_atlas_view = nullptr; // Dragging enum DragType { @@ -178,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 { @@ -189,7 +193,6 @@ private: TILE_CREATE_ALTERNATIVE, TILE_DELETE, - ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE, ADVANCED_AUTO_CREATE_TILES, ADVANCED_AUTO_REMOVE_TILES, }; @@ -199,20 +202,20 @@ private: // Tool buttons. Ref<ButtonGroup> tools_button_group; - Button *tool_setup_atlas_source_button; - Button *tool_select_button; - Button *tool_paint_button; - Label *tool_tile_id_label; + Button *tool_setup_atlas_source_button = nullptr; + Button *tool_select_button = nullptr; + Button *tool_paint_button = nullptr; + Label *tool_tile_id_label = nullptr; // Tool settings. - HBoxContainer *tool_settings; - VSeparator *tool_settings_vsep; - HBoxContainer *tool_settings_tile_data_toolbar_container; - Button *tools_settings_erase_button; - MenuButton *tool_advanced_menu_buttom; + HBoxContainer *tool_settings = nullptr; + VSeparator *tool_settings_vsep = nullptr; + HBoxContainer *tool_settings_tile_data_toolbar_container = nullptr; + Button *tools_settings_erase_button = nullptr; + 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(); @@ -220,12 +223,12 @@ private: // A control on the tile atlas to draw and handle input events. Vector2i hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; - PopupMenu *base_tile_popup_menu; - PopupMenu *empty_base_tile_popup_menu; + PopupMenu *base_tile_popup_menu = nullptr; + PopupMenu *empty_base_tile_popup_menu = nullptr; Ref<Texture2D> resize_handle; Ref<Texture2D> resize_handle_disabled; - Control *tile_atlas_control; - Control *tile_atlas_control_unscaled; + Control *tile_atlas_control = nullptr; + Control *tile_atlas_control_unscaled = nullptr; void _tile_atlas_control_draw(); void _tile_atlas_control_unscaled_draw(); void _tile_atlas_control_mouse_exited(); @@ -235,9 +238,9 @@ private: // A control over the alternative tiles. Vector3i hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); - PopupMenu *alternative_tile_popup_menu; - Control *alternative_tiles_control; - Control *alternative_tiles_control_unscaled; + PopupMenu *alternative_tile_popup_menu = nullptr; + Control *alternative_tiles_control = nullptr; + Control *alternative_tiles_control_unscaled = nullptr; void _tile_alternatives_control_draw(); void _tile_alternatives_control_unscaled_draw(); void _tile_alternatives_control_mouse_exited(); @@ -261,9 +264,10 @@ private: // -- Misc -- void _auto_create_tiles(); void _auto_remove_tiles(); - AcceptDialog *confirm_auto_create_tiles; + AcceptDialog *confirm_auto_create_tiles = nullptr; - void _tile_set_atlas_source_changed(); + void _tile_set_changed(); + void _tile_proxy_object_changed(String p_what); void _atlas_source_proxy_object_changed(String p_what); void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); @@ -280,4 +284,34 @@ public: ~TileSetAtlasSourceEditor(); }; +class EditorPropertyTilePolygon : public EditorProperty { + GDCLASS(EditorPropertyTilePolygon, EditorProperty); + + StringName count_property; + String element_pattern; + String base_type; + + void _add_focusable_children(Node *p_node); + + GenericTilePolygonEditor *generic_tile_polygon_editor = nullptr; + void _polygons_changed(); + +public: + virtual void update_property() override; + void setup_single_mode(const StringName &p_property, const String &p_base_type); + void setup_multiple_mode(const StringName &p_property, const StringName &p_count_property, const String &p_element_pattern, const String &p_base_type); + EditorPropertyTilePolygon(); +}; + +class EditorInspectorPluginTileData : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginTileData, EditorInspectorPlugin); + + void _occlusion_polygon_set_callback(); + void _polygons_changed(Object *p_generic_tile_polygon_editor, Object *p_object, const String &p_path); + +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; +}; + #endif // TILE_SET_ATLAS_SOURCE_EDITOR_H diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index ba98a7d6b3..81804710b4 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,6 +33,8 @@ #include "tile_data_editors.h" #include "tiles_editor_plugin.h" +#include "editor/editor_file_system.h" +#include "editor/editor_node.h" #include "editor/editor_scale.h" #include "scene/gui/box_container.h" @@ -41,10 +43,10 @@ TileSetEditor *TileSetEditor::singleton = nullptr; -void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { +void TileSetEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { ERR_FAIL_COND(!tile_set.is_valid()); - if (!can_drop_data_fw(p_point, p_data, p_from)) { + if (!_can_drop_data_fw(p_point, p_data, p_from)) { return; } @@ -81,7 +83,7 @@ void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, C } } -bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { +bool TileSetEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { ERR_FAIL_COND_V(!tile_set.is_valid(), false); if (p_from == sources_list) { @@ -137,22 +139,28 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { sources_list->clear(); // Update the atlas sources. - for (int i = 0; i < tile_set->get_source_count(); i++) { - int source_id = tile_set->get_source_id(i); - + List<int> source_ids = TilesEditorPlugin::get_singleton()->get_sorted_sources(tile_set); + for (const int &source_id : source_ids) { TileSetSource *source = *tile_set->get_source(source_id); Ref<Texture2D> texture; String item_text; + // Common to all type of sources. + if (!source->get_name().is_empty()) { + item_text = vformat(TTR("%s (id:%d)"), source->get_name(), source_id); + } + // Atlas source. TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { texture = atlas_source->get_texture(); - if (texture.is_valid()) { - item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); - } else { - item_text = vformat(TTR("No Texture Atlas Source (id:%d)"), source_id); + if (item_text.is_empty()) { + if (texture.is_valid()) { + item_text = vformat("%s (ID: %d)", texture->get_path().get_file(), source_id); + } else { + item_text = vformat(TTR("No Texture Atlas Source (ID: %d)"), source_id); + } } } @@ -160,19 +168,21 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); if (scene_collection_source) { texture = get_theme_icon(SNAME("PackedScene"), SNAME("EditorIcons")); - item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id); + if (item_text.is_empty()) { + item_text = vformat(TTR("Scene Collection Source (ID: %d)"), source_id); + } } // Use default if not valid. if (item_text.is_empty()) { - item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id); + item_text = vformat(TTR("Unknown Type Source (ID: %d)"), source_id); } if (!texture.is_valid()) { texture = missing_texture_texture; } sources_list->add_item(item_text, texture); - sources_list->set_item_metadata(i, source_id); + sources_list->set_item_metadata(-1, source_id); } // Set again the current selected item if needed. @@ -180,6 +190,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { for (int i = 0; i < sources_list->get_item_count(); i++) { if ((int)sources_list->get_item_metadata(i) == to_select) { sources_list->set_current(i); + sources_list->ensure_current_is_visible(); if (old_selected != to_select) { sources_list->emit_signal(SNAME("item_selected"), sources_list->get_current()); } @@ -200,7 +211,7 @@ void TileSetEditor::_update_sources_list(int force_selected_id) { _source_selected(sources_list->get_current()); // Synchronize the lists. - TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current()); + TilesEditorPlugin::get_singleton()->set_sources_lists_current(sources_list->get_current()); } void TileSetEditor::_source_selected(int p_source_index) { @@ -303,38 +314,282 @@ void TileSetEditor::_sources_advanced_menu_id_pressed(int p_id_pressed) { } } +void TileSetEditor::_set_source_sort(int p_sort) { + TilesEditorPlugin::get_singleton()->set_sorting_option(p_sort); + for (int i = 0; i != TilesEditorPlugin::SOURCE_SORT_MAX; i++) { + source_sort_button->get_popup()->set_item_checked(i, (i == (int)p_sort)); + } + + int old_selected = TileSet::INVALID_SOURCE; + if (sources_list->get_current() >= 0) { + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + if (tile_set->has_source(source_id)) { + old_selected = source_id; + } + } + _update_sources_list(old_selected); +} + void TileSetEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { sources_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); sources_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + source_sort_button->set_icon(get_theme_icon(SNAME("Sort"), SNAME("EditorIcons"))); sources_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); missing_texture_texture = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); - break; - case NOTIFICATION_INTERNAL_PROCESS: + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (tile_set_changed_needs_update) { if (tile_set.is_valid()) { tile_set->set_edited(true); } _update_sources_list(); + _update_patterns_list(); tile_set_changed_needs_update = false; } + } break; + } +} + +void TileSetEditor::_patterns_item_list_gui_input(const Ref<InputEvent> &p_event) { + ERR_FAIL_COND(!tile_set.is_valid()); + + if (ED_IS_SHORTCUT("tiles_editor/delete", p_event) && p_event->is_pressed() && !p_event->is_echo()) { + Vector<int> selected = patterns_item_list->get_selected_items(); + undo_redo->create_action(TTR("Remove TileSet patterns")); + for (int i = 0; i < selected.size(); i++) { + int pattern_index = selected[i]; + undo_redo->add_do_method(*tile_set, "remove_pattern", pattern_index); + undo_redo->add_undo_method(*tile_set, "add_pattern", tile_set->get_pattern(pattern_index), pattern_index); + } + undo_redo->commit_action(); + patterns_item_list->accept_event(); + } +} + +void TileSetEditor::_pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture) { + // TODO optimize ? + for (int i = 0; i < patterns_item_list->get_item_count(); i++) { + if (patterns_item_list->get_item_metadata(i) == p_pattern) { + patterns_item_list->set_item_icon(i, p_texture); break; - default: - break; + } } } +void TileSetEditor::_update_patterns_list() { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Recreate the items. + patterns_item_list->clear(); + for (int i = 0; i < tile_set->get_patterns_count(); i++) { + int id = patterns_item_list->add_item(""); + patterns_item_list->set_item_metadata(id, tile_set->get_pattern(i)); + TilesEditorPlugin::get_singleton()->queue_pattern_preview(tile_set, tile_set->get_pattern(i), callable_mp(this, &TileSetEditor::_pattern_preview_done)); + } + + // Update the label visibility. + patterns_help_label->set_visible(patterns_item_list->get_item_count() == 0); +} + void TileSetEditor::_tile_set_changed() { tile_set_changed_needs_update = true; } +void TileSetEditor::_tab_changed(int p_tab_changed) { + split_container->set_visible(p_tab_changed == 0); + patterns_item_list->set_visible(p_tab_changed == 1); +} + +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); + + TileSet *tile_set = Object::cast_to<TileSet>(p_edited); + if (!tile_set) { + return; + } + + Vector<String> components = String(p_array_prefix).split("/", true, 2); + + // Compute the array indices to save. + int begin = 0; + int end; + if (p_array_prefix == "occlusion_layer_") { + end = tile_set->get_occlusion_layers_count(); + } else if (p_array_prefix == "physics_layer_") { + end = tile_set->get_physics_layers_count(); + } else if (p_array_prefix == "terrain_set_") { + end = tile_set->get_terrain_sets_count(); + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + end = tile_set->get_terrains_count(terrain_set); + } else if (p_array_prefix == "navigation_layer_") { + end = tile_set->get_navigation_layers_count(); + } else if (p_array_prefix == "custom_data_layer_") { + end = tile_set->get_custom_data_layers_count(); + } else { + ERR_FAIL_MSG("Invalid array prefix for TileSet."); + } + if (p_from_index < 0) { + // Adding new. + if (p_to_pos >= 0) { + begin = p_to_pos; + } else { + end = 0; // Nothing to save when adding at the end. + } + } else if (p_to_pos < 0) { + // Removing. + begin = p_from_index; + } else { + // Moving. + begin = MIN(p_from_index, p_to_pos); + end = MIN(MAX(p_from_index, p_to_pos) + 1, end); + } + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); + // Save layers' properties. + List<PropertyInfo> properties; + tile_set->get_property_list(&properties); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(p_array_prefix)) { + String str = pi.name.trim_prefix(p_array_prefix); + int to_char_index = 0; + while (to_char_index < str.length()) { + if (!is_digit(str[to_char_index])) { + break; + } + to_char_index++; + } + if (to_char_index > 0) { + int array_index = str.left(to_char_index).to_int(); + if (array_index >= begin && array_index < end) { + ADD_UNDO(tile_set, pi.name); + } + } + } + } + + // Save properties for TileSetAtlasSources tile data + for (int i = 0; i < tile_set->get_source_count(); i++) { + int source_id = tile_set->get_source_id(i); + + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + if (tas.is_valid()) { + for (int j = 0; j < tas->get_tiles_count(); j++) { + Vector2i tile_id = tas->get_tile_id(j); + for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { + int alternative_id = tas->get_alternative_tile_id(tile_id, k); + TileData *tile_data = tas->get_tile_data(tile_id, alternative_id); + ERR_FAIL_COND(!tile_data); + + // Actually saving stuff. + if (p_array_prefix == "occlusion_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "physics_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", layer_index)); + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(layer_index); polygon_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, polygon_index)); + } + } + } else if (p_array_prefix == "terrain_set_") { + ADD_UNDO(tile_data, "terrain_set"); + 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_terrain_peering_bit(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); + } + } + } + } 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_terrain_peering_bit(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[terrain_index])); + } + } + } else if (p_array_prefix == "navigation_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", layer_index)); + } + } else if (p_array_prefix == "custom_data_layer_") { + for (int layer_index = begin; layer_index < end; layer_index++) { + ADD_UNDO(tile_data, vformat("custom_data_%d", layer_index)); + } + } + } + } + } + } +#undef ADD_UNDO + + // Add do method. + if (p_array_prefix == "occlusion_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_occlusion_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_occlusion_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_occlusion_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "physics_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_physics_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_physics_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_physics_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "terrain_set_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain_set", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain_set", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain_set", p_from_index, p_to_pos); + } + } else if (components.size() >= 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrain_") { + int terrain_set = components[0].trim_prefix("terrain_set_").to_int(); + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_terrain", terrain_set, p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_terrain", terrain_set, p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_terrain", terrain_set, p_from_index, p_to_pos); + } + } else if (p_array_prefix == "navigation_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_navigation_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_navigation_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_navigation_layer", p_from_index, p_to_pos); + } + } else if (p_array_prefix == "custom_data_layer_") { + if (p_from_index < 0) { + undo_redo->add_do_method(tile_set, "add_custom_data_layer", p_to_pos); + } else if (p_to_pos < 0) { + undo_redo->add_do_method(tile_set, "remove_custom_data_layer", p_from_index); + } else { + undo_redo->add_do_method(tile_set, "move_custom_data_layer", p_from_index, p_to_pos); + } + } +} + 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); -#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); TileSet *tile_set = Object::cast_to<TileSet>(p_edited); if (tile_set) { Vector<String> components = p_property.split("/", true, 3); @@ -347,56 +602,18 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p Vector2i tile_id = tas->get_tile_id(j); for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { int alternative_id = tas->get_alternative_tile_id(tile_id, k); - TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); + TileData *tile_data = tas->get_tile_data(tile_id, alternative_id); ERR_FAIL_COND(!tile_data); - if (p_property == "occlusion_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_occlusion_layers_count(); - if (new_layer_count < old_layer_count) { - for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) { - ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index)); - } - } - } else if (p_property == "physics_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_physics_layers_count(); - if (new_layer_count < old_layer_count) { - for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index)); - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index)); - } - } - } - } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") || - (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) { + 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])); } } - } else if (p_property == "navigation_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_navigation_layers_count(); - if (new_layer_count < old_layer_count) { - for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) { - ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index)); - } - } - } else if (p_property == "custom_data_layers_count") { - int new_layer_count = p_new_value; - int old_layer_count = tile_set->get_custom_data_layers_count(); - if (new_layer_count < old_layer_count) { - for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) { - ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index)); - } - } } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") { int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int(); ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer)); @@ -410,8 +627,8 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p } void TileSetEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetEditor::_can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetEditor::_drop_data_fw); } void TileSetEditor::edit(Ref<TileSet> p_tile_set) { @@ -431,6 +648,7 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) { if (tile_set.is_valid()) { tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); _update_sources_list(); + _update_patterns_list(); } tile_set_atlas_source_editor->hide(); @@ -441,10 +659,26 @@ void TileSetEditor::edit(Ref<TileSet> p_tile_set) { TileSetEditor::TileSetEditor() { singleton = this; + undo_redo = EditorNode::get_undo_redo(); + set_process_internal(true); + // TabBar. + tabs_bar = memnew(TabBar); + tabs_bar->set_tab_alignment(TabBar::ALIGNMENT_CENTER); + tabs_bar->set_clip_tabs(false); + tabs_bar->add_tab(TTR("Tiles")); + tabs_bar->add_tab(TTR("Patterns")); + tabs_bar->connect("tab_changed", callable_mp(this, &TileSetEditor::_tab_changed)); + + tile_set_toolbar = memnew(HBoxContainer); + tile_set_toolbar->set_h_size_flags(SIZE_EXPAND_FILL); + tile_set_toolbar->add_child(tabs_bar); + add_child(tile_set_toolbar); + + //// Tiles //// // Split container. - HSplitContainer *split_container = memnew(HSplitContainer); + split_container = memnew(HSplitContainer); split_container->set_name(TTR("Tiles")); split_container->set_h_size_flags(SIZE_EXPAND_FILL); split_container->set_v_size_flags(SIZE_EXPAND_FILL); @@ -458,19 +692,33 @@ TileSetEditor::TileSetEditor() { split_container_left_side->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); split_container->add_child(split_container_left_side); + source_sort_button = memnew(MenuButton); + source_sort_button->set_flat(true); + source_sort_button->set_tooltip(TTR("Sort sources")); + + PopupMenu *p = source_sort_button->get_popup(); + p->connect("id_pressed", callable_mp(this, &TileSetEditor::_set_source_sort)); + p->add_radio_check_item(TTR("Sort by ID (Ascending)"), TilesEditorPlugin::SOURCE_SORT_ID); + p->add_radio_check_item(TTR("Sort by ID (Descending)"), TilesEditorPlugin::SOURCE_SORT_ID_REVERSE); + p->add_radio_check_item(TTR("Sort by Name (Ascending)"), TilesEditorPlugin::SOURCE_SORT_NAME); + p->add_radio_check_item(TTR("Sort by Name (Descending)"), TilesEditorPlugin::SOURCE_SORT_NAME_REVERSE); + p->set_item_checked(TilesEditorPlugin::SOURCE_SORT_ID, true); + sources_list = memnew(ItemList); sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); sources_list->set_h_size_flags(SIZE_EXPAND_FILL); sources_list->set_v_size_flags(SIZE_EXPAND_FILL); sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected)); - sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current)); - sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list)); + 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).bind(sources_list, source_sort_button)); + sources_list->add_user_signal(MethodInfo("sort_request")); + sources_list->connect("sort_request", callable_mp(this, &TileSetEditor::_update_sources_list).bind(-1)); sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->set_drag_forwarding(this); split_container_left_side->add_child(sources_list); HBoxContainer *sources_bottom_actions = memnew(HBoxContainer); - sources_bottom_actions->set_alignment(HBoxContainer::ALIGN_END); + sources_bottom_actions->set_alignment(BoxContainer::ALIGNMENT_END); split_container_left_side->add_child(sources_bottom_actions); sources_delete_button = memnew(Button); @@ -492,6 +740,7 @@ TileSetEditor::TileSetEditor() { sources_advanced_menu_button->get_popup()->add_item(TTR("Manage Tile Proxies")); sources_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_sources_advanced_menu_id_pressed)); sources_bottom_actions->add_child(sources_advanced_menu_button); + sources_bottom_actions->add_child(source_sort_button); atlas_merging_dialog = memnew(AtlasMergingDialog); add_child(atlas_merging_dialog); @@ -510,8 +759,8 @@ TileSetEditor::TileSetEditor() { no_source_selected_label->set_text(TTR("No TileSet source selected. Select or create a TileSet source.")); no_source_selected_label->set_h_size_flags(SIZE_EXPAND_FILL); no_source_selected_label->set_v_size_flags(SIZE_EXPAND_FILL); - no_source_selected_label->set_align(Label::ALIGN_CENTER); - no_source_selected_label->set_valign(Label::VALIGN_CENTER); + no_source_selected_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + no_source_selected_label->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); split_container_right_side->add_child(no_source_selected_label); // Atlases editor. @@ -530,7 +779,26 @@ TileSetEditor::TileSetEditor() { split_container_right_side->add_child(tile_set_scenes_collection_source_editor); tile_set_scenes_collection_source_editor->hide(); + //// Patterns //// + int thumbnail_size = 64; + patterns_item_list = memnew(ItemList); + patterns_item_list->set_max_columns(0); + patterns_item_list->set_icon_mode(ItemList::ICON_MODE_TOP); + patterns_item_list->set_fixed_column_width(thumbnail_size * 3 / 2); + patterns_item_list->set_max_text_lines(2); + patterns_item_list->set_fixed_icon_size(Size2(thumbnail_size, thumbnail_size)); + patterns_item_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + patterns_item_list->connect("gui_input", callable_mp(this, &TileSetEditor::_patterns_item_list_gui_input)); + add_child(patterns_item_list); + patterns_item_list->hide(); + + patterns_help_label = memnew(Label); + patterns_help_label->set_text(TTR("Add new patterns in the TileMap editing mode.")); + patterns_help_label->set_anchors_and_offsets_preset(Control::PRESET_CENTER); + patterns_item_list->add_child(patterns_help_label); + // Registers UndoRedo inspector callback. + EditorNode::get_singleton()->get_editor_data().add_move_array_element_function(SNAME("TileSet"), callable_mp(this, &TileSetEditor::_move_tile_set_array_element)); EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); } diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 970e3fabb6..c45240043e 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,6 +33,7 @@ #include "atlas_merging_dialog.h" #include "scene/gui/box_container.h" +#include "scene/gui/tab_bar.h" #include "scene/resources/tile_set.h" #include "tile_proxies_manager_dialog.h" #include "tile_set_atlas_source_editor.h" @@ -46,31 +47,52 @@ class TileSetEditor : public VBoxContainer { private: Ref<TileSet> tile_set; bool tile_set_changed_needs_update = false; + HSplitContainer *split_container = nullptr; - Label *no_source_selected_label; - TileSetAtlasSourceEditor *tile_set_atlas_source_editor; - TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; + // TabBar. + HBoxContainer *tile_set_toolbar = nullptr; + TabBar *tabs_bar = nullptr; - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + // Tiles. + Label *no_source_selected_label = nullptr; + TileSetAtlasSourceEditor *tile_set_atlas_source_editor = nullptr; + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor = nullptr; + + UndoRedo *undo_redo = nullptr; + + void _drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + bool _can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void _update_sources_list(int force_selected_id = -1); // Sources management. - Button *sources_delete_button; - MenuButton *sources_add_button; - MenuButton *sources_advanced_menu_button; - ItemList *sources_list; + Button *sources_delete_button = nullptr; + MenuButton *sources_add_button = nullptr; + MenuButton *source_sort_button = nullptr; + MenuButton *sources_advanced_menu_button = nullptr; + ItemList *sources_list = nullptr; Ref<Texture2D> missing_texture_texture; void _source_selected(int p_source_index); void _source_delete_pressed(); void _source_add_id_pressed(int p_id_pressed); void _sources_advanced_menu_id_pressed(int p_id_pressed); + void _set_source_sort(int p_sort); + + AtlasMergingDialog *atlas_merging_dialog = nullptr; + TileProxiesManagerDialog *tile_proxies_manager_dialog = nullptr; - AtlasMergingDialog *atlas_merging_dialog; - TileProxiesManagerDialog *tile_proxies_manager_dialog; + // Patterns. + ItemList *patterns_item_list = nullptr; + Label *patterns_help_label = nullptr; + void _patterns_item_list_gui_input(const Ref<InputEvent> &p_event); + void _pattern_preview_done(Ref<TileMapPattern> p_pattern, Ref<Texture2D> p_texture); + bool select_last_pattern = false; + void _update_patterns_list(); void _tile_set_changed(); + void _tab_changed(int p_tab_changed); + void _move_tile_set_array_element(Object *p_undo_redo, Object *p_edited, String p_array_prefix, int p_from_index, int p_to_pos); void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); protected: @@ -81,11 +103,9 @@ public: _FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; } void edit(Ref<TileSet> p_tile_set); - 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; TileSetEditor(); ~TileSetEditor(); }; -#endif // TILE_SET_EDITOR_PLUGIN_H +#endif // TILE_SET_EDITOR_H diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp index f74b3bf9c2..9a4b14616f 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,6 +30,8 @@ #include "tile_set_scenes_collection_source_editor.h" +#include "editor/editor_file_system.h" +#include "editor/editor_node.h" #include "editor/editor_resource_preview.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -56,10 +58,15 @@ int TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get } bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_set(const StringName &p_name, const Variant &p_value) { + String name = p_name; + if (name == "name") { + // Use the resource_name property to store the source's name. + name = "resource_name"; + } bool valid = false; - tile_set_scenes_collection_source->set(p_name, p_value, &valid); + tile_set_scenes_collection_source->set(name, p_value, &valid); if (valid) { - emit_signal(SNAME("changed"), String(p_name).utf8().get_data()); + emit_signal(SNAME("changed"), String(name).utf8().get_data()); } return valid; } @@ -68,11 +75,20 @@ bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_g if (!tile_set_scenes_collection_source) { return false; } + String name = p_name; + if (name == "name") { + // Use the resource_name property to store the source's name. + name = "resource_name"; + } bool valid = false; - r_ret = tile_set_scenes_collection_source->get(p_name, &valid); + r_ret = tile_set_scenes_collection_source->get(name, &valid); return valid; } +void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::STRING, "name", PROPERTY_HINT_NONE, "")); +} + void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_bind_methods() { // -- Shape and layout -- ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id); @@ -89,6 +105,10 @@ void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::ed ERR_FAIL_COND(p_source_id < 0); ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source); + if (tile_set == p_tile_set && tile_set_scenes_collection_source == p_tile_set_scenes_collection_source && source_id == p_source_id) { + return; + } + // Disconnect to changes. if (tile_set_scenes_collection_source) { tile_set_scenes_collection_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); @@ -174,6 +194,10 @@ void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::edit(TileSetScen ERR_FAIL_COND(!p_tile_set_scenes_collection_source); ERR_FAIL_COND(!p_tile_set_scenes_collection_source->has_scene_tile_id(p_scene_id)); + if (tile_set_scenes_collection_source == p_tile_set_scenes_collection_source && scene_id == p_scene_id) { + return; + } + tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; scene_id = p_scene_id; @@ -306,12 +330,13 @@ void TileSetScenesCollectionSourceEditor::_update_scenes_list() { void TileSetScenesCollectionSourceEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: + case NOTIFICATION_THEME_CHANGED: { scene_tile_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); scene_tile_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); _update_scenes_list(); - break; - case NOTIFICATION_INTERNAL_PROCESS: + } break; + + case NOTIFICATION_INTERNAL_PROCESS: { if (tile_set_scenes_collection_source_changed_needs_update) { // Update everything. _update_source_inspector(); @@ -320,14 +345,13 @@ void TileSetScenesCollectionSourceEditor::_notification(int p_what) { _update_tile_inspector(); tile_set_scenes_collection_source_changed_needs_update = false; } - break; - case NOTIFICATION_VISIBILITY_CHANGED: + } break; + + case NOTIFICATION_VISIBILITY_CHANGED: { // Update things just in case. _update_scenes_list(); _update_action_buttons(); - break; - default: - break; + } break; } } @@ -363,20 +387,19 @@ void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetS _update_tile_inspector(); } -void TileSetScenesCollectionSourceEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { - if (!can_drop_data_fw(p_point, p_data, p_from)) { +void TileSetScenesCollectionSourceEditor::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!_can_drop_data_fw(p_point, p_data, p_from)) { return; } if (p_from == scene_tiles_list) { // Handle dropping a texture in the list of atlas resources. - int scene_id = -1; Dictionary d = p_data; Vector<String> files = d["files"]; for (int i = 0; i < files.size(); i++) { Ref<PackedScene> resource = ResourceLoader::load(files[i]); if (resource.is_valid()) { - scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); undo_redo->create_action(TTR("Add a Scene Tile")); undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", resource, scene_id); undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); @@ -390,7 +413,7 @@ void TileSetScenesCollectionSourceEditor::drop_data_fw(const Point2 &p_point, co } } -bool TileSetScenesCollectionSourceEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { +bool TileSetScenesCollectionSourceEditor::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { if (p_from == scene_tiles_list) { Dictionary d = p_data; @@ -425,11 +448,13 @@ void TileSetScenesCollectionSourceEditor::_bind_methods() { ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id"))); ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileSetScenesCollectionSourceEditor::_scene_thumbnail_done); - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetScenesCollectionSourceEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::_can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetScenesCollectionSourceEditor::_drop_data_fw); } TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { + undo_redo = EditorNode::get_undo_redo(); + // -- Right side -- HSplitContainer *split_container_right_side = memnew(HSplitContainer); split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); @@ -437,7 +462,7 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { // Middle panel. ScrollContainer *middle_panel = memnew(ScrollContainer); - middle_panel->set_enable_h_scroll(false); + middle_panel->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE); split_container_right_side->add_child(middle_panel); @@ -455,7 +480,7 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { scenes_collection_source_inspector = memnew(EditorInspector); scenes_collection_source_inspector->set_undo_redo(undo_redo); - scenes_collection_source_inspector->set_enable_v_scroll(false); + scenes_collection_source_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object); middle_vbox_container->add_child(scenes_collection_source_inspector); @@ -471,7 +496,7 @@ TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { tile_inspector = memnew(EditorInspector); tile_inspector->set_undo_redo(undo_redo); - tile_inspector->set_enable_v_scroll(false); + tile_inspector->set_vertical_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED); tile_inspector->edit(tile_proxy_object); tile_inspector->set_use_folding(true); middle_vbox_container->add_child(tile_inspector); 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 195aa79bc4..77a583e522 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,10 +31,14 @@ #ifndef TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H #define TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H -#include "editor/editor_node.h" +#include "editor/editor_inspector.h" #include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/item_list.h" #include "scene/resources/tile_set.h" +class UndoRedo; + class TileSetScenesCollectionSourceEditor : public HBoxContainer { GDCLASS(TileSetScenesCollectionSourceEditor, HBoxContainer); @@ -51,6 +55,7 @@ private: protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; static void _bind_methods(); public: @@ -65,7 +70,7 @@ private: GDCLASS(SceneTileProxyObject, Object); private: - TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor = nullptr; TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; int source_id; @@ -92,23 +97,23 @@ private: TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; int tile_set_source_id = -1; - UndoRedo *undo_redo = EditorNode::get_undo_redo(); + UndoRedo *undo_redo = nullptr; bool tile_set_scenes_collection_source_changed_needs_update = false; // Source inspector. - TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object; - Label *scenes_collection_source_inspector_label; - EditorInspector *scenes_collection_source_inspector; + TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object = nullptr; + Label *scenes_collection_source_inspector_label = nullptr; + EditorInspector *scenes_collection_source_inspector = nullptr; // Tile inspector. - SceneTileProxyObject *tile_proxy_object; - Label *tile_inspector_label; - EditorInspector *tile_inspector; + SceneTileProxyObject *tile_proxy_object = nullptr; + Label *tile_inspector_label = nullptr; + EditorInspector *tile_inspector = nullptr; - ItemList *scene_tiles_list; - Button *scene_tile_add_button; - Button *scene_tile_delete_button; + ItemList *scene_tiles_list = nullptr; + Button *scene_tile_add_button = nullptr; + Button *scene_tile_delete_button = nullptr; void _tile_set_scenes_collection_source_changed(); void _scenes_collection_source_proxy_object_changed(String p_what); @@ -124,16 +129,17 @@ private: void _update_scenes_list(); void _update_action_buttons(); + 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; + protected: void _notification(int p_what); static void _bind_methods(); public: void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id); - 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; TileSetScenesCollectionSourceEditor(); ~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 79b869b511..b5134f6893 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,248 +30,401 @@ #include "tiles_editor_plugin.h" +#include "core/os/mutex.h" + #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/plugins/canvas_item_editor_plugin.h" #include "scene/2d/tile_map.h" -#include "scene/resources/tile_set.h" - #include "scene/gui/box_container.h" #include "scene/gui/button.h" #include "scene/gui/control.h" #include "scene/gui/separator.h" +#include "scene/resources/tile_set.h" #include "tile_set_editor.h" -TilesEditor *TilesEditor::singleton = nullptr; +TilesEditorPlugin *TilesEditorPlugin::singleton = nullptr; + +void TilesEditorPlugin::_preview_frame_started() { + RS::get_singleton()->request_frame_drawn_callback(callable_mp(const_cast<TilesEditorPlugin *>(this), &TilesEditorPlugin::_pattern_preview_done)); +} + +void TilesEditorPlugin::_pattern_preview_done() { + pattern_preview_done.post(); +} + +void TilesEditorPlugin::_thread_func(void *ud) { + TilesEditorPlugin *te = static_cast<TilesEditorPlugin *>(ud); + te->_thread(); +} + +void TilesEditorPlugin::_thread() { + pattern_thread_exited.clear(); + while (!pattern_thread_exit.is_set()) { + pattern_preview_sem.wait(); + + pattern_preview_mutex.lock(); + if (pattern_preview_queue.size()) { + QueueItem item = pattern_preview_queue.front()->get(); + pattern_preview_queue.pop_front(); + pattern_preview_mutex.unlock(); + + int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); + thumbnail_size *= EDSCALE; + Vector2 thumbnail_size2 = Vector2(thumbnail_size, thumbnail_size); + + if (item.pattern.is_valid() && !item.pattern->is_empty()) { + // Generate the pattern preview + SubViewport *viewport = memnew(SubViewport); + viewport->set_size(thumbnail_size2); + viewport->set_disable_input(true); + viewport->set_transparent_background(true); + viewport->set_update_mode(SubViewport::UPDATE_ONCE); + + TileMap *tile_map = memnew(TileMap); + tile_map->set_tileset(item.tile_set); + tile_map->set_pattern(0, Vector2(), item.pattern); + viewport->add_child(tile_map); + + TypedArray<Vector2i> used_cells = tile_map->get_used_cells(0); + + Rect2 encompassing_rect = Rect2(); + encompassing_rect.set_position(tile_map->map_to_world(used_cells[0])); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i cell = used_cells[i]; + Vector2 world_pos = tile_map->map_to_world(cell); + encompassing_rect.expand_to(world_pos); + + // Texture. + Ref<TileSetAtlasSource> atlas_source = tile_set->get_source(tile_map->get_cell_source_id(0, cell)); + if (atlas_source.is_valid()) { + Vector2i coords = tile_map->get_cell_atlas_coords(0, cell); + int alternative = tile_map->get_cell_alternative_tile(0, cell); + + Vector2 center = world_pos - atlas_source->get_tile_effective_texture_offset(coords, alternative); + encompassing_rect.expand_to(center - atlas_source->get_tile_texture_region(coords).size / 2); + encompassing_rect.expand_to(center + atlas_source->get_tile_texture_region(coords).size / 2); + } + } + + Vector2 scale = thumbnail_size2 / MAX(encompassing_rect.size.x, encompassing_rect.size.y); + tile_map->set_scale(scale); + tile_map->set_position(-(scale * encompassing_rect.get_center()) + thumbnail_size2 / 2); + + // 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), Object::CONNECT_ONESHOT); + + pattern_preview_done.wait(); + + Ref<Image> image = viewport->get_texture()->get_image(); + + // Find the index for the given pattern. TODO: optimize. + Variant args[] = { item.pattern, ImageTexture::create_from_image(image) }; + const Variant *args_ptr[] = { &args[0], &args[1] }; + Variant r; + Callable::CallError error; + item.callback.callp(args_ptr, 2, r, error); + + viewport->queue_delete(); + } else { + pattern_preview_mutex.unlock(); + } + } + } + pattern_thread_exited.set(); +} + +void TilesEditorPlugin::_tile_map_changed() { + tile_map_changed_needs_update = true; +} + +void TilesEditorPlugin::_update_editors() { + // If tile_map is not edited, we change the edited only if we are not editing a tile_set. + tileset_editor->edit(tile_set); + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map) { + tilemap_editor->edit(tile_map); + } else { + tilemap_editor->edit(nullptr); + } + + // 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()); -void TilesEditor::_notification(int p_what) { + // Update visibility of bottom panel buttons. + if (tileset_editor_button->is_pressed() && !tile_set.is_valid()) { + if (tile_map) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(tilemap_editor); + } else { + EditorNode::get_singleton()->hide_bottom_panel(); + } + } +} + +void TilesEditorPlugin::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - tileset_tilemap_switch_button->set_icon(get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons"))); - } break; case NOTIFICATION_INTERNAL_PROCESS: { if (tile_map_changed_needs_update) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (tile_map) { tile_set = tile_map->get_tileset(); } - _update_switch_button(); _update_editors(); + tile_map_changed_needs_update = false; } } break; } } -void TilesEditor::_tile_map_changed() { - tile_map_changed_needs_update = true; -} - -void TilesEditor::_update_switch_button() { - // Force the buttons status if needed. - TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); - if (tile_map && !tile_set.is_valid()) { - tileset_tilemap_switch_button->set_pressed(false); - } else if (!tile_map && tile_set.is_valid()) { - tileset_tilemap_switch_button->set_pressed(true); - } -} +void TilesEditorPlugin::make_visible(bool p_visible) { + is_visible = p_visible; -void TilesEditor::_update_editors() { - // Set editors visibility. - tilemap_toolbar->set_visible(!tileset_tilemap_switch_button->is_pressed()); - tilemap_editor->set_visible(!tileset_tilemap_switch_button->is_pressed()); - tileset_editor->set_visible(tileset_tilemap_switch_button->is_pressed()); - - // Enable/disable the switch button. - if (!tileset_tilemap_switch_button->is_pressed()) { - if (!tile_set.is_valid()) { - tileset_tilemap_switch_button->set_disabled(true); - tileset_tilemap_switch_button->set_tooltip(TTR("This TileMap has no assigned TileSet, assign a TileSet to this TileMap to edit it.")); - } else { - tileset_tilemap_switch_button->set_disabled(false); - tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor.")); - } - } else { + if (is_visible) { + // Disable and hide invalid editors. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); - if (!tile_map) { - tileset_tilemap_switch_button->set_disabled(true); - tileset_tilemap_switch_button->set_tooltip(TTR("You are editing a TileSet resource. Select a TileMap node to paint.")); + tileset_editor_button->set_visible(tile_set.is_valid()); + tilemap_editor_button->set_visible(tile_map); + if (tile_map && !is_editing_tile_set) { + EditorNode::get_singleton()->make_bottom_panel_item_visible(tilemap_editor); } else { - tileset_tilemap_switch_button->set_disabled(false); - tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor.")); + EditorNode::get_singleton()->make_bottom_panel_item_visible(tileset_editor); } - } - // If tile_map is not edited, we change the edited only if we are not editing a tile_set. - TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); - if (tile_map) { - tilemap_editor->edit(tile_map); } else { - tilemap_editor->edit(nullptr); + tileset_editor_button->hide(); + tilemap_editor_button->hide(); + EditorNode::get_singleton()->hide_bottom_panel(); } - tileset_editor->edit(tile_set); +} - // Update the viewport - CanvasItemEditor::get_singleton()->update_viewport(); +void TilesEditorPlugin::queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_pattern.is_valid()); + { + MutexLock lock(pattern_preview_mutex); + pattern_preview_queue.push_back({ p_tile_set, p_pattern, p_callback }); + } + pattern_preview_sem.post(); } -void TilesEditor::set_sources_lists_current(int p_current) { +void TilesEditorPlugin::set_sources_lists_current(int p_current) { atlas_sources_lists_current = p_current; } -void TilesEditor::synchronize_sources_list(Object *p_current) { - ItemList *item_list = Object::cast_to<ItemList>(p_current); +void TilesEditorPlugin::synchronize_sources_list(Object *p_current_list, Object *p_current_sort_button) { + ItemList *item_list = Object::cast_to<ItemList>(p_current_list); + MenuButton *sorting_button = Object::cast_to<MenuButton>(p_current_sort_button); ERR_FAIL_COND(!item_list); + ERR_FAIL_COND(!sorting_button); + + if (sorting_button->is_visible_in_tree()) { + for (int i = 0; i != SOURCE_SORT_MAX; i++) { + sorting_button->get_popup()->set_item_checked(i, (i == (int)source_sort)); + } + } if (item_list->is_visible_in_tree()) { + // Make sure the selection is not overwritten after sorting. + int atlas_sources_lists_current_mem = atlas_sources_lists_current; + item_list->emit_signal(SNAME("sort_request")); + atlas_sources_lists_current = atlas_sources_lists_current_mem; + if (atlas_sources_lists_current < 0 || atlas_sources_lists_current >= item_list->get_item_count()) { item_list->deselect_all(); } else { item_list->set_current(atlas_sources_lists_current); + item_list->ensure_current_is_visible(); item_list->emit_signal(SNAME("item_selected"), atlas_sources_lists_current); } } } -void TilesEditor::set_atlas_view_transform(float p_zoom, Vector2 p_scroll) { +void TilesEditorPlugin::set_atlas_view_transform(float p_zoom, Vector2 p_scroll) { atlas_view_zoom = p_zoom; atlas_view_scroll = p_scroll; } -void TilesEditor::synchronize_atlas_view(Object *p_current) { +void TilesEditorPlugin::synchronize_atlas_view(Object *p_current) { TileAtlasView *tile_atlas_view = Object::cast_to<TileAtlasView>(p_current); ERR_FAIL_COND(!tile_atlas_view); if (tile_atlas_view->is_visible_in_tree()) { - tile_atlas_view->set_transform(atlas_view_zoom, Vector2(atlas_view_scroll.x, atlas_view_scroll.y)); + tile_atlas_view->set_transform(atlas_view_zoom, atlas_view_scroll); } } -void TilesEditor::edit(Object *p_object) { +void TilesEditorPlugin::set_sorting_option(int p_option) { + source_sort = p_option; +} + +List<int> TilesEditorPlugin::get_sorted_sources(const Ref<TileSet> tile_set) const { + SourceNameComparator::tile_set = tile_set; + List<int> source_ids; + + for (int i = 0; i < tile_set->get_source_count(); i++) { + source_ids.push_back(tile_set->get_source_id(i)); + } + + switch (source_sort) { + case SOURCE_SORT_ID_REVERSE: + // Already sorted. + source_ids.reverse(); + break; + case SOURCE_SORT_NAME: + source_ids.sort_custom<SourceNameComparator>(); + break; + case SOURCE_SORT_NAME_REVERSE: + source_ids.sort_custom<SourceNameComparator>(); + source_ids.reverse(); + break; + default: // SOURCE_SORT_ID + break; + } + + SourceNameComparator::tile_set.unref(); + return source_ids; +} + +Ref<TileSet> TilesEditorPlugin::SourceNameComparator::tile_set; + +bool TilesEditorPlugin::SourceNameComparator::operator()(const int &p_a, const int &p_b) const { + String name_a; + String name_b; + + { + TileSetSource *source = *tile_set->get_source(p_a); + + if (!source->get_name().is_empty()) { + name_a = source->get_name(); + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Ref<Texture2D> texture = atlas_source->get_texture(); + if (name_a.is_empty() && texture.is_valid()) { + name_a = texture->get_path().get_file(); + } + } + + if (name_a.is_empty()) { + name_a = itos(p_a); + } + } + + { + TileSetSource *source = *tile_set->get_source(p_b); + + if (!source->get_name().is_empty()) { + name_b = source->get_name(); + } + + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + Ref<Texture2D> texture = atlas_source->get_texture(); + if (name_b.is_empty() && texture.is_valid()) { + name_b = texture->get_path().get_file(); + } + } + + if (name_b.is_empty()) { + name_b = itos(p_b); + } + } + + return NaturalNoCaseComparator()(name_a, name_b); +} + +void TilesEditorPlugin::edit(Object *p_object) { // Disconnect to changes. TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (tile_map) { - tile_map->disconnect("changed", callable_mp(this, &TilesEditor::_tile_map_changed)); + tile_map->disconnect("changed", callable_mp(this, &TilesEditorPlugin::_tile_map_changed)); } // Update edited objects. tile_set = Ref<TileSet>(); + is_editing_tile_set = false; + if (p_object) { if (p_object->is_class("TileMap")) { tile_map_id = p_object->get_instance_id(); tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); tile_set = tile_map->get_tileset(); + EditorNode::get_singleton()->make_bottom_panel_item_visible(tilemap_editor); } else if (p_object->is_class("TileSet")) { tile_set = Ref<TileSet>(p_object); if (tile_map) { - if (tile_map->get_tileset() != tile_set) { + if (tile_map->get_tileset() != tile_set || !tile_map->is_inside_tree()) { tile_map = nullptr; + tile_map_id = ObjectID(); } } - } - - // Update pressed status button. - if (p_object->is_class("TileMap")) { - tileset_tilemap_switch_button->set_pressed(false); - } else if (p_object->is_class("TileSet")) { - tileset_tilemap_switch_button->set_pressed(true); + is_editing_tile_set = true; + EditorNode::get_singleton()->make_bottom_panel_item_visible(tileset_editor); } } // Update the editors. - _update_switch_button(); _update_editors(); // Add change listener. if (tile_map) { - tile_map->connect("changed", callable_mp(this, &TilesEditor::_tile_map_changed)); + tile_map->connect("changed", callable_mp(this, &TilesEditorPlugin::_tile_map_changed)); } } -void TilesEditor::_bind_methods() { +bool TilesEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("TileMap") || p_object->is_class("TileSet"); } -TilesEditor::TilesEditor(EditorNode *p_editor) { +TilesEditorPlugin::TilesEditorPlugin() { set_process_internal(true); // Update the singleton. singleton = this; - // Toolbar. - HBoxContainer *toolbar = memnew(HBoxContainer); - toolbar->set_h_size_flags(SIZE_EXPAND_FILL); - add_child(toolbar); - - // Switch button. - tileset_tilemap_switch_button = memnew(Button); - tileset_tilemap_switch_button->set_flat(true); - tileset_tilemap_switch_button->set_toggle_mode(true); - tileset_tilemap_switch_button->connect("toggled", callable_mp(this, &TilesEditor::_update_editors).unbind(1)); - toolbar->add_child(tileset_tilemap_switch_button); + // Tileset editor. + tileset_editor = memnew(TileSetEditor); + tileset_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + tileset_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); + tileset_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); + tileset_editor->hide(); // Tilemap editor. tilemap_editor = memnew(TileMapEditor); - tilemap_editor->set_h_size_flags(SIZE_EXPAND_FILL); - tilemap_editor->set_v_size_flags(SIZE_EXPAND_FILL); + tilemap_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + tilemap_editor->set_v_size_flags(Control::SIZE_EXPAND_FILL); + tilemap_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); tilemap_editor->hide(); - add_child(tilemap_editor); - tilemap_toolbar = tilemap_editor->get_toolbar(); - toolbar->add_child(tilemap_toolbar); + // Pattern preview generation thread. + pattern_preview_thread.start(_thread_func, this); - // Tileset editor. - tileset_editor = memnew(TileSetEditor); - tileset_editor->set_h_size_flags(SIZE_EXPAND_FILL); - tileset_editor->set_v_size_flags(SIZE_EXPAND_FILL); - tileset_editor->hide(); - add_child(tileset_editor); + // Bottom buttons. + tileset_editor_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("TileSet"), tileset_editor); + tileset_editor_button->hide(); + tilemap_editor_button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("TileMap"), tilemap_editor); + tilemap_editor_button->hide(); // Initialization. - _update_switch_button(); _update_editors(); } -TilesEditor::~TilesEditor() { -} - -/////////////////////////////////////////////////////////////// - -void TilesEditorPlugin::_notification(int p_what) { -} - -void TilesEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - tiles_editor_button->show(); - editor_node->make_bottom_panel_item_visible(tiles_editor); - //get_tree()->connect_compat("idle_frame", tileset_editor, "_on_workspace_process"); - } else { - editor_node->hide_bottom_panel(); - tiles_editor_button->hide(); - //get_tree()->disconnect_compat("idle_frame", tileset_editor, "_on_workspace_process"); - } -} - -void TilesEditorPlugin::edit(Object *p_object) { - tiles_editor->edit(p_object); -} - -bool TilesEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("TileMap") || p_object->is_class("TileSet"); -} - -TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) { - editor_node = p_node; - - tiles_editor = memnew(TilesEditor(p_node)); - tiles_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); - tiles_editor->hide(); - - tiles_editor_button = p_node->add_bottom_panel_item(TTR("Tiles"), tiles_editor); - tiles_editor_button->hide(); -} - TilesEditorPlugin::~TilesEditorPlugin() { + if (pattern_preview_thread.is_started()) { + pattern_thread_exit.set(); + pattern_preview_sem.post(); + while (!pattern_thread_exited.is_set()) { + OS::get_singleton()->delay_usec(10000); + RenderingServer::get_singleton()->sync(); //sync pending stuff, as thread may be blocked on visual server + } + pattern_preview_thread.wait_to_finish(); + } } diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index f976d68938..b1fe6f8df6 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -38,24 +38,34 @@ #include "tile_map_editor.h" #include "tile_set_editor.h" -class TilesEditor : public VBoxContainer { - GDCLASS(TilesEditor, VBoxContainer); +class TilesEditorPlugin : public EditorPlugin { + GDCLASS(TilesEditorPlugin, EditorPlugin); - static TilesEditor *singleton; + static TilesEditorPlugin *singleton; + +public: + enum SourceSortOption { + SOURCE_SORT_ID = 0, + SOURCE_SORT_ID_REVERSE, + SOURCE_SORT_NAME, + SOURCE_SORT_NAME_REVERSE, + SOURCE_SORT_MAX + }; private: + 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 *tileset_tilemap_switch_button; + Button *tilemap_editor_button = nullptr; + TileMapEditor *tilemap_editor = nullptr; - Control *tilemap_toolbar; - TileMapEditor *tilemap_editor; + Button *tileset_editor_button = nullptr; + TileSetEditor *tileset_editor = nullptr; - TileSetEditor *tileset_editor; - - void _update_switch_button(); void _update_editors(); // For synchronization. @@ -65,49 +75,60 @@ private: void _tile_map_changed(); + // Source sorting. + int source_sort = SOURCE_SORT_ID; + + struct SourceNameComparator { + static Ref<TileSet> tile_set; + bool operator()(const int &p_a, const int &p_b) const; + }; + + // Patterns preview generation. + struct QueueItem { + Ref<TileSet> tile_set; + Ref<TileMapPattern> pattern; + Callable callback; + }; + List<QueueItem> pattern_preview_queue; + Mutex pattern_preview_mutex; + Semaphore pattern_preview_sem; + Thread pattern_preview_thread; + SafeFlag pattern_thread_exit; + SafeFlag pattern_thread_exited; + Semaphore pattern_preview_done; + void _preview_frame_started(); + void _pattern_preview_done(); + static void _thread_func(void *ud); + void _thread(); + protected: void _notification(int p_what); - static void _bind_methods(); public: - _FORCE_INLINE_ static TilesEditor *get_singleton() { return singleton; } + _FORCE_INLINE_ static TilesEditorPlugin *get_singleton() { return singleton; } + + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tilemap_editor->forward_canvas_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); } - bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return tilemap_editor->forward_canvas_gui_input(p_event); } - void forward_canvas_draw_over_viewport(Control *p_overlay) { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); } + // Pattern preview API. + void queue_pattern_preview(Ref<TileSet> p_tile_set, Ref<TileMapPattern> p_pattern, Callable p_callback); // To synchronize the atlas sources lists. void set_sources_lists_current(int p_current); - void synchronize_sources_list(Object *p_current); + void synchronize_sources_list(Object *p_current_list, Object *p_current_sort_button); void set_atlas_view_transform(float p_zoom, Vector2 p_scroll); void synchronize_atlas_view(Object *p_current); - void edit(Object *p_object); - - TilesEditor(EditorNode *p_editor); - ~TilesEditor(); -}; - -class TilesEditorPlugin : public EditorPlugin { - GDCLASS(TilesEditorPlugin, EditorPlugin); - -private: - EditorNode *editor_node; - TilesEditor *tiles_editor; - Button *tiles_editor_button; - -protected: - void _notification(int p_what); - -public: - virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tiles_editor->forward_canvas_gui_input(p_event); } - virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tiles_editor->forward_canvas_draw_over_viewport(p_overlay); } + // Sorting. + void set_sorting_option(int p_option); + List<int> get_sorted_sources(const Ref<TileSet> tile_set) const; virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - TilesEditorPlugin(EditorNode *p_node); + TilesEditorPlugin(); ~TilesEditorPlugin(); }; diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index aaa29bcb7a..cf55465417 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -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; @@ -49,6 +51,11 @@ void VersionControlEditorPlugin::_bind_methods() { BIND_ENUM_CONSTANT(CHANGE_TYPE_TYPECHANGE); } +void VersionControlEditorPlugin::_create_vcs_metadata_files() { + String dir = "res://"; + EditorVCSInterface::create_vcs_metadata_files(EditorVCSInterface::VCSMetadata(metadata_selection->get_selected()), dir); +} + void VersionControlEditorPlugin::_selected_a_vcs(int p_id) { List<StringName> available_addons = get_available_vcs_names(); const StringName selected_vcs = set_up_choice->get_item_text(p_id); @@ -71,6 +78,10 @@ VersionControlEditorPlugin *VersionControlEditorPlugin::get_singleton() { return singleton ? singleton : memnew(VersionControlEditorPlugin); } +void VersionControlEditorPlugin::popup_vcs_metadata_dialog() { + metadata_dialog->popup_centered(); +} + void VersionControlEditorPlugin::popup_vcs_set_up_dialog(const Control *p_gui_base) { fetch_available_vcs_addon_names(); List<StringName> available_addons = get_available_vcs_names(); @@ -257,7 +268,7 @@ void VersionControlEditorPlugin::_display_file_diff(String p_file_path) { void VersionControlEditorPlugin::_refresh_file_diff() { String open_file = diff_file_name->get_text(); - if (open_file != "") { + if (!open_file.is_empty()) { _display_file_diff(diff_file_name->get_text()); } } @@ -290,7 +301,7 @@ void VersionControlEditorPlugin::_update_commit_status() { } void VersionControlEditorPlugin::_update_commit_button() { - commit_button->set_disabled(commit_message->get_text().strip_edges() == ""); + commit_button->set_disabled(commit_message->get_text().strip_edges().is_empty()); } void VersionControlEditorPlugin::_commit_message_gui_input(const Ref<InputEvent> &p_event) { @@ -320,7 +331,7 @@ void VersionControlEditorPlugin::register_editor() { if (!EditorVCSInterface::get_singleton()) { EditorNode::get_singleton()->add_control_to_dock(EditorNode::DOCK_SLOT_RIGHT_UL, version_commit_dock); TabContainer *dock_vbc = (TabContainer *)version_commit_dock->get_parent_control(); - dock_vbc->set_tab_title(version_commit_dock->get_index(), TTR("Commit")); + dock_vbc->set_tab_title(dock_vbc->get_tab_idx_from_control(version_commit_dock), TTR("Commit")); Button *vc = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Version Control"), version_control_dock); set_version_control_tool_button(vc); @@ -374,6 +385,30 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { version_control_actions = memnew(PopupMenu); + metadata_dialog = memnew(ConfirmationDialog); + metadata_dialog->set_title(TTR("Create Version Control Metadata")); + metadata_dialog->set_min_size(Size2(200, 40)); + version_control_actions->add_child(metadata_dialog); + + VBoxContainer *metadata_vb = memnew(VBoxContainer); + HBoxContainer *metadata_hb = memnew(HBoxContainer); + metadata_hb->set_custom_minimum_size(Size2(200, 20)); + Label *l = memnew(Label); + l->set_text(TTR("Create VCS metadata files for:")); + metadata_hb->add_child(l); + metadata_selection = memnew(OptionButton); + metadata_selection->set_custom_minimum_size(Size2(100, 20)); + metadata_selection->add_item("None", (int)EditorVCSInterface::VCSMetadata::NONE); + metadata_selection->add_item("Git", (int)EditorVCSInterface::VCSMetadata::GIT); + metadata_selection->select((int)EditorVCSInterface::VCSMetadata::GIT); + metadata_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VersionControlEditorPlugin::_create_vcs_metadata_files)); + metadata_hb->add_child(metadata_selection); + metadata_vb->add_child(metadata_hb); + l = memnew(Label); + l->set_text(TTR("Existing VCS metadata files will be overwritten.")); + metadata_vb->add_child(l); + metadata_dialog->add_child(metadata_vb); + set_up_dialog = memnew(AcceptDialog); set_up_dialog->set_title(TTR("Set Up Version Control")); set_up_dialog->set_min_size(Size2(400, 100)); @@ -383,11 +418,11 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { set_up_ok_button->set_text(TTR("Close")); set_up_vbc = memnew(VBoxContainer); - set_up_vbc->set_alignment(VBoxContainer::ALIGN_CENTER); + set_up_vbc->set_alignment(BoxContainer::ALIGNMENT_CENTER); set_up_dialog->add_child(set_up_vbc); set_up_hbc = memnew(HBoxContainer); - set_up_hbc->set_h_size_flags(HBoxContainer::SIZE_EXPAND_FILL); + set_up_hbc->set_h_size_flags(BoxContainer::SIZE_EXPAND_FILL); set_up_vbc->add_child(set_up_hbc); set_up_vcs_status = memnew(RichTextLabel); @@ -414,7 +449,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { version_commit_dock->set_visible(false); commit_box_vbc = memnew(VBoxContainer); - commit_box_vbc->set_alignment(VBoxContainer::ALIGN_BEGIN); + commit_box_vbc->set_alignment(VBoxContainer::ALIGNMENT_BEGIN); commit_box_vbc->set_h_size_flags(VBoxContainer::SIZE_EXPAND_FILL); commit_box_vbc->set_v_size_flags(VBoxContainer::SIZE_EXPAND_FILL); version_commit_dock->add_child(commit_box_vbc); @@ -488,7 +523,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { commit_message->connect("text_changed", callable_mp(this, &VersionControlEditorPlugin::_update_commit_button)); commit_message->connect("gui_input", callable_mp(this, &VersionControlEditorPlugin::_commit_message_gui_input)); commit_box_vbc->add_child(commit_message); - ED_SHORTCUT("version_control/commit", TTR("Commit"), KEY_MASK_CMD | KEY_ENTER); + ED_SHORTCUT("version_control/commit", TTR("Commit"), KeyModifierMask::CMD | Key::ENTER); commit_button = memnew(Button); commit_button->set_text(TTR("Commit Changes")); @@ -497,7 +532,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { commit_box_vbc->add_child(commit_button); commit_status = memnew(Label); - commit_status->set_align(Label::ALIGN_CENTER); + commit_status->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); commit_box_vbc->add_child(commit_status); version_control_dock = memnew(PanelContainer); @@ -522,7 +557,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { diff_file_name = memnew(Label); diff_file_name->set_text(TTR("No file diff is active")); diff_file_name->set_h_size_flags(Label::SIZE_EXPAND_FILL); - diff_file_name->set_align(Label::ALIGN_RIGHT); + diff_file_name->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); diff_hbc->add_child(diff_file_name); diff_refresh_button = memnew(Button); diff --git a/editor/plugins/version_control_editor_plugin.h b/editor/plugins/version_control_editor_plugin.h index d2ba63c86c..fa721268ba 100644 --- a/editor/plugins/version_control_editor_plugin.h +++ b/editor/plugins/version_control_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -33,8 +33,9 @@ #include "editor/editor_plugin.h" #include "editor/editor_vcs_interface.h" -#include "scene/gui/container.h" +#include "scene/gui/box_container.h" #include "scene/gui/rich_text_label.h" +#include "scene/gui/split_container.h" #include "scene/gui/text_edit.h" #include "scene/gui/tree.h" @@ -56,48 +57,51 @@ private: int staged_files_count; List<StringName> available_addons; - PopupMenu *version_control_actions; - AcceptDialog *set_up_dialog; - VBoxContainer *set_up_vbc; - HBoxContainer *set_up_hbc; - Label *set_up_vcs_label; - OptionButton *set_up_choice; - PanelContainer *set_up_init_settings; - Button *set_up_init_button; - RichTextLabel *set_up_vcs_status; - Button *set_up_ok_button; + PopupMenu *version_control_actions = nullptr; + ConfirmationDialog *metadata_dialog = nullptr; + OptionButton *metadata_selection = nullptr; + AcceptDialog *set_up_dialog = nullptr; + VBoxContainer *set_up_vbc = nullptr; + HBoxContainer *set_up_hbc = nullptr; + Label *set_up_vcs_label = nullptr; + OptionButton *set_up_choice = nullptr; + PanelContainer *set_up_init_settings = nullptr; + Button *set_up_init_button = nullptr; + RichTextLabel *set_up_vcs_status = nullptr; + Button *set_up_ok_button = nullptr; HashMap<ChangeType, String> change_type_to_strings; HashMap<ChangeType, Color> change_type_to_color; - VBoxContainer *version_commit_dock; - VBoxContainer *commit_box_vbc; - HSplitContainer *stage_tools; - Tree *stage_files; - TreeItem *new_files; - TreeItem *modified_files; - TreeItem *renamed_files; - TreeItem *deleted_files; - TreeItem *typechange_files; - Label *staging_area_label; - HSplitContainer *stage_buttons; - Button *stage_all_button; - Button *stage_selected_button; - Button *refresh_button; - TextEdit *commit_message; - Button *commit_button; - Label *commit_status; - - PanelContainer *version_control_dock; - Button *version_control_dock_button; - VBoxContainer *diff_vbc; - HBoxContainer *diff_hbc; - Button *diff_refresh_button; - Label *diff_file_name; - Label *diff_heading; - RichTextLabel *diff; + VBoxContainer *version_commit_dock = nullptr; + VBoxContainer *commit_box_vbc = nullptr; + HSplitContainer *stage_tools = nullptr; + Tree *stage_files = nullptr; + TreeItem *new_files = nullptr; + TreeItem *modified_files = nullptr; + TreeItem *renamed_files = nullptr; + TreeItem *deleted_files = nullptr; + TreeItem *typechange_files = nullptr; + Label *staging_area_label = nullptr; + HSplitContainer *stage_buttons = nullptr; + Button *stage_all_button = nullptr; + Button *stage_selected_button = nullptr; + Button *refresh_button = nullptr; + TextEdit *commit_message = nullptr; + Button *commit_button = nullptr; + Label *commit_status = nullptr; + + PanelContainer *version_control_dock = nullptr; + Button *version_control_dock_button = nullptr; + VBoxContainer *diff_vbc = nullptr; + HBoxContainer *diff_hbc = nullptr; + Button *diff_refresh_button = nullptr; + Label *diff_file_name = nullptr; + Label *diff_heading = nullptr; + RichTextLabel *diff = nullptr; void _populate_available_vcs_names(); + void _create_vcs_metadata_files(); void _selected_a_vcs(int p_id); void _initialize_vcs(); void _send_commit_msg(); @@ -121,6 +125,7 @@ protected: public: static VersionControlEditorPlugin *get_singleton(); + void popup_vcs_metadata_dialog(); void popup_vcs_set_up_dialog(const Control *p_gui_base); void set_version_control_tool_button(Button *p_button) { version_control_dock_button = p_button; } @@ -143,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 5b1da11f12..4a144bc391 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,16 +31,27 @@ #include "visual_shader_editor_plugin.h" #include "core/config/project_settings.h" +#include "core/core_string_names.h" #include "core/input/input.h" #include "core/io/resource_loader.h" #include "core/math/math_defs.h" #include "core/os/keyboard.h" #include "editor/editor_log.h" +#include "editor/editor_node.h" #include "editor/editor_properties.h" #include "editor/editor_scale.h" +#include "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" #include "scene/resources/visual_shader_particle_nodes.h" @@ -69,6 +80,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)) { @@ -83,17 +98,6 @@ void VisualShaderNodePlugin::_bind_methods() { /////////////////// -static Ref<StyleBoxEmpty> make_empty_stylebox(float p_margin_left = -1, float p_margin_top = -1, float p_margin_right = -1, float p_margin_bottom = -1) { - Ref<StyleBoxEmpty> style(memnew(StyleBoxEmpty)); - style->set_default_margin(SIDE_LEFT, p_margin_left * EDSCALE); - style->set_default_margin(SIDE_RIGHT, p_margin_right * EDSCALE); - style->set_default_margin(SIDE_BOTTOM, p_margin_bottom * EDSCALE); - style->set_default_margin(SIDE_TOP, p_margin_top * EDSCALE); - return style; -} - -/////////////////// - VisualShaderGraphPlugin::VisualShaderGraphPlugin() { } @@ -103,7 +107,6 @@ void VisualShaderGraphPlugin::_bind_methods() { ClassDB::bind_method("connect_nodes", &VisualShaderGraphPlugin::connect_nodes); ClassDB::bind_method("disconnect_nodes", &VisualShaderGraphPlugin::disconnect_nodes); ClassDB::bind_method("set_node_position", &VisualShaderGraphPlugin::set_node_position); - ClassDB::bind_method("set_node_size", &VisualShaderGraphPlugin::set_node_size); ClassDB::bind_method("update_node", &VisualShaderGraphPlugin::update_node); ClassDB::bind_method("update_node_deferred", &VisualShaderGraphPlugin::update_node_deferred); ClassDB::bind_method("set_input_port_default_value", &VisualShaderGraphPlugin::set_input_port_default_value); @@ -113,26 +116,30 @@ 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); } -void VisualShaderGraphPlugin::set_connections(List<VisualShader::Connection> &p_connections) { +void VisualShaderGraphPlugin::set_connections(const List<VisualShader::Connection> &p_connections) { connections = p_connections; } void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].output_ports.has(p_port_id)) { - for (Map<int, Port>::Element *E = links[p_node_id].output_ports.front(); E; E = E->next()) { - if (E->value().preview_button != nullptr) { - E->value().preview_button->set_pressed(false); + for (const KeyValue<int, Port> &E : links[p_node_id].output_ports) { + if (E.value.preview_button != nullptr) { + E.value.preview_button->set_pressed(false); } } if (links[p_node_id].preview_visible && !is_dirty() && links[p_node_id].preview_box != nullptr) { links[p_node_id].graph_node->remove_child(links[p_node_id].preview_box); memdelete(links[p_node_id].preview_box); - links[p_node_id].graph_node->set_size(Vector2(-1, -1)); + links[p_node_id].graph_node->reset_size(); links[p_node_id].preview_visible = false; } @@ -146,6 +153,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)); @@ -184,8 +192,10 @@ void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_ switch (p_value.get_type()) { case Variant::COLOR: { button->set_custom_minimum_size(Size2(30, 0) * EDSCALE); - if (!button->is_connected("draw", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_draw_color_over_button))) { - button->connect("draw", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_draw_color_over_button), varray(button, p_value)); + + Callable ce = callable_mp(editor, &VisualShaderEditor::_draw_color_over_button); + if (!button->is_connected("draw", ce)) { + button->connect("draw", ce.bind(button, p_value)); } } break; case Variant::BOOL: { @@ -195,10 +205,18 @@ void VisualShaderGraphPlugin::set_input_port_default_value(VisualShader::Type p_ case Variant::FLOAT: { button->set_text(String::num(p_value, 4)); } break; + case Variant::VECTOR2: { + Vector2 v = p_value; + button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3)); + } break; case Variant::VECTOR3: { Vector3 v = p_value; button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3)); } break; + case Variant::QUATERNION: { + Quaternion v = p_value; + button->set_text(String::num(v.x, 3) + "," + String::num(v.y, 3) + "," + String::num(v.z, 3) + "," + String::num(v.w, 3)); + } break; default: { } } @@ -212,19 +230,27 @@ void VisualShaderGraphPlugin::set_uniform_name(VisualShader::Type p_type, int p_ void VisualShaderGraphPlugin::update_curve(int p_node_id) { if (links.has(p_node_id) && links[p_node_id].curve_editors[0]) { - if (((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture().is_valid()) { - links[p_node_id].curve_editors[0]->set_curve(((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture()->get_curve()); + Ref<VisualShaderNodeCurveTexture> tex = Object::cast_to<VisualShaderNodeCurveTexture>(links[p_node_id].visual_node); + ERR_FAIL_COND(!tex.is_valid()); + + if (tex->get_texture().is_valid()) { + links[p_node_id].curve_editors[0]->set_curve(tex->get_texture()->get_curve()); } + tex->emit_signal(CoreStringNames::get_singleton()->changed); } } void VisualShaderGraphPlugin::update_curve_xyz(int p_node_id) { if (links.has(p_node_id) && links[p_node_id].curve_editors[0] && links[p_node_id].curve_editors[1] && links[p_node_id].curve_editors[2]) { - if (((VisualShaderNodeCurveXYZTexture *)links[p_node_id].visual_node)->get_texture().is_valid()) { - links[p_node_id].curve_editors[0]->set_curve(((VisualShaderNodeCurveXYZTexture *)links[p_node_id].visual_node)->get_texture()->get_curve_x()); - links[p_node_id].curve_editors[1]->set_curve(((VisualShaderNodeCurveXYZTexture *)links[p_node_id].visual_node)->get_texture()->get_curve_y()); - links[p_node_id].curve_editors[2]->set_curve(((VisualShaderNodeCurveXYZTexture *)links[p_node_id].visual_node)->get_texture()->get_curve_z()); + Ref<VisualShaderNodeCurveXYZTexture> tex = Object::cast_to<VisualShaderNodeCurveXYZTexture>(links[p_node_id].visual_node); + ERR_FAIL_COND(!tex.is_valid()); + + if (tex->get_texture().is_valid()) { + links[p_node_id].curve_editors[0]->set_curve(tex->get_texture()->get_curve_x()); + links[p_node_id].curve_editors[1]->set_curve(tex->get_texture()->get_curve_y()); + links[p_node_id].curve_editors[2]->set_curve(tex->get_texture()->get_curve_z()); } + tex->emit_signal(CoreStringNames::get_singleton()->changed); } } @@ -248,7 +274,7 @@ void VisualShaderGraphPlugin::update_node_size(int p_node_id) { if (!links.has(p_node_id)) { return; } - links[p_node_id].graph_node->set_size(Size2(-1, -1)); + links[p_node_id].graph_node->reset_size(); } void VisualShaderGraphPlugin::register_default_input_button(int p_node_id, int p_port_id, Button *p_button) { @@ -264,11 +290,11 @@ void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, int p_index, } void VisualShaderGraphPlugin::update_uniform_refs() { - for (Map<int, Link>::Element *E = links.front(); E; E = E->next()) { - VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E->get().visual_node); + for (KeyValue<int, Link> &E : links) { + VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E.value.visual_node); if (ref) { - remove_node(E->get().type, E->key()); - add_node(E->get().type, E->key()); + remove_node(E.value.type, E.key); + add_node(E.value.type, E.key); } } } @@ -283,12 +309,6 @@ void VisualShaderGraphPlugin::set_node_position(VisualShader::Type p_type, int p } } -void VisualShaderGraphPlugin::set_node_size(VisualShader::Type p_type, int p_id, const Vector2 &p_size) { - if (visual_shader->get_shader_type() == p_type && links.has(p_id)) { - links[p_id].graph_node->set_size(p_size); - } -} - bool VisualShaderGraphPlugin::is_preview_visible(int p_id) const { return links[p_id].preview_visible; } @@ -306,7 +326,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) { @@ -318,35 +338,54 @@ void VisualShaderGraphPlugin::register_uniform_name(int p_node_id, LineEdit *p_u } void VisualShaderGraphPlugin::update_theme() { - vector_expanded_color[0] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); // red - vector_expanded_color[1] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); // green - vector_expanded_color[2] = VisualShaderEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); // blue + vector_expanded_color[0] = editor->get_theme_color(SNAME("axis_x_color"), SNAME("Editor")); // red + vector_expanded_color[1] = editor->get_theme_color(SNAME("axis_y_color"), SNAME("Editor")); // green + vector_expanded_color[2] = editor->get_theme_color(SNAME("axis_z_color"), SNAME("Editor")); // blue + vector_expanded_color[3] = editor->get_theme_color(SNAME("axis_w_color"), SNAME("Editor")); // alpha } void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { - if (p_type != visual_shader->get_shader_type()) { + if (!visual_shader.is_valid() || p_type != visual_shader->get_shader_type()) { return; } + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + VisualShaderGraphPlugin *graph_plugin = editor->get_graph_plugin(); + if (!graph_plugin) { + return; + } + Shader::Mode mode = visual_shader->get_mode(); Control *offset; - static Ref<StyleBoxEmpty> label_style = make_empty_stylebox(2, 1, 2, 1); - - static const Color type_color[6] = { + static const Color type_color[] = { Color(0.38, 0.85, 0.96), // scalar (float) Color(0.49, 0.78, 0.94), // scalar (int) - Color(0.84, 0.49, 0.93), // vector + Color(0.74, 0.57, 0.95), // vector2 + Color(0.84, 0.49, 0.93), // vector3 + Color(1.0, 0.125, 0.95), // vector4 Color(0.55, 0.65, 0.94), // boolean Color(0.96, 0.66, 0.43), // transform Color(1.0, 1.0, 0.0), // sampler }; - static const String vector_expanded_name[3] = { + static const String vector_expanded_name[4] = { "red", "green", - "blue" + "blue", + "alpha" }; + // Visual shader specific theme for MSDF font. + Ref<Theme> vstheme; + vstheme.instantiate(); + Ref<Font> label_font = EditorNode::get_singleton()->get_editor_theme()->get_font("main_msdf", "EditorFonts"); + vstheme->set_font("font", "Label", label_font); + vstheme->set_font("font", "LineEdit", label_font); + vstheme->set_font("font", "Button", label_font); + Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_id); Ref<VisualShaderNodeResizableBase> resizable_node = Object::cast_to<VisualShaderNodeResizableBase>(vsnode.ptr()); @@ -367,14 +406,18 @@ 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); if (is_resizable) { size = resizable_node->get_size(); node->set_resizable(true); - node->connect("resize_request", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_node_resized), varray((int)p_type, p_id)); + node->connect("resize_request", callable_mp(editor, &VisualShaderEditor::_node_resized).bind((int)p_type, p_id)); } if (is_expression) { expression = expression_node->get_expression(); @@ -386,10 +429,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (p_id >= 2) { node->set_show_close_button(true); - node->connect("close_request", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_delete_node_request), varray(p_type, p_id), CONNECT_DEFERRED); + node->connect("close_request", callable_mp(editor, &VisualShaderEditor::_delete_node_request).bind(p_type, p_id), CONNECT_DEFERRED); } - node->connect("dragged", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_node_dragged), varray(p_id)); + node->connect("dragged", callable_mp(editor, &VisualShaderEditor::_node_dragged).bind(p_id)); Control *custom_editor = nullptr; int port_offset = 1; @@ -414,6 +457,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { comment_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); comment_label->set_text(comment_node->get_description()); } + editor->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); } Ref<VisualShaderNodeParticleEmit> emit = vsnode; @@ -421,33 +465,37 @@ 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; - if (uniform.is_valid()) { - VisualShaderEditor::get_singleton()->graph->add_child(node); - VisualShaderEditor::get_singleton()->_update_created_node(node); + HBoxContainer *hb = nullptr; + if (uniform.is_valid()) { LineEdit *uniform_name = memnew(LineEdit); 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()); - node->add_child(uniform_name); - uniform_name->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_changed), varray(p_id)); - uniform_name->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_focus_out), varray(uniform_name, p_id)); - - if (vsnode->get_input_port_count() == 0 && vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { - //shortcut - VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0); - node->set_slot(1, false, VisualShaderNode::PORT_TYPE_SCALAR, Color(), true, port_right, type_color[port_right]); - if (!vsnode->is_use_prop_slots()) { - return; - } + 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); + hb->add_child(uniform_name); + node->add_child(hb); + } else { + node->add_child(uniform_name); } port_offset++; } - for (int i = 0; i < VisualShaderEditor::get_singleton()->plugins.size(); i++) { + for (int i = 0; i < editor->plugins.size(); i++) { vsnode->set_meta("id", p_id); vsnode->set_meta("shader_type", (int)p_type); - custom_editor = VisualShaderEditor::get_singleton()->plugins.write[i]->create_editor(visual_shader, vsnode); + custom_editor = editor->plugins.write[i]->create_editor(visual_shader, vsnode); vsnode->remove_meta("id"); vsnode->remove_meta("shader_type"); if (custom_editor) { @@ -459,117 +507,76 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { } Ref<VisualShaderNodeCurveTexture> curve = vsnode; - if (curve.is_valid()) { - if (curve->get_texture().is_valid() && !curve->get_texture()->is_connected("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve))) { - curve->get_texture()->connect("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve), varray(p_id)); - } - - HBoxContainer *hbox = memnew(HBoxContainer); - custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - hbox->add_child(custom_editor); - custom_editor = hbox; - } - Ref<VisualShaderNodeCurveXYZTexture> curve_xyz = vsnode; - if (curve_xyz.is_valid()) { - if (curve_xyz->get_texture().is_valid() && !curve_xyz->get_texture()->is_connected("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve_xyz))) { - curve_xyz->get_texture()->connect("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve_xyz), varray(p_id)); - } - HBoxContainer *hbox = memnew(HBoxContainer); - custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - hbox->add_child(custom_editor); - custom_editor = hbox; + bool is_curve = curve.is_valid() || curve_xyz.is_valid(); + if (is_curve) { + hb = memnew(HBoxContainer); + node->add_child(hb); } - if (custom_editor && !vsnode->is_use_prop_slots() && vsnode->get_output_port_count() > 0 && vsnode->get_output_port_name(0) == "" && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == "")) { - //will be embedded in first port - } else if (custom_editor) { - port_offset++; - node->add_child(custom_editor); - - bool is_curve = curve.is_valid() || curve_xyz.is_valid(); - - if (is_curve) { - VisualShaderEditor::get_singleton()->graph->add_child(node); - VisualShaderEditor::get_singleton()->_update_created_node(node); - - TextureButton *preview = memnew(TextureButton); - preview->set_toggle_mode(true); - preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); - preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); - preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - - register_output_port(p_id, 0, preview); - - preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, 0), CONNECT_DEFERRED); - custom_editor->add_child(preview); + if (curve.is_valid()) { + custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (vsnode->get_output_port_for_preview() >= 0) { - show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview()); - } + Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve); + if (curve->get_texture().is_valid() && !curve->get_texture()->is_connected("changed", ce)) { + curve->get_texture()->connect("changed", ce.bind(p_id)); } - if (curve.is_valid()) { - CurveEditor *curve_editor = memnew(CurveEditor); - node->add_child(curve_editor); - register_curve_editor(p_id, 0, curve_editor); - curve_editor->set_custom_minimum_size(Size2(300, 0)); - curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve->get_texture().is_valid()) { - curve_editor->set_curve(curve->get_texture()->get_curve()); - } + CurveEditor *curve_editor = memnew(CurveEditor); + node->add_child(curve_editor); + register_curve_editor(p_id, 0, curve_editor); + curve_editor->set_custom_minimum_size(Size2(300, 0)); + curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve->get_texture().is_valid()) { + curve_editor->set_curve(curve->get_texture()->get_curve()); } + } - if (curve_xyz.is_valid()) { - CurveEditor *curve_editor_x = memnew(CurveEditor); - node->add_child(curve_editor_x); - register_curve_editor(p_id, 0, curve_editor_x); - curve_editor_x->set_custom_minimum_size(Size2(300, 0)); - curve_editor_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve_xyz->get_texture().is_valid()) { - curve_editor_x->set_curve(curve_xyz->get_texture()->get_curve_x()); - } - - CurveEditor *curve_editor_y = memnew(CurveEditor); - node->add_child(curve_editor_y); - register_curve_editor(p_id, 1, curve_editor_y); - curve_editor_y->set_custom_minimum_size(Size2(300, 0)); - curve_editor_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve_xyz->get_texture().is_valid()) { - curve_editor_y->set_curve(curve_xyz->get_texture()->get_curve_y()); - } + if (curve_xyz.is_valid()) { + custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - CurveEditor *curve_editor_z = memnew(CurveEditor); - node->add_child(curve_editor_z); - register_curve_editor(p_id, 2, curve_editor_z); - curve_editor_z->set_custom_minimum_size(Size2(300, 0)); - curve_editor_z->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve_xyz->get_texture().is_valid()) { - curve_editor_z->set_curve(curve_xyz->get_texture()->get_curve_z()); - } + Callable ce = callable_mp(graph_plugin, &VisualShaderGraphPlugin::update_curve_xyz); + if (curve_xyz->get_texture().is_valid() && !curve_xyz->get_texture()->is_connected("changed", ce)) { + curve_xyz->get_texture()->connect("changed", ce.bind(p_id)); } - if (is_curve) { - VisualShaderNode::PortType port_left = vsnode->get_input_port_type(0); - VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0); - node->set_slot(1, true, port_left, type_color[port_left], true, port_right, type_color[port_right]); + CurveEditor *curve_editor_x = memnew(CurveEditor); + node->add_child(curve_editor_x); + register_curve_editor(p_id, 0, curve_editor_x); + curve_editor_x->set_custom_minimum_size(Size2(300, 0)); + curve_editor_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve_xyz->get_texture().is_valid()) { + curve_editor_x->set_curve(curve_xyz->get_texture()->get_curve_x()); + } - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); + CurveEditor *curve_editor_y = memnew(CurveEditor); + node->add_child(curve_editor_y); + register_curve_editor(p_id, 1, curve_editor_y); + curve_editor_y->set_custom_minimum_size(Size2(300, 0)); + curve_editor_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve_xyz->get_texture().is_valid()) { + curve_editor_y->set_curve(curve_xyz->get_texture()->get_curve_y()); } - if (vsnode->is_use_prop_slots()) { - String error = vsnode->get_warning(visual_shader->get_mode(), p_type); - if (error != String()) { - Label *error_label = memnew(Label); - error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); - error_label->set_text(error); - node->add_child(error_label); - } + CurveEditor *curve_editor_z = memnew(CurveEditor); + node->add_child(curve_editor_z); + register_curve_editor(p_id, 2, curve_editor_z); + curve_editor_z->set_custom_minimum_size(Size2(300, 0)); + curve_editor_z->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve_xyz->get_texture().is_valid()) { + curve_editor_z->set_curve(curve_xyz->get_texture()->get_curve_z()); + } + } - return; + if (custom_editor) { + if (is_curve || (hb == nullptr && !vsnode->is_use_prop_slots() && (vsnode->get_output_port_count() == 0 || vsnode->get_output_port_name(0) == "") && (vsnode->get_input_port_count() == 0 || vsnode->get_input_port_name(0) == ""))) { + //will be embedded in first port + } else { + port_offset++; + node->add_child(custom_editor); + custom_editor = nullptr; } - custom_editor = nullptr; } if (is_group) { @@ -594,14 +601,14 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { Button *add_input_btn = memnew(Button); add_input_btn->set_text(TTR("Add Input")); - add_input_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_input_port), varray(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, input_port_name), CONNECT_DEFERRED); + add_input_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_add_input_port).bind(p_id, group_node->get_free_input_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, input_port_name), CONNECT_DEFERRED); hb2->add_child(add_input_btn); hb2->add_spacer(); Button *add_output_btn = memnew(Button); add_output_btn->set_text(TTR("Add Output")); - add_output_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_add_output_port), varray(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR, output_port_name), CONNECT_DEFERRED); + add_output_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_add_output_port).bind(p_id, group_node->get_free_output_port_id(), VisualShaderNode::PORT_TYPE_VECTOR_3D, output_port_name), CONNECT_DEFERRED); hb2->add_child(add_output_btn); node->add_child(hb2); @@ -611,8 +618,18 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { int output_port_count = 0; for (int i = 0; i < vsnode->get_output_port_count(); i++) { if (vsnode->_is_output_port_expanded(i)) { - if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { - output_port_count += 3; + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + output_port_count += 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + output_port_count += 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + output_port_count += 4; + } break; + default: + break; } } output_port_count++; @@ -622,10 +639,30 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { int expanded_port_counter = 0; for (int i = 0, j = 0; i < max_ports; i++, j++) { - if (expanded_type == VisualShaderNode::PORT_TYPE_VECTOR && expanded_port_counter >= 3) { - expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; - expanded_port_counter = 0; - i -= 3; + switch (expanded_type) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + if (expanded_port_counter >= 2) { + expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + expanded_port_counter = 0; + i -= 2; + } + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + if (expanded_port_counter >= 3) { + expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + expanded_port_counter = 0; + i -= 3; + } + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + if (expanded_port_counter >= 4) { + expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + expanded_port_counter = 0; + i -= 4; + } + } break; + default: + break; } if (vsnode->is_port_separator(i)) { @@ -643,6 +680,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { for (const VisualShader::Connection &E : connections) { if (E.to_node == p_id && E.to_port == j) { port_left_used = true; + break; } } } @@ -661,7 +699,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_right = vector_expanded_name[expanded_port_counter++]; } - HBoxContainer *hb = memnew(HBoxContainer); + bool is_first_hbox = false; + if (i == 0 && hb != nullptr) { + is_first_hbox = true; + } else { + hb = memnew(HBoxContainer); + } hb->add_theme_constant_override("separation", 7 * EDSCALE); Variant default_value; @@ -673,7 +716,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { Button *button = memnew(Button); hb->add_child(button); register_default_input_button(p_id, i, button); - button->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_edit_port_default_input), varray(button, p_id, i)); + button->connect("pressed", callable_mp(editor, &VisualShaderEditor::_edit_port_default_input).bind(button, p_id, i)); if (default_value.get_type() != Variant::NIL) { // only a label set_input_port_default_value(p_type, p_id, i, default_value); } else { @@ -690,44 +733,46 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { hb->add_child(type_box); type_box->add_item(TTR("Float")); type_box->add_item(TTR("Int")); - type_box->add_item(TTR("Vector")); + type_box->add_item(TTR("Vector2")); + type_box->add_item(TTR("Vector3")); + type_box->add_item(TTR("Vector4")); type_box->add_item(TTR("Boolean")); type_box->add_item(TTR("Transform")); type_box->add_item(TTR("Sampler")); type_box->select(group_node->get_input_port_type(i)); type_box->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - type_box->connect("item_selected", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_type), varray(p_id, i), CONNECT_DEFERRED); + type_box->connect("item_selected", callable_mp(editor, &VisualShaderEditor::_change_input_port_type).bind(p_id, i), CONNECT_DEFERRED); LineEdit *name_box = memnew(LineEdit); hb->add_child(name_box); name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_left); - name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); - name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, false), CONNECT_DEFERRED); + name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_input_port_name).bind(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, i, false), CONNECT_DEFERRED); Button *remove_btn = memnew(Button); remove_btn->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); remove_btn->set_tooltip(TTR("Remove") + " " + name_left); - remove_btn->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_remove_input_port), varray(p_id, i), CONNECT_DEFERRED); + remove_btn->connect("pressed", callable_mp(editor, &VisualShaderEditor::_remove_input_port).bind(p_id, i), CONNECT_DEFERRED); hb->add_child(remove_btn); } else { Label *label = memnew(Label); label->set_text(name_left); - label->add_theme_style_override("normal", label_style); //more compact + label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); //more compact hb->add_child(label); - if (vsnode->get_input_port_default_hint(i) != "" && !port_left_used) { + if (vsnode->is_input_port_default(i, mode) && !port_left_used) { Label *hint_label = memnew(Label); - hint_label->set_text("[" + vsnode->get_input_port_default_hint(i) + "]"); - hint_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("font_readonly_color"), SNAME("TextEdit"))); - hint_label->add_theme_style_override("normal", label_style); + hint_label->set_text(TTR("[default]")); + hint_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("font_readonly_color"), SNAME("TextEdit"))); + hint_label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); hb->add_child(hint_label); } } } - if (!is_group) { + if (!is_group && !is_first_hbox) { hb->add_spacer(); } @@ -736,7 +781,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(VisualShaderEditor::get_singleton(), &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); @@ -744,23 +789,25 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_right); - name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); - name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, true), CONNECT_DEFERRED); + name_box->connect("text_submitted", callable_mp(editor, &VisualShaderEditor::_change_output_port_name).bind(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_port_name_focus_out).bind(name_box, p_id, i, true), CONNECT_DEFERRED); OptionButton *type_box = memnew(OptionButton); hb->add_child(type_box); type_box->add_item(TTR("Float")); type_box->add_item(TTR("Int")); - type_box->add_item(TTR("Vector")); + type_box->add_item(TTR("Vector2")); + type_box->add_item(TTR("Vector3")); + type_box->add_item(TTR("Vector4")); type_box->add_item(TTR("Boolean")); type_box->add_item(TTR("Transform")); type_box->select(group_node->get_output_port_type(i)); type_box->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - type_box->connect("item_selected", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_type), varray(p_id, i), CONNECT_DEFERRED); + type_box->connect("item_selected", callable_mp(editor, &VisualShaderEditor::_change_output_port_type).bind(p_id, i), CONNECT_DEFERRED); } else { Label *label = memnew(Label); label->set_text(name_right); - label->add_theme_style_override("normal", label_style); //more compact + label->add_theme_style_override("normal", editor->get_theme_stylebox(SNAME("label_style"), SNAME("VShaderEditor"))); //more compact hb->add_child(label); } } @@ -770,23 +817,23 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (vsnode->is_output_port_expandable(i)) { TextureButton *expand = memnew(TextureButton); expand->set_toggle_mode(true); - expand->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); - expand->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiTreeArrowRight"), SNAME("EditorIcons"))); + expand->set_normal_texture(editor->get_theme_icon(SNAME("GuiTreeArrowDown"), SNAME("EditorIcons"))); + 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(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); + expand->connect("pressed", callable_mp(editor, &VisualShaderEditor::_expand_output_port).bind(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); hb->add_child(expand); } - if (visual_shader->get_shader_type() == VisualShader::TYPE_FRAGMENT && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { + if (vsnode->has_output_port_preview(i) && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { TextureButton *preview = memnew(TextureButton); preview->set_toggle_mode(true); - preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); - preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); + preview->set_normal_texture(editor->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); + preview->set_pressed_texture(editor->get_theme_icon(SNAME("GuiVisibilityVisible"), SNAME("EditorIcons"))); preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); register_output_port(p_id, j, preview); - preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, j), CONNECT_DEFERRED); + preview->connect("pressed", callable_mp(editor, &VisualShaderEditor::_preview_select_port).bind(p_id, j), CONNECT_DEFERRED); hb->add_child(preview); } } @@ -798,56 +845,121 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { port_offset++; } - node->add_child(hb); + if (!is_first_hbox) { + node->add_child(hb); + } if (expanded_type != VisualShaderNode::PORT_TYPE_SCALAR) { continue; } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); + int idx = 1; + if (!is_first_hbox) { + idx = i + port_offset; + } + node->set_slot(idx, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); if (vsnode->_is_output_port_expanded(i)) { - if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { - port_offset++; - valid_left = (i + 1) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 1); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); - port_offset++; + switch (vsnode->get_output_port_type(i)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; - valid_left = (i + 2) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 2); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); - port_offset++; + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_2D; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; - valid_left = (i + 3) < vsnode->get_input_port_count(); - port_left = VisualShaderNode::PORT_TYPE_SCALAR; - if (valid_left) { - port_left = vsnode->get_input_port_type(i + 3); - } - node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); - expanded_type = VisualShaderNode::PORT_TYPE_VECTOR; + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + port_offset++; + + valid_left = (i + 3) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 3); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_3D; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; + + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + port_offset++; + + valid_left = (i + 3) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 3); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + port_offset++; + + valid_left = (i + 4) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 4); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[3]); + + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR_4D; + } break; + default: + break; } } } 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(visual_shader->get_mode(), p_type); - if (error != String()) { + String error = vsnode->get_warning(mode, p_type); + if (!error.is_empty()) { Label *error_label = memnew(Label); - error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); + error_label->add_theme_color_override("font_color", editor->get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_label->set_text(error); node->add_child(error_label); } @@ -873,7 +985,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expression_box->set_syntax_highlighter(expression_syntax_highlighter); expression_box->add_theme_color_override("background_color", background_color); - for (const String &E : VisualShaderEditor::get_singleton()->keyword_list) { + for (const String &E : editor->keyword_list) { if (ShaderLanguage::is_control_flow_keyword(E)) { expression_syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); } else { @@ -881,8 +993,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { } } - expression_box->add_theme_font_override("font", VisualShaderEditor::get_singleton()->get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); - expression_box->add_theme_font_size_override("font_size", VisualShaderEditor::get_singleton()->get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); + expression_box->add_theme_font_override("font", editor->get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); + expression_box->add_theme_font_size_override("font_size", editor->get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); expression_box->add_theme_color_override("font_color", text_color); expression_syntax_highlighter->set_number_color(number_color); expression_syntax_highlighter->set_symbol_color(symbol_color); @@ -903,18 +1015,11 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expression_box->set_context_menu_enabled(false); expression_box->set_draw_line_numbers(true); - expression_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expression_focus_out), varray(expression_box, p_id)); + expression_box->connect("focus_exited", callable_mp(editor, &VisualShaderEditor::_expression_focus_out).bind(expression_box, p_id)); } - if (!uniform.is_valid()) { - VisualShaderEditor::get_singleton()->graph->add_child(node); - if (is_comment) { - VisualShaderEditor::get_singleton()->graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content - } - VisualShaderEditor::get_singleton()->_update_created_node(node); - if (is_resizable) { - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_set_node_size"), (int)p_type, p_id, size); - } + if (is_comment) { + graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content } } @@ -927,8 +1032,14 @@ 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) { - if (visual_shader->get_shader_type() == p_type) { - VisualShaderEditor::get_singleton()->graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + + if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) { + graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + connections.push_back({ p_from_node, p_from_port, p_to_node, p_to_port }); if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr) { links[p_to_node].input_ports[p_to_port].default_input_button->hide(); @@ -937,8 +1048,14 @@ void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_fro } void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { - if (visual_shader->get_shader_type() == p_type) { - VisualShaderEditor::get_singleton()->graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + GraphEdit *graph = editor->graph; + if (!graph) { + return; + } + + if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) { + graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + for (const List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { connections.erase(E); @@ -957,6 +1074,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) { @@ -969,36 +1107,18 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { } visual_shader = Ref<VisualShader>(p_visual_shader); graph_plugin->register_shader(visual_shader.ptr()); - if (!visual_shader->is_connected("changed", callable_mp(this, &VisualShaderEditor::_update_preview))) { - visual_shader->connect("changed", callable_mp(this, &VisualShaderEditor::_update_preview)); - } -#ifndef DISABLE_DEPRECATED - Dictionary engine_version = Engine::get_singleton()->get_version_info(); - static Array components; - if (components.is_empty()) { - components.push_back("major"); - components.push_back("minor"); - } - const Dictionary vs_version = visual_shader->get_engine_version(); - if (!vs_version.has_all(components)) { - visual_shader->update_engine_version(engine_version); - print_line(vformat(TTR("The shader (\"%s\") has been updated to correspond Godot %s.%s version."), visual_shader->get_path(), engine_version["major"], engine_version["minor"])); - } else { - for (int i = 0; i < components.size(); i++) { - if (vs_version[components[i]] != engine_version[components[i]]) { - visual_shader->update_engine_version(engine_version); - print_line(vformat(TTR("The shader (\"%s\") has been updated to correspond Godot %s.%s version."), visual_shader->get_path(), engine_version["major"], engine_version["minor"])); - break; - } - } + + Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); + if (!visual_shader->is_connected("changed", ce)) { + visual_shader->connect("changed", ce); } -#endif visual_shader->set_graph_offset(graph->get_scroll_ofs() / EDSCALE); _set_mode(visual_shader->get_mode()); } else { if (visual_shader.is_valid()) { - if (visual_shader->is_connected("changed", callable_mp(this, &VisualShaderEditor::_update_preview))) { - visual_shader->disconnect("changed", callable_mp(this, &VisualShaderEditor::_update_preview)); + Callable ce = callable_mp(this, &VisualShaderEditor::_update_preview); + if (visual_shader->is_connected("changed", ce)) { + visual_shader->disconnect("changed", ce); } } visual_shader.unref(); @@ -1008,7 +1128,7 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { hide(); } else { if (changed) { // to avoid tree collapse - _clear_buffer(); + _update_varying_tree(); _update_options_menu(); _update_preview(); _update_graph(); @@ -1016,8 +1136,12 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { } } +void VisualShaderEditor::update_nodes() { + _update_nodes(); +} + void VisualShaderEditor::add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin) { - if (plugins.find(p_plugin) != -1) { + if (plugins.has(p_plugin)) { return; } plugins.push_back(p_plugin); @@ -1030,7 +1154,7 @@ void VisualShaderEditor::remove_plugin(const Ref<VisualShaderNodePlugin> &p_plug void VisualShaderEditor::clear_custom_types() { for (int i = 0; i < add_options.size(); i++) { if (add_options[i].is_custom) { - add_options.remove(i); + add_options.remove_at(i); i--; } } @@ -1099,10 +1223,7 @@ bool VisualShaderEditor::_is_available(int p_mode) { return (p_mode == -1 || (p_mode & current_mode) != 0); } -void VisualShaderEditor::update_custom_nodes() { - if (members_dialog->is_visible()) { - return; - } +void VisualShaderEditor::_update_nodes() { clear_custom_types(); List<StringName> class_list; ScriptServer::get_global_class_list(&class_list); @@ -1118,6 +1239,9 @@ void VisualShaderEditor::update_custom_nodes() { Ref<VisualShaderNodeCustom> ref; ref.instantiate(); ref->set_script(script); + if (!ref->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { + continue; + } String name; if (ref->has_method("_get_name")) { @@ -1160,7 +1284,7 @@ void VisualShaderEditor::update_custom_nodes() { category = category.rstrip("/"); category = category.lstrip("/"); category = "Addons/" + category; - if (subcategory != "") { + if (!subcategory.is_empty()) { category += "/" + subcategory; } @@ -1174,6 +1298,32 @@ void VisualShaderEditor::update_custom_nodes() { } } + // Disables not-supported copied items. + { + for (CopyItem &item : copy_items_buffer) { + Ref<VisualShaderNodeCustom> custom = Object::cast_to<VisualShaderNodeCustom>(item.node.ptr()); + + if (custom.is_valid()) { + if (!custom->is_available(visual_shader->get_mode(), visual_shader->get_shader_type())) { + item.disabled = true; + } else { + item.disabled = false; + } + } else { + for (int i = 0; i < add_options.size(); i++) { + if (add_options[i].type == item.node->get_class_name()) { + if ((add_options[i].func != visual_shader->get_mode() && add_options[i].func != -1) || !_is_available(add_options[i].mode)) { + item.disabled = true; + } else { + item.disabled = false; + } + break; + } + } + } + } + } + Array keys = added.keys(); keys.sort(); @@ -1207,9 +1357,9 @@ void VisualShaderEditor::_update_options_menu() { Color unsupported_color = get_theme_color(SNAME("error_color"), SNAME("Editor")); Color supported_color = get_theme_color(SNAME("warning_color"), SNAME("Editor")); - static bool low_driver = ProjectSettings::get_singleton()->get("rendering/driver/driver_name") == "GLES2"; + 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; @@ -1246,7 +1396,9 @@ void VisualShaderEditor::_update_options_menu() { if (input.is_valid()) { input->set_shader_mode(visual_shader->get_mode()); input->set_shader_type(visual_shader->get_shader_type()); - input->set_input_name(add_options[i].sub_func_str); + if (!add_options[i].ops.is_empty() && add_options[i].ops[0].get_type() == Variant::STRING) { + input->set_input_name((String)add_options[i].ops[0]); + } } Ref<VisualShaderNodeExpression> expression = Object::cast_to<VisualShaderNodeExpression>(vsn.ptr()); @@ -1363,9 +1515,15 @@ void VisualShaderEditor::_update_options_menu() { case VisualShaderNode::PORT_TYPE_SCALAR_INT: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons"))); break; - case VisualShaderNode::PORT_TYPE_VECTOR: + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons"))); + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons"))); break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons"))); + break; case VisualShaderNode::PORT_TYPE_BOOLEAN: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons"))); break; @@ -1387,26 +1545,41 @@ void VisualShaderEditor::_set_mode(int p_which) { edit_type_standard->set_visible(false); edit_type_particles->set_visible(false); edit_type_sky->set_visible(true); + edit_type_fog->set_visible(false); edit_type = edit_type_sky; custom_mode_box->set_visible(false); + varying_button->hide(); mode = MODE_FLAGS_SKY; + } else if (p_which == VisualShader::MODE_FOG) { + edit_type_standard->set_visible(false); + edit_type_particles->set_visible(false); + edit_type_sky->set_visible(false); + edit_type_fog->set_visible(true); + edit_type = edit_type_fog; + custom_mode_box->set_visible(false); + varying_button->hide(); + mode = MODE_FLAGS_FOG; } else if (p_which == VisualShader::MODE_PARTICLES) { edit_type_standard->set_visible(false); edit_type_particles->set_visible(true); edit_type_sky->set_visible(false); + edit_type_fog->set_visible(false); edit_type = edit_type_particles; if ((edit_type->get_selected() + 3) > VisualShader::TYPE_PROCESS) { custom_mode_box->set_visible(false); } else { custom_mode_box->set_visible(true); } + varying_button->hide(); mode = MODE_FLAGS_PARTICLES; } else { edit_type_particles->set_visible(false); edit_type_standard->set_visible(true); edit_type_sky->set_visible(false); + edit_type_fog->set_visible(false); edit_type = edit_type_standard; custom_mode_box->set_visible(false); + varying_button->show(); mode = MODE_FLAGS_SPATIAL_CANVASITEM; } visual_shader->set_shader_type(get_current_shader_type()); @@ -1439,7 +1612,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); @@ -1450,7 +1623,9 @@ void VisualShaderEditor::_update_uniforms(bool p_update_refs) { if (uniform.is_valid()) { Ref<VisualShaderNodeFloatUniform> float_uniform = vsnode; Ref<VisualShaderNodeIntUniform> int_uniform = vsnode; + Ref<VisualShaderNodeVec2Uniform> vec2_uniform = vsnode; Ref<VisualShaderNodeVec3Uniform> vec3_uniform = vsnode; + Ref<VisualShaderNodeVec4Uniform> vec4_uniform = vsnode; Ref<VisualShaderNodeColorUniform> color_uniform = vsnode; Ref<VisualShaderNodeBooleanUniform> bool_uniform = vsnode; Ref<VisualShaderNodeTransformUniform> transform_uniform = vsnode; @@ -1462,8 +1637,12 @@ void VisualShaderEditor::_update_uniforms(bool p_update_refs) { uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_INT; } else if (bool_uniform.is_valid()) { uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_BOOLEAN; + } else if (vec2_uniform.is_valid()) { + uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_VECTOR2; } else if (vec3_uniform.is_valid()) { - uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_VECTOR; + uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_VECTOR3; + } else if (vec4_uniform.is_valid()) { + uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_VECTOR4; } else if (transform_uniform.is_valid()) { uniform_type = VisualShaderNodeUniformRef::UniformType::UNIFORM_TYPE_TRANSFORM; } else if (color_uniform.is_valid()) { @@ -1471,7 +1650,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); } } } @@ -1480,7 +1659,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); @@ -1532,6 +1711,7 @@ void VisualShaderEditor::_update_graph() { Vector<int> nodes = visual_shader->get_node_list(type); _update_uniforms(false); + _update_varyings(); graph_plugin->clear_links(); graph_plugin->make_dirty(true); @@ -1554,6 +1734,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 { @@ -1562,6 +1744,8 @@ VisualShader::Type VisualShaderEditor::get_current_shader_type() const { type = VisualShader::Type(edit_type->get_selected() + 3 + (custom_mode_enabled ? 3 : 0)); } else if (mode & MODE_FLAGS_SKY) { type = VisualShader::Type(edit_type->get_selected() + 8); + } else if (mode & MODE_FLAGS_FOG) { + type = VisualShader::Type(edit_type->get_selected() + 9); } else { type = VisualShader::Type(edit_type->get_selected()); } @@ -1643,7 +1827,7 @@ void VisualShaderEditor::_change_input_port_name(const String &p_text, Object *p ERR_FAIL_COND(!line_edit); String validated_name = visual_shader->validate_port_name(p_text, node.ptr(), p_port_id, false); - if (validated_name == String() || prev_name == validated_name) { + if (validated_name.is_empty() || prev_name == validated_name) { line_edit->set_text(node->get_input_port_name(p_port_id)); return; } @@ -1651,8 +1835,6 @@ void VisualShaderEditor::_change_input_port_name(const String &p_text, Object *p undo_redo->create_action(TTR("Change Input Port Name")); undo_redo->add_do_method(node.ptr(), "set_input_port_name", p_port_id, validated_name); undo_redo->add_undo_method(node.ptr(), "set_input_port_name", p_port_id, node->get_input_port_name(p_port_id)); - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node_id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->commit_action(); } @@ -1671,7 +1853,7 @@ void VisualShaderEditor::_change_output_port_name(const String &p_text, Object * ERR_FAIL_COND(!line_edit); String validated_name = visual_shader->validate_port_name(p_text, node.ptr(), p_port_id, true); - if (validated_name == String() || prev_name == validated_name) { + if (validated_name.is_empty() || prev_name == validated_name) { line_edit->set_text(node->get_output_port_name(p_port_id)); return; } @@ -1679,8 +1861,6 @@ void VisualShaderEditor::_change_output_port_name(const String &p_text, Object * undo_redo->create_action(TTR("Change Output Port Name")); undo_redo->add_do_method(node.ptr(), "set_output_port_name", p_port_id, validated_name); undo_redo->add_undo_method(node.ptr(), "set_output_port_name", p_port_id, prev_name); - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node_id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node_id); undo_redo->commit_action(); } @@ -1700,8 +1880,18 @@ void VisualShaderEditor::_expand_output_port(int p_node, int p_port, bool p_expa undo_redo->add_undo_method(node.ptr(), "_set_output_port_expanded", p_port, !p_expand); int type_size = 0; - if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_VECTOR) { - type_size = 3; + switch (node->get_output_port_type(p_port)) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + type_size = 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + type_size = 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + type_size = 4; + } break; + default: + break; } List<VisualShader::Connection> conns; @@ -1932,21 +2122,19 @@ void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p } gn->set_custom_minimum_size(size); - gn->set_size(Size2(1, 1)); + gn->reset_size(); if (!expression_node.is_null() && text_box) { Size2 box_size = size; - if (gn != nullptr) { - if (box_size.x < 150 * EDSCALE || box_size.y < 0) { - box_size.x = gn->get_size().x; - } + if (box_size.x < 150 * EDSCALE || box_size.y < 0) { + box_size.x = gn->get_size().x; } box_size.x -= text_box->get_offset(SIDE_LEFT); box_size.x -= 28 * EDSCALE; box_size.y -= text_box->get_offset(SIDE_TOP); box_size.y -= 28 * EDSCALE; - text_box->set_custom_minimum_size(Size2(box_size.x, box_size.y)); - text_box->set_size(Size2(1, 1)); + text_box->set_custom_minimum_size(box_size); + text_box->reset_size(); } } } @@ -1994,8 +2182,8 @@ void VisualShaderEditor::_comment_title_popup_show(const Point2 &p_position, int } void VisualShaderEditor::_comment_title_text_changed(const String &p_new_text) { - comment_title_change_edit->set_size(Size2(-1, -1)); - comment_title_change_popup->set_size(Size2(-1, -1)); + comment_title_change_edit->reset_size(); + comment_title_change_popup->reset_size(); } void VisualShaderEditor::_comment_title_text_submitted(const String &p_new_text) { @@ -2034,13 +2222,14 @@ void VisualShaderEditor::_comment_desc_popup_show(const Point2 &p_position, int } comment_desc_change_edit->set_text(node->get_description()); comment_desc_change_popup->set_meta("id", p_node_id); + comment_desc_change_popup->reset_size(); comment_desc_change_popup->popup(); comment_desc_change_popup->set_position(p_position); } void VisualShaderEditor::_comment_desc_text_changed() { - comment_desc_change_edit->set_size(Size2(-1, -1)); - comment_desc_change_popup->set_size(Size2(-1, -1)); + comment_desc_change_edit->reset_size(); + comment_desc_change_popup->reset_size(); } void VisualShaderEditor::_comment_desc_confirm() { @@ -2090,7 +2279,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); @@ -2109,10 +2298,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()); @@ -2120,41 +2307,85 @@ 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); - Ref<VisualShaderNode> vsn = visual_shader->get_node(type, p_node); + edited_property_holder->set_edited_property(value); + + if (property_editor) { + property_editor->disconnect("property_changed", callable_mp(this, &VisualShaderEditor::_port_edited)); + property_editor_popup->remove_child(property_editor); + } + + // TODO: Define these properties with actual PropertyInfo and feed it to the property editor widget. + property_editor = EditorInspector::instantiate_property_editor(edited_property_holder.ptr(), value.get_type(), "edited_property", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE); + if (property_editor) { + property_editor->set_object_and_property(edited_property_holder.ptr(), "edited_property"); + property_editor->update_property(); + property_editor->set_name_split_ratio(0); + property_editor_popup->add_child(property_editor); + + property_editor->connect("property_changed", callable_mp(this, &VisualShaderEditor::_port_edited)); + + Button *button = Object::cast_to<Button>(p_button); + if (button) { + property_editor_popup->set_position(button->get_screen_position() + Vector2(0, button->get_size().height) * graph->get_zoom()); + } + property_editor_popup->reset_size(); + if (button) { + property_editor_popup->popup(); + } else { + property_editor_popup->popup_centered_ratio(); + } + } - Button *button = Object::cast_to<Button>(p_button); - ERR_FAIL_COND(!button); - Variant value = vsn->get_input_port_default_value(p_port); - property_editor->set_position(button->get_screen_position() + Vector2(0, button->get_size().height)); - property_editor->edit(nullptr, "", value.get_type(), value, 0, ""); - property_editor->popup(); editing_node = p_node; editing_port = p_port; } -void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { +void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, const Vector<Variant> &p_ops) { + // INPUT + { + VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(p_node); + + if (input) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::STRING); + input->set_input_name((String)p_ops[0]); + return; + } + } + + // FLOAT_CONST + { + VisualShaderNodeFloatConstant *float_const = Object::cast_to<VisualShaderNodeFloatConstant>(p_node); + + if (float_const) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::FLOAT); + float_const->set_constant((float)p_ops[0]); + return; + } + } + // FLOAT_OP { VisualShaderNodeFloatOp *floatOp = Object::cast_to<VisualShaderNodeFloatOp>(p_node); if (floatOp) { - floatOp->set_operator((VisualShaderNodeFloatOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + floatOp->set_operator((VisualShaderNodeFloatOp::Operator)(int)p_ops[0]); return; } } @@ -2164,7 +2395,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeFloatFunc *floatFunc = Object::cast_to<VisualShaderNodeFloatFunc>(p_node); if (floatFunc) { - floatFunc->set_function((VisualShaderNodeFloatFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + floatFunc->set_function((VisualShaderNodeFloatFunc::Function)(int)p_ops[0]); return; } } @@ -2174,7 +2406,10 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeVectorOp *vecOp = Object::cast_to<VisualShaderNodeVectorOp>(p_node); if (vecOp) { - vecOp->set_operator((VisualShaderNodeVectorOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + ERR_FAIL_COND(p_ops[1].get_type() != Variant::INT); + vecOp->set_operator((VisualShaderNodeVectorOp::Operator)(int)p_ops[0]); + vecOp->set_op_type((VisualShaderNodeVectorOp::OpType)(int)p_ops[1]); return; } } @@ -2184,7 +2419,10 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeVectorFunc *vecFunc = Object::cast_to<VisualShaderNodeVectorFunc>(p_node); if (vecFunc) { - vecFunc->set_function((VisualShaderNodeVectorFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + ERR_FAIL_COND(p_ops[1].get_type() != Variant::INT); + vecFunc->set_function((VisualShaderNodeVectorFunc::Function)(int)p_ops[0]); + vecFunc->set_op_type((VisualShaderNodeVectorFunc::OpType)(int)p_ops[1]); return; } } @@ -2194,7 +2432,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeColorOp *colorOp = Object::cast_to<VisualShaderNodeColorOp>(p_node); if (colorOp) { - colorOp->set_operator((VisualShaderNodeColorOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + colorOp->set_operator((VisualShaderNodeColorOp::Operator)(int)p_ops[0]); return; } } @@ -2204,7 +2443,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeColorFunc *colorFunc = Object::cast_to<VisualShaderNodeColorFunc>(p_node); if (colorFunc) { - colorFunc->set_function((VisualShaderNodeColorFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + colorFunc->set_function((VisualShaderNodeColorFunc::Function)(int)p_ops[0]); return; } } @@ -2214,7 +2454,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeIntOp *intOp = Object::cast_to<VisualShaderNodeIntOp>(p_node); if (intOp) { - intOp->set_operator((VisualShaderNodeIntOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + intOp->set_operator((VisualShaderNodeIntOp::Operator)(int)p_ops[0]); return; } } @@ -2224,7 +2465,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeIntFunc *intFunc = Object::cast_to<VisualShaderNodeIntFunc>(p_node); if (intFunc) { - intFunc->set_function((VisualShaderNodeIntFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + intFunc->set_function((VisualShaderNodeIntFunc::Function)(int)p_ops[0]); return; } } @@ -2234,7 +2476,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeTransformOp *matOp = Object::cast_to<VisualShaderNodeTransformOp>(p_node); if (matOp) { - matOp->set_operator((VisualShaderNodeTransformOp::Operator)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + matOp->set_operator((VisualShaderNodeTransformOp::Operator)(int)p_ops[0]); return; } } @@ -2244,17 +2487,41 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeTransformFunc *matFunc = Object::cast_to<VisualShaderNodeTransformFunc>(p_node); if (matFunc) { - matFunc->set_function((VisualShaderNodeTransformFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + matFunc->set_function((VisualShaderNodeTransformFunc::Function)(int)p_ops[0]); return; } } - //UV_FUNC + // VECTOR_COMPOSE + { + VisualShaderNodeVectorCompose *vecCompose = Object::cast_to<VisualShaderNodeVectorCompose>(p_node); + + if (vecCompose) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + vecCompose->set_op_type((VisualShaderNodeVectorCompose::OpType)(int)p_ops[0]); + return; + } + } + + // VECTOR_DECOMPOSE + { + VisualShaderNodeVectorDecompose *vecDecompose = Object::cast_to<VisualShaderNodeVectorDecompose>(p_node); + + if (vecDecompose) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + vecDecompose->set_op_type((VisualShaderNodeVectorDecompose::OpType)(int)p_ops[0]); + return; + } + } + + // UV_FUNC { VisualShaderNodeUVFunc *uvFunc = Object::cast_to<VisualShaderNodeUVFunc>(p_node); if (uvFunc) { - uvFunc->set_function((VisualShaderNodeUVFunc::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + uvFunc->set_function((VisualShaderNodeUVFunc::Function)(int)p_ops[0]); return; } } @@ -2264,7 +2531,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeIs *is = Object::cast_to<VisualShaderNodeIs>(p_node); if (is) { - is->set_function((VisualShaderNodeIs::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + is->set_function((VisualShaderNodeIs::Function)(int)p_ops[0]); return; } } @@ -2274,24 +2542,32 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeCompare *cmp = Object::cast_to<VisualShaderNodeCompare>(p_node); if (cmp) { - cmp->set_function((VisualShaderNodeCompare::Function)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + cmp->set_function((VisualShaderNodeCompare::Function)(int)p_ops[0]); return; } } - // DERIVATIVE + // DISTANCE { - VisualShaderNodeScalarDerivativeFunc *sderFunc = Object::cast_to<VisualShaderNodeScalarDerivativeFunc>(p_node); + VisualShaderNodeVectorDistance *dist = Object::cast_to<VisualShaderNodeVectorDistance>(p_node); - if (sderFunc) { - sderFunc->set_function((VisualShaderNodeScalarDerivativeFunc::Function)p_op_idx); + if (dist) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + dist->set_op_type((VisualShaderNodeVectorDistance::OpType)(int)p_ops[0]); return; } + } - VisualShaderNodeVectorDerivativeFunc *vderFunc = Object::cast_to<VisualShaderNodeVectorDerivativeFunc>(p_node); + // DERIVATIVE + { + VisualShaderNodeDerivativeFunc *derFunc = Object::cast_to<VisualShaderNodeDerivativeFunc>(p_node); - if (vderFunc) { - vderFunc->set_function((VisualShaderNodeVectorDerivativeFunc::Function)p_op_idx); + if (derFunc) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + ERR_FAIL_COND(p_ops[1].get_type() != Variant::INT); + derFunc->set_function((VisualShaderNodeDerivativeFunc::Function)(int)p_ops[0]); + derFunc->set_op_type((VisualShaderNodeDerivativeFunc::OpType)(int)p_ops[1]); return; } } @@ -2301,7 +2577,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeMix *mix = Object::cast_to<VisualShaderNodeMix>(p_node); if (mix) { - mix->set_op_type((VisualShaderNodeMix::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + mix->set_op_type((VisualShaderNodeMix::OpType)(int)p_ops[0]); return; } } @@ -2311,7 +2588,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeClamp *clampFunc = Object::cast_to<VisualShaderNodeClamp>(p_node); if (clampFunc) { - clampFunc->set_op_type((VisualShaderNodeClamp::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + clampFunc->set_op_type((VisualShaderNodeClamp::OpType)(int)p_ops[0]); return; } } @@ -2321,7 +2599,28 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeSwitch *switchFunc = Object::cast_to<VisualShaderNodeSwitch>(p_node); if (switchFunc) { - switchFunc->set_op_type((VisualShaderNodeSwitch::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + switchFunc->set_op_type((VisualShaderNodeSwitch::OpType)(int)p_ops[0]); + return; + } + } + + // FACEFORWARD + { + VisualShaderNodeFaceForward *faceForward = Object::cast_to<VisualShaderNodeFaceForward>(p_node); + if (faceForward) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + faceForward->set_op_type((VisualShaderNodeFaceForward::OpType)(int)p_ops[0]); + return; + } + } + + // LENGTH + { + VisualShaderNodeVectorLen *length = Object::cast_to<VisualShaderNodeVectorLen>(p_node); + if (length) { + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + length->set_op_type((VisualShaderNodeVectorLen::OpType)(int)p_ops[0]); return; } } @@ -2331,7 +2630,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeSmoothStep *smoothStepFunc = Object::cast_to<VisualShaderNodeSmoothStep>(p_node); if (smoothStepFunc) { - smoothStepFunc->set_op_type((VisualShaderNodeSmoothStep::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + smoothStepFunc->set_op_type((VisualShaderNodeSmoothStep::OpType)(int)p_ops[0]); return; } } @@ -2341,7 +2641,8 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeStep *stepFunc = Object::cast_to<VisualShaderNodeStep>(p_node); if (stepFunc) { - stepFunc->set_op_type((VisualShaderNodeStep::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + stepFunc->set_op_type((VisualShaderNodeStep::OpType)(int)p_ops[0]); return; } } @@ -2351,12 +2652,13 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { VisualShaderNodeMultiplyAdd *fmaFunc = Object::cast_to<VisualShaderNodeMultiplyAdd>(p_node); if (fmaFunc) { - fmaFunc->set_op_type((VisualShaderNodeMultiplyAdd::OpType)p_op_idx); + ERR_FAIL_COND(p_ops[0].get_type() != Variant::INT); + fmaFunc->set_op_type((VisualShaderNodeMultiplyAdd::OpType)(int)p_ops[0]); } } } -void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_path, int p_node_idx) { +void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, String p_resource_path, int p_node_idx) { ERR_FAIL_INDEX(p_idx, add_options.size()); VisualShader::Type type = get_current_shader_type(); @@ -2365,30 +2667,13 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa bool is_custom = add_options[p_idx].is_custom; - if (!is_custom && add_options[p_idx].type != String()) { + if (!is_custom && !add_options[p_idx].type.is_empty()) { VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(add_options[p_idx].type)); ERR_FAIL_COND(!vsn); - - VisualShaderNodeFloatConstant *constant = Object::cast_to<VisualShaderNodeFloatConstant>(vsn); - - if (constant) { - if ((int)add_options[p_idx].value != -1) { - constant->set_constant(add_options[p_idx].value); - } - } else { - if (p_op_idx != -1) { - VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(vsn); - - if (input) { - input->set_input_name(add_options[p_idx].sub_func_str); - } else { - _setup_node(vsn, p_op_idx); - } - } + if (!p_ops.is_empty()) { + _setup_node(vsn, p_ops); } - VisualShaderNodeUniformRef *uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(vsn); - if (uniform_ref && to_node != -1 && to_slot != -1) { VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot); bool success = false; @@ -2413,13 +2698,21 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa vsnode = Ref<VisualShaderNode>(vsn); } else { ERR_FAIL_COND(add_options[p_idx].script.is_null()); - String base_type = add_options[p_idx].script->get_instance_base_type(); + StringName base_type = add_options[p_idx].script->get_instance_base_type(); VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(base_type)); ERR_FAIL_COND(!vsn); vsnode = Ref<VisualShaderNode>(vsn); vsnode->set_script(add_options[p_idx].script); } + bool is_texture2d = (Object::cast_to<VisualShaderNodeTexture>(vsnode.ptr()) != nullptr); + bool is_texture3d = (Object::cast_to<VisualShaderNodeTexture3D>(vsnode.ptr()) != nullptr); + bool is_texture2d_array = (Object::cast_to<VisualShaderNodeTexture2DArray>(vsnode.ptr()) != nullptr); + bool is_cubemap = (Object::cast_to<VisualShaderNodeCubemap>(vsnode.ptr()) != nullptr); + bool is_curve = (Object::cast_to<VisualShaderNodeCurveTexture>(vsnode.ptr()) != nullptr); + bool is_curve_xyz = (Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()) != nullptr); + bool is_uniform = (Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr()) != nullptr); + Point2 position = graph->get_scroll_ofs(); if (saved_node_pos_dirty) { @@ -2466,9 +2759,15 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa case VisualShaderNode::PORT_TYPE_SCALAR_INT: initial_expression_code = "output0 = 1;"; break; - case VisualShaderNode::PORT_TYPE_VECTOR: + case VisualShaderNode::PORT_TYPE_VECTOR_2D: + initial_expression_code = "output0 = vec2(1.0, 1.0);"; + break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: initial_expression_code = "output0 = vec3(1.0, 1.0, 1.0);"; break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: + initial_expression_code = "output0 = vec4(1.0, 1.0, 1.0, 1.0);"; + break; case VisualShaderNode::PORT_TYPE_BOOLEAN: initial_expression_code = "output0 = true;"; break; @@ -2486,9 +2785,9 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } if (vsnode->get_output_port_count() > 0 || created_expression_port) { int _from_node = id_to_use; - int _from_slot = 0; if (created_expression_port) { + int _from_slot = 0; undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, _from_node, _from_slot, to_node, to_slot); undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, _from_node, _from_slot, to_node, to_slot); undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, _from_node, _from_slot, to_node, to_slot); @@ -2526,9 +2825,9 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa if (vsnode->get_input_port_count() > 0 || created_expression_port) { int _to_node = id_to_use; - int _to_slot = 0; if (created_expression_port) { + int _to_slot = 0; undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot); undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_slot, _to_node, _to_slot); undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_slot, _to_node, _to_slot); @@ -2545,22 +2844,32 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } } } + + if (output_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) { + if (is_texture2d) { + undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeTexture::SOURCE_PORT); + } + if (is_texture3d || is_texture2d_array) { + undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeSample3D::SOURCE_PORT); + } + if (is_cubemap) { + undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeCubemap::SOURCE_PORT); + } + } } } + _member_cancel(); - VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr()); - if (uniform) { + if (is_uniform) { undo_redo->add_do_method(this, "_update_uniforms", true); undo_redo->add_undo_method(this, "_update_uniforms", true); } - VisualShaderNodeCurveTexture *curve = Object::cast_to<VisualShaderNodeCurveTexture>(vsnode.ptr()); - if (curve) { + if (is_curve) { graph_plugin->call_deferred(SNAME("update_curve"), id_to_use); } - VisualShaderNodeCurveXYZTexture *curve_xyz = Object::cast_to<VisualShaderNodeCurveXYZTexture>(vsnode.ptr()); - if (curve_xyz) { + if (is_curve_xyz) { graph_plugin->call_deferred(SNAME("update_curve_xyz"), id_to_use); } @@ -2569,27 +2878,132 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } else { //post-initialization - VisualShaderNodeTexture *texture2d = Object::cast_to<VisualShaderNodeTexture>(vsnode.ptr()); - VisualShaderNodeTexture3D *texture3d = Object::cast_to<VisualShaderNodeTexture3D>(vsnode.ptr()); - - if (texture2d || texture3d || curve || curve_xyz) { + if (is_texture2d || is_texture3d || is_curve || is_curve_xyz) { undo_redo->add_do_method(vsnode.ptr(), "set_texture", ResourceLoader::load(p_resource_path)); return; } - VisualShaderNodeCubemap *cubemap = Object::cast_to<VisualShaderNodeCubemap>(vsnode.ptr()); - if (cubemap) { + if (is_cubemap) { undo_redo->add_do_method(vsnode.ptr(), "set_cube_map", ResourceLoader::load(p_resource_path)); return; } - VisualShaderNodeTexture2DArray *texture2d_array = Object::cast_to<VisualShaderNodeTexture2DArray>(vsnode.ptr()); - if (texture2d_array) { + if (is_texture2d_array) { undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path)); } } } +void VisualShaderEditor::_add_varying(const String &p_name, VisualShader::VaryingMode p_mode, VisualShader::VaryingType p_type) { + undo_redo->create_action(vformat(TTR("Add Varying to Visual Shader: %s"), p_name)); + + undo_redo->add_do_method(visual_shader.ptr(), "add_varying", p_name, p_mode, p_type); + undo_redo->add_undo_method(visual_shader.ptr(), "remove_varying", p_name); + + undo_redo->add_do_method(this, "_update_varyings"); + undo_redo->add_undo_method(this, "_update_varyings"); + + for (int i = 0; i <= VisualShader::TYPE_LIGHT; i++) { + if (p_mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT && i == 0) { + continue; + } + + VisualShader::Type type = VisualShader::Type(i); + Vector<int> nodes = visual_shader->get_node_list(type); + + for (int j = 0; j < nodes.size(); j++) { + int node_id = nodes[j]; + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + Ref<VisualShaderNodeVarying> var = vsnode; + + if (var.is_valid()) { + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, node_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, node_id); + } + } + } + + undo_redo->add_do_method(this, "_update_varying_tree"); + undo_redo->add_undo_method(this, "_update_varying_tree"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_remove_varying(const String &p_name) { + undo_redo->create_action(vformat(TTR("Remove Varying from Visual Shader: %s"), p_name)); + + VisualShader::VaryingMode mode = visual_shader->get_varying_mode(p_name); + + undo_redo->add_do_method(visual_shader.ptr(), "remove_varying", p_name); + undo_redo->add_undo_method(visual_shader.ptr(), "add_varying", p_name, mode, visual_shader->get_varying_type(p_name)); + + undo_redo->add_do_method(this, "_update_varyings"); + undo_redo->add_undo_method(this, "_update_varyings"); + + for (int i = 0; i <= VisualShader::TYPE_LIGHT; i++) { + if (mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT && i == 0) { + continue; + } + + VisualShader::Type type = VisualShader::Type(i); + Vector<int> nodes = visual_shader->get_node_list(type); + + for (int j = 0; j < nodes.size(); j++) { + int node_id = nodes[j]; + Ref<VisualShaderNode> vsnode = visual_shader->get_node(type, node_id); + Ref<VisualShaderNodeVarying> var = vsnode; + + if (var.is_valid()) { + String var_name = var->get_varying_name(); + + if (var_name == p_name) { + undo_redo->add_do_method(var.ptr(), "set_varying_name", "[None]"); + undo_redo->add_undo_method(var.ptr(), "set_varying_name", var_name); + undo_redo->add_do_method(var.ptr(), "set_varying_type", VisualShader::VARYING_TYPE_FLOAT); + undo_redo->add_undo_method(var.ptr(), "set_varying_type", var->get_varying_type()); + } + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, node_id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, node_id); + } + } + + List<VisualShader::Connection> connections; + visual_shader->get_node_connections(type, &connections); + + for (VisualShader::Connection &E : connections) { + Ref<VisualShaderNodeVaryingGetter> var_getter = Object::cast_to<VisualShaderNodeVaryingGetter>(visual_shader->get_node(type, E.from_node).ptr()); + if (var_getter.is_valid() && E.from_port > 0) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + } + Ref<VisualShaderNodeVaryingSetter> var_setter = Object::cast_to<VisualShaderNodeVaryingSetter>(visual_shader->get_node(type, E.to_node).ptr()); + if (var_setter.is_valid() && E.to_port > 0) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + } + } + } + + undo_redo->add_do_method(this, "_update_varying_tree"); + undo_redo->add_undo_method(this, "_update_varying_tree"); + undo_redo->commit_action(); +} + +void VisualShaderEditor::_update_varyings() { + VisualShaderNodeVarying::clear_varyings(); + + for (int i = 0; i < visual_shader->get_varyings_count(); i++) { + const VisualShader::Varying *var = visual_shader->get_varying_by_index(i); + + if (var != nullptr) { + VisualShaderNodeVarying::add_varying(var->name, var->mode, var->type); + } + } +} + void VisualShaderEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node) { VisualShader::Type type = get_current_shader_type(); drag_buffer.push_back({ type, p_node, p_from, p_to }); @@ -2703,7 +3117,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); @@ -2712,9 +3126,6 @@ void VisualShaderEditor::_delete_nodes(int p_type, const List<int> &p_nodes) { undo_redo->add_undo_method(visual_shader.ptr(), "add_node", type, node, visual_shader->get_node_position(type, F), F); undo_redo->add_undo_method(graph_plugin.ptr(), "add_node", type, F); - undo_redo->add_do_method(this, "_clear_buffer"); - undo_redo->add_undo_method(this, "_clear_buffer"); - // restore size, inputs and outputs if node is group VisualShaderNodeGroupBase *group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr()); if (group) { @@ -2810,11 +3221,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; @@ -2874,6 +3285,25 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { } } + // vec2 + if (!caught) { + if (!p_vice_versa) { + Ref<VisualShaderNodeVec2Constant> vec2_const = Object::cast_to<VisualShaderNodeVec2Constant>(node.ptr()); + if (vec2_const.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec2Constant", "VisualShaderNodeVec2Uniform"); + var = vec2_const->get_constant(); + caught = true; + } + } else { + Ref<VisualShaderNodeVec2Uniform> vec2_uniform = Object::cast_to<VisualShaderNodeVec2Uniform>(node.ptr()); + if (vec2_uniform.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec2Uniform", "VisualShaderNodeVec2Constant"); + var = vec2_uniform->get_default_value(); + caught = true; + } + } + } + // vec3 if (!caught) { if (!p_vice_versa) { @@ -2893,6 +3323,25 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { } } + // vec4 + if (!caught) { + if (!p_vice_versa) { + Ref<VisualShaderNodeVec4Constant> vec4_const = Object::cast_to<VisualShaderNodeVec4Constant>(node.ptr()); + if (vec4_const.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec4Constant", "VisualShaderNodeVec4Uniform"); + var = vec4_const->get_constant(); + caught = true; + } + } else { + Ref<VisualShaderNodeVec4Uniform> vec4_uniform = Object::cast_to<VisualShaderNodeVec4Uniform>(node.ptr()); + if (vec4_uniform.is_valid()) { + _replace_node(type_id, node_id, "VisualShaderNodeVec4Uniform", "VisualShaderNodeVec4Constant"); + var = vec4_uniform->get_default_value(); + caught = true; + } + } + } + // color if (!caught) { if (!p_vice_versa) { @@ -2969,16 +3418,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()) { @@ -3009,7 +3465,7 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> mb = p_event; VisualShader::Type type = get_current_shader_type(); - if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) { selected_constants.clear(); selected_uniforms.clear(); selected_comment = -1; @@ -3050,13 +3506,23 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { selected_float_constant = -1; } - if (to_change.is_empty() && copy_nodes_buffer.is_empty()) { + bool copy_buffer_empty = true; + for (const CopyItem &item : copy_items_buffer) { + if (!item.disabled) { + copy_buffer_empty = false; + break; + } + } + + if (to_change.is_empty() && copy_buffer_empty) { _show_members_dialog(true); } else { + popup_menu->set_item_disabled(NodeMenuOptions::CUT, to_change.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::COPY, to_change.is_empty()); - popup_menu->set_item_disabled(NodeMenuOptions::PASTE, copy_nodes_buffer.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::PASTE, copy_buffer_empty); popup_menu->set_item_disabled(NodeMenuOptions::DELETE, to_change.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::DUPLICATE, to_change.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::CLEAR_COPY_BUFFER, copy_buffer_empty); int temp = popup_menu->get_item_index(NodeMenuOptions::SEPARATOR2); if (temp != -1) { @@ -3121,9 +3587,9 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { } menu_point = graph->get_local_mouse_position(); - Point2 gpos = Input::get_singleton()->get_mouse_position(); + Point2 gpos = get_screen_position() + get_local_mouse_position(); popup_menu->set_position(gpos); - popup_menu->set_size(Size2(-1, -1)); + popup_menu->reset_size(); popup_menu->popup(); } } @@ -3140,123 +3606,173 @@ void VisualShaderEditor::_show_members_dialog(bool at_mouse_pos, VisualShaderNod saved_node_pos_dirty = true; saved_node_pos = graph->get_local_mouse_position(); - Point2 gpos = Input::get_singleton()->get_mouse_position(); - members_dialog->popup(); + Point2 gpos = get_screen_position() + get_local_mouse_position(); members_dialog->set_position(gpos); } else { - members_dialog->popup(); saved_node_pos_dirty = false; - members_dialog->set_position(graph->get_global_position() + Point2(5 * EDSCALE, 65 * EDSCALE)); + members_dialog->set_position(graph->get_screen_position() + Point2(5 * EDSCALE, 65 * EDSCALE)); } + members_dialog->popup(); - // keep dialog within window bounds - Size2 window_size = DisplayServer::get_singleton()->window_get_size(); + // Keep dialog within window bounds. + Rect2 window_rect = Rect2(DisplayServer::get_singleton()->window_get_position(), DisplayServer::get_singleton()->window_get_size()); Rect2 dialog_rect = Rect2(members_dialog->get_position(), members_dialog->get_size()); - if (dialog_rect.position.y + dialog_rect.size.y > window_size.y) { - int difference = dialog_rect.position.y + dialog_rect.size.y - window_size.y; - members_dialog->set_position(members_dialog->get_position() - Point2(0, difference)); - } - if (dialog_rect.position.x + dialog_rect.size.x > window_size.x) { - int difference = dialog_rect.position.x + dialog_rect.size.x - window_size.x; - members_dialog->set_position(members_dialog->get_position() - Point2(difference, 0)); - } + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + members_dialog->set_position(members_dialog->get_position() - difference); - node_filter->call_deferred(SNAME("grab_focus")); // still not visible + node_filter->call_deferred(SNAME("grab_focus")); // Still not visible. node_filter->select_all(); } +void VisualShaderEditor::_show_varying_menu() { + varying_options->set_item_disabled(int(VaryingMenuOptions::REMOVE), visual_shader->get_varyings_count() == 0); + varying_options->set_position(graph->get_screen_position() + varying_button->get_position() + Size2(0, varying_button->get_size().height)); + varying_options->popup(); +} + +void VisualShaderEditor::_varying_menu_id_pressed(int p_idx) { + switch (VaryingMenuOptions(p_idx)) { + case VaryingMenuOptions::ADD: { + _show_add_varying_dialog(); + } break; + case VaryingMenuOptions::REMOVE: { + _show_remove_varying_dialog(); + } break; + default: + break; + } +} + +void VisualShaderEditor::_show_add_varying_dialog() { + _varying_name_changed(varying_name->get_text()); + + add_varying_dialog->set_position(graph->get_screen_position() + varying_button->get_position() + Point2(5 * EDSCALE, 65 * EDSCALE)); + add_varying_dialog->popup(); + + // Keep dialog within window bounds. + Rect2 window_rect = Rect2(DisplayServer::get_singleton()->window_get_position(), DisplayServer::get_singleton()->window_get_size()); + Rect2 dialog_rect = Rect2(add_varying_dialog->get_position(), add_varying_dialog->get_size()); + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + add_varying_dialog->set_position(add_varying_dialog->get_position() - difference); +} + +void VisualShaderEditor::_show_remove_varying_dialog() { + remove_varying_dialog->set_position(graph->get_screen_position() + varying_button->get_position() + Point2(5 * EDSCALE, 65 * EDSCALE)); + remove_varying_dialog->popup(); + + // Keep dialog within window bounds. + Rect2 window_rect = Rect2(DisplayServer::get_singleton()->window_get_position(), DisplayServer::get_singleton()->window_get_size()); + Rect2 dialog_rect = Rect2(remove_varying_dialog->get_position(), remove_varying_dialog->get_size()); + Vector2 difference = (dialog_rect.get_end() - window_rect.get_end()).max(Vector2()); + remove_varying_dialog->set_position(remove_varying_dialog->get_position() - difference); +} + void VisualShaderEditor::_sbox_input(const Ref<InputEvent> &p_ie) { Ref<InputEventKey> ie = p_ie; - if (ie.is_valid() && (ie->get_keycode() == KEY_UP || - ie->get_keycode() == KEY_DOWN || - ie->get_keycode() == KEY_ENTER || - ie->get_keycode() == KEY_KP_ENTER)) { + if (ie.is_valid() && (ie->get_keycode() == Key::UP || ie->get_keycode() == Key::DOWN || ie->get_keycode() == Key::ENTER || ie->get_keycode() == Key::KP_ENTER)) { members->gui_input(ie); node_filter->accept_event(); } } void VisualShaderEditor::_notification(int p_what) { - if (p_what == NOTIFICATION_ENTER_TREE) { - node_filter->set_clear_button_enabled(true); - - // collapse tree by default - - TreeItem *category = members->get_root()->get_first_child(); - while (category) { - category->set_collapsed(true); - TreeItem *sub_category = category->get_first_child(); - while (sub_category) { - sub_category->set_collapsed(true); - sub_category = sub_category->get_next(); - } - category = category->get_next(); - } - } - - if (p_what == NOTIFICATION_DRAG_BEGIN) { - Dictionary dd = get_viewport()->gui_get_drag_data(); - if (members->is_visible_in_tree() && dd.has("id")) { - members->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); - } - } else if (p_what == NOTIFICATION_DRAG_END) { - members->set_drop_mode_flags(0); - } - - if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { - highend_label->set_modulate(get_theme_color(SNAME("vulkan_color"), SNAME("Editor"))); - - node_filter->set_right_icon(Control::get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); - - preview_shader->set_icon(Control::get_theme_icon(SNAME("Shader"), SNAME("EditorIcons"))); + switch (p_what) { + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { + graph->get_panner()->setup((ViewPanner::ControlScheme)EDITOR_GET("editors/panning/sub_editors_panning_scheme").operator int(), ED_GET_SHORTCUT("canvas_item_editor/pan_view"), bool(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; - { - Color background_color = EDITOR_GET("text_editor/theme/highlighting/background_color"); - Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); - Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); - Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); - Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); - Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); - Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); - Color number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); - Color members_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); + case NOTIFICATION_ENTER_TREE: { + node_filter->set_clear_button_enabled(true); - preview_text->add_theme_color_override("background_color", background_color); + // collapse tree by default - for (const String &E : keyword_list) { - if (ShaderLanguage::is_control_flow_keyword(E)) { - syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); - } else { - syntax_highlighter->add_keyword_color(E, keyword_color); + TreeItem *category = members->get_root()->get_first_child(); + while (category) { + category->set_collapsed(true); + TreeItem *sub_category = category->get_first_child(); + while (sub_category) { + sub_category->set_collapsed(true); + sub_category = sub_category->get_next(); } + category = category->get_next(); } - preview_text->add_theme_font_override("font", get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); - preview_text->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); - preview_text->add_theme_color_override("font_color", text_color); - syntax_highlighter->set_number_color(number_color); - syntax_highlighter->set_symbol_color(symbol_color); - syntax_highlighter->set_function_color(function_color); - syntax_highlighter->set_member_variable_color(members_color); - syntax_highlighter->clear_color_regions(); - syntax_highlighter->add_color_region("/*", "*/", comment_color, false); - syntax_highlighter->add_color_region("//", "", comment_color, true); + 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"))); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + highend_label->set_modulate(get_theme_color(SNAME("vulkan_color"), SNAME("Editor"))); + + node_filter->set_right_icon(Control::get_theme_icon(SNAME("Search"), SNAME("EditorIcons"))); + + preview_shader->set_icon(Control::get_theme_icon(SNAME("Shader"), SNAME("EditorIcons"))); + + { + Color background_color = EDITOR_GET("text_editor/theme/highlighting/background_color"); + Color text_color = EDITOR_GET("text_editor/theme/highlighting/text_color"); + Color keyword_color = EDITOR_GET("text_editor/theme/highlighting/keyword_color"); + Color control_flow_keyword_color = EDITOR_GET("text_editor/theme/highlighting/control_flow_keyword_color"); + Color comment_color = EDITOR_GET("text_editor/theme/highlighting/comment_color"); + Color symbol_color = EDITOR_GET("text_editor/theme/highlighting/symbol_color"); + Color function_color = EDITOR_GET("text_editor/theme/highlighting/function_color"); + Color number_color = EDITOR_GET("text_editor/theme/highlighting/number_color"); + Color members_color = EDITOR_GET("text_editor/theme/highlighting/member_variable_color"); + Color error_color = get_theme_color(SNAME("error_color"), SNAME("Editor")); + + preview_text->add_theme_color_override("background_color", background_color); + varying_error_label->add_theme_color_override("font_color", error_color); + + for (const String &E : keyword_list) { + if (ShaderLanguage::is_control_flow_keyword(E)) { + syntax_highlighter->add_keyword_color(E, control_flow_keyword_color); + } else { + syntax_highlighter->add_keyword_color(E, keyword_color); + } + } - preview_text->clear_comment_delimiters(); - preview_text->add_comment_delimiter("/*", "*/", false); - preview_text->add_comment_delimiter("//", "", true); + preview_text->add_theme_font_override("font", get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); + preview_text->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); + preview_text->add_theme_color_override("font_color", text_color); + syntax_highlighter->set_number_color(number_color); + syntax_highlighter->set_symbol_color(symbol_color); + syntax_highlighter->set_function_color(function_color); + syntax_highlighter->set_member_variable_color(members_color); + syntax_highlighter->clear_color_regions(); + syntax_highlighter->add_color_region("/*", "*/", comment_color, false); + syntax_highlighter->add_color_region("//", "", comment_color, true); + + preview_text->clear_comment_delimiters(); + preview_text->add_comment_delimiter("/*", "*/", false); + preview_text->add_comment_delimiter("//", "", true); + + error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); + error_label->add_theme_font_override("font", get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); + error_label->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); + error_label->add_theme_color_override("font_color", error_color); + } - error_panel->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Panel"))); - error_label->add_theme_font_override("font", get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); - error_label->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); - error_label->add_theme_color_override("font_color", get_theme_color(SNAME("error_color"), SNAME("Editor"))); - } + tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); - tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); + if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { + _update_graph(); + } + } break; - if (p_what == NOTIFICATION_THEME_CHANGED && is_visible_in_tree()) { - _update_graph(); - } + case NOTIFICATION_DRAG_BEGIN: { + Dictionary dd = get_viewport()->gui_get_drag_data(); + if (members->is_visible_in_tree() && dd.has("id")) { + members->set_drop_mode_flags(Tree::DROP_MODE_ON_ITEM); + } + } break; + + case NOTIFICATION_DRAG_END: { + members->set_drop_mode_flags(0); + } break; } } @@ -3279,118 +3795,141 @@ void VisualShaderEditor::_node_changed(int p_id) { } } -void VisualShaderEditor::_dup_update_excluded(int p_type, Set<int> &r_excluded) { - r_excluded.clear(); - VisualShader::Type type = (VisualShader::Type)p_type; - - for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - int id = String(gn->get_name()).to_int(); - Ref<VisualShaderNode> node = visual_shader->get_node(type, id); - Ref<VisualShaderNodeOutput> output = node; - if (output.is_valid()) { - r_excluded.insert(id); - continue; - } - r_excluded.insert(id); - } - } -} - -void VisualShaderEditor::_dup_copy_nodes(int p_type, List<int> &r_nodes, Set<int> &r_excluded) { +void VisualShaderEditor::_dup_copy_nodes(int p_type, List<CopyItem> &r_items, List<VisualShader::Connection> &r_connections) { VisualShader::Type type = (VisualShader::Type)p_type; selection_center.x = 0.0f; selection_center.y = 0.0f; + HashSet<int> nodes; + for (int i = 0; i < graph->get_child_count(); i++) { GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); if (gn) { int id = String(gn->get_name()).to_int(); + Ref<VisualShaderNode> node = visual_shader->get_node(type, id); Ref<VisualShaderNodeOutput> output = node; if (output.is_valid()) { // can't duplicate output - r_excluded.insert(id); continue; } + if (node.is_valid() && gn->is_selected()) { Vector2 pos = visual_shader->get_node_position(type, id); selection_center += pos; - r_nodes.push_back(id); + + CopyItem item; + item.id = id; + item.node = visual_shader->get_node(type, id)->duplicate(); + item.position = visual_shader->get_node_position(type, id); + + Ref<VisualShaderNodeResizableBase> resizable_base = node; + if (resizable_base.is_valid()) { + item.size = resizable_base->get_size(); + } + + Ref<VisualShaderNodeGroupBase> group = node; + if (group.is_valid()) { + item.group_inputs = group->get_inputs(); + item.group_outputs = group->get_outputs(); + } + + Ref<VisualShaderNodeExpression> expression = node; + if (expression.is_valid()) { + item.expression = expression->get_expression(); + } + + r_items.push_back(item); + + nodes.insert(id); } - r_excluded.insert(id); } } - selection_center /= (float)r_nodes.size(); + List<VisualShader::Connection> connections; + visual_shader->get_node_connections(type, &connections); + + for (const VisualShader::Connection &E : connections) { + if (nodes.has(E.from_node) && nodes.has(E.to_node)) { + r_connections.push_back(E); + } + } + + selection_center /= (float)r_items.size(); } -void VisualShaderEditor::_dup_paste_nodes(int p_type, int p_pasted_type, List<int> &r_nodes, Set<int> &r_excluded, const Vector2 &p_offset, bool p_select) { +void VisualShaderEditor::_dup_paste_nodes(int p_type, List<CopyItem> &r_items, const List<VisualShader::Connection> &p_connections, const Vector2 &p_offset, bool p_duplicate) { + if (p_duplicate) { + undo_redo->create_action(TTR("Duplicate VisualShader Node(s)")); + } else { + bool copy_buffer_empty = true; + for (const CopyItem &item : copy_items_buffer) { + if (!item.disabled) { + copy_buffer_empty = false; + break; + } + } + if (copy_buffer_empty) { + return; + } + + undo_redo->create_action(TTR("Paste VisualShader Node(s)")); + } + VisualShader::Type type = (VisualShader::Type)p_type; - VisualShader::Type pasted_type = (VisualShader::Type)p_pasted_type; int base_id = visual_shader->get_valid_node_id(type); int id_from = base_id; - Map<int, int> connection_remap; - Set<int> unsupported_set; - - for (int &E : r_nodes) { - connection_remap[E] = id_from; - Ref<VisualShaderNode> node = visual_shader->get_node(pasted_type, E); - - bool unsupported = false; - for (int i = 0; i < add_options.size(); i++) { - if (add_options[i].type == node->get_class_name()) { - if (!_is_available(add_options[i].mode)) { - unsupported = true; - } - break; - } - } - if (unsupported) { - unsupported_set.insert(E); + HashMap<int, int> connection_remap; + HashSet<int> unsupported_set; + HashSet<int> added_set; + + for (CopyItem &item : r_items) { + if (item.disabled) { + unsupported_set.insert(item.id); continue; } + connection_remap[item.id] = id_from; + Ref<VisualShaderNode> node = item.node->duplicate(); - Ref<VisualShaderNode> dupli = node->duplicate(); - - undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, dupli, visual_shader->get_node_position(pasted_type, E) + p_offset, id_from); - undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_from); + Ref<VisualShaderNodeResizableBase> resizable_base = Object::cast_to<VisualShaderNodeResizableBase>(node.ptr()); + if (resizable_base.is_valid()) { + undo_redo->add_do_method(node.ptr(), "set_size", item.size); + } - // duplicate size, inputs and outputs if node is group Ref<VisualShaderNodeGroupBase> group = Object::cast_to<VisualShaderNodeGroupBase>(node.ptr()); - if (!group.is_null()) { - undo_redo->add_do_method(dupli.ptr(), "set_size", group->get_size()); - undo_redo->add_do_method(graph_plugin.ptr(), "set_node_size", type, id_from, group->get_size()); - undo_redo->add_do_method(dupli.ptr(), "set_inputs", group->get_inputs()); - undo_redo->add_do_method(dupli.ptr(), "set_outputs", group->get_outputs()); + if (group.is_valid()) { + undo_redo->add_do_method(node.ptr(), "set_inputs", item.group_inputs); + undo_redo->add_do_method(node.ptr(), "set_outputs", item.group_outputs); } - // duplicate expression text if node is expression + Ref<VisualShaderNodeExpression> expression = Object::cast_to<VisualShaderNodeExpression>(node.ptr()); - if (!expression.is_null()) { - undo_redo->add_do_method(dupli.ptr(), "set_expression", expression->get_expression()); + if (expression.is_valid()) { + undo_redo->add_do_method(node.ptr(), "set_expression", item.expression); } + undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, node, item.position + p_offset, id_from); + undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_from); + + added_set.insert(id_from); id_from++; } - List<VisualShader::Connection> conns; - visual_shader->get_node_connections(pasted_type, &conns); - - for (const VisualShader::Connection &E : conns) { + for (const VisualShader::Connection &E : p_connections) { if (unsupported_set.has(E.from_node) || unsupported_set.has(E.to_node)) { continue; } - if (connection_remap.has(E.from_node) && connection_remap.has(E.to_node)) { - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port); - } + + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, connection_remap[E.from_node], E.from_port, connection_remap[E.to_node], E.to_port); } id_from = base_id; - for (int i = 0; i < r_nodes.size(); i++) { + for (const CopyItem &item : r_items) { + if (item.disabled) { + continue; + } undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_from); undo_redo->add_undo_method(graph_plugin.ptr(), "remove_node", type, id_from); id_from++; @@ -3398,54 +3937,61 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, int p_pasted_type, List<in undo_redo->commit_action(); - if (p_select) { - // reselect duplicated nodes by excluding the other ones - for (int i = 0; i < graph->get_child_count(); i++) { - GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); - if (gn) { - int id = String(gn->get_name()).to_int(); - if (!r_excluded.has(id)) { - gn->set_selected(true); - } else { - gn->set_selected(false); - } + // reselect nodes by excluding the other ones + for (int i = 0; i < graph->get_child_count(); i++) { + GraphNode *gn = Object::cast_to<GraphNode>(graph->get_child(i)); + if (gn) { + int id = String(gn->get_name()).to_int(); + if (added_set.has(id)) { + gn->set_selected(true); + } else { + gn->set_selected(false); } } } } -void VisualShaderEditor::_clear_buffer() { - copy_nodes_buffer.clear(); - copy_nodes_excluded_buffer.clear(); +void VisualShaderEditor::_clear_copy_buffer() { + copy_items_buffer.clear(); + copy_connections_buffer.clear(); } void VisualShaderEditor::_duplicate_nodes() { int type = get_current_shader_type(); - List<int> nodes; - Set<int> excluded; + List<CopyItem> items; + List<VisualShader::Connection> connections; - _dup_copy_nodes(type, nodes, excluded); + _dup_copy_nodes(type, items, connections); - if (nodes.is_empty()) { + if (items.is_empty()) { return; } - undo_redo->create_action(TTR("Duplicate VisualShader Node(s)")); - - _dup_paste_nodes(type, type, nodes, excluded, Vector2(10, 10) * EDSCALE, true); + _dup_paste_nodes(type, items, connections, Vector2(10, 10) * EDSCALE, true); } -void VisualShaderEditor::_copy_nodes() { - copy_type = get_current_shader_type(); +void VisualShaderEditor::_copy_nodes(bool p_cut) { + _clear_copy_buffer(); - _clear_buffer(); + _dup_copy_nodes(get_current_shader_type(), copy_items_buffer, copy_connections_buffer); - _dup_copy_nodes(copy_type, copy_nodes_buffer, copy_nodes_excluded_buffer); + if (p_cut) { + undo_redo->create_action(TTR("Cut VisualShader Node(s)")); + + List<int> ids; + for (const CopyItem &E : copy_items_buffer) { + ids.push_back(E.id); + } + + _delete_nodes(get_current_shader_type(), ids); + + undo_redo->commit_action(); + } } void VisualShaderEditor::_paste_nodes(bool p_use_custom_position, const Vector2 &p_custom_position) { - if (copy_nodes_buffer.is_empty()) { + if (copy_items_buffer.is_empty()) { return; } @@ -3460,11 +4006,7 @@ void VisualShaderEditor::_paste_nodes(bool p_use_custom_position, const Vector2 mpos = graph->get_local_mouse_position(); } - undo_redo->create_action(TTR("Paste VisualShader Node(s)")); - - _dup_paste_nodes(type, copy_type, copy_nodes_buffer, copy_nodes_excluded_buffer, (graph->get_scroll_ofs() / scale + mpos / scale - selection_center), false); - - _dup_update_excluded(type, copy_nodes_excluded_buffer); // to prevent selection of previous copies at new paste + _dup_paste_nodes(type, copy_items_buffer, copy_connections_buffer, graph->get_scroll_ofs() / scale + mpos / scale - selection_center, false); } void VisualShaderEditor::_mode_selected(int p_id) { @@ -3483,11 +4025,15 @@ void VisualShaderEditor::_mode_selected(int p_id) { } } else if (mode & MODE_FLAGS_SKY) { offset = 8; + } else if (mode & MODE_FLAGS_FOG) { + offset = 9; } visual_shader->set_shader_type(VisualShader::Type(p_id + offset)); - _update_options_menu(); + _update_nodes(); _update_graph(); + + graph->grab_focus(); } void VisualShaderEditor::_custom_mode_toggled(bool p_enabled) { @@ -3512,7 +4058,10 @@ void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input, return; } - bool type_changed = p_input->get_input_type_by_name(p_name) != p_input->get_input_type_by_name(prev_name); + VisualShaderNode::PortType next_input_type = p_input->get_input_type_by_name(p_name); + VisualShaderNode::PortType prev_input_type = p_input->get_input_type_by_name(prev_name); + + bool type_changed = next_input_type != prev_input_type; UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); undo_redo->create_action(TTR("Visual Shader Input Type Changed")); @@ -3520,31 +4069,54 @@ void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input, undo_redo->add_do_method(p_input.ptr(), "set_input_name", p_name); undo_redo->add_undo_method(p_input.ptr(), "set_input_name", prev_name); - // update output port - for (int type_id = 0; type_id < VisualShader::TYPE_MAX; type_id++) { - VisualShader::Type type = VisualShader::Type(type_id); - int id = visual_shader->find_node_id(type, p_input); - if (id != VisualShader::NODE_ID_INVALID) { - if (type_changed) { + if (type_changed) { + for (int type_id = 0; type_id < VisualShader::TYPE_MAX; type_id++) { + VisualShader::Type type = VisualShader::Type(type_id); + + int id = visual_shader->find_node_id(type, p_input); + if (id != VisualShader::NODE_ID_INVALID) { + bool is_expanded = p_input->is_output_port_expandable(0) && p_input->_is_output_port_expanded(0); + + int type_size = 0; + if (is_expanded) { + switch (next_input_type) { + case VisualShaderNode::PORT_TYPE_VECTOR_2D: { + type_size = 2; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_3D: { + type_size = 3; + } break; + case VisualShaderNode::PORT_TYPE_VECTOR_4D: { + type_size = 4; + } break; + default: + break; + } + } + List<VisualShader::Connection> conns; visual_shader->get_node_connections(type, &conns); for (const VisualShader::Connection &E : conns) { - if (E.from_node == id) { - if (visual_shader->is_port_types_compatible(p_input->get_input_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) { - undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - continue; + int from_node = E.from_node; + int from_port = E.from_port; + int to_node = E.to_node; + int to_port = E.to_port; + + if (from_node == id) { + bool is_incompatible_types = !visual_shader->is_port_types_compatible(p_input->get_input_type_by_name(p_name), visual_shader->get_node(type, to_node)->get_input_port_type(to_port)); + + if (is_incompatible_types || from_port > type_size) { + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); } - undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); - undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); } } + + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); } - undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); - undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); - break; } } @@ -3595,6 +4167,75 @@ void VisualShaderEditor::_uniform_select_item(Ref<VisualShaderNodeUniformRef> p_ undo_redo->commit_action(); } +void VisualShaderEditor::_varying_select_item(Ref<VisualShaderNodeVarying> p_varying, String p_name) { + String prev_name = p_varying->get_varying_name(); + + if (p_name == prev_name) { + return; + } + + bool is_getter = Ref<VisualShaderNodeVaryingGetter>(p_varying.ptr()).is_valid(); + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + undo_redo->create_action(TTR("Varying Name Changed")); + + undo_redo->add_do_method(p_varying.ptr(), "set_varying_name", p_name); + undo_redo->add_undo_method(p_varying.ptr(), "set_varying_name", prev_name); + + VisualShader::VaryingType vtype = p_varying->get_varying_type_by_name(p_name); + VisualShader::VaryingType prev_vtype = p_varying->get_varying_type_by_name(prev_name); + + bool type_changed = vtype != prev_vtype; + + if (type_changed) { + undo_redo->add_do_method(p_varying.ptr(), "set_varying_type", vtype); + undo_redo->add_undo_method(p_varying.ptr(), "set_varying_type", prev_vtype); + } + + // update ports + for (int type_id = 0; type_id < VisualShader::TYPE_MAX; type_id++) { + VisualShader::Type type = VisualShader::Type(type_id); + int id = visual_shader->find_node_id(type, p_varying); + + if (id != VisualShader::NODE_ID_INVALID) { + if (type_changed) { + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + + for (const VisualShader::Connection &E : conns) { + if (is_getter) { + if (E.from_node == id) { + if (visual_shader->is_port_types_compatible(p_varying->get_varying_type_by_name(p_name), visual_shader->get_node(type, E.to_node)->get_input_port_type(E.to_port))) { + continue; + } + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + } + } else { + if (E.to_node == id) { + if (visual_shader->is_port_types_compatible(p_varying->get_varying_type_by_name(p_name), visual_shader->get_node(type, E.from_node)->get_output_port_type(E.from_port))) { + continue; + } + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, E.from_node, E.from_port, E.to_node, E.to_port); + } + } + } + } + + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type_id, id); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type_id, id); + break; + } + } + + undo_redo->commit_action(); +} + void VisualShaderEditor::_float_constant_selected(int p_which) { ERR_FAIL_INDEX(p_which, MAX_FLOAT_CONST_DEFS); @@ -3637,7 +4278,7 @@ void VisualShaderEditor::_member_create() { TreeItem *item = members->get_selected(); if (item != nullptr && item->has_meta("id")) { int idx = members->get_selected()->get_meta("id"); - _add_node(idx, add_options[idx].sub_func); + _add_node(idx, add_options[idx].ops); members_dialog->hide(); } } @@ -3649,6 +4290,95 @@ void VisualShaderEditor::_member_cancel() { from_slot = -1; } +void VisualShaderEditor::_update_varying_tree() { + varyings->clear(); + TreeItem *root = varyings->create_item(); + + int count = visual_shader->get_varyings_count(); + + for (int i = 0; i < count; i++) { + const VisualShader::Varying *varying = visual_shader->get_varying_by_index(i); + + if (varying) { + TreeItem *item = varyings->create_item(root); + item->set_text(0, varying->name); + + if (i == 0) { + item->select(0); + } + + switch (varying->type) { + case VisualShader::VARYING_TYPE_FLOAT: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_VECTOR_2D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_VECTOR_3D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_VECTOR_4D: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_COLOR: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Color"), SNAME("EditorIcons"))); + break; + case VisualShader::VARYING_TYPE_TRANSFORM: + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons"))); + break; + default: + break; + } + } + } + + varying_options->set_item_disabled(int(VaryingMenuOptions::REMOVE), count == 0); +} + +void VisualShaderEditor::_varying_create() { + _add_varying(varying_name->get_text(), (VisualShader::VaryingMode)varying_mode->get_selected(), (VisualShader::VaryingType)varying_type->get_selected()); + add_varying_dialog->hide(); +} + +void VisualShaderEditor::_varying_name_changed(const String &p_text) { + String name = p_text; + + if (!name.is_valid_identifier()) { + varying_error_label->show(); + varying_error_label->set_text(TTR("Invalid name for varying.")); + add_varying_dialog->get_ok_button()->set_disabled(true); + return; + } + if (visual_shader->has_varying(name)) { + varying_error_label->show(); + varying_error_label->set_text(TTR("Varying with that name is already exist.")); + add_varying_dialog->get_ok_button()->set_disabled(true); + return; + } + if (varying_error_label->is_visible()) { + varying_error_label->hide(); + add_varying_dialog->set_size(Size2(add_varying_dialog->get_size().x, 0)); + } + add_varying_dialog->get_ok_button()->set_disabled(false); +} + +void VisualShaderEditor::_varying_deleted() { + TreeItem *item = varyings->get_selected(); + + if (item != nullptr) { + _remove_varying(item->get_text(0)); + remove_varying_dialog->hide(); + } +} + +void VisualShaderEditor::_varying_selected() { + add_varying_dialog->get_ok_button()->set_disabled(false); +} + +void VisualShaderEditor::_varying_unselected() { + add_varying_dialog->get_ok_button()->set_disabled(true); +} + void VisualShaderEditor::_tools_menu_option(int p_idx) { TreeItem *category = members->get_root()->get_first_child(); @@ -3690,18 +4420,24 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { case NodeMenuOptions::ADD: _show_members_dialog(true); break; + case NodeMenuOptions::CUT: + _copy_nodes(true); + break; case NodeMenuOptions::COPY: - _copy_nodes(); + _copy_nodes(false); break; case NodeMenuOptions::PASTE: _paste_nodes(true, menu_point); break; case NodeMenuOptions::DELETE: - _delete_nodes_request(); + _delete_nodes_request(TypedArray<StringName>()); break; case NodeMenuOptions::DUPLICATE: _duplicate_nodes(); break; + case NodeMenuOptions::CLEAR_COPY_BUFFER: + _clear_copy_buffer(); + break; case NodeMenuOptions::CONVERT_CONSTANTS_TO_UNIFORMS: _convert_constants_to_uniforms(false); break; @@ -3709,10 +4445,10 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { _convert_constants_to_uniforms(true); break; case NodeMenuOptions::SET_COMMENT_TITLE: - _comment_title_popup_show(get_global_mouse_position(), selected_comment); + _comment_title_popup_show(get_screen_position() + get_local_mouse_position(), selected_comment); break; case NodeMenuOptions::SET_COMMENT_DESCRIPTION: - _comment_desc_popup_show(get_global_mouse_position(), selected_comment); + _comment_desc_popup_show(get_screen_position() + get_local_mouse_position(), selected_comment); break; default: break; @@ -3734,11 +4470,6 @@ Variant VisualShaderEditor::get_drag_data_fw(const Point2 &p_point, Control *p_f Dictionary d; d["id"] = id; - if (op.sub_func == -1) { - d["sub_func"] = op.sub_func_str; - } else { - d["sub_func"] = op.sub_func; - } Label *label = memnew(Label); label->set_text(it->get_text(0)); @@ -3771,7 +4502,7 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da int idx = d["id"]; saved_node_pos = p_point; saved_node_pos_dirty = true; - _add_node(idx, add_options[idx].sub_func); + _add_node(idx, add_options[idx].ops); } else if (d.has("files")) { undo_redo->create_action(TTR("Add Node(s) to Visual Shader")); @@ -3796,33 +4527,33 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da } } if (idx != -1) { - _add_node(idx, -1, arr[i], i); + _add_node(idx, {}, arr[i], i); } } } else if (type == "CurveTexture") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(curve_node_option_idx, -1, arr[i], i); + _add_node(curve_node_option_idx, {}, arr[i], i); } else if (type == "CurveXYZTexture") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(curve_xyz_node_option_idx, -1, arr[i], i); + _add_node(curve_xyz_node_option_idx, {}, arr[i], i); } else if (ClassDB::get_parent_class(type) == "Texture2D") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(texture2d_node_option_idx, -1, arr[i], i); + _add_node(texture2d_node_option_idx, {}, arr[i], i); } else if (type == "Texture2DArray") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(texture2d_array_node_option_idx, -1, arr[i], i); + _add_node(texture2d_array_node_option_idx, {}, arr[i], i); } else if (ClassDB::get_parent_class(type) == "Texture3D") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(texture3d_node_option_idx, -1, arr[i], i); + _add_node(texture3d_node_option_idx, {}, arr[i], i); } else if (type == "Cubemap") { saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_node(cubemap_node_option_idx, -1, arr[i], i); + _add_node(cubemap_node_option_idx, {}, arr[i], i); } } } @@ -3862,9 +4593,9 @@ void VisualShaderEditor::_preview_size_changed() { preview_vbox->set_custom_minimum_size(preview_window->get_size()); } -static ShaderLanguage::DataType _get_global_variable_type(const StringName &p_variable) { - RS::GlobalVariableType gvt = RS::get_singleton()->global_variable_get_type(p_variable); - return RS::global_variable_type_get_shader_datatype(gvt); +static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) { + RS::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() { @@ -3877,9 +4608,15 @@ void VisualShaderEditor::_update_preview() { preview_text->set_text(code); + ShaderLanguage::ShaderCompileInfo info; + 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_shader_uniform_type_func = _get_global_shader_uniform_type; + ShaderLanguage sl; - Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(visual_shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(visual_shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type); + Error err = sl.compile(code, info); for (int i = 0; i < preview_text->get_line_count(); i++) { preview_text->set_line_background_color(i, Color(0, 0, 0, 0)); @@ -3909,15 +4646,18 @@ void VisualShaderEditor::_visibility_changed() { } void VisualShaderEditor::_bind_methods() { + ClassDB::bind_method("_update_nodes", &VisualShaderEditor::_update_nodes); ClassDB::bind_method("_update_graph", &VisualShaderEditor::_update_graph); - ClassDB::bind_method("_update_options_menu", &VisualShaderEditor::_update_options_menu); ClassDB::bind_method("_add_node", &VisualShaderEditor::_add_node); ClassDB::bind_method("_node_changed", &VisualShaderEditor::_node_changed); ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item); ClassDB::bind_method("_uniform_select_item", &VisualShaderEditor::_uniform_select_item); + ClassDB::bind_method("_varying_select_item", &VisualShaderEditor::_varying_select_item); ClassDB::bind_method("_set_node_size", &VisualShaderEditor::_set_node_size); - ClassDB::bind_method("_clear_buffer", &VisualShaderEditor::_clear_buffer); + ClassDB::bind_method("_clear_copy_buffer", &VisualShaderEditor::_clear_copy_buffer); ClassDB::bind_method("_update_uniforms", &VisualShaderEditor::_update_uniforms); + ClassDB::bind_method("_update_varyings", &VisualShaderEditor::_update_varyings); + ClassDB::bind_method("_update_varying_tree", &VisualShaderEditor::_update_varying_tree); ClassDB::bind_method("_set_mode", &VisualShaderEditor::_set_mode); ClassDB::bind_method("_nodes_dragged", &VisualShaderEditor::_nodes_dragged); ClassDB::bind_method("_float_constant_selected", &VisualShaderEditor::_float_constant_selected); @@ -3932,46 +4672,37 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_is_available", &VisualShaderEditor::_is_available); } -VisualShaderEditor *VisualShaderEditor::singleton = nullptr; - VisualShaderEditor::VisualShaderEditor() { - singleton = this; - updating = false; - saved_node_pos_dirty = false; - saved_node_pos = Point2(0, 0); ShaderLanguage::get_keyword_list(&keyword_list); - pending_update_preview = false; - shader_error = false; - - to_node = -1; - to_slot = -1; - from_node = -1; - from_slot = -1; - 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); - graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_TRANSFORM); graph->add_valid_right_disconnect_type(VisualShaderNode::PORT_TYPE_SAMPLER); //graph->add_valid_left_disconnect_type(0); graph->set_v_size_flags(SIZE_EXPAND_FILL); - graph->connect("connection_request", callable_mp(this, &VisualShaderEditor::_connection_request), varray(), CONNECT_DEFERRED); - graph->connect("disconnection_request", callable_mp(this, &VisualShaderEditor::_disconnection_request), varray(), CONNECT_DEFERRED); + graph->connect("connection_request", callable_mp(this, &VisualShaderEditor::_connection_request), CONNECT_DEFERRED); + graph->connect("disconnection_request", callable_mp(this, &VisualShaderEditor::_disconnection_request), CONNECT_DEFERRED); graph->connect("node_selected", callable_mp(this, &VisualShaderEditor::_node_selected)); graph->connect("scroll_offset_changed", callable_mp(this, &VisualShaderEditor::_scroll_changed)); graph->connect("duplicate_nodes_request", callable_mp(this, &VisualShaderEditor::_duplicate_nodes)); - graph->connect("copy_nodes_request", callable_mp(this, &VisualShaderEditor::_copy_nodes)); - 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)); @@ -3979,20 +4710,46 @@ VisualShaderEditor::VisualShaderEditor() { graph->connect("visibility_changed", callable_mp(this, &VisualShaderEditor::_visibility_changed)); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR_INT); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_BOOLEAN); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_SCALAR_INT); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_SCALAR); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SCALAR_INT, VisualShaderNode::PORT_TYPE_BOOLEAN); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_SCALAR); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_SCALAR_INT); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_BOOLEAN); + + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_VECTOR_4D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_2D, VisualShaderNode::PORT_TYPE_BOOLEAN); + + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_VECTOR_4D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_3D, VisualShaderNode::PORT_TYPE_BOOLEAN); + + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_SCALAR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_SCALAR_INT); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_VECTOR_4D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_VECTOR_4D, VisualShaderNode::PORT_TYPE_BOOLEAN); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_SCALAR); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_SCALAR_INT); - graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR_2D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR_3D); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_VECTOR_4D); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_BOOLEAN); + graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_TRANSFORM, VisualShaderNode::PORT_TYPE_TRANSFORM); graph->add_valid_connection_type(VisualShaderNode::PORT_TYPE_SAMPLER, VisualShaderNode::PORT_TYPE_SAMPLER); @@ -4025,6 +4782,11 @@ VisualShaderEditor::VisualShaderEditor() { edit_type_sky->select(0); edit_type_sky->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected)); + edit_type_fog = memnew(OptionButton); + edit_type_fog->add_item(TTR("Fog")); + edit_type_fog->select(0); + edit_type_fog->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected)); + edit_type = edit_type_standard; graph->get_zoom_hbox()->add_child(custom_mode_box); @@ -4035,13 +4797,27 @@ VisualShaderEditor::VisualShaderEditor() { graph->get_zoom_hbox()->move_child(edit_type_particles, 0); graph->get_zoom_hbox()->add_child(edit_type_sky); graph->get_zoom_hbox()->move_child(edit_type_sky, 0); + graph->get_zoom_hbox()->add_child(edit_type_fog); + graph->get_zoom_hbox()->move_child(edit_type_fog, 0); add_node = memnew(Button); add_node->set_flat(true); - graph->get_zoom_hbox()->add_child(add_node); add_node->set_text(TTR("Add Node...")); + graph->get_zoom_hbox()->add_child(add_node); graph->get_zoom_hbox()->move_child(add_node, 0); - add_node->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_members_dialog), varray(false)); + 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); + varying_button->set_text(TTR("Manage Varyings")); + graph->get_zoom_hbox()->add_child(varying_button); + varying_button->connect("pressed", callable_mp(this, &VisualShaderEditor::_show_varying_menu)); + + varying_options = memnew(PopupMenu); + add_child(varying_options); + varying_options->add_item(TTR("Add Varying"), int(VaryingMenuOptions::ADD)); + varying_options->add_item(TTR("Remove Varying"), int(VaryingMenuOptions::REMOVE)); + varying_options->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_varying_menu_id_pressed)); preview_shader = memnew(Button); preview_shader->set_flat(true); @@ -4055,7 +4831,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)); @@ -4079,7 +4855,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 @@ -4089,10 +4865,12 @@ VisualShaderEditor::VisualShaderEditor() { add_child(popup_menu); popup_menu->add_item(TTR("Add Node"), NodeMenuOptions::ADD); popup_menu->add_separator(); + popup_menu->add_item(TTR("Cut"), NodeMenuOptions::CUT); popup_menu->add_item(TTR("Copy"), NodeMenuOptions::COPY); popup_menu->add_item(TTR("Paste"), NodeMenuOptions::PASTE); popup_menu->add_item(TTR("Delete"), NodeMenuOptions::DELETE); popup_menu->add_item(TTR("Duplicate"), NodeMenuOptions::DUPLICATE); + popup_menu->add_item(TTR("Clear Copy Buffer"), NodeMenuOptions::CLEAR_COPY_BUFFER); popup_menu->connect("id_pressed", callable_mp(this, &VisualShaderEditor::_node_menu_id_pressed)); /////////////////////////////////////// @@ -4158,16 +4936,85 @@ VisualShaderEditor::VisualShaderEditor() { members_dialog->set_title(TTR("Create Shader Node")); members_dialog->set_exclusive(false); members_dialog->add_child(members_vb); - members_dialog->get_ok_button()->set_text(TTR("Create")); + members_dialog->set_ok_button_text(TTR("Create")); members_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualShaderEditor::_member_create)); members_dialog->get_ok_button()->set_disabled(true); members_dialog->connect("cancelled", callable_mp(this, &VisualShaderEditor::_member_cancel)); add_child(members_dialog); + // add varyings dialog + { + add_varying_dialog = memnew(ConfirmationDialog); + add_varying_dialog->set_title(TTR("Create Shader Varying")); + add_varying_dialog->set_exclusive(false); + add_varying_dialog->set_ok_button_text(TTR("Create")); + add_varying_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualShaderEditor::_varying_create)); + add_varying_dialog->get_ok_button()->set_disabled(true); + add_child(add_varying_dialog); + + VBoxContainer *vb = memnew(VBoxContainer); + add_varying_dialog->add_child(vb); + + HBoxContainer *hb = memnew(HBoxContainer); + vb->add_child(hb); + hb->set_h_size_flags(SIZE_EXPAND_FILL); + + varying_type = memnew(OptionButton); + hb->add_child(varying_type); + varying_type->add_item("Float"); + varying_type->add_item("Vector2"); + varying_type->add_item("Vector3"); + varying_type->add_item("Vector4"); + varying_type->add_item("Color"); + varying_type->add_item("Transform"); + + varying_name = memnew(LineEdit); + hb->add_child(varying_name); + varying_name->set_custom_minimum_size(Size2(150 * EDSCALE, 0)); + varying_name->set_h_size_flags(SIZE_EXPAND_FILL); + varying_name->connect("text_changed", callable_mp(this, &VisualShaderEditor::_varying_name_changed)); + + varying_mode = memnew(OptionButton); + hb->add_child(varying_mode); + varying_mode->add_item("Vertex -> [Fragment, Light]"); + varying_mode->add_item("Fragment -> Light"); + + varying_error_label = memnew(Label); + vb->add_child(varying_error_label); + varying_error_label->set_h_size_flags(SIZE_EXPAND_FILL); + + varying_error_label->hide(); + } + + // remove varying dialog + { + remove_varying_dialog = memnew(ConfirmationDialog); + remove_varying_dialog->set_title(TTR("Delete Shader Varying")); + remove_varying_dialog->set_exclusive(false); + remove_varying_dialog->set_ok_button_text(TTR("Delete")); + remove_varying_dialog->get_ok_button()->connect("pressed", callable_mp(this, &VisualShaderEditor::_varying_deleted)); + add_child(remove_varying_dialog); + + VBoxContainer *vb = memnew(VBoxContainer); + remove_varying_dialog->add_child(vb); + + varyings = memnew(Tree); + vb->add_child(varyings); + varyings->set_h_size_flags(SIZE_EXPAND_FILL); + varyings->set_v_size_flags(SIZE_EXPAND_FILL); + varyings->set_hide_root(true); + varyings->set_allow_reselect(true); + varyings->set_hide_folding(false); + varyings->set_custom_minimum_size(Size2(180 * EDSCALE, 200 * EDSCALE)); + varyings->connect("item_activated", callable_mp(this, &VisualShaderEditor::_varying_deleted)); + varyings->connect("item_selected", callable_mp(this, &VisualShaderEditor::_varying_selected)); + varyings->connect("nothing_selected", callable_mp(this, &VisualShaderEditor::_varying_unselected)); + } + alert = memnew(AcceptDialog); - alert->get_label()->set_autowrap_mode(Label::AUTOWRAP_WORD); - alert->get_label()->set_align(Label::ALIGN_CENTER); - alert->get_label()->set_valign(Label::VALIGN_CENTER); + 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); add_child(alert); @@ -4177,8 +5024,8 @@ VisualShaderEditor::VisualShaderEditor() { comment_title_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_comment_title_text_changed)); comment_title_change_edit->connect("text_submitted", callable_mp(this, &VisualShaderEditor::_comment_title_text_submitted)); comment_title_change_popup->add_child(comment_title_change_edit); - comment_title_change_edit->set_size(Size2(-1, -1)); - comment_title_change_popup->set_size(Size2(-1, -1)); + comment_title_change_edit->reset_size(); + comment_title_change_popup->reset_size(); comment_title_change_popup->connect("focus_exited", callable_mp(this, &VisualShaderEditor::_comment_title_popup_focus_out)); comment_title_change_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_comment_title_popup_hide)); add_child(comment_title_change_popup); @@ -4190,8 +5037,8 @@ VisualShaderEditor::VisualShaderEditor() { comment_desc_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_comment_desc_text_changed)); comment_desc_vbox->add_child(comment_desc_change_edit); comment_desc_change_edit->set_custom_minimum_size(Size2(300 * EDSCALE, 150 * EDSCALE)); - comment_desc_change_edit->set_size(Size2(-1, -1)); - comment_desc_change_popup->set_size(Size2(-1, -1)); + comment_desc_change_edit->reset_size(); + comment_desc_change_popup->reset_size(); comment_desc_change_popup->connect("focus_exited", callable_mp(this, &VisualShaderEditor::_comment_desc_confirm)); comment_desc_change_popup->connect("popup_hide", callable_mp(this, &VisualShaderEditor::_comment_desc_popup_hide)); Button *comment_desc_confirm_button = memnew(Button); @@ -4206,461 +5053,615 @@ VisualShaderEditor::VisualShaderEditor() { // COLOR - add_options.push_back(AddOption("ColorFunc", "Color", "Common", "VisualShaderNodeColorFunc", TTR("Color function."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ColorOp", "Color", "Common", "VisualShaderNodeColorOp", TTR("Color operator."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("ColorFunc", "Color", "Common", "VisualShaderNodeColorFunc", TTR("Color function."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ColorOp", "Color", "Common", "VisualShaderNodeColorOp", TTR("Color operator."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + + add_options.push_back(AddOption("Grayscale", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Grayscale function."), { VisualShaderNodeColorFunc::FUNC_GRAYSCALE }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("HSV2RGB", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Converts HSV vector to RGB equivalent."), { VisualShaderNodeColorFunc::FUNC_HSV2RGB, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("RGB2HSV", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Converts RGB vector to HSV equivalent."), { VisualShaderNodeColorFunc::FUNC_RGB2HSV, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sepia", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Sepia function."), { VisualShaderNodeColorFunc::FUNC_SEPIA }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + + add_options.push_back(AddOption("Burn", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Burn operator."), { VisualShaderNodeColorOp::OP_BURN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Darken", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Darken operator."), { VisualShaderNodeColorOp::OP_DARKEN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Difference", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Difference operator."), { VisualShaderNodeColorOp::OP_DIFFERENCE }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Dodge", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Dodge operator."), { VisualShaderNodeColorOp::OP_DODGE }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("HardLight", "Color", "Operators", "VisualShaderNodeColorOp", TTR("HardLight operator."), { VisualShaderNodeColorOp::OP_HARD_LIGHT }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Lighten", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Lighten operator."), { VisualShaderNodeColorOp::OP_LIGHTEN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Overlay", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Overlay operator."), { VisualShaderNodeColorOp::OP_OVERLAY }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Screen", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Screen operator."), { VisualShaderNodeColorOp::OP_SCREEN }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SoftLight", "Color", "Operators", "VisualShaderNodeColorOp", TTR("SoftLight operator."), { VisualShaderNodeColorOp::OP_SOFT_LIGHT }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("Grayscale", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Grayscale function."), VisualShaderNodeColorFunc::FUNC_GRAYSCALE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("HSV2RGB", "Color", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts HSV vector to RGB equivalent."), VisualShaderNodeVectorFunc::FUNC_HSV2RGB, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("RGB2HSV", "Color", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts RGB vector to HSV equivalent."), VisualShaderNodeVectorFunc::FUNC_RGB2HSV, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sepia", "Color", "Functions", "VisualShaderNodeColorFunc", TTR("Sepia function."), VisualShaderNodeColorFunc::FUNC_SEPIA, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("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)); - add_options.push_back(AddOption("Burn", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Burn operator."), VisualShaderNodeColorOp::OP_BURN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Darken", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Darken operator."), VisualShaderNodeColorOp::OP_DARKEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Difference", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Difference operator."), VisualShaderNodeColorOp::OP_DIFFERENCE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Dodge", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Dodge operator."), VisualShaderNodeColorOp::OP_DODGE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("HardLight", "Color", "Operators", "VisualShaderNodeColorOp", TTR("HardLight operator."), VisualShaderNodeColorOp::OP_HARD_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Lighten", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Lighten operator."), VisualShaderNodeColorOp::OP_LIGHTEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Overlay", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Overlay operator."), VisualShaderNodeColorOp::OP_OVERLAY, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Screen", "Color", "Operators", "VisualShaderNodeColorOp", TTR("Screen operator."), VisualShaderNodeColorOp::OP_SCREEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SoftLight", "Color", "Operators", "VisualShaderNodeColorOp", TTR("SoftLight operator."), VisualShaderNodeColorOp::OP_SOFT_LIGHT, VisualShaderNode::PORT_TYPE_VECTOR)); + // COMMON - add_options.push_back(AddOption("ColorConstant", "Color", "Variables", "VisualShaderNodeColorConstant", TTR("Color constant."), -1, -1)); - add_options.push_back(AddOption("ColorUniform", "Color", "Variables", "VisualShaderNodeColorUniform", TTR("Color uniform."), -1, -1)); + add_options.push_back(AddOption("DerivativeFunc", "Common", "", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) Derivative function."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); // CONDITIONAL const String &compare_func_desc = TTR("Returns the boolean result of the %s comparison between two parameters."); - add_options.push_back(AddOption("Equal", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Equal (==)")), VisualShaderNodeCompare::FUNC_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("GreaterThan", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than (>)")), VisualShaderNodeCompare::FUNC_GREATER_THAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("GreaterThanEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than or Equal (>=)")), VisualShaderNodeCompare::FUNC_GREATER_THAN_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("If", "Conditional", "Functions", "VisualShaderNodeIf", TTR("Returns an associated vector if the provided scalars are equal, greater or less."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("IsInf", "Conditional", "Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF and a scalar parameter."), VisualShaderNodeIs::FUNC_IS_INF, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("IsNaN", "Conditional", "Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between NaN and a scalar parameter."), VisualShaderNodeIs::FUNC_IS_NAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("LessThan", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than (<)")), VisualShaderNodeCompare::FUNC_LESS_THAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("LessThanEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than or Equal (<=)")), VisualShaderNodeCompare::FUNC_LESS_THAN_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("NotEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Not Equal (!=)")), VisualShaderNodeCompare::FUNC_NOT_EQUAL, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("Switch", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated vector if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SwitchBool", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated boolean if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_BOOLEAN, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("SwitchFloat", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated floating-point scalar if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_FLOAT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("SwitchInt", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated integer scalar if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_INT, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("SwitchTransform", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated transform if the provided boolean value is true or false."), VisualShaderNodeSwitch::OP_TYPE_TRANSFORM, VisualShaderNode::PORT_TYPE_TRANSFORM)); - - add_options.push_back(AddOption("Compare", "Conditional", "Common", "VisualShaderNodeCompare", TTR("Returns the boolean result of the comparison between two parameters."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("Is", "Conditional", "Common", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF (or NaN) and a scalar parameter."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); - - add_options.push_back(AddOption("BooleanConstant", "Conditional", "Variables", "VisualShaderNodeBooleanConstant", TTR("Boolean constant."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); - add_options.push_back(AddOption("BooleanUniform", "Conditional", "Variables", "VisualShaderNodeBooleanUniform", TTR("Boolean uniform."), -1, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("Equal", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Equal (==)")), { VisualShaderNodeCompare::FUNC_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("GreaterThan", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than (>)")), { VisualShaderNodeCompare::FUNC_GREATER_THAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("GreaterThanEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Greater Than or Equal (>=)")), { VisualShaderNodeCompare::FUNC_GREATER_THAN_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("If", "Conditional", "Functions", "VisualShaderNodeIf", TTR("Returns an associated vector if the provided scalars are equal, greater or less."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("IsInf", "Conditional", "Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF and a scalar parameter."), { VisualShaderNodeIs::FUNC_IS_INF }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("IsNaN", "Conditional", "Functions", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between NaN and a scalar parameter."), { VisualShaderNodeIs::FUNC_IS_NAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("LessThan", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than (<)")), { VisualShaderNodeCompare::FUNC_LESS_THAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("LessThanEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Less Than or Equal (<=)")), { VisualShaderNodeCompare::FUNC_LESS_THAN_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("NotEqual", "Conditional", "Functions", "VisualShaderNodeCompare", vformat(compare_func_desc, TTR("Not Equal (!=)")), { VisualShaderNodeCompare::FUNC_NOT_EQUAL }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("Switch", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 3D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Switch2D", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated 2D vector if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SwitchBool", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated boolean if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_BOOLEAN }, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("SwitchFloat", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated floating-point scalar if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_FLOAT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("SwitchInt", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated integer scalar if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_INT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("SwitchTransform", "Conditional", "Functions", "VisualShaderNodeSwitch", TTR("Returns an associated transform if the provided boolean value is true or false."), { VisualShaderNodeSwitch::OP_TYPE_TRANSFORM }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + + add_options.push_back(AddOption("Compare", "Conditional", "Common", "VisualShaderNodeCompare", TTR("Returns the boolean result of the comparison between two parameters."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("Is", "Conditional", "Common", "VisualShaderNodeIs", TTR("Returns the boolean result of the comparison between INF (or NaN) and a scalar parameter."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); + + add_options.push_back(AddOption("BooleanConstant", "Conditional", "Variables", "VisualShaderNodeBooleanConstant", TTR("Boolean constant."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); + add_options.push_back(AddOption("BooleanUniform", "Conditional", "Variables", "VisualShaderNodeBooleanUniform", TTR("Boolean uniform."), {}, VisualShaderNode::PORT_TYPE_BOOLEAN)); // INPUT - const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes."); + const String translation_gdsl = "\n\n" + TTR("Translated to '%s' in Godot Shading Language."); + const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes.") + translation_gdsl; - // SPATIAL-FOR-ALL + // NODE3D-FOR-ALL - add_options.push_back(AddOption("Camera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "camera"), "camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InvCamera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_camera"), "inv_camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InvProjection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection"), "inv_projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Normal", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "normal"), "normal", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("OutputIsSRGB", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "output_is_srgb"), "output_is_srgb", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Projection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "projection"), "projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv"), "uv", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("UV2", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv2"), "uv2", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ViewportSize", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "viewport_size"), "viewport_size", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("World", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InvProjectionMatrix", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection_matrix", "INV_PROJECTION_MATRIX"), { "inv_projection_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InvViewMatrix", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_view_matrix", "INV_VIEW_MATRIX"), { "inv_view_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ModelMatrix", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "model_matrix", "MODEL_MATRIX"), { "model_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Normal", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "normal", "NORMAL"), { "normal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("OutputIsSRGB", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "output_is_srgb", "OUTPUT_IS_SRGB"), { "output_is_srgb" }, VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ProjectionMatrix", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "projection_matrix", "PROJECTION_MATRIX"), { "projection_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv", "UV"), { "uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("UV2", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv2", "UV2"), { "uv2" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewMatrix", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "view_matrix", "VIEW_MATRIX"), { "view_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewportSize", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "viewport_size", "VIEWPORT_SIZE"), { "viewport_size" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_SPATIAL)); // CANVASITEM-FOR-ALL - add_options.push_back(AddOption("Alpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("TexturePixelSize", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "texture_pixel_size"), "texture_pixel_size", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv"), "uv", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color", "COLOR"), { "color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("TexturePixelSize", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "texture_pixel_size", "TEXTURE_PIXEL_SIZE"), { "texture_pixel_size" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv", "UV"), { "uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, -1, Shader::MODE_CANVAS_ITEM)); // PARTICLES-FOR-ALL - add_options.push_back(AddOption("Active", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Alpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("AttractorForce", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "attractor_force"), "attractor_force", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Custom", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("CustomAlpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Delta", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("EmissionTransform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Index", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("LifeTime", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Restart", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Transform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Velocity", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Active", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active", "ACTIVE"), { "active" }, VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("AttractorForce", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "attractor_force", "ATTRACTOR_FORCE"), { "attractor_force" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color", "COLOR"), { "color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Custom", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom", "CUSTOM"), { "custom" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Delta", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta", "DELTA"), { "delta" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("EmissionTransform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform", "EMISSION_TRANSFORM"), { "emission_transform" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Index", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index", "INDEX"), { "index" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("LifeTime", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime", "LIFETIME"), { "lifetime" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Restart", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart", "RESTART"), { "restart" }, VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Transform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform", "TRANSFORM"), { "transform" }, VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Velocity", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity", "VELOCITY"), { "velocity" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, -1, Shader::MODE_PARTICLES)); ///////////////// add_options.push_back(AddOption("Input", "Input", "Common", "VisualShaderNodeInput", TTR("Input parameter."))); - const String input_param_for_vertex_and_fragment_shader_modes = TTR("'%s' input parameter for vertex and fragment shader modes."); - const String input_param_for_fragment_and_light_shader_modes = TTR("'%s' input parameter for fragment and light shader modes."); - const String input_param_for_fragment_shader_mode = TTR("'%s' input parameter for fragment shader mode."); - const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode."); - const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode."); - const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode."); - const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode."); - const String input_param_for_process_shader_mode = TTR("'%s' input parameter for process shader mode."); - const String input_param_for_collide_shader_mode = TTR("'%s' input parameter for collide shader mode."); - const String input_param_for_start_and_process_shader_mode = TTR("'%s' input parameter for start and process shader modes."); - const String input_param_for_process_and_collide_shader_mode = TTR("'%s' input parameter for process and collide shader modes."); - const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader modes."); + const String input_param_for_vertex_and_fragment_shader_modes = TTR("'%s' input parameter for vertex and fragment shader modes.") + translation_gdsl; + const String input_param_for_fragment_and_light_shader_modes = TTR("'%s' input parameter for fragment and light shader modes.") + translation_gdsl; + const String input_param_for_fragment_shader_mode = TTR("'%s' input parameter for fragment shader mode.") + translation_gdsl; + const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode.") + translation_gdsl; + const String input_param_for_fog_shader_mode = TTR("'%s' input parameter for fog shader mode.") + translation_gdsl; + const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode.") + translation_gdsl; + const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode.") + translation_gdsl; + const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode.") + translation_gdsl; + const String input_param_for_process_shader_mode = TTR("'%s' input parameter for process shader mode.") + translation_gdsl; + const String input_param_for_collide_shader_mode = TTR("'%s' input parameter for collide shader mode." + translation_gdsl); + const String input_param_for_start_and_process_shader_mode = TTR("'%s' input parameter for start and process shader modes.") + translation_gdsl; + const String input_param_for_process_and_collide_shader_mode = TTR("'%s' input parameter for process and collide shader modes.") + translation_gdsl; + const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader modes.") + translation_gdsl; // NODE3D INPUTS - add_options.push_back(AddOption("Alpha", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Binormal", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Color", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InstanceId", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_id"), "instance_id", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InstanceCustom", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom"), "instance_custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("InstanceCustomAlpha", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom_alpha"), "instance_custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ModelView", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "modelview"), "modelview", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("PointSize", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size"), "point_size", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Tangent", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "tangent"), "tangent", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - - add_options.push_back(AddOption("Alpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Binormal", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Color", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("DepthTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "depth_texture"), "depth_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("FrontFacing", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "front_facing"), "front_facing", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ScreenTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_texture"), "screen_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Tangent", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "tangent"), "tangent", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Vertex", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("View", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view"), "view", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); - - add_options.push_back(AddOption("Albedo", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "albedo"), "albedo", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Attenuation", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "attenuation"), "attenuation", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Backlight", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "backlight"), "backlight", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Diffuse", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "diffuse"), "diffuse", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light"), "light", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("LightColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color"), "light_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Metallic", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "metallic"), "metallic", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Roughness", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "roughness"), "roughness", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Specular", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "specular"), "specular", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("View", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view"), "view", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Binormal", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal", "BINORMAL"), { "binormal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Color", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "color", "COLOR"), { "color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InstanceId", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_id", "INSTANCE_ID"), { "instance_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("InstanceCustom", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom", "INSTANCE_CUSTOM"), { "instance_custom" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ModelViewMatrix", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "modelview_matrix", "MODELVIEW_MATRIX"), { "modelview_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("PointSize", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size", "POINT_SIZE"), { "point_size" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Tangent", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "tangent", "TANGENT"), { "tangent" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("VertexId", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex_id", "VERTEX_ID"), { "vertex_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewIndex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_index", "VIEW_INDEX"), { "view_index" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewMonoLeft", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_mono_left", "VIEW_MONO_LEFT"), { "view_mono_left" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewRight", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "view_right", "VIEW_RIGHT"), { "view_right" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("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)); + add_options.push_back(AddOption("DepthTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "depth_texture", "DEPTH_TEXTURE"), { "depth_texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("FrontFacing", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "front_facing", "FRONT_FACING"), { "front_facing" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "point_coord", "POINT_COORD"), { "point_coord" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ScreenTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_texture", "SCREEN_TEXTURE"), { "screen_texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Tangent", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "tangent", "TANGENT"), { "tangent" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Vertex", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("View", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view", "VIEW"), { "view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("ViewIndex", "Input", "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)); + add_options.push_back(AddOption("Backlight", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "backlight", "BACKLIGHT"), { "backlight" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Diffuse", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "diffuse", "DIFFUSE_LIGHT"), { "diffuse" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light", "LIGHT"), { "light" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("LightColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color", "LIGHT_COLOR"), { "light_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Metallic", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "metallic", "METALLIC"), { "metallic" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Roughness", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "roughness", "ROUGHNESS"), { "roughness" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Specular", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "specular", "SPECULAR_LIGHT"), { "specular" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("View", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "view", "VIEW"), { "view" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); // CANVASITEM INPUTS - add_options.push_back(AddOption("AtLightPass", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass"), "at_light_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Canvas", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "canvas"), "canvas", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("InstanceCustom", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom"), "instance_custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("InstanceCustomAlpha", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom_alpha"), "instance_custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("PointSize", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size"), "point_size", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Screen", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "screen"), "screen", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("World", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - - add_options.push_back(AddOption("AtLightPass", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass"), "at_light_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("NormalTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "normal_texture"), "normal_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenPixelSize", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_pixel_size"), "screen_pixel_size", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_texture"), "screen_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininess", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess"), "specular_shininess", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininessAlpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess_alpha"), "specular_shininess_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininessTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "specular_shininess_texture"), "specular_shininess_texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Texture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture"), "texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Vertex", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - - add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord"), "fragcoord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light"), "light", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_alpha"), "light_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color"), "light_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightColorAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color_alpha"), "light_color_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightPosition", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_position"), "light_position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("LightVertex", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "light_vertex"), "light_vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Normal", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "normal"), "normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("PointCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord"), "point_coord", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScreenUV", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Shadow", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow"), "shadow", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ShadowAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow_alpha"), "shadow_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininess", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess"), "specular_shininess", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SpecularShininessAlpha", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess_alpha"), "specular_shininess_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("Texture", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture"), "texture", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("AtLightPass", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass", "AT_LIGHT_PASS"), { "at_light_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("CanvasMatrix", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "canvas_matrix", "CANVAS_MATRIX"), { "canvas_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("InstanceCustom", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_custom", "INSTANCE_CUSTOM"), { "instance_custom" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("InstanceId", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "instance_id", "INSTANCE_ID"), { "instance_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ModelMatrix", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "model_matrix", "MODEL_MATRIX"), { "model_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("PointSize", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "point_size", "POINT_SIZE"), { "point_size" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenMatrix", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "screen_matrix", "SCREEN_MATRIX"), { "screen_matrix" }, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("VertexId", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex_id", "VERTEX_ID"), { "vertex_id" }, VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); + + add_options.push_back(AddOption("AtLightPass", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "at_light_pass", "AT_LIGHT_PASS"), { "at_light_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("FragCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("NormalTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "normal_texture", "NORMAL_TEXTURE"), { "normal_texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("PointCoord", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord", "POINT_COORD"), { "point_coord" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenPixelSize", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_pixel_size", "SCREEN_PIXEL_SIZE"), { "screen_pixel_size" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "screen_texture", "SCREEN_TEXTURE"), { "screen_texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenUV", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininess", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess", "SPECULAR_SHININESS"), { "specular_shininess" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininessTexture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_shader_mode, "specular_shininess_texture", "SPECULAR_SHININESS_TEXTURE"), { "specular_shininess_texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Texture", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture", "TEXTURE"), { "texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Vertex", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_mode, "vertex", "VERTEX"), { "vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); + + add_options.push_back(AddOption("FragCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Light", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light", "LIGHT"), { "light" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightColor", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_color", "LIGHT_COLOR"), { "light_color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightPosition", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "light_position", "LIGHT_POSITION"), { "light_position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("LightVertex", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "light_vertex", "LIGHT_VERTEX"), { "light_vertex" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Normal", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "normal", "NORMAL"), { "normal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("PointCoord", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "point_coord", "POINT_COORD"), { "point_coord" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenUV", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Shadow", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_light_shader_mode, "shadow", "SHADOW_MODULATE"), { "shadow" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SpecularShininess", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "specular_shininess", "SPECULAR_SHININESS"), { "specular_shininess" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("Texture", "Input", "Light", "VisualShaderNodeInput", vformat(input_param_for_fragment_and_light_shader_modes, "texture", "TEXTURE"), { "texture" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); // SKY INPUTS - add_options.push_back(AddOption("AtCubeMapPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_cubemap_pass"), "at_cubemap_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("AtHalfResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_half_res_pass"), "at_half_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("AtQuarterResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_quarter_res_pass"), "at_quarter_res_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("EyeDir", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "eyedir"), "eyedir", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("HalfResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_color"), "half_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("HalfResAlpha", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_alpha"), "half_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_color"), "light0_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_direction"), "light0_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_enabled"), "light0_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light0Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_energy"), "light0_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_color"), "light1_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_direction"), "light1_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_enabled"), "light1_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light1Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_energy"), "light1_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_color"), "light2_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_direction"), "light2_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_enabled"), "light2_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light2Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_energy"), "light2_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_color"), "light3_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_direction"), "light3_direction", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_enabled"), "light3_enabled", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Light3Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_energy"), "light3_energy", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Position", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "position"), "position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("QuarterResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_color"), "quarter_res_color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("QuarterResAlpha", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_alpha"), "quarter_res_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Radiance", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "radiance"), "radiance", VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("ScreenUV", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "screen_uv"), "screen_uv", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("SkyCoords", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "sky_coords"), "sky_coords", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); - add_options.push_back(AddOption("Time", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("AtCubeMapPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_cubemap_pass", "AT_CUBEMAP_PASS"), { "at_cubemap_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("AtHalfResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_half_res_pass", "AT_HALF_RES_PASS"), { "at_half_res_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("AtQuarterResPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_quarter_res_pass", "AT_QUARTER_RES_PASS"), { "at_quarter_res_pass" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("EyeDir", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "eyedir", "EYEDIR"), { "eyedir" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("HalfResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "half_res_color", "HALF_RES_COLOR"), { "half_res_color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_color", "LIGHT0_COLOR"), { "light0_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_direction", "LIGHT0_DIRECTION"), { "light0_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_enabled", "LIGHT0_ENABLED"), { "light0_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light0Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light0_energy", "LIGHT0_ENERGY"), { "light0_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_color", "LIGHT1_COLOR"), { "light1_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_direction", "LIGHT1_DIRECTION"), { "light1_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_enabled", "LIGHT1_ENABLED"), { "light1_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light1Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light1_energy", "LIGHT1_ENERGY"), { "light1_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_color", "LIGHT2_COLOR"), { "light2_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_direction", "LIGHT2_DIRECTION"), { "light2_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_enabled", "LIGHT2_ENABLED"), { "light2_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light2Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light2_energy", "LIGHT2_ENERGY"), { "light2_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Color", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_color", "LIGHT3_COLOR"), { "light3_color" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Direction", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_direction", "LIGHT3_DIRECTION"), { "light3_direction" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Enabled", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_enabled", "LIGHT3_ENABLED"), { "light3_enabled" }, VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Light3Energy", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "light3_energy", "LIGHT3_ENERGY"), { "light3_energy" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Position", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "position", "POSITION"), { "position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("QuarterResColor", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "quarter_res_color", "QUARTER_RES_COLOR"), { "quarter_res_color" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Radiance", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "radiance", "RADIANCE"), { "radiance" }, VisualShaderNode::PORT_TYPE_SAMPLER, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("ScreenUV", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "screen_uv", "SCREEN_UV"), { "screen_uv" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("FragCoord", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "fragcoord", "FRAGCOORD"), { "fragcoord" }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + + add_options.push_back(AddOption("SkyCoords", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "sky_coords", "SKY_COORDS"), { "sky_coords" }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + add_options.push_back(AddOption("Time", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + + // FOG INPUTS + + add_options.push_back(AddOption("WorldPosition", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "world_position", "WORLD_POSITION"), { "world_position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("ObjectPosition", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "object_position", "OBJECT_POSITION"), { "object_position" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("UVW", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "uvw", "UVW"), { "uvw" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("Extents", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "extents", "EXTENTS"), { "extents" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("SDF", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "sdf", "SDF"), { "sdf" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("Time", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "time", "TIME"), { "time" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); // PARTICLES INPUTS - add_options.push_back(AddOption("CollisionDepth", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_depth"), "collision_depth", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("CollisionNormal", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_normal"), "collision_normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("CollisionDepth", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_depth", "COLLISION_DEPTH"), { "collision_depth" }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("CollisionNormal", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_normal", "COLLISION_NORMAL"), { "collision_normal" }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); // PARTICLES - add_options.push_back(AddOption("EmitParticle", "Particles", "", "VisualShaderNodeParticleEmit", "", -1, -1, TYPE_FLAGS_PROCESS | TYPE_FLAGS_PROCESS_CUSTOM | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("ParticleAccelerator", "Particles", "", "VisualShaderNodeParticleAccelerator", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("ParticleRandomness", "Particles", "", "VisualShaderNodeParticleRandomness", "", -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("MultiplyByAxisAngle", "Particles", "Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", "A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters.", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("EmitParticle", "Particles", "", "VisualShaderNodeParticleEmit", "", {}, -1, TYPE_FLAGS_PROCESS | TYPE_FLAGS_PROCESS_CUSTOM | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ParticleAccelerator", "Particles", "", "VisualShaderNodeParticleAccelerator", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ParticleRandomness", "Particles", "", "VisualShaderNodeParticleRandomness", "", {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("MultiplyByAxisAngle", "Particles", "Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", TTR("A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("MeshEmitter", "Particles", "Emitters", "VisualShaderNodeParticleMeshEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("ConeVelocity", "Particles", "Velocity", "VisualShaderNodeParticleConeVelocity", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ConeVelocity", "Particles", "Velocity", "VisualShaderNodeParticleConeVelocity", "", {}, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); // SCALAR - add_options.push_back(AddOption("FloatFunc", "Scalar", "Common", "VisualShaderNodeFloatFunc", TTR("Float function."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntFunc", "Scalar", "Common", "VisualShaderNodeIntFunc", TTR("Integer function."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("FloatOp", "Scalar", "Common", "VisualShaderNodeFloatOp", TTR("Float operator."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntOp", "Scalar", "Common", "VisualShaderNodeIntOp", TTR("Integer operator."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("FloatFunc", "Scalar", "Common", "VisualShaderNodeFloatFunc", TTR("Float function."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("FloatOp", "Scalar", "Common", "VisualShaderNodeFloatOp", TTR("Float operator."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("IntFunc", "Scalar", "Common", "VisualShaderNodeIntFunc", TTR("Integer function."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("IntOp", "Scalar", "Common", "VisualShaderNodeIntOp", TTR("Integer operator."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); // CONSTANTS for (int i = 0; i < MAX_FLOAT_CONST_DEFS; i++) { - add_options.push_back(AddOption(float_constant_defs[i].name, "Scalar", "Constants", "VisualShaderNodeFloatConstant", float_constant_defs[i].desc, -1, VisualShaderNode::PORT_TYPE_SCALAR, -1, -1, float_constant_defs[i].value)); + add_options.push_back(AddOption(float_constant_defs[i].name, "Scalar", "Constants", "VisualShaderNodeFloatConstant", float_constant_defs[i].desc, { float_constant_defs[i].value }, VisualShaderNode::PORT_TYPE_SCALAR)); } // FUNCTIONS - add_options.push_back(AddOption("Abs", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the absolute value of the parameter."), VisualShaderNodeFloatFunc::FUNC_ABS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Abs", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the absolute value of the parameter."), VisualShaderNodeIntFunc::FUNC_ABS, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("ACos", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ACOS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ACosH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ACOSH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ASin", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ASIN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ASinH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_ASINH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ATan", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_ATAN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ATan2", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the arc-tangent of the parameters."), VisualShaderNodeFloatOp::OP_ATAN2, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("ATanH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_ATANH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Ceil", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), VisualShaderNodeFloatFunc::FUNC_CEIL, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Clamp", "Scalar", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), VisualShaderNodeClamp::OP_TYPE_FLOAT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Clamp", "Scalar", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), VisualShaderNodeClamp::OP_TYPE_INT, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Cos", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_COS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("CosH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic cosine of the parameter."), VisualShaderNodeFloatFunc::FUNC_COSH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Degrees", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in radians to degrees."), VisualShaderNodeFloatFunc::FUNC_DEGREES, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Exp", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-e Exponential."), VisualShaderNodeFloatFunc::FUNC_EXP, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Exp2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 Exponential."), VisualShaderNodeFloatFunc::FUNC_EXP2, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Floor", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer less than or equal to the parameter."), VisualShaderNodeFloatFunc::FUNC_FLOOR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Fract", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Computes the fractional part of the argument."), VisualShaderNodeFloatFunc::FUNC_FRAC, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("InverseSqrt", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse of the square root of the parameter."), VisualShaderNodeFloatFunc::FUNC_INVERSE_SQRT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Log", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Natural logarithm."), VisualShaderNodeFloatFunc::FUNC_LOG, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Log2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 logarithm."), VisualShaderNodeFloatFunc::FUNC_LOG2, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Max", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the greater of two values."), VisualShaderNodeFloatOp::OP_MAX, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Min", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the lesser of two values."), VisualShaderNodeFloatOp::OP_MIN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Mix", "Scalar", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two scalars."), VisualShaderNodeMix::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("MultiplyAdd", "Scalar", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on scalars."), VisualShaderNodeMultiplyAdd::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Negate", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the opposite value of the parameter."), VisualShaderNodeFloatFunc::FUNC_NEGATE, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Negate", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the opposite value of the parameter."), VisualShaderNodeIntFunc::FUNC_NEGATE, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("OneMinus", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("1.0 - scalar"), VisualShaderNodeFloatFunc::FUNC_ONEMINUS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Pow", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the value of the first parameter raised to the power of the second."), VisualShaderNodeFloatOp::OP_POW, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Radians", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in degrees to radians."), VisualShaderNodeFloatFunc::FUNC_RADIANS, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Reciprocal", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("1.0 / scalar"), VisualShaderNodeFloatFunc::FUNC_RECIPROCAL, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Round", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer to the parameter."), VisualShaderNodeFloatFunc::FUNC_ROUND, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("RoundEven", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest even integer to the parameter."), VisualShaderNodeFloatFunc::FUNC_ROUNDEVEN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Saturate", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Clamps the value between 0.0 and 1.0."), VisualShaderNodeFloatFunc::FUNC_SATURATE, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Sign", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Extracts the sign of the parameter."), VisualShaderNodeFloatFunc::FUNC_SIGN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Sign", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Extracts the sign of the parameter."), VisualShaderNodeIntFunc::FUNC_SIGN, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Sin", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_SIN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("SinH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic sine of the parameter."), VisualShaderNodeFloatFunc::FUNC_SINH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Sqrt", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the square root of the parameter."), VisualShaderNodeFloatFunc::FUNC_SQRT, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("SmoothStep", "Scalar", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if x is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), VisualShaderNodeSmoothStep::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Step", "Scalar", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), VisualShaderNodeStep::OP_TYPE_SCALAR, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Tan", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_TAN, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("TanH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic tangent of the parameter."), VisualShaderNodeFloatFunc::FUNC_TANH, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Trunc", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the truncated value of the parameter."), VisualShaderNodeFloatFunc::FUNC_TRUNC, VisualShaderNode::PORT_TYPE_SCALAR)); - - add_options.push_back(AddOption("Add", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Sums two floating-point scalars."), VisualShaderNodeFloatOp::OP_ADD, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Add", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Sums two integer scalars."), VisualShaderNodeIntOp::OP_ADD, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Divide", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Divides two floating-point scalars."), VisualShaderNodeFloatOp::OP_DIV, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Divide", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Divides two integer scalars."), VisualShaderNodeIntOp::OP_DIV, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Multiply", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Multiplies two floating-point scalars."), VisualShaderNodeFloatOp::OP_MUL, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Multiply", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Multiplies two integer scalars."), VisualShaderNodeIntOp::OP_MUL, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Remainder", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Returns the remainder of the two floating-point scalars."), VisualShaderNodeFloatOp::OP_MOD, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Remainder", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the remainder of the two integer scalars."), VisualShaderNodeIntOp::OP_MOD, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("Subtract", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Subtracts two floating-point scalars."), VisualShaderNodeFloatOp::OP_SUB, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Subtract", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Subtracts two integer scalars."), VisualShaderNodeIntOp::OP_SUB, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - - add_options.push_back(AddOption("FloatConstant", "Scalar", "Variables", "VisualShaderNodeFloatConstant", TTR("Scalar floating-point constant."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntConstant", "Scalar", "Variables", "VisualShaderNodeIntConstant", TTR("Scalar integer constant."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); - add_options.push_back(AddOption("FloatUniform", "Scalar", "Variables", "VisualShaderNodeFloatUniform", TTR("Scalar floating-point uniform."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("IntUniform", "Scalar", "Variables", "VisualShaderNodeIntUniform", TTR("Scalar integer uniform."), -1, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Abs", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ABS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Abs", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeIntFunc::FUNC_ABS }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("ACos", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ACOS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ACosH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ACOSH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ASin", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ASIN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ASinH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ASINH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ATan", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ATAN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ATan2", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeFloatOp::OP_ATAN2 }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("ATanH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_ATANH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("BitwiseNOT", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the result of bitwise NOT (~a) operation on the integer."), { VisualShaderNodeIntFunc::FUNC_BITWISE_NOT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Ceil", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeFloatFunc::FUNC_CEIL }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Clamp", "Scalar", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_FLOAT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Clamp", "Scalar", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_INT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Cos", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_COS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("CosH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_COSH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Degrees", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeFloatFunc::FUNC_DEGREES }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("DFdX", "Scalar", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Scalar", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Exp", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-e Exponential."), { VisualShaderNodeFloatFunc::FUNC_EXP }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Exp2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 Exponential."), { VisualShaderNodeFloatFunc::FUNC_EXP2 }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Floor", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeFloatFunc::FUNC_FLOOR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Fract", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeFloatFunc::FUNC_FRACT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("InverseSqrt", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeFloatFunc::FUNC_INVERSE_SQRT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Log", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Natural logarithm."), { VisualShaderNodeFloatFunc::FUNC_LOG }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Log2", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Base-2 logarithm."), { VisualShaderNodeFloatFunc::FUNC_LOG2 }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Max", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the greater of two values."), { VisualShaderNodeFloatOp::OP_MAX }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Min", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the lesser of two values."), { VisualShaderNodeFloatOp::OP_MIN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Mix", "Scalar", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two scalars."), { VisualShaderNodeMix::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("MultiplyAdd", "Scalar", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on scalars."), { VisualShaderNodeMultiplyAdd::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Negate", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeFloatFunc::FUNC_NEGATE }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Negate", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeIntFunc::FUNC_NEGATE }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("OneMinus", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("1.0 - scalar"), { VisualShaderNodeFloatFunc::FUNC_ONEMINUS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Pow", "Scalar", "Functions", "VisualShaderNodeFloatOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeFloatOp::OP_POW }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Radians", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeFloatFunc::FUNC_RADIANS }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Reciprocal", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("1.0 / scalar"), { VisualShaderNodeFloatFunc::FUNC_RECIPROCAL }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Round", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeFloatFunc::FUNC_ROUND }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("RoundEven", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeFloatFunc::FUNC_ROUNDEVEN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Saturate", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeFloatFunc::FUNC_SATURATE }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sign", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SIGN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sign", "Scalar", "Functions", "VisualShaderNodeIntFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeIntFunc::FUNC_SIGN }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Sin", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SIN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("SinH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SINH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sqrt", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeFloatFunc::FUNC_SQRT }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("SmoothStep", "Scalar", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if x is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Step", "Scalar", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), scalar(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Sum", "Scalar", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_SCALAR }, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Tan", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_TAN }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("TanH", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeFloatFunc::FUNC_TANH }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Trunc", "Scalar", "Functions", "VisualShaderNodeFloatFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeFloatFunc::FUNC_TRUNC }, VisualShaderNode::PORT_TYPE_SCALAR)); + + add_options.push_back(AddOption("Add", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Sums two floating-point scalars."), { VisualShaderNodeFloatOp::OP_ADD }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Add", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Sums two integer scalars."), { VisualShaderNodeIntOp::OP_ADD }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseAND", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise AND (a & b) operation for two integers."), { VisualShaderNodeIntOp::OP_BITWISE_AND }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseLeftShift", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise left shift (a << b) operation on the integer."), { VisualShaderNodeIntOp::OP_BITWISE_LEFT_SHIFT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseOR", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise OR (a | b) operation for two integers."), { VisualShaderNodeIntOp::OP_BITWISE_OR }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseRightShift", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise right shift (a >> b) operation on the integer."), { VisualShaderNodeIntOp::OP_BITWISE_RIGHT_SHIFT }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("BitwiseXOR", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the result of bitwise XOR (a ^ b) operation on the integer."), { VisualShaderNodeIntOp::OP_BITWISE_XOR }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Divide", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Divides two floating-point scalars."), { VisualShaderNodeFloatOp::OP_DIV }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Divide", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Divides two integer scalars."), { VisualShaderNodeIntOp::OP_DIV }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Multiply", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Multiplies two floating-point scalars."), { VisualShaderNodeFloatOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Multiply", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Multiplies two integer scalars."), { VisualShaderNodeIntOp::OP_MUL }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Remainder", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Returns the remainder of the two floating-point scalars."), { VisualShaderNodeFloatOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Remainder", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Returns the remainder of the two integer scalars."), { VisualShaderNodeIntOp::OP_MOD }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("Subtract", "Scalar", "Operators", "VisualShaderNodeFloatOp", TTR("Subtracts two floating-point scalars."), { VisualShaderNodeFloatOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Subtract", "Scalar", "Operators", "VisualShaderNodeIntOp", TTR("Subtracts two integer scalars."), { VisualShaderNodeIntOp::OP_SUB }, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + + add_options.push_back(AddOption("FloatConstant", "Scalar", "Variables", "VisualShaderNodeFloatConstant", TTR("Scalar floating-point constant."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("IntConstant", "Scalar", "Variables", "VisualShaderNodeIntConstant", TTR("Scalar integer constant."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); + add_options.push_back(AddOption("FloatUniform", "Scalar", "Variables", "VisualShaderNodeFloatUniform", TTR("Scalar floating-point uniform."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("IntUniform", "Scalar", "Variables", "VisualShaderNodeIntUniform", TTR("Scalar integer uniform."), {}, VisualShaderNode::PORT_TYPE_SCALAR_INT)); // SDF { - add_options.push_back(AddOption("ScreenUVToSDF", "SDF", "", "VisualShaderNodeScreenUVToSDF", TTR("Converts screen UV to a SDF."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SDFRaymarch", "SDF", "", "VisualShaderNodeSDFRaymarch", TTR("Casts a ray against the screen SDF and returns the distance travelled."), -1, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("SDFToScreenUV", "SDF", "", "VisualShaderNodeSDFToScreenUV", TTR("Converts a SDF to screen UV."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("TextureSDF", "SDF", "", "VisualShaderNodeTextureSDF", TTR("Performs a SDF texture lookup."), -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("TextureSDFNormal", "SDF", "", "VisualShaderNodeTextureSDFNormal", TTR("Performs a SDF normal texture lookup."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("ScreenUVToSDF", "SDF", "", "VisualShaderNodeScreenUVToSDF", TTR("Converts screen UV to a SDF."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SDFRaymarch", "SDF", "", "VisualShaderNodeSDFRaymarch", TTR("Casts a ray against the screen SDF and returns the distance travelled."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("SDFToScreenUV", "SDF", "", "VisualShaderNodeSDFToScreenUV", TTR("Converts a SDF to screen UV."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("TextureSDF", "SDF", "", "VisualShaderNodeTextureSDF", TTR("Performs a SDF texture lookup."), {}, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("TextureSDFNormal", "SDF", "", "VisualShaderNodeTextureSDFNormal", TTR("Performs a SDF normal texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); } // TEXTURES - add_options.push_back(AddOption("UVFunc", "Textures", "Common", "VisualShaderNodeUVFunc", TTR("Function to be applied on texture coordinates."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("UVFunc", "Textures", "Common", "VisualShaderNodeUVFunc", TTR("Function to be applied on texture coordinates."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); cubemap_node_option_idx = add_options.size(); - add_options.push_back(AddOption("CubeMap", "Textures", "Functions", "VisualShaderNodeCubemap", TTR("Perform the cubic texture lookup."), -1, -1)); + add_options.push_back(AddOption("CubeMap", "Textures", "Functions", "VisualShaderNodeCubemap", TTR("Perform the cubic texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); curve_node_option_idx = add_options.size(); - add_options.push_back(AddOption("CurveTexture", "Textures", "Functions", "VisualShaderNodeCurveTexture", TTR("Perform the curve texture lookup."), -1, -1)); + add_options.push_back(AddOption("CurveTexture", "Textures", "Functions", "VisualShaderNodeCurveTexture", TTR("Perform the curve texture lookup."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); curve_xyz_node_option_idx = add_options.size(); - add_options.push_back(AddOption("CurveXYZTexture", "Textures", "Functions", "VisualShaderNodeCurveXYZTexture", TTR("Perform the three components curve texture lookup."), -1, -1)); + add_options.push_back(AddOption("CurveXYZTexture", "Textures", "Functions", "VisualShaderNodeCurveXYZTexture", TTR("Perform the three components curve texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); texture2d_node_option_idx = add_options.size(); - add_options.push_back(AddOption("Texture2D", "Textures", "Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."), -1, -1)); + add_options.push_back(AddOption("Texture2D", "Textures", "Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); texture2d_array_node_option_idx = add_options.size(); - add_options.push_back(AddOption("Texture2DArray", "Textures", "Functions", "VisualShaderNodeTexture2DArray", TTR("Perform the 2D-array texture lookup."), -1, -1, -1, -1, -1)); + add_options.push_back(AddOption("Texture2DArray", "Textures", "Functions", "VisualShaderNodeTexture2DArray", TTR("Perform the 2D-array texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); texture3d_node_option_idx = add_options.size(); - add_options.push_back(AddOption("Texture3D", "Textures", "Functions", "VisualShaderNodeTexture3D", TTR("Perform the 3D texture lookup."), -1, -1)); - add_options.push_back(AddOption("UVPanning", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply panning function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_PANNING, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("UVScaling", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply scaling function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_SCALING, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("Texture3D", "Textures", "Functions", "VisualShaderNodeTexture3D", TTR("Perform the 3D texture lookup."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("UVPanning", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply panning function on texture coordinates."), { VisualShaderNodeUVFunc::FUNC_PANNING }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("UVScaling", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply scaling function on texture coordinates."), { VisualShaderNodeUVFunc::FUNC_SCALING }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); - add_options.push_back(AddOption("CubeMapUniform", "Textures", "Variables", "VisualShaderNodeCubemapUniform", TTR("Cubic texture uniform lookup."), -1, -1)); - add_options.push_back(AddOption("TextureUniform", "Textures", "Variables", "VisualShaderNodeTextureUniform", TTR("2D texture uniform lookup."), -1, -1)); - add_options.push_back(AddOption("TextureUniformTriplanar", "Textures", "Variables", "VisualShaderNodeTextureUniformTriplanar", TTR("2D texture uniform lookup with triplanar."), -1, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Texture2DArrayUniform", "Textures", "Variables", "VisualShaderNodeTexture2DArrayUniform", TTR("2D array of textures uniform lookup."), -1, -1, -1, -1, -1)); - add_options.push_back(AddOption("Texture3DUniform", "Textures", "Variables", "VisualShaderNodeTexture3DUniform", TTR("3D texture uniform lookup."), -1, -1, -1, -1, -1)); + add_options.push_back(AddOption("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."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); + add_options.push_back(AddOption("Texture3DUniform", "Textures", "Variables", "VisualShaderNodeTexture3DUniform", TTR("3D texture uniform lookup."), {}, VisualShaderNode::PORT_TYPE_SAMPLER)); // TRANSFORM - add_options.push_back(AddOption("TransformFunc", "Transform", "Common", "VisualShaderNodeTransformFunc", TTR("Transform function."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformOp", "Transform", "Common", "VisualShaderNodeTransformOp", TTR("Transform operator."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformFunc", "Transform", "Common", "VisualShaderNodeTransformFunc", TTR("Transform function."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformOp", "Transform", "Common", "VisualShaderNodeTransformOp", TTR("Transform operator."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("OuterProduct", "Transform", "Composition", "VisualShaderNodeOuterProduct", TTR("Calculate the outer product of a pair of vectors.\n\nOuterProduct treats the first parameter 'c' as a column vector (matrix with one column) and the second parameter 'r' as a row vector (matrix with one row) and does a linear algebraic matrix multiply 'c * r', yielding a matrix whose number of rows is the number of components in 'c' and whose number of columns is the number of components in 'r'."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformCompose", "Transform", "Composition", "VisualShaderNodeTransformCompose", TTR("Composes transform from four vectors."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("OuterProduct", "Transform", "Composition", "VisualShaderNodeOuterProduct", TTR("Calculate the outer product of a pair of vectors.\n\nOuterProduct treats the first parameter 'c' as a column vector (matrix with one column) and the second parameter 'r' as a row vector (matrix with one row) and does a linear algebraic matrix multiply 'c * r', yielding a matrix whose number of rows is the number of components in 'c' and whose number of columns is the number of components in 'r'."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformCompose", "Transform", "Composition", "VisualShaderNodeTransformCompose", TTR("Composes transform from four vectors."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); add_options.push_back(AddOption("TransformDecompose", "Transform", "Composition", "VisualShaderNodeTransformDecompose", TTR("Decomposes transform to four vectors."))); - add_options.push_back(AddOption("Determinant", "Transform", "Functions", "VisualShaderNodeDeterminant", TTR("Calculates the determinant of a transform."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("GetBillboardMatrix", "Transform", "Functions", "VisualShaderNodeBillboard", TTR("Calculates how the object should face the camera to be applied on Model View Matrix output port for 3D objects."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); - add_options.push_back(AddOption("Inverse", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the inverse of a transform."), VisualShaderNodeTransformFunc::FUNC_INVERSE, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Transpose", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the transpose of a transform."), VisualShaderNodeTransformFunc::FUNC_TRANSPOSE, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Determinant", "Transform", "Functions", "VisualShaderNodeDeterminant", TTR("Calculates the determinant of a transform."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("GetBillboardMatrix", "Transform", "Functions", "VisualShaderNodeBillboard", TTR("Calculates how the object should face the camera to be applied on Model View Matrix output port for 3D objects."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("Inverse", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the inverse of a transform."), { VisualShaderNodeTransformFunc::FUNC_INVERSE }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Transpose", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the transpose of a transform."), { VisualShaderNodeTransformFunc::FUNC_TRANSPOSE }, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Add", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Sums two transforms."), VisualShaderNodeTransformOp::OP_ADD, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Divide", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Divides two transforms."), VisualShaderNodeTransformOp::OP_A_DIV_B, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Multiply", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Multiplies two transforms."), VisualShaderNodeTransformOp::OP_AxB, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("MultiplyComp", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Performs per-component multiplication of two transforms."), VisualShaderNodeTransformOp::OP_AxB_COMP, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("Subtract", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Subtracts two transforms."), VisualShaderNodeTransformOp::OP_A_MINUS_B, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformVectorMult", "Transform", "Operators", "VisualShaderNodeTransformVecMult", TTR("Multiplies vector by transform."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("Add", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Sums two transforms."), { VisualShaderNodeTransformOp::OP_ADD }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Divide", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Divides two transforms."), { VisualShaderNodeTransformOp::OP_A_DIV_B }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Multiply", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Multiplies two transforms."), { VisualShaderNodeTransformOp::OP_AxB }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("MultiplyComp", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Performs per-component multiplication of two transforms."), { VisualShaderNodeTransformOp::OP_AxB_COMP }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("Subtract", "Transform", "Operators", "VisualShaderNodeTransformOp", TTR("Subtracts two transforms."), { VisualShaderNodeTransformOp::OP_A_MINUS_B }, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformVectorMult", "Transform", "Operators", "VisualShaderNodeTransformVecMult", TTR("Multiplies vector by transform."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); - add_options.push_back(AddOption("TransformConstant", "Transform", "Variables", "VisualShaderNodeTransformConstant", TTR("Transform constant."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); - add_options.push_back(AddOption("TransformUniform", "Transform", "Variables", "VisualShaderNodeTransformUniform", TTR("Transform uniform."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformConstant", "Transform", "Variables", "VisualShaderNodeTransformConstant", TTR("Transform constant."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); + add_options.push_back(AddOption("TransformUniform", "Transform", "Variables", "VisualShaderNodeTransformUniform", TTR("Transform uniform."), {}, VisualShaderNode::PORT_TYPE_TRANSFORM)); // VECTOR - add_options.push_back(AddOption("VectorFunc", "Vector", "Common", "VisualShaderNodeVectorFunc", TTR("Vector function."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("VectorOp", "Vector", "Common", "VisualShaderNodeVectorOp", TTR("Vector operator."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - - add_options.push_back(AddOption("VectorCompose", "Vector", "Composition", "VisualShaderNodeVectorCompose", TTR("Composes vector from three scalars."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("VectorDecompose", "Vector", "Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes vector to three scalars."))); - - add_options.push_back(AddOption("Abs", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ACos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ACosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ASin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ASinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ATan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ATan2", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("ATanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Ceil", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Clamp", "Vector", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), VisualShaderNodeClamp::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Cos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("CosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Cross", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Calculates the cross product of two vectors."), VisualShaderNodeVectorOp::OP_CROSS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Degrees", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Distance", "Vector", "Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Dot", "Vector", "Functions", "VisualShaderNodeDotProduct", TTR("Calculates the dot product of two vectors."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Exp", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Exp2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("FaceForward", "Vector", "Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), VisualShaderNodeVectorFunc::FUNC_FRAC, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("InverseSqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Length", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); - add_options.push_back(AddOption("Log", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Log2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Max", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), VisualShaderNodeVectorOp::OP_MAX, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Min", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), VisualShaderNodeVectorOp::OP_MIN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Mix", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), VisualShaderNodeMix::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("MixS", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), VisualShaderNodeMix::OP_TYPE_VECTOR_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("MultiplyAdd", "Vector", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Negate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Normalize", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("OneMinus", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Pow", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), VisualShaderNodeVectorOp::OP_POW, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Radians", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Reciprocal", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Reflect", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Refract", "Vector", "Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Round", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("RoundEven", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Saturate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sign", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Sqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SmoothStep", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), VisualShaderNodeSmoothStep::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("SmoothStepS", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Step", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( vector(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), VisualShaderNodeStep::OP_TYPE_VECTOR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("StepS", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), VisualShaderNodeStep::OP_TYPE_VECTOR_SCALAR, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Tan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("TanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Trunc", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNode::PORT_TYPE_VECTOR)); - - add_options.push_back(AddOption("Add", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Adds vector to vector."), VisualShaderNodeVectorOp::OP_ADD, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Divide", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Divides vector by vector."), VisualShaderNodeVectorOp::OP_DIV, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Multiply", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Multiplies vector by vector."), VisualShaderNodeVectorOp::OP_MUL, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Remainder", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two vectors."), VisualShaderNodeVectorOp::OP_MOD, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("Subtract", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Subtracts vector from vector."), VisualShaderNodeVectorOp::OP_SUB, VisualShaderNode::PORT_TYPE_VECTOR)); - - add_options.push_back(AddOption("VectorConstant", "Vector", "Variables", "VisualShaderNodeVec3Constant", TTR("Vector constant."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); - add_options.push_back(AddOption("VectorUniform", "Vector", "Variables", "VisualShaderNodeVec3Uniform", TTR("Vector uniform."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("VectorFunc", "Vector", "Common", "VisualShaderNodeVectorFunc", TTR("Vector function."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("VectorOp", "Vector", "Common", "VisualShaderNodeVectorOp", TTR("Vector operator."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("VectorCompose", "Vector", "Common", "VisualShaderNodeVectorCompose", TTR("Composes vector from scalars."))); + add_options.push_back(AddOption("VectorDecompose", "Vector", "Common", "VisualShaderNodeVectorDecompose", TTR("Decomposes vector to scalars."))); + + add_options.push_back(AddOption("Vector2Compose", "Vector", "Composition", "VisualShaderNodeVectorCompose", TTR("Composes 2D vector from two scalars."), { VisualShaderNodeVectorCompose::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Vector2Decompose", "Vector", "Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes 2D vector to two scalars."), { VisualShaderNodeVectorDecompose::OP_TYPE_VECTOR_2D })); + add_options.push_back(AddOption("Vector3Compose", "Vector", "Composition", "VisualShaderNodeVectorCompose", TTR("Composes 3D vector from three scalars."), { VisualShaderNodeVectorCompose::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Vector3Decompose", "Vector", "Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes 3D vector to three scalars."), { VisualShaderNodeVectorDecompose::OP_TYPE_VECTOR_3D })); + add_options.push_back(AddOption("Vector4Compose", "Vector", "Composition", "VisualShaderNodeVectorCompose", TTR("Composes 4D vector from four scalars."), { VisualShaderNodeVectorCompose::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Vector4Decompose", "Vector", "Composition", "VisualShaderNodeVectorDecompose", TTR("Decomposes 4D vector to four scalars."), { VisualShaderNodeVectorDecompose::OP_TYPE_VECTOR_4D })); + + add_options.push_back(AddOption("Abs", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Abs", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Abs", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the absolute value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ABS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ACos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ACos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ACos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ACosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ACosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ACosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ACOSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ASin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ASin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ASin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ASinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ASinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ASinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ASINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ATan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ATan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ATan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the arc-tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ATan2", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ATan2", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ATan2", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the arc-tangent of the parameters."), { VisualShaderNodeVectorOp::OP_ATAN2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("ATanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("ATanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("ATanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Ceil", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Ceil", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Ceil", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Clamp", "Vector", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Clamp", "Vector", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Clamp", "Vector", "Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Cos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Cos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Cos", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("CosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("CosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("CosH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic cosine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_COSH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Cross", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Calculates the cross product of two vectors."), { VisualShaderNodeVectorOp::OP_CROSS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Degrees", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Degrees", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Degrees", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in radians to degrees."), { VisualShaderNodeVectorFunc::FUNC_DEGREES, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("DFdX", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdX", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdX", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_X, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("DFdY", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), { VisualShaderNodeDerivativeFunc::FUNC_Y, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Distance2D", "Vector", "Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), { VisualShaderNodeVectorDistance::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Distance3D", "Vector", "Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), { VisualShaderNodeVectorDistance::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Distance4D", "Vector", "Functions", "VisualShaderNodeVectorDistance", TTR("Returns the distance between two points."), { VisualShaderNodeVectorDistance::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Dot", "Vector", "Functions", "VisualShaderNodeDotProduct", TTR("Calculates the dot product of two vectors."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Exp", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Exp", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Exp", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-e Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Exp2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Exp2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Exp2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 Exponential."), { VisualShaderNodeVectorFunc::FUNC_EXP2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("FaceForward", "Vector", "Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), { VisualShaderNodeFaceForward::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("FaceForward", "Vector", "Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), { VisualShaderNodeFaceForward::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("FaceForward", "Vector", "Functions", "VisualShaderNodeFaceForward", TTR("Returns the vector that points in the same direction as a reference vector. The function has three vector parameters : N, the vector to orient, I, the incident vector, and Nref, the reference vector. If the dot product of I and Nref is smaller than zero the return value is N. Otherwise -N is returned."), { VisualShaderNodeFaceForward::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Floor", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer less than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_FLOOR, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Fract", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Computes the fractional part of the argument."), { VisualShaderNodeVectorFunc::FUNC_FRACT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Fresnel", "Vector", "Functions", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), {}, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("InverseSqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("InverseSqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("InverseSqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse of the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_INVERSE_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Length2D", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Length3D", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Length4D", "Vector", "Functions", "VisualShaderNodeVectorLen", TTR("Calculates the length of a vector."), { VisualShaderNodeVectorLen::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("Log", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Log", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Log", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Natural logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Log2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Log2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Log2", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Base-2 logarithm."), { VisualShaderNodeVectorFunc::FUNC_LOG2, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Max", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), { VisualShaderNodeVectorOp::OP_MAX, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Max", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), { VisualShaderNodeVectorOp::OP_MAX, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Max", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the greater of two values."), { VisualShaderNodeVectorOp::OP_MAX, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Min", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), { VisualShaderNodeVectorOp::OP_MIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Min", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), { VisualShaderNodeVectorOp::OP_MIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Min", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the lesser of two values."), { VisualShaderNodeVectorOp::OP_MIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Mix", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), { VisualShaderNodeMix::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Mix", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), { VisualShaderNodeMix::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Mix", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors."), { VisualShaderNodeMix::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("MixS", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), { VisualShaderNodeMix::OP_TYPE_VECTOR_2D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("MixS", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), { VisualShaderNodeMix::OP_TYPE_VECTOR_3D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("MixS", "Vector", "Functions", "VisualShaderNodeMix", TTR("Linear interpolation between two vectors using scalar."), { VisualShaderNodeMix::OP_TYPE_VECTOR_4D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("MultiplyAdd", "Vector", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), { VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("MultiplyAdd", "Vector", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), { VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("MultiplyAdd", "Vector", "Functions", "VisualShaderNodeMultiplyAdd", TTR("Performs a fused multiply-add operation (a * b + c) on vectors."), { VisualShaderNodeMultiplyAdd::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Negate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Negate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Negate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the opposite value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_NEGATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Normalize", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), { VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Normalize", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), { VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Normalize", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Calculates the normalize product of vector."), { VisualShaderNodeVectorFunc::FUNC_NORMALIZE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("OneMinus", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), { VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("OneMinus", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), { VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("OneMinus", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 - vector"), { VisualShaderNodeVectorFunc::FUNC_ONEMINUS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Pow", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeVectorOp::OP_POW, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Pow", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeVectorOp::OP_POW, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Pow", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the value of the first parameter raised to the power of the second."), { VisualShaderNodeVectorOp::OP_POW, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Radians", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Radians", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Radians", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Converts a quantity in degrees to radians."), { VisualShaderNodeVectorFunc::FUNC_RADIANS, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Reciprocal", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), { VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Reciprocal", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), { VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Reciprocal", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("1.0 / vector"), { VisualShaderNodeVectorFunc::FUNC_RECIPROCAL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Reflect", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), { VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Reflect", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), { VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Reflect", "Vector", "Functions", "VisualShaderNodeVectorOp", TTR("Returns the vector that points in the direction of reflection ( a : incident vector, b : normal vector )."), { VisualShaderNodeVectorOp::OP_REFLECT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Refract", "Vector", "Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Refract", "Vector", "Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Refract", "Vector", "Functions", "VisualShaderNodeVectorRefract", TTR("Returns the vector that points in the direction of refraction."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Round", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Round", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Round", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUND, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("RoundEven", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("RoundEven", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("RoundEven", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest even integer to the parameter."), { VisualShaderNodeVectorFunc::FUNC_ROUNDEVEN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Saturate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Saturate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Saturate", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Clamps the value between 0.0 and 1.0."), { VisualShaderNodeVectorFunc::FUNC_SATURATE, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sign", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Sign", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sign", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Extracts the sign of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIGN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Sin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sin", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SIN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("SinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SinH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic sine of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SINH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Sqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Sqrt", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the square root of the parameter."), { VisualShaderNodeVectorFunc::FUNC_SQRT, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("SmoothStep", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SmoothStep", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SmoothStep", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( vector(edge0), vector(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("SmoothStepS", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_2D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("SmoothStepS", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_3D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("SmoothStepS", "Vector", "Functions", "VisualShaderNodeSmoothStep", TTR("SmoothStep function( scalar(edge0), scalar(edge1), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge0' and 1.0 if 'x' is larger than 'edge1'. Otherwise the return value is interpolated between 0.0 and 1.0 using Hermite polynomials."), { VisualShaderNodeSmoothStep::OP_TYPE_VECTOR_4D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Step", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( vector(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Step", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( vector(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("StepS", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_2D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("StepS", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_3D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("StepS", "Vector", "Functions", "VisualShaderNodeStep", TTR("Step function( scalar(edge), vector(x) ).\n\nReturns 0.0 if 'x' is smaller than 'edge' and otherwise 1.0."), { VisualShaderNodeStep::OP_TYPE_VECTOR_4D_SCALAR }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Sum", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Sum", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Sum", "Vector", "Functions", "VisualShaderNodeDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), { VisualShaderNodeDerivativeFunc::FUNC_SUM, VisualShaderNodeDerivativeFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, true)); + add_options.push_back(AddOption("Tan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Tan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Tan", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TAN, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("TanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("TanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("TanH", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Returns the hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Trunc", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Trunc", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Trunc", "Vector", "Functions", "VisualShaderNodeVectorFunc", TTR("Finds the truncated value of the parameter."), { VisualShaderNodeVectorFunc::FUNC_TRUNC, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + + add_options.push_back(AddOption("Add", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Adds 2D vector to 2D vector."), { VisualShaderNodeVectorOp::OP_ADD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Add", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Adds 3D vector to 3D vector."), { VisualShaderNodeVectorOp::OP_ADD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Add", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Adds 4D vector to 4D vector."), { VisualShaderNodeVectorOp::OP_ADD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Divide", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Divides 2D vector by 2D vector."), { VisualShaderNodeVectorOp::OP_DIV, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Divide", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Divides 3D vector by 3D vector."), { VisualShaderNodeVectorOp::OP_DIV, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Divide", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Divides 4D vector by 4D vector."), { VisualShaderNodeVectorOp::OP_DIV, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Multiply", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 2D vector by 2D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Multiply", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 3D vector by 3D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Multiply", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Multiplies 4D vector by 4D vector."), { VisualShaderNodeVectorOp::OP_MUL, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Remainder", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 2D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Remainder", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 3D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Remainder", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Returns the remainder of the two 4D vectors."), { VisualShaderNodeVectorOp::OP_MOD, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Subtract", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 2D vector from 2D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Subtract", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 3D vector from 3D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Subtract", "Vector", "Operators", "VisualShaderNodeVectorOp", TTR("Subtracts 4D vector from 4D vector."), { VisualShaderNodeVectorOp::OP_SUB, VisualShaderNodeVectorOp::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + + add_options.push_back(AddOption("Vector2Constant", "Vector", "Variables", "VisualShaderNodeVec2Constant", TTR("2D vector constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Vector2Uniform", "Vector", "Variables", "VisualShaderNodeVec2Uniform", TTR("2D vector uniform."), {}, VisualShaderNode::PORT_TYPE_VECTOR_2D)); + add_options.push_back(AddOption("Vector3Constant", "Vector", "Variables", "VisualShaderNodeVec3Constant", TTR("3D vector constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Vector3Uniform", "Vector", "Variables", "VisualShaderNodeVec3Uniform", TTR("3D vector uniform."), {}, VisualShaderNode::PORT_TYPE_VECTOR_3D)); + add_options.push_back(AddOption("Vector4Constant", "Vector", "Variables", "VisualShaderNodeVec4Constant", TTR("4D vector constant."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); + add_options.push_back(AddOption("Vector4Uniform", "Vector", "Variables", "VisualShaderNodeVec4Uniform", TTR("4D vector uniform."), {}, VisualShaderNode::PORT_TYPE_VECTOR_4D)); // SPECIAL add_options.push_back(AddOption("Comment", "Special", "", "VisualShaderNodeComment", TTR("A rectangular area with a description string for better graph organization."))); add_options.push_back(AddOption("Expression", "Special", "", "VisualShaderNodeExpression", TTR("Custom Godot Shader Language expression, with custom amount of input and output ports. This is a direct injection of code into the vertex/fragment/light function, do not use it to write the function declarations inside."))); - add_options.push_back(AddOption("Fresnel", "Special", "", "VisualShaderNodeFresnel", TTR("Returns falloff based on the dot product of surface normal and view direction of camera (pass associated inputs to it)."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); add_options.push_back(AddOption("GlobalExpression", "Special", "", "VisualShaderNodeGlobalExpression", TTR("Custom Godot Shader Language expression, which is placed on top of the resulted shader. You can place various function definitions inside and call it later in the Expressions. You can also declare varyings, uniforms and constants."))); add_options.push_back(AddOption("UniformRef", "Special", "", "VisualShaderNodeUniformRef", TTR("A reference to an existing uniform."))); + add_options.push_back(AddOption("VaryingGetter", "Special", "", "VisualShaderNodeVaryingGetter", TTR("Get varying parameter."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("VaryingSetter", "Special", "", "VisualShaderNodeVaryingSetter", TTR("Set varying parameter."), {}, -1, TYPE_FLAGS_VERTEX | TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); + add_options.push_back(AddOption("VaryingGetter", "Special", "", "VisualShaderNodeVaryingGetter", TTR("Get varying parameter."), {}, -1, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, Shader::MODE_CANVAS_ITEM)); + add_options.push_back(AddOption("VaryingSetter", "Special", "", "VisualShaderNodeVaryingSetter", TTR("Set varying parameter."), {}, -1, TYPE_FLAGS_VERTEX | TYPE_FLAGS_FRAGMENT, Shader::MODE_CANVAS_ITEM)); - add_options.push_back(AddOption("ScalarDerivativeFunc", "Special", "Common", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) Scalar derivative function."), -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("VectorDerivativeFunc", "Special", "Common", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) Vector derivative function."), -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - - add_options.push_back(AddOption("DdX", "Special", "Derivative", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'x' using local differencing."), VisualShaderNodeVectorDerivativeFunc::FUNC_X, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("DdXS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'x' using local differencing."), VisualShaderNodeScalarDerivativeFunc::FUNC_X, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("DdY", "Special", "Derivative", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Derivative in 'y' using local differencing."), VisualShaderNodeVectorDerivativeFunc::FUNC_Y, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("DdYS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Derivative in 'y' using local differencing."), VisualShaderNodeScalarDerivativeFunc::FUNC_Y, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("Sum", "Special", "Derivative", "VisualShaderNodeVectorDerivativeFunc", TTR("(Fragment/Light mode only) (Vector) Sum of absolute derivative in 'x' and 'y'."), VisualShaderNodeVectorDerivativeFunc::FUNC_SUM, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); - add_options.push_back(AddOption("SumS", "Special", "Derivative", "VisualShaderNodeScalarDerivativeFunc", TTR("(Fragment/Light mode only) (Scalar) Sum of absolute derivative in 'x' and 'y'."), VisualShaderNodeScalarDerivativeFunc::FUNC_SUM, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT | TYPE_FLAGS_LIGHT, -1, -1, true)); custom_node_option_idx = add_options.size(); ///////////////////////////////////////////////////////////////////// @@ -4671,81 +5672,47 @@ VisualShaderEditor::VisualShaderEditor() { 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)); -} + property_editor_popup = memnew(PopupPanel); + property_editor_popup->set_min_size(Size2i(180, 0) * EDSCALE); + add_child(property_editor_popup); -bool VisualShaderEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("VisualShader"); + edited_property_holder.instantiate(); } -void VisualShaderEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - //editor->hide_animation_player_editors(); - //editor->animation_panel_make_visible(true); - button->show(); - editor->make_bottom_panel_item_visible(visual_shader_editor); - visual_shader_editor->update_custom_nodes(); - visual_shader_editor->set_process_input(true); - //visual_shader_editor->set_process(true); - } else { - if (visual_shader_editor->is_visible_in_tree()) { - editor->hide_bottom_panel(); - } - button->hide(); - visual_shader_editor->set_process_input(false); - //visual_shader_editor->set_process(false); - } -} - -VisualShaderEditorPlugin::VisualShaderEditorPlugin(EditorNode *p_node) { - editor = p_node; - visual_shader_editor = memnew(VisualShaderEditor); - visual_shader_editor->set_custom_minimum_size(Size2(0, 300) * EDSCALE); - - button = editor->add_bottom_panel_item(TTR("VisualShader"), visual_shader_editor); - button->hide(); -} - -VisualShaderEditorPlugin::~VisualShaderEditorPlugin() { -} - -//////////////// - class VisualShaderNodePluginInputEditor : public OptionButton { GDCLASS(VisualShaderNodePluginInputEditor, OptionButton); + VisualShaderEditor *editor = nullptr; Ref<VisualShaderNodeInput> input; public: void _notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - connect("item_selected", callable_mp(this, &VisualShaderNodePluginInputEditor::_item_selected)); + switch (p_what) { + case NOTIFICATION_READY: { + connect("item_selected", callable_mp(this, &VisualShaderNodePluginInputEditor::_item_selected)); + } break; } } void _item_selected(int p_item) { - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_input_select_item"), input, get_item_text(p_item)); + editor->call_deferred(SNAME("_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[6] = { + 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("bool"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons")), @@ -4768,30 +5735,112 @@ public: //////////////// +class VisualShaderNodePluginVaryingEditor : public OptionButton { + GDCLASS(VisualShaderNodePluginVaryingEditor, OptionButton); + + VisualShaderEditor *editor = nullptr; + Ref<VisualShaderNodeVarying> varying; + +public: + void _notification(int p_what) { + if (p_what == NOTIFICATION_READY) { + connect("item_selected", callable_mp(this, &VisualShaderNodePluginVaryingEditor::_item_selected)); + } + } + + void _item_selected(int p_item) { + editor->call_deferred(SNAME("_varying_select_item"), varying, get_item_text(p_item)); + } + + 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("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("Transform3D"), SNAME("EditorIcons")), + }; + + bool is_getter = Ref<VisualShaderNodeVaryingGetter>(p_varying.ptr()).is_valid(); + + add_item("[None]"); + + int to_select = -1; + for (int i = 0, j = 0; i < varying->get_varyings_count(); i++) { + VisualShader::VaryingMode mode = varying->get_varying_mode_by_index(i); + if (is_getter) { + if (mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT) { + if (p_type != VisualShader::TYPE_LIGHT) { + j++; + continue; + } + } else { + if (p_type != VisualShader::TYPE_FRAGMENT && p_type != VisualShader::TYPE_LIGHT) { + j++; + continue; + } + } + } else { + if (mode == VisualShader::VARYING_MODE_FRAG_TO_LIGHT) { + if (p_type != VisualShader::TYPE_FRAGMENT) { + j++; + continue; + } + } else { + if (p_type != VisualShader::TYPE_VERTEX) { + j++; + continue; + } + } + } + if (varying->get_varying_name() == varying->get_varying_name_by_index(i)) { + to_select = i - j + 1; + } + add_icon_item(type_icon[varying->get_varying_type_by_index(i)], varying->get_varying_name_by_index(i)); + } + + if (to_select >= 0) { + select(to_select); + } + } +}; + +//////////////// + class VisualShaderNodePluginUniformRefEditor : public OptionButton { GDCLASS(VisualShaderNodePluginUniformRefEditor, OptionButton); + VisualShaderEditor *editor = nullptr; Ref<VisualShaderNodeUniformRef> uniform_ref; public: void _notification(int p_what) { - if (p_what == NOTIFICATION_READY) { - connect("item_selected", callable_mp(this, &VisualShaderNodePluginUniformRefEditor::_item_selected)); + switch (p_what) { + case NOTIFICATION_READY: { + connect("item_selected", callable_mp(this, &VisualShaderNodePluginUniformRefEditor::_item_selected)); + } break; } } void _item_selected(int p_item) { - VisualShaderEditor::get_singleton()->call_deferred(SNAME("_uniform_select_item"), uniform_ref, get_item_text(p_item)); + editor->call_deferred(SNAME("_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[7] = { + Ref<Texture2D> type_icon[] = { EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("float"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("int"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector2"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Color"), SNAME("EditorIcons")), EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("ImageTexture"), SNAME("EditorIcons")), @@ -4816,8 +5865,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: @@ -4834,23 +5884,26 @@ public: undo_redo->add_undo_property(node.ptr(), p_property, node->get(p_property)); if (p_value.get_type() == Variant::OBJECT) { - RES prev_res = node->get(p_property); - RES curr_res = p_value; + Ref<Resource> prev_res = node->get(p_property); + Ref<Resource> curr_res = p_value; if (curr_res.is_null()) { - undo_redo->add_do_method(this, "_open_inspector", (RES)parent_resource.ptr()); + undo_redo->add_do_method(this, "_open_inspector", (Ref<Resource>)parent_resource.ptr()); } else { - undo_redo->add_do_method(this, "_open_inspector", (RES)curr_res.ptr()); + undo_redo->add_do_method(this, "_open_inspector", (Ref<Resource>)curr_res.ptr()); } if (!prev_res.is_null()) { - undo_redo->add_undo_method(this, "_open_inspector", (RES)prev_res.ptr()); + undo_redo->add_undo_method(this, "_open_inspector", (Ref<Resource>)prev_res.ptr()); } else { - undo_redo->add_undo_method(this, "_open_inspector", (RES)parent_resource.ptr()); + undo_redo->add_undo_method(this, "_open_inspector", (Ref<Resource>)parent_resource.ptr()); } } if (p_property != "constant") { - undo_redo->add_do_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_node_deferred", shader_type, node_id); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton()->get_graph_plugin(), "update_node_deferred", shader_type, node_id); + VisualShaderGraphPlugin *graph_plugin = editor->get_graph_plugin(); + if (graph_plugin) { + undo_redo->add_do_method(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(); @@ -4866,15 +5919,15 @@ public: } } - void _resource_selected(const String &p_path, RES p_resource) { + void _resource_selected(const String &p_path, Ref<Resource> p_resource) { _open_inspector(p_resource); } - void _open_inspector(RES p_resource) { - EditorNode::get_singleton()->get_inspector()->edit(p_resource.ptr()); + void _open_inspector(Ref<Resource> p_resource) { + InspectorDock::get_inspector_singleton()->edit(p_resource.ptr()); } - bool updating; + bool updating = false; Ref<VisualShaderNode> node; Vector<EditorProperty *> properties; Vector<Label *> prop_names; @@ -4885,7 +5938,8 @@ public: } } - void setup(Ref<Resource> p_parent_resource, Vector<EditorProperty *> p_properties, const Vector<StringName> &p_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; @@ -4901,7 +5955,11 @@ public: Label *prop_name = memnew(Label); String prop_name_str = p_names[i]; - prop_name_str = prop_name_str.capitalize() + ":"; + if (p_overrided_names.has(p_names[i])) { + prop_name_str = p_overrided_names[p_names[i]] + ":"; + } else { + prop_name_str = prop_name_str.capitalize() + ":"; + } prop_name->set_text(prop_name_str); prop_name->set_visible(false); hbox->add_child(prop_name); @@ -4930,18 +5988,24 @@ public: }; Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) { + Ref<VisualShader> p_shader = Ref<VisualShader>(p_parent_resource.ptr()); + + if (p_shader.is_valid() && (p_node->is_class("VisualShaderNodeVaryingGetter") || p_node->is_class("VisualShaderNodeVaryingSetter"))) { + VisualShaderNodePluginVaryingEditor *editor = memnew(VisualShaderNodePluginVaryingEditor); + editor->setup(vseditor, p_node, p_shader->get_shader_type()); + return editor; + } + if (p_node->is_class("VisualShaderNodeUniformRef")) { - //create input - VisualShaderNodePluginUniformRefEditor *uniform_editor = memnew(VisualShaderNodePluginUniformRefEditor); - uniform_editor->setup(p_node); - return uniform_editor; + VisualShaderNodePluginUniformRefEditor *editor = memnew(VisualShaderNodePluginUniformRefEditor); + editor->setup(vseditor, p_node); + return editor; } if (p_node->is_class("VisualShaderNodeInput")) { - //create input - VisualShaderNodePluginInputEditor *input_editor = memnew(VisualShaderNodePluginInputEditor); - input_editor->setup(p_node); - return input_editor; + VisualShaderNodePluginInputEditor *editor = memnew(VisualShaderNodePluginInputEditor); + editor->setup(vseditor, p_node); + return editor; } Vector<StringName> properties = p_node->get_editable_properties(); @@ -4982,6 +6046,8 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); } else if (Object::cast_to<EditorPropertyTransform3D>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) { prop->set_custom_minimum_size(Size2(250 * EDSCALE, 0)); + } else if (Object::cast_to<EditorPropertyQuaternion>(prop)) { + prop->set_custom_minimum_size(Size2(320 * EDSCALE, 0)); } else if (Object::cast_to<EditorPropertyFloat>(prop)) { prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); } else if (Object::cast_to<EditorPropertyEnum>(prop)) { @@ -4993,28 +6059,33 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par properties.push_back(pinfo[i].name); } VisualShaderNodePluginDefaultEditor *editor = memnew(VisualShaderNodePluginDefaultEditor); - editor->setup(p_parent_resource, editors, properties, p_node); + editor->setup(vseditor, p_parent_resource, editors, properties, p_node->get_editable_properties_names(), p_node); return editor; } void EditorPropertyShaderMode::_option_selected(int p_which) { - //will not use this, instead will do all the logic setting manually - //emit_signal(SNAME("property_changed"), get_edited_property(), p_which); - Ref<VisualShader> visual_shader(Object::cast_to<VisualShader>(get_edited_object())); - if (visual_shader->get_mode() == p_which) { return; } + 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(); undo_redo->create_action(TTR("Visual Shader Mode Changed")); //do is easy undo_redo->add_do_method(visual_shader.ptr(), "set_mode", p_which); undo_redo->add_undo_method(visual_shader.ptr(), "set_mode", visual_shader->get_mode()); - undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_set_mode", p_which); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_set_mode", visual_shader->get_mode()); + undo_redo->add_do_method(editor, "_set_mode", p_which); + undo_redo->add_undo_method(editor, "_set_mode", visual_shader->get_mode()); //now undo is hell @@ -5053,12 +6124,27 @@ void EditorPropertyShaderMode::_option_selected(int p_which) { } } - undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_options_menu"); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_options_menu"); + //4. delete varyings (if needed) + if (p_which == VisualShader::MODE_PARTICLES || p_which == VisualShader::MODE_SKY || p_which == VisualShader::MODE_FOG) { + int var_count = visual_shader->get_varyings_count(); - //update graph - undo_redo->add_do_method(VisualShaderEditor::get_singleton(), "_update_graph"); - undo_redo->add_undo_method(VisualShaderEditor::get_singleton(), "_update_graph"); + if (var_count > 0) { + for (int i = 0; i < var_count; i++) { + const VisualShader::Varying *var = visual_shader->get_varying_by_index(i); + undo_redo->add_do_method(visual_shader.ptr(), "remove_varying", var->name); + undo_redo->add_undo_method(visual_shader.ptr(), "add_varying", var->name, var->mode, var->type); + } + + undo_redo->add_do_method(editor, "_update_varyings"); + undo_redo->add_undo_method(editor, "_update_varyings"); + } + } + + undo_redo->add_do_method(editor, "_update_nodes"); + undo_redo->add_undo_method(editor, "_update_nodes"); + + undo_redo->add_do_method(editor, "_update_graph"); + undo_redo->add_undo_method(editor, "_update_graph"); undo_redo->commit_action(); } @@ -5090,28 +6176,20 @@ EditorPropertyShaderMode::EditorPropertyShaderMode() { } bool EditorInspectorShaderModePlugin::can_handle(Object *p_object) { - return true; //can handle everything -} - -void EditorInspectorShaderModePlugin::parse_begin(Object *p_object) { - //do none + 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) { if (p_path == "mode" && p_object->is_class("VisualShader") && p_type == Variant::INT) { - EditorPropertyShaderMode *editor = memnew(EditorPropertyShaderMode); + EditorPropertyShaderMode *mode_editor = memnew(EditorPropertyShaderMode); 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; } - return false; //can be overridden, although it will most likely be last anyway -} - -void EditorInspectorShaderModePlugin::parse_end() { - //do none + return false; } ////////////////////////////////// @@ -5128,7 +6206,9 @@ void VisualShaderNodePortPreview::_shader_changed() { preview_shader.instantiate(); preview_shader->set_code(shader_code); for (int i = 0; i < default_textures.size(); i++) { - preview_shader->set_default_texture_param(default_textures[i].name, default_textures[i].param); + for (int j = 0; j < default_textures[i].params.size(); j++) { + preview_shader->set_default_texture_param(default_textures[i].name, default_textures[i].params[j], j); + } } Ref<ShaderMaterial> material; @@ -5137,8 +6217,8 @@ void VisualShaderNodePortPreview::_shader_changed() { //find if a material is also being edited and copy parameters to this one - for (int i = EditorNode::get_singleton()->get_editor_history()->get_path_size() - 1; i >= 0; i--) { - Object *object = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_history()->get_path_object(i)); + for (int i = EditorNode::get_singleton()->get_editor_selection_history()->get_path_size() - 1; i >= 0; i--) { + Object *object = ObjectDB::get_instance(EditorNode::get_singleton()->get_editor_selection_history()->get_path_object(i)); ShaderMaterial *src_mat; if (!object) { continue; @@ -5152,7 +6232,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)); } @@ -5173,28 +6253,36 @@ 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) { - if (p_what == NOTIFICATION_DRAW) { - Vector<Vector2> points; - Vector<Vector2> uvs; - Vector<Color> colors; - points.push_back(Vector2()); - uvs.push_back(Vector2(0, 0)); - colors.push_back(Color(1, 1, 1, 1)); - points.push_back(Vector2(get_size().width, 0)); - uvs.push_back(Vector2(1, 0)); - colors.push_back(Color(1, 1, 1, 1)); - points.push_back(get_size()); - uvs.push_back(Vector2(1, 1)); - colors.push_back(Color(1, 1, 1, 1)); - points.push_back(Vector2(0, get_size().height)); - uvs.push_back(Vector2(0, 1)); - colors.push_back(Color(1, 1, 1, 1)); - - draw_primitive(points, colors, uvs); + switch (p_what) { + case NOTIFICATION_DRAW: { + Vector<Vector2> points = { + Vector2(), + Vector2(get_size().width, 0), + get_size(), + Vector2(0, get_size().height) + }; + + Vector<Vector2> uvs = { + Vector2(0, 0), + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1) + }; + + Vector<Color> colors = { + Color(1, 1, 1, 1), + Color(1, 1, 1, 1), + Color(1, 1, 1, 1), + Color(1, 1, 1, 1) + }; + + draw_primitive(points, colors, uvs); + } break; } } diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 9f24c5af72..b846c34f9e 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -31,25 +31,37 @@ #ifndef VISUAL_SHADER_EDITOR_PLUGIN_H #define VISUAL_SHADER_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "editor/plugins/curve_editor_plugin.h" -#include "editor/property_editor.h" -#include "scene/gui/button.h" -#include "scene/gui/graph_edit.h" -#include "scene/gui/popup.h" -#include "scene/gui/tree.h" +#include "editor/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 VisualShaderNodePlugin : public RefCounted { GDCLASS(VisualShaderNodePlugin, RefCounted); protected: + VisualShaderEditor *vseditor = nullptr; + +protected: static void _bind_methods(); - GDVIRTUAL2RC(Object *, _create_editor, RES, Ref<VisualShaderNode>) + GDVIRTUAL2RC(Object *, _create_editor, Ref<Resource>, Ref<VisualShaderNode>) public: + void set_editor(VisualShaderEditor *p_editor); virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node); }; @@ -57,6 +69,8 @@ class VisualShaderGraphPlugin : public RefCounted { GDCLASS(VisualShaderGraphPlugin, RefCounted); private: + VisualShaderEditor *editor = nullptr; + struct InputPort { Button *default_input_button = nullptr; }; @@ -71,8 +85,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; @@ -80,18 +94,19 @@ private: }; Ref<VisualShader> visual_shader; - Map<int, Link> links; + HashMap<int, Link> links; List<VisualShader::Connection> connections; bool dirty = false; - Color vector_expanded_color[3]; + Color vector_expanded_color[4]; protected: static void _bind_methods(); public: + void set_editor(VisualShaderEditor *p_editor); void register_shader(VisualShader *p_visual_shader); - void set_connections(List<VisualShader::Connection> &p_connections); + void set_connections(const List<VisualShader::Connection> &p_connections); void register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node); void register_output_port(int p_id, int p_port, TextureButton *p_button); void register_uniform_name(int p_id, LineEdit *p_uniform_name); @@ -111,7 +126,6 @@ public: void disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port); void show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id); void set_node_position(VisualShader::Type p_type, int p_id, const Vector2 &p_position); - void set_node_size(VisualShader::Type p_type, int p_id, const Vector2 &p_size); void refresh_node_ports(VisualShader::Type p_type, int p_node); void set_input_port_default_value(VisualShader::Type p_type, int p_node_id, int p_port_id, Variant p_value); void update_uniform_refs(); @@ -128,45 +142,75 @@ 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; - int editing_node; - int editing_port; + PopupPanel *property_editor_popup = nullptr; + EditorProperty *property_editor = nullptr; + int editing_node = -1; + int editing_port = -1; + Ref<VisualShaderEditedProperty> edited_property_holder; Ref<VisualShader> visual_shader; - GraphEdit *graph; - Button *add_node; - Button *preview_shader; + GraphEdit *graph = nullptr; + Button *add_node = nullptr; + Button *varying_button = nullptr; + PopupMenu *varying_options = nullptr; + Button *preview_shader = nullptr; OptionButton *edit_type = nullptr; - OptionButton *edit_type_standard; - OptionButton *edit_type_particles; - OptionButton *edit_type_sky; - CheckBox *custom_mode_box; + OptionButton *edit_type_standard = nullptr; + OptionButton *edit_type_particles = nullptr; + OptionButton *edit_type_sky = nullptr; + OptionButton *edit_type_fog = nullptr; + CheckBox *custom_mode_box = nullptr; bool custom_mode_enabled = false; - bool pending_update_preview; - bool shader_error; - Window *preview_window; - VBoxContainer *preview_vbox; - CodeEdit *preview_text; - Ref<CodeHighlighter> syntax_highlighter; - PanelContainer *error_panel; - Label *error_label; + bool pending_update_preview = false; + bool shader_error = false; + Window *preview_window = nullptr; + VBoxContainer *preview_vbox = nullptr; + CodeEdit *preview_text = nullptr; + Ref<CodeHighlighter> syntax_highlighter = nullptr; + PanelContainer *error_panel = nullptr; + Label *error_label = nullptr; - UndoRedo *undo_redo; + UndoRedo *undo_redo = nullptr; Point2 saved_node_pos; - bool saved_node_pos_dirty; + bool saved_node_pos_dirty = false; - ConfirmationDialog *members_dialog; + ConfirmationDialog *members_dialog = nullptr; VisualShaderNode::PortType members_input_port_type = VisualShaderNode::PORT_TYPE_MAX; VisualShaderNode::PortType members_output_port_type = VisualShaderNode::PORT_TYPE_MAX; - PopupMenu *popup_menu; + PopupMenu *popup_menu = nullptr; PopupMenu *constants_submenu = nullptr; - MenuButton *tools; + MenuButton *tools = nullptr; + + ConfirmationDialog *add_varying_dialog = nullptr; + OptionButton *varying_type = nullptr; + LineEdit *varying_name = nullptr; + OptionButton *varying_mode = nullptr; + Label *varying_error_label = nullptr; + + ConfirmationDialog *remove_varying_dialog = nullptr; + Tree *varyings = nullptr; PopupPanel *comment_title_change_popup = nullptr; LineEdit *comment_title_change_edit = nullptr; @@ -180,7 +224,8 @@ class VisualShaderEditor : public VBoxContainer { enum ShaderModeFlags { MODE_FLAGS_SPATIAL_CANVASITEM = 1, MODE_FLAGS_SKY = 2, - MODE_FLAGS_PARTICLES = 4 + MODE_FLAGS_PARTICLES = 4, + MODE_FLAGS_FOG = 8, }; int mode = MODE_FLAGS_SPATIAL_CANVASITEM; @@ -203,6 +248,10 @@ class VisualShaderEditor : public VBoxContainer { TYPE_FLAGS_SKY = 1, }; + enum FogTypeFlags { + TYPE_FLAGS_FOG = 1, + }; + enum ToolsMenuOptions { EXPAND_ALL, COLLAPSE_ALL @@ -211,10 +260,12 @@ class VisualShaderEditor : public VBoxContainer { enum NodeMenuOptions { ADD, SEPARATOR, // ignore + CUT, COPY, PASTE, DELETE, DUPLICATE, + CLEAR_COPY_BUFFER, SEPARATOR2, // ignore FLOAT_CONSTANTS, CONVERT_CONSTANTS_TO_UNIFORMS, @@ -224,15 +275,26 @@ class VisualShaderEditor : public VBoxContainer { SET_COMMENT_DESCRIPTION, }; - Tree *members; - AcceptDialog *alert; - LineEdit *node_filter; - RichTextLabel *node_desc; - Label *highend_label; + enum class VaryingMenuOptions { + ADD, + REMOVE, + }; + + Tree *members = nullptr; + AcceptDialog *alert = nullptr; + LineEdit *node_filter = nullptr; + RichTextLabel *node_desc = nullptr; + Label *highend_label = nullptr; void _tools_menu_option(int p_idx); void _show_members_dialog(bool at_mouse_pos, VisualShaderNode::PortType p_input_port_type = VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PortType p_output_port_type = VisualShaderNode::PORT_TYPE_MAX); + void _show_varying_menu(); + void _varying_menu_id_pressed(int p_idx); + void _show_add_varying_dialog(); + void _show_remove_varying_dialog(); + + void _update_nodes(); void _update_graph(); struct AddOption { @@ -240,44 +302,25 @@ class VisualShaderEditor : public VBoxContainer { String category; String type; String description; - int sub_func = 0; - String sub_func_str; + Vector<Variant> ops; Ref<Script> script; int mode = 0; int return_type = 0; int func = 0; - float value = 0; bool highend = false; bool is_custom = false; int temp_idx = 0; - AddOption(const String &p_name = String(), const String &p_category = String(), const String &p_sub_category = String(), const String &p_type = String(), const String &p_description = String(), int p_sub_func = -1, int p_return_type = -1, int p_mode = -1, int p_func = -1, float p_value = -1, bool p_highend = false) { - name = p_name; - type = p_type; - category = p_category + "/" + p_sub_category; - description = p_description; - sub_func = p_sub_func; - return_type = p_return_type; - mode = p_mode; - func = p_func; - value = p_value; - highend = p_highend; - is_custom = false; - } - - AddOption(const String &p_name, const String &p_category, const String &p_sub_category, const String &p_type, const String &p_description, const String &p_sub_func, int p_return_type = -1, int p_mode = -1, int p_func = -1, float p_value = -1, bool p_highend = false) { + AddOption(const String &p_name = String(), const String &p_category = String(), const String &p_sub_category = String(), const String &p_type = String(), const String &p_description = String(), const Vector<Variant> &p_ops = Vector<Variant>(), int p_return_type = -1, int p_mode = -1, int p_func = -1, bool p_highend = false) { name = p_name; type = p_type; category = p_category + "/" + p_sub_category; description = p_description; - sub_func = 0; - sub_func_str = p_sub_func; + ops = p_ops; return_type = p_return_type; mode = p_mode; func = p_func; - value = p_value; highend = p_highend; - is_custom = false; } }; struct _OptionComparator { @@ -300,8 +343,10 @@ class VisualShaderEditor : public VBoxContainer { void _draw_color_over_button(Object *obj, Color p_color); - void _setup_node(VisualShaderNode *p_node, int p_op_idx); - void _add_node(int p_idx, int p_op_idx = -1, String p_resource_path = "", int p_node_idx = -1); + void _setup_node(VisualShaderNode *p_node, const Vector<Variant> &p_ops); + void _add_node(int p_idx, const Vector<Variant> &p_ops, String p_resource_path = "", int p_node_idx = -1); + void _add_varying(const String &p_name, VisualShader::VaryingMode p_mode, VisualShader::VaryingType p_type); + void _remove_varying(const String &p_name); void _update_options_menu(); void _set_mode(int p_which); @@ -311,8 +356,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; @@ -323,7 +366,7 @@ class VisualShaderEditor : public VBoxContainer { bool drag_dirty = false; void _node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node); void _nodes_dragged(); - bool updating; + bool updating = false; void _connection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); void _disconnection_request(const String &p_from, int p_from_index, const String &p_to, int p_to_index); @@ -333,22 +376,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 _removed_from_graph(); + void _delete_nodes_request(const TypedArray<StringName> &p_nodes); void _node_changed(int p_id); void _edit_port_default_input(Object *p_button, int p_node, int p_port); - void _port_edited(); + void _port_edited(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); - int to_node; - int to_slot; - int from_node; - int from_slot; + int to_node = -1; + int to_slot = -1; + int from_node = -1; + int from_slot = -1; - Set<int> selected_constants; - Set<int> selected_uniforms; + HashSet<int> selected_constants; + HashSet<int> selected_uniforms; int selected_comment = -1; int selected_float_constant = -1; @@ -376,19 +417,28 @@ class VisualShaderEditor : public VBoxContainer { void _port_name_focus_out(Object *line_edit, int p_node_id, int p_port_id, bool p_output); - void _dup_copy_nodes(int p_type, List<int> &r_nodes, Set<int> &r_excluded); - void _dup_update_excluded(int p_type, Set<int> &r_excluded); - void _dup_paste_nodes(int p_type, int p_pasted_type, List<int> &r_nodes, Set<int> &r_excluded, const Vector2 &p_offset, bool p_select); + struct CopyItem { + int id; + Ref<VisualShaderNode> node; + Vector2 position; + Vector2 size; + String group_inputs; + String group_outputs; + String expression; + bool disabled = false; + }; + + void _dup_copy_nodes(int p_type, List<CopyItem> &r_nodes, List<VisualShader::Connection> &r_connections); + void _dup_paste_nodes(int p_type, List<CopyItem> &r_items, const List<VisualShader::Connection> &p_connections, const Vector2 &p_offset, bool p_duplicate); void _duplicate_nodes(); - Vector2 selection_center; - int copy_type; // shader type - List<int> copy_nodes_buffer; - Set<int> copy_nodes_excluded_buffer; + static Vector2 selection_center; + static List<CopyItem> copy_items_buffer; + static List<VisualShader::Connection> copy_connections_buffer; - void _clear_buffer(); - void _copy_nodes(); + void _clear_copy_buffer(); + void _copy_nodes(bool p_cut); void _paste_nodes(bool p_use_custom_position = false, const Vector2 &p_custom_position = Vector2()); Vector<Ref<VisualShaderNodePlugin>> plugins; @@ -399,6 +449,7 @@ class VisualShaderEditor : public VBoxContainer { void _input_select_item(Ref<VisualShaderNodeInput> input, String name); void _uniform_select_item(Ref<VisualShaderNodeUniformRef> p_uniform, String p_name); + void _varying_select_item(Ref<VisualShaderNodeVarying> p_varying, String p_name); void _float_constant_selected(int p_which); @@ -430,6 +481,13 @@ class VisualShaderEditor : public VBoxContainer { void _member_create(); void _member_cancel(); + void _varying_create(); + void _varying_name_changed(const String &p_text); + void _varying_deleted(); + void _varying_selected(); + void _varying_unselected(); + void _update_varying_tree(); + Vector2 menu_point; void _node_menu_id_pressed(int p_idx); @@ -440,7 +498,8 @@ 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(); @@ -449,11 +508,10 @@ protected: static void _bind_methods(); public: - void update_custom_nodes(); + void update_nodes(); void add_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); void remove_plugin(const Ref<VisualShaderNodePlugin> &p_plugin); - static VisualShaderEditor *get_singleton() { return singleton; } VisualShaderGraphPlugin *get_graph_plugin() { return graph_plugin.ptr(); } void clear_custom_types(); @@ -464,24 +522,6 @@ public: VisualShaderEditor(); }; -class VisualShaderEditorPlugin : public EditorPlugin { - GDCLASS(VisualShaderEditorPlugin, EditorPlugin); - - VisualShaderEditor *visual_shader_editor; - EditorNode *editor; - Button *button; - -public: - virtual String get_name() const override { return "VisualShader"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - VisualShaderEditorPlugin(EditorNode *p_node); - ~VisualShaderEditorPlugin(); -}; - class VisualShaderNodePluginDefault : public VisualShaderNodePlugin { GDCLASS(VisualShaderNodePluginDefault, VisualShaderNodePlugin); @@ -491,7 +531,7 @@ public: class EditorPropertyShaderMode : public EditorProperty { GDCLASS(EditorPropertyShaderMode, EditorProperty); - OptionButton *options; + OptionButton *options = nullptr; void _option_selected(int p_which); @@ -510,9 +550,7 @@ class EditorInspectorShaderModePlugin : public 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; - virtual void parse_end() override; }; class VisualShaderNodePortPreview : public Control { diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index 5bbc0c9dd5..e3b2be33df 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -30,11 +30,14 @@ #include "voxel_gi_editor_plugin.h" +#include "editor/editor_file_dialog.h" +#include "editor/editor_node.h" + void VoxelGIEditorPlugin::_bake() { if (voxel_gi) { if (voxel_gi->get_probe_data().is_null()) { - String path = get_tree()->get_edited_scene_root()->get_filename(); - if (path == String()) { + String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); + if (path.is_empty()) { path = "res://" + voxel_gi->get_name() + "_data.res"; } else { String ext = path.get_extension(); @@ -62,36 +65,43 @@ bool VoxelGIEditorPlugin::handles(Object *p_object) const { } void VoxelGIEditorPlugin::_notification(int p_what) { - if (p_what == NOTIFICATION_PROCESS) { - if (!voxel_gi) { - return; - } + switch (p_what) { + case NOTIFICATION_PROCESS: { + if (!voxel_gi) { + return; + } - const Vector3i size = voxel_gi->get_estimated_cell_size(); - String text = vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z); - const int data_size = 4; - const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); - text += " - " + vformat(TTR("VRAM Size: %s MB"), String::num(size_mb, 2)); + // Set information tooltip on the Bake button. This information is useful + // to optimize performance (video RAM size) and reduce light leaking (individual cell size). - if (bake_info->get_text() == text) { - return; - } + const Vector3i size = voxel_gi->get_estimated_cell_size(); - // Color the label depending on the estimated performance level. - Color color; - if (size_mb <= 16.0 + CMP_EPSILON) { - // Fast. - color = bake_info->get_theme_color(SNAME("success_color"), SNAME("Editor")); - } else if (size_mb <= 64.0 + CMP_EPSILON) { - // Medium. - color = bake_info->get_theme_color(SNAME("warning_color"), SNAME("Editor")); - } else { - // Slow. - color = bake_info->get_theme_color(SNAME("error_color"), SNAME("Editor")); - } - bake_info->add_theme_color_override("font_color", color); + const Vector3 extents = voxel_gi->get_extents(); + + const int data_size = 4; + const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); + // Add a qualitative measurement to help the user assess whether a VoxelGI node is using a lot of VRAM. + String size_quality; + if (size_mb < 16.0) { + size_quality = TTR("Low"); + } else if (size_mb < 64.0) { + size_quality = TTR("Moderate"); + } else { + size_quality = TTR("High"); + } + + String text; + text += vformat(TTR("Subdivisions: %s"), vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z)) + "\n"; + text += vformat(TTR("Cell size: %s"), vformat(String::utf8("%.3f × %.3f × %.3f"), extents.x / size.x, extents.y / size.y, extents.z / size.z)) + "\n"; + text += vformat(TTR("Video RAM size: %s MB (%s)"), String::num(size_mb, 2), size_quality); + + // Only update the tooltip when needed to avoid constant redrawing. + if (bake->get_tooltip(Point2()) == text) { + return; + } - bake_info->set_text(text); + bake->set_tooltip(text); + } break; } } @@ -110,7 +120,7 @@ EditorProgress *VoxelGIEditorPlugin::tmp_progress = nullptr; void VoxelGIEditorPlugin::bake_func_begin(int p_steps) { ERR_FAIL_COND(tmp_progress != nullptr); - tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake GI Probe"), p_steps)); + tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake VoxelGI"), p_steps)); } void VoxelGIEditorPlugin::bake_func_step(int p_step, const String &p_description) { @@ -129,28 +139,23 @@ void VoxelGIEditorPlugin::_voxel_gi_save_path_and_bake(const String &p_path) { if (voxel_gi) { voxel_gi->bake(); ERR_FAIL_COND(voxel_gi->get_probe_data().is_null()); - ResourceSaver::save(p_path, voxel_gi->get_probe_data(), ResourceSaver::FLAG_CHANGE_PATH); + ResourceSaver::save(voxel_gi->get_probe_data(), p_path, ResourceSaver::FLAG_CHANGE_PATH); } } void VoxelGIEditorPlugin::_bind_methods() { } -VoxelGIEditorPlugin::VoxelGIEditorPlugin(EditorNode *p_node) { - editor = p_node; +VoxelGIEditorPlugin::VoxelGIEditorPlugin() { bake_hb = memnew(HBoxContainer); bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); bake_hb->hide(); bake = memnew(Button); bake->set_flat(true); - bake->set_icon(editor->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); - bake->set_text(TTR("Bake GI Probe")); + bake->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Bake"), SNAME("EditorIcons"))); + bake->set_text(TTR("Bake VoxelGI")); bake->connect("pressed", callable_mp(this, &VoxelGIEditorPlugin::_bake)); bake_hb->add_child(bake); - bake_info = memnew(Label); - bake_info->set_h_size_flags(Control::SIZE_EXPAND_FILL); - bake_info->set_clip_text(true); - bake_hb->add_child(bake_info); add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake_hb); voxel_gi = nullptr; diff --git a/editor/plugins/voxel_gi_editor_plugin.h b/editor/plugins/voxel_gi_editor_plugin.h index 4d3cfe90f6..43d6f71e26 100644 --- a/editor/plugins/voxel_gi_editor_plugin.h +++ b/editor/plugins/voxel_gi_editor_plugin.h @@ -5,8 +5,8 @@ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* 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 */ @@ -28,25 +28,25 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef VOXEL_GIEDITORPLUGIN_H -#define VOXEL_GIEDITORPLUGIN_H +#ifndef VOXEL_GI_EDITOR_PLUGIN_H +#define VOXEL_GI_EDITOR_PLUGIN_H -#include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/voxel_gi.h" #include "scene/resources/material.h" +class EditorFileDialog; +struct EditorProgress; + class VoxelGIEditorPlugin : public EditorPlugin { GDCLASS(VoxelGIEditorPlugin, EditorPlugin); - VoxelGI *voxel_gi; + VoxelGI *voxel_gi = nullptr; - HBoxContainer *bake_hb; - Label *bake_info; - Button *bake; - EditorNode *editor; + HBoxContainer *bake_hb = nullptr; + Button *bake = nullptr; - EditorFileDialog *probe_file; + EditorFileDialog *probe_file = nullptr; static EditorProgress *tmp_progress; static void bake_func_begin(int p_steps); @@ -67,8 +67,8 @@ public: virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - VoxelGIEditorPlugin(EditorNode *p_node); + VoxelGIEditorPlugin(); ~VoxelGIEditorPlugin(); }; -#endif // VOXEL_GIEDITORPLUGIN_H +#endif // VOXEL_GI_EDITOR_PLUGIN_H |