diff options
Diffstat (limited to 'editor/plugins')
111 files changed, 7430 insertions, 4840 deletions
diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index 36a814c30a..72d94e61ca 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -245,11 +245,11 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) 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 +264,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; @@ -326,7 +326,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 +335,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 +346,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 +384,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 +395,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 +443,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 +460,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(); } } @@ -554,7 +554,7 @@ void AbstractPolygon2DEditor::forward_canvas_draw_over_viewport(Control *p_overl 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)); + 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 +599,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); diff --git a/editor/plugins/abstract_polygon_2d_editor.h b/editor/plugins/abstract_polygon_2d_editor.h index 4f9adfff25..5fea8b75d6 100644 --- a/editor/plugins/abstract_polygon_2d_editor.h +++ b/editor/plugins/abstract_polygon_2d_editor.h @@ -106,7 +106,6 @@ protected: 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); diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index ad2d9866fa..f16ea36a23 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -42,7 +42,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 +51,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(); @@ -98,7 +98,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 +111,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 +133,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 +162,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 +185,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(); @@ -222,7 +223,7 @@ 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_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); } @@ -551,7 +552,7 @@ void AnimationNodeBlendSpace1DEditor::_notification(int p_what) { if (error != error_label->get_text()) { error_label->set_text(error); - if (error != String()) { + if (!error.is_empty()) { error_panel->show(); } else { error_panel->hide(); diff --git a/editor/plugins/animation_blend_space_1d_editor.h b/editor/plugins/animation_blend_space_1d_editor.h index fe98a91ab3..503e066894 100644 --- a/editor/plugins/animation_blend_space_1d_editor.h +++ b/editor/plugins/animation_blend_space_1d_editor.h @@ -109,8 +109,6 @@ class AnimationNodeBlendSpace1DEditor : public AnimationTreeNodeEditorPlugin { void _edit_point_pos(double); void _open_editor(); - void _goto_parent(); - EditorFileDialog *open_file; 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..b107478724 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -70,7 +70,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 +79,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(); @@ -121,7 +121,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 +130,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,7 +174,7 @@ 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; @@ -209,14 +209,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 +235,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 +269,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()); @@ -413,14 +412,14 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { 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_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); } 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_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); } @@ -467,8 +466,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()); @@ -503,8 +501,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()); @@ -702,8 +699,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; @@ -765,7 +761,7 @@ void AnimationNodeBlendSpace2DEditor::_notification(int p_what) { if (error != error_label->get_text()) { error_label->set_text(error); - if (error != String()) { + if (!error.is_empty()) { error_panel->show(); } else { error_panel->hide(); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 030d90eeca..d9a6f19384 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -58,7 +58,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 +66,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 +83,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 { @@ -288,7 +292,7 @@ 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); @@ -309,9 +313,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 +337,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")); @@ -431,14 +481,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); @@ -558,7 +600,7 @@ bool AnimationNodeBlendTreeEditor::_update_filters(const Ref<AnimationNode> &ano 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; @@ -710,7 +752,7 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { if (error != error_label->get_text()) { error_label->set_text(error); - if (error != String()) { + if (!error.is_empty()) { error_panel->show(); } else { error_panel->hide(); @@ -735,16 +777,16 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { } if (player) { - for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) { - Ref<AnimationNodeAnimation> an = blend_tree->get_node(E->key()); + 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->get()->set_max(anim->get_length()); + 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->get()->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); + 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)); } } } @@ -779,13 +821,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.find(".") != -1 || new_name.find("/") != -1); if (new_name == prev_name) { return; //nothing to do @@ -877,7 +919,7 @@ 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); @@ -890,6 +932,8 @@ AnimationNodeBlendTreeEditor::AnimationNodeBlendTreeEditor() { 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); @@ -902,16 +946,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), varray(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..68da5c6f79 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.h +++ b/editor/plugins/animation_blend_tree_editor_plugin.h @@ -49,8 +49,8 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { Ref<AnimationNodeBlendTree> blend_tree; GraphEdit *graph; MenuButton *add_node; - Vector2 popup_menu_position; - bool use_popup_menu_position; + Vector2 position_from_popup_menu; + bool use_position_from_popup_menu; PanelContainer *error_panel; Label *error_label; @@ -64,22 +64,28 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { Map<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(), bool 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; @@ -98,7 +104,6 @@ class AnimationNodeBlendTreeEditor : public AnimationTreeNodeEditorPlugin { 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); bool _update_filters(const Ref<AnimationNode> &anode); void _edit_filters(const String &p_which); @@ -106,6 +111,11 @@ 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(); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 830b010d01..14390511de 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -41,6 +41,8 @@ #include "editor/plugins/canvas_item_editor_plugin.h" // For onion skinning. #include "editor/plugins/node_3d_editor_plugin.h" // For onion skinning. #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) { @@ -72,7 +74,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()); } } } @@ -120,7 +122,7 @@ 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()); @@ -188,7 +190,7 @@ void AnimationPlayerEditor::_play_pressed() { current = animation->get_item_text(animation->get_selected()); } - if (current != "") { + if (!current.is_empty()) { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } @@ -205,7 +207,7 @@ void AnimationPlayerEditor::_play_from_pressed() { current = animation->get_item_text(animation->get_selected()); } - if (current != "") { + if (!current.is_empty()) { float time = player->get_current_animation_position(); if (current == player->get_assigned_animation() && player->is_playing()) { @@ -226,7 +228,7 @@ void AnimationPlayerEditor::_play_bw_pressed() { current = animation->get_item_text(animation->get_selected()); } - if (current != "") { + if (!current.is_empty()) { if (current == player->get_assigned_animation()) { player->stop(); //so it won't blend with itself } @@ -243,7 +245,7 @@ void AnimationPlayerEditor::_play_bw_from_pressed() { current = animation->get_item_text(animation->get_selected()); } - 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 @@ -278,7 +280,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { current = animation->get_item_text(animation->get_selected()); } - if (current != "") { + if (!current.is_empty()) { player->set_assigned_animation(current); Ref<Animation> anim = player->get_animation(current); @@ -289,7 +291,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { 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>()); @@ -298,7 +300,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { autoplay->set_pressed(current == player->get_autoplay()); - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); _animation_key_editor_seek(timeline_position, false); } @@ -395,7 +397,7 @@ void AnimationPlayerEditor::_animation_save_as(const Ref<Resource> &p_resource) String path; //file->set_current_path(current_path); - if (p_resource->get_path() != "") { + if (!p_resource->get_path().is_empty()) { path = p_resource->get_path(); if (extensions.size()) { if (extensions.find(p_resource->get_path().get_extension().to_lower()) == nullptr) { @@ -404,7 +406,7 @@ void AnimationPlayerEditor::_animation_save_as(const Ref<Resource> &p_resource) } } else { if (extensions.size()) { - if (p_resource->get_name() != "") { + if (!p_resource->get_name().is_empty()) { path = p_resource->get_name() + "." + extensions.front()->get().to_lower(); } else { String resource_name_snake_case = p_resource->get_class().camelcase_to_underscore(); @@ -474,7 +476,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,7 +486,7 @@ 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 (new_name.is_empty() || new_name.find(":") != -1 || new_name.find("/") != -1) { error_dialog->set_text(TTR("Invalid animation name!")); error_dialog->popup_centered(); return; @@ -718,7 +720,7 @@ void AnimationPlayerEditor::_animation_edit() { void AnimationPlayerEditor::_save_animation(String p_file) { String current = animation->get_item_text(animation->get_selected()); - if (current != "") { + if (!current.is_empty()) { Ref<Animation> anim = player->get_animation(current); ERR_FAIL_COND(!Object::cast_to<Resource>(*anim)); @@ -826,7 +828,7 @@ void AnimationPlayerEditor::_update_player() { pin->set_disabled(player == nullptr); if (!player) { - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); return; } @@ -835,12 +837,12 @@ void AnimationPlayerEditor::_update_player() { for (const StringName &E : animlist) { Ref<Texture2D> icon; if (E == player->get_autoplay()) { - if (E == "RESET") { + if (E == SceneStringNames::get_singleton()->RESET) { icon = autoplay_reset_icon; } else { icon = autoplay_icon; } - } else if (E == "RESET") { + } else if (E == SceneStringNames::get_singleton()->RESET) { icon = reset_icon; } animation->add_icon_item(icon, E); @@ -904,7 +906,7 @@ void AnimationPlayerEditor::edit(AnimationPlayer *p_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; } @@ -1005,7 +1007,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 +1016,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()); } @@ -1084,7 +1086,7 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { } Ref<Animation> anim; - if (current != String()) { + if (!current.is_empty()) { anim = player->get_animation(current); } @@ -1139,7 +1141,7 @@ void AnimationPlayerEditor::_animation_tool_menu(int p_option) { } String name = anim2->get_name(); - if (name == "") { + if (name.is_empty()) { name = TTR("Pasted Animation"); } @@ -1228,7 +1230,7 @@ void AnimationPlayerEditor::unhandled_key_input(const Ref<InputEvent> &p_ev) { 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 +1238,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 { @@ -1424,7 +1426,7 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { 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::LoopMode::LOOP_NONE || (pos >= 0 && pos <= anim->get_length()); onion.captures_valid.write[cidx] = valid; if (valid) { player->seek(pos, true); @@ -1466,15 +1468,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(); diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index be80b7f4e3..26bcff891d 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -128,6 +128,7 @@ class AnimationPlayerEditor : public VBoxContainer { bool updating_blends; AnimationTrackEditor *track_editor; + static AnimationPlayerEditor *singleton; // Onion skinning. struct { @@ -186,7 +187,6 @@ class AnimationPlayerEditor : public VBoxContainer { 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); @@ -216,7 +216,6 @@ class AnimationPlayerEditor : public VBoxContainer { void _pin_pressed(); - AnimationPlayerEditor(); ~AnimationPlayerEditor(); protected: @@ -226,7 +225,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,7 +238,7 @@ 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); }; @@ -262,7 +262,8 @@ 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(); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index a1f96f21bf..cf3bc58af9 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -66,7 +66,7 @@ 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 (tool_select->is_pressed() && k.is_valid() && k->is_pressed() && k->get_keycode() == Key::KEY_DELETE && !k->is_echo()) { if (selected_node != StringName() || selected_transition_to != StringName() || selected_transition_from != StringName()) { _erase_selected(); accept_event(); @@ -76,7 +76,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv 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))) { + if (mb.is_valid() && mb->is_pressed() && ((tool_select->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) || (tool_create->is_pressed() && mb->get_button_index() == MouseButton::LEFT))) { menu->clear(); animations_menu->clear(); animations_to_add.clear(); @@ -118,13 +118,14 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv menu->add_separator(); menu->add_item(TTR("Load..."), MENU_LOAD_FILE); - menu->set_position(state_machine_draw->get_screen_transform().xform(mb->get_position())); + menu->set_position(state_machine_draw->get_screen_position() + mb->get_position()); + menu->reset_size(); menu->popup(); add_node_pos = mb->get_position() / EDSCALE + state_machine->get_graph_offset(); } // select node or push a field inside - if (mb.is_valid() && !mb->is_shift_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_shift_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MouseButton::LEFT) { selected_transition_from = StringName(); selected_transition_to = StringName(); selected_node = StringName(); @@ -151,7 +152,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv 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(); @@ -216,7 +217,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } //end moving node - if (mb.is_valid() && dragging_selected_attempt && mb->get_button_index() == MOUSE_BUTTON_LEFT && !mb->is_pressed()) { + 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; @@ -237,7 +238,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } //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()) { + 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; @@ -250,7 +251,7 @@ 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()) { + 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!")); @@ -284,7 +285,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv Ref<InputEventMouseMotion> mm = p_event; //pan window - if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) { + 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); } @@ -437,7 +438,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", ""); } @@ -760,12 +761,12 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { 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); + state_machine_draw->draw_string(font, offset + Vector2(0, -font->get_height(font_size) - 3 * EDSCALE + font->get_ascent(font_size)), TTR("Start"), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } if (state_machine->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); + state_machine_draw->draw_string(font, offset + Vector2(endofs, -font->get_height(font_size) - 3 * EDSCALE + font->get_ascent(font_size)), TTR("End"), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } offset.x += sb->get_offset().x; @@ -785,7 +786,7 @@ void AnimationNodeStateMachineEditor::_state_machine_draw() { 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) { @@ -926,7 +927,7 @@ void AnimationNodeStateMachineEditor::_notification(int p_what) { if (error != error_label->get_text()) { error_label->set_text(error); - if (error != String()) { + if (!error.is_empty()) { error_panel->show(); } else { error_panel->hide(); @@ -1058,7 +1059,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.find(".") != -1 || new_name.find("/") != -1); if (new_name == prev_name) { return; // Nothing to do. diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index cd84be0c25..d65ed9cea2 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -55,7 +55,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 { @@ -226,8 +226,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:")))); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 5405723d10..951af92467 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -230,7 +230,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); } @@ -288,8 +289,7 @@ 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); @@ -344,7 +344,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,7 +355,7 @@ 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. @@ -620,7 +620,7 @@ void EditorAssetLibrary::unhandled_key_input(const Ref<InputEvent> &p_event) { 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(); @@ -830,9 +830,9 @@ 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")) { @@ -844,14 +844,14 @@ void EditorAssetLibrary::_update_image_queue() { } } - 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++; } } @@ -922,7 +922,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 +935,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(); } @@ -1188,7 +1188,7 @@ 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()) { + if (!filter->get_text().is_empty()) { library_error->set_text( vformat(TTR("No results for \"%s\"."), filter->get_text())); } else { @@ -1219,7 +1219,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 +1256,7 @@ 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"] != "") { + 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); } @@ -1431,8 +1431,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); @@ -1454,11 +1453,11 @@ 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_loading->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); library_vb->add_child(library_loading); library_error = memnew(Label); - library_error->set_align(Label::ALIGN_CENTER); + library_error->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); library_error->hide(); library_vb->add_child(library_error); @@ -1499,8 +1498,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { set_process_unhandled_key_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); diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index 286546f962..5fbf2833b2 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -285,7 +285,6 @@ 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 _repository_changed(int p_repository_id); diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp index 482c08f50a..c621ade5c8 100644 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ b/editor/plugins/audio_stream_editor_plugin.cpp @@ -157,7 +157,7 @@ void AudioStreamEditor::_draw_indicator() { 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_valid() && mb->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { _seek_to(mb->get_position().x); } @@ -232,7 +232,7 @@ AudioStreamEditor::AudioStreamEditor() { 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)); + _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); @@ -241,7 +241,7 @@ AudioStreamEditor::AudioStreamEditor() { _stop_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_stop)); _current_label = memnew(Label); - _current_label->set_align(Label::ALIGN_RIGHT); + _current_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT); _current_label->set_h_size_flags(SIZE_EXPAND_FILL); _current_label->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font(SNAME("status_source"), SNAME("EditorFonts"))); _current_label->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size(SNAME("status_source_size"), SNAME("EditorFonts"))); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index d96cc1cd18..4a19e7752e 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -242,7 +242,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 +333,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 +387,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()); @@ -461,7 +461,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 { @@ -482,7 +482,7 @@ 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(); } @@ -513,7 +513,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 +525,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) { @@ -564,7 +564,7 @@ void CanvasItemEditor::_expand_encompassing_rect_using_children(Rect2 &r_rect, c Transform2D xform = p_parent_xform * p_canvas_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)); @@ -658,7 +658,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; @@ -748,8 +748,8 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_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()); + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retreive_locked || !_is_node_locked(canvas_item))) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); if (se) { @@ -877,7 +877,7 @@ 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) { @@ -950,7 +950,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,7 +1009,7 @@ 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; @@ -1045,7 +1045,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 +1078,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_"); @@ -1123,7 +1123,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo 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))) { + (b->get_button_index() == MouseButton::WHEEL_LEFT || (b->is_shift_pressed() && b->get_button_index() == MouseButton::WHEEL_UP))) { // Pan left view_offset.x -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); @@ -1131,7 +1131,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } if (b->is_pressed() && - (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->is_shift_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN))) { + (b->get_button_index() == MouseButton::WHEEL_RIGHT || (b->is_shift_pressed() && b->get_button_index() == MouseButton::WHEEL_DOWN))) { // Pan right view_offset.x += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); @@ -1139,13 +1139,13 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } } - if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + if (b->is_pressed() && b->get_button_index() == MouseButton::WHEEL_DOWN) { // Scroll or pan down if (pan_on_scroll) { view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); } else { - zoom_widget->set_zoom_by_increments(-1, Input::get_singleton()->is_key_pressed(KEY_ALT)); + 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)); @@ -1155,13 +1155,13 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo return true; } - if (b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + if (b->is_pressed() && b->get_button_index() == MouseButton::WHEEL_UP) { // Scroll or pan up if (pan_on_scroll) { view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); } else { - zoom_widget->set_zoom_by_increments(1, Input::get_singleton()->is_key_pressed(KEY_ALT)); + 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)); @@ -1173,17 +1173,16 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo 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))) { + (b->get_button_index() == MouseButton::MIDDLE || + (b->get_button_index() == MouseButton::LEFT && tool == TOOL_PAN) || + (b->get_button_index() == MouseButton::LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { // Pan the viewport panning = true; } } 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))) { + if (!b->is_pressed() && (pan_on_scroll || (b->get_button_index() != MouseButton::WHEEL_DOWN && b->get_button_index() != MouseButton::WHEEL_UP))) { // Stop panning the viewport (for any mouse button press except zooming) panning = false; } @@ -1232,8 +1231,9 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } } - if (is_pan_key) { + if (is_pan_key && pan_pressed != k->is_pressed()) { pan_pressed = k->is_pressed(); + _update_cursor(); } } @@ -1294,8 +1294,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 +1345,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( @@ -1359,7 +1359,7 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { } // 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; viewport->update(); @@ -1375,7 +1375,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 +1418,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, @@ -1442,7 +1442,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { } // 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; viewport->update(); @@ -1456,12 +1456,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 != editor->get_edited_scene()) { + editor->open_request(canvas_item->get_scene_file_path()); return true; } } @@ -1475,7 +1475,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]); @@ -1595,7 +1595,7 @@ 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())); @@ -1604,7 +1604,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { } // 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; viewport->update(); @@ -1620,7 +1620,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]; @@ -1774,7 +1774,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. @@ -1811,7 +1811,7 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { } // 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; @@ -1829,7 +1829,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,7 +1876,7 @@ 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); @@ -1925,7 +1925,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } // 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, @@ -1950,7 +1950,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { } // 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; viewport->update(); @@ -1967,7 +1967,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 +2050,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( @@ -2083,7 +2083,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } // 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; @@ -2095,7 +2095,7 @@ 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(); @@ -2112,13 +2112,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 +2166,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, @@ -2192,7 +2192,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { 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) { @@ -2202,8 +2202,8 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &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))) { + ((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()); @@ -2258,21 +2258,22 @@ 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(get_screen_position() + 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->reset_size(); + add_node_menu->set_position(get_screen_transform().xform(get_local_mouse_position())); add_node_menu->popup(); node_create_position = transform.affine_inverse().xform((get_local_mouse_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) { // Single item selection Point2 click = transform.affine_inverse().xform(b->get_position()); @@ -2339,7 +2340,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,7 +2349,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } if (drag_type == DRAG_BOX_SELECTION) { - if (b.is_valid() && !b->is_pressed() && b->get_button_index() == 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(); if (scene) { @@ -2377,7 +2378,7 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { 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; viewport->update(); @@ -2392,7 +2393,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 +2415,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 { @@ -2615,7 +2616,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) { @@ -2638,7 +2651,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) { @@ -2698,7 +2711,7 @@ void CanvasItemEditor::_draw_guides() { 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); + viewport->draw_string(font, Point2(dragged_guide_pos.x + 10, RULER_WIDTH + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color, outline_size, outline_color); 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) { @@ -2706,7 +2719,7 @@ void CanvasItemEditor::_draw_guides() { 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); + viewport->draw_string(font, Point2(RULER_WIDTH + 10, dragged_guide_pos.y + text_size.y / 2 + 10), str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color, outline_size, outline_color); viewport->draw_line(Point2(0, dragged_guide_pos.y), Point2(viewport->get_size().x, dragged_guide_pos.y), guide_color, Math::round(EDSCALE)); } } @@ -2778,7 +2791,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 +2811,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 { @@ -2901,14 +2914,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 +2929,42 @@ 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(font, text_pos, (String)ruler_tool_origin, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); + Ref<Texture2D> position_icon = get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")); + viewport->draw_texture(get_theme_icon(SNAME("EditorPosition"), SNAME("EditorIcons")), (ruler_tool_origin - view_offset) * zoom - position_icon->get_size() / 2); + return; + } + + viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); + + 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(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); 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(font, v_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); 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(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); 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 +2981,7 @@ 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(font, h_angle_text_pos, TS->format_number(vformat(String::utf8("%d°"), horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); // Angle arcs int arc_point_count = 8; @@ -2971,18 +2992,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 +3016,17 @@ 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(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); 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(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); 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(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color, outline_size, outline_color); } 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(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color, outline_size, outline_color); } } } else { @@ -3344,8 +3363,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(); @@ -3381,7 +3400,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) { @@ -3553,7 +3572,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, 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,7 +3589,7 @@ 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)); } } @@ -3655,8 +3674,6 @@ 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(); @@ -3782,8 +3799,8 @@ void CanvasItemEditor::_notification(int p_what) { } // 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); + for (KeyValue<BoneKey, BoneList> &E : bone_list) { + Object *b = ObjectDB::get_instance(E.key.from); if (!b) { viewport->update(); break; @@ -3796,14 +3813,14 @@ void CanvasItemEditor::_notification(int p_what) { Transform2D global_xform = b2->get_global_transform(); - if (global_xform != E->get().xform) { - E->get().xform = global_xform; + 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->get().length) { - E->get().length = bone->get_length(); + if (bone && bone->get_length() != E.value.length) { + E.value.length = bone->get_length(); viewport->update(); } } @@ -3816,7 +3833,7 @@ void CanvasItemEditor::_notification(int p_what) { select_sb->set_default_margin(Side(i), 4); } - AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); + AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); _keying_changed(); } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { @@ -3909,11 +3926,6 @@ void CanvasItemEditor::_notification(int p_what) { 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) { @@ -3968,7 +3980,7 @@ 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"); + const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor")); context_menu_stylebox->set_bg_color(accent_color * Color(1, 1, 1, 0.1)); // Add an underline to the StyleBox, but prevent its minimum vertical size from changing. context_menu_stylebox->set_border_color(accent_color); @@ -4069,34 +4081,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; @@ -4254,17 +4238,13 @@ 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(); - 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; } @@ -4277,13 +4257,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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); } if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { @@ -4309,13 +4289,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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); } } } @@ -4325,13 +4305,13 @@ 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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_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); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); } } } @@ -4695,8 +4675,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { 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()); + 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; } @@ -4741,8 +4721,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { case ANIM_CLEAR_POSE: { 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()); + 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 +4749,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()); - */ } } @@ -4816,8 +4792,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { 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(); @@ -4861,8 +4837,8 @@ void CanvasItemEditor::_focus_selection(int p_op) { 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()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item) { continue; } @@ -4896,10 +4872,9 @@ void CanvasItemEditor::_focus_selection(int p_op) { }; if (p_op == VIEW_CENTER_TO_SELECTION) { - center = rect.position + rect.size / 2; + center = rect.get_center(); 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); + view_offset -= (offset / zoom).round(); update_viewport(); } else { // VIEW_FRAME_TO_SELECTION @@ -5135,19 +5110,6 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { viewport->update(); } -void CanvasItemEditor::add_control_to_info_overlay(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); -} - -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::add_control_to_menu_panel(Control *p_control) { ERR_FAIL_COND(!p_control); @@ -5204,7 +5166,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { snap_rotation = false; snap_scale = false; snap_relative = false; - snap_pixel = false; + // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. + // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. + snap_pixel = true; snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; @@ -5278,23 +5242,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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); - h_scroll = memnew(HScrollBar); viewport->add_child(h_scroll); h_scroll->connect("value_changed", callable_mp(this, &CanvasItemEditor::_update_scroll)); @@ -5327,9 +5274,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { select_button->set_toggle_mode(true); select_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(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)); @@ -5338,7 +5285,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->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->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")); @@ -5347,7 +5294,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->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->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")); @@ -5356,9 +5303,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->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->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)); @@ -5381,16 +5328,16 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { hb->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->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); 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->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")); @@ -5402,7 +5349,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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); @@ -5411,7 +5358,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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); @@ -5454,7 +5401,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { lock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(LOCK_SELECTED)); lock_button->set_tooltip(TTR("Lock selected node, preventing selection and movement.")); // 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); @@ -5462,7 +5409,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { unlock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(UNLOCK_SELECTED)); unlock_button->set_tooltip(TTR("Unlock selected node, allowing selection and movement.")); // 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); @@ -5470,7 +5417,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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.")); // 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); @@ -5478,7 +5425,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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.")); // 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)); @@ -5492,7 +5439,7 @@ 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)); @@ -5516,21 +5463,21 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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); + 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); 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); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/preview_canvas_scale", TTR("Preview Canvas Scale"), KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::P), PREVIEW_CANVAS_SCALE); hb->add_child(memnew(VSeparator)); @@ -5601,7 +5548,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_insert_button->set_focus_mode(FOCUS_NONE); key_insert_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_KEY)); key_insert_button->set_tooltip(TTR("Insert keys (based on mask).")); - key_insert_button->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 +5571,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)); @@ -5648,9 +5595,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { 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); + pan_view_shortcut = ED_SHORTCUT("canvas_item_editor/pan_view", TTR("Pan View"), Key::SPACE); skeleton_menu->get_popup()->set_item_checked(skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES), true); singleton = this; @@ -5659,16 +5606,16 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { // 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); + ED_SHORTCUT("canvas_item_editor/zoom_3.125_percent", TTR("Zoom to 3.125%"), KeyModifierMask::SHIFT | Key::KEY_5); + ED_SHORTCUT("canvas_item_editor/zoom_6.25_percent", TTR("Zoom to 6.25%"), KeyModifierMask::SHIFT | Key::KEY_4); + ED_SHORTCUT("canvas_item_editor/zoom_12.5_percent", TTR("Zoom to 12.5%"), KeyModifierMask::SHIFT | Key::KEY_3); + ED_SHORTCUT("canvas_item_editor/zoom_25_percent", TTR("Zoom to 25%"), KeyModifierMask::SHIFT | Key::KEY_2); + ED_SHORTCUT("canvas_item_editor/zoom_50_percent", TTR("Zoom to 50%"), KeyModifierMask::SHIFT | Key::KEY_1); + ED_SHORTCUT("canvas_item_editor/zoom_100_percent", TTR("Zoom to 100%"), Key::KEY_1); + ED_SHORTCUT("canvas_item_editor/zoom_200_percent", TTR("Zoom to 200%"), Key::KEY_2); + ED_SHORTCUT("canvas_item_editor/zoom_400_percent", TTR("Zoom to 400%"), Key::KEY_3); + ED_SHORTCUT("canvas_item_editor/zoom_800_percent", TTR("Zoom to 800%"), Key::KEY_4); + ED_SHORTCUT("canvas_item_editor/zoom_1600_percent", TTR("Zoom to 1600%"), Key::KEY_5); set_process_unhandled_key_input(true); @@ -5799,7 +5746,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; } @@ -5833,7 +5780,7 @@ void CanvasItemEditorViewport::_create_nodes(Node *parent, Node *child, String & Ref<Texture2D> texture = Ref<Texture2D>(Object::cast_to<Texture2D>(ResourceCache::get(path))); if (parent) { - editor_data->get_undo_redo().add_do_method(parent, "add_child", child); + editor_data->get_undo_redo().add_do_method(parent, "add_child", child, true); editor_data->get_undo_redo().add_do_method(child, "set_owner", editor->get_edited_scene()); editor_data->get_undo_redo().add_do_reference(child); editor_data->get_undo_redo().add_undo_method(parent, "remove_child", child); @@ -5896,14 +5843,14 @@ 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)) { + if (!editor->get_edited_scene()->get_scene_file_path().is_empty()) { // cyclical instancing + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { 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()); @@ -6056,9 +6003,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; @@ -6189,14 +6136,14 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte 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..b6576b7144 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -235,11 +235,6 @@ private: PanelContainer *context_menu_container; HBoxContainer *hbc_context_menu; - Map<Control *, Timer *> popup_temporarily_timers; - - Label *warning_child_of_container; - VBoxContainer *info_overlay; - Transform2D transform; bool show_grid; bool show_rulers; @@ -421,8 +416,6 @@ private: CanvasItem *ref_item; - void _add_canvas_item(CanvasItem *p_canvas_item); - 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); void _commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones = false); @@ -434,7 +427,6 @@ private: bool updating_scroll; 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(); @@ -519,7 +511,6 @@ private: 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(); @@ -540,8 +531,6 @@ private: 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); void _set_owner_for_node_and_children(Node *p_node, Node *p_owner); @@ -551,15 +540,9 @@ 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; public: @@ -588,9 +571,6 @@ 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); - HSplitContainer *get_palette_split(); VSplitContainer *get_bottom_split(); diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp index 8b354c33a1..4c728ff757 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp @@ -103,16 +103,16 @@ void CollisionPolygon3DEditor::_wip_close() { undo_redo->commit_action(); } -bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } 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); + Plane p(n, gt.origin + n * depth); Ref<InputEventMouseButton> mb = p_event; @@ -124,7 +124,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); @@ -142,7 +142,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con 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,29 +151,29 @@ 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) { @@ -184,7 +184,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con 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 @@ -219,7 +219,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con _polygon_draw(); snap_ignore = true; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } else { //look for points to move @@ -244,7 +244,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,7 +253,7 @@ 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); @@ -263,11 +263,11 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con 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; @@ -285,12 +285,12 @@ 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); + poly.remove_at(closest_idx); undo_redo->add_do_method(node, "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 +301,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 +310,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,7 +332,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } float CollisionPolygon3DEditor::_get_depth() { diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/collision_polygon_3d_editor_plugin.h index 5db0f7308a..10b0adf76c 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.h +++ b/editor/plugins/collision_polygon_3d_editor_plugin.h @@ -88,7 +88,7 @@ protected: static void _bind_methods(); public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); void edit(Node *p_collision_polygon); CollisionPolygon3DEditor(EditorNode *p_editor); ~CollisionPolygon3DEditor(); @@ -101,7 +101,7 @@ class Polygon3DEditorPlugin : public EditorPlugin { EditorNode *editor; 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 collision_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; } diff --git a/editor/plugins/collision_shape_2d_editor_plugin.cpp b/editor/plugins/collision_shape_2d_editor_plugin.cpp index bfcc293625..94fd9a5a08 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.cpp +++ b/editor/plugins/collision_shape_2d_editor_plugin.cpp @@ -39,7 +39,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 +70,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 +147,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 +183,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 +255,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"); } @@ -333,7 +333,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 +394,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 +421,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)) { @@ -490,8 +490,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(); diff --git a/editor/plugins/collision_shape_2d_editor_plugin.h b/editor/plugins/collision_shape_2d_editor_plugin.h index 421e674df8..ab95600a52 100644 --- a/editor/plugins/collision_shape_2d_editor_plugin.h +++ b/editor/plugins/collision_shape_2d_editor_plugin.h @@ -46,7 +46,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 diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp index 6f246c1661..fb9f8696fe 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp @@ -83,7 +83,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; diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 4a22dc5b62..3bb707d415 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -115,16 +115,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 +132,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 +210,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 +240,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 +354,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 +383,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 +460,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); @@ -675,11 +675,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 +688,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 @@ -750,10 +750,10 @@ void CurveEditor::_draw() { 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_string(font, Vector2(50 * EDSCALE, font_height), TTR("Hold Shift to edit tangents individually"), HORIZONTAL_ALIGNMENT_LEFT, -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_string(font, Vector2(50 * EDSCALE, font_height), TTR("Right click to add point"), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, text_color); } } diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 1512e1817a..51e1b639a4 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -34,16 +34,17 @@ #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/fileserver/editor_file_server.h" #include "scene/gui/menu_button.h" DebuggerEditorPlugin::DebuggerEditorPlugin(EditorNode *p_editor, 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 +53,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("hseparation", 6 * EDSCALE); debugger->set_tool_button(db); // Main editor debug menu. diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 415832ab3b..3b85fad345 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -297,12 +297,14 @@ 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 { @@ -320,14 +322,9 @@ Ref<Texture2D> EditorMaterialPreviewPlugin::generate(const RES &p_from, const Si 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 + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMaterialPreviewPlugin *>(this), &EditorMaterialPreviewPlugin::_generate_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); - preview_done.clear(); - RS::get_singleton()->request_frame_drawn_callback(const_cast<EditorMaterialPreviewPlugin *>(this), "_preview_done", Variant()); - - 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()); @@ -480,7 +477,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size } String code = scr->get_source_code().strip_edges(); - if (code == "") { + if (code.is_empty()) { return Ref<Texture2D>(); } @@ -699,12 +696,14 @@ 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 { @@ -718,7 +717,7 @@ Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2 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 +734,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 + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<EditorMeshPreviewPlugin *>(this), &EditorMeshPreviewPlugin::_generate_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); - preview_done.clear(); - RS::get_singleton()->request_frame_drawn_callback(const_cast<EditorMeshPreviewPlugin *>(this), "_preview_done", Variant()); - - 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>()); @@ -814,12 +808,14 @@ 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 { @@ -855,15 +851,11 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, Ref<Font> font = sampled_font; - font->draw_string(canvas_item, pos, sample, HALIGN_LEFT, -1.f, 50, Color(1, 1, 1)); + font->draw_string(canvas_item, pos, sample, HORIZONTAL_ALIGNMENT_LEFT, -1.f, 50, Color(1, 1, 1)); - 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), Vector<Variant>(), 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); diff --git a/editor/plugins/editor_preview_plugins.h b/editor/plugins/editor_preview_plugins.h index 6e8b9a34cf..bf52f5771d 100644 --- a/editor/plugins/editor_preview_plugins.h +++ b/editor/plugins/editor_preview_plugins.h @@ -92,12 +92,10 @@ 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; @@ -136,12 +134,10 @@ 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; @@ -158,12 +154,10 @@ 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; @@ -173,4 +167,20 @@ public: EditorFontPreviewPlugin(); ~EditorFontPreviewPlugin(); }; + +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 RES &p_from, const Size2 &p_size) const override; + + EditorTileMapPatternPreviewPlugin(); + ~EditorTileMapPatternPreviewPlugin(); +}; #endif // EDITORPREVIEWPLUGINS_H diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index dd91df747a..4b50f484a4 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -57,6 +57,27 @@ void GPUParticles2DEditorPlugin::_file_selected(const String &p_file) { emission_mask->popup_centered(); } +void GPUParticles2DEditorPlugin::_selection_changed() { + List<Node *> selected_nodes = editor->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: { @@ -158,7 +179,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; @@ -334,6 +355,7 @@ void GPUParticles2DEditorPlugin::_notification(int p_what) { 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)); } } diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.h b/editor/plugins/gpu_particles_2d_editor_plugin.h index 0b2028b745..bdfc021aa7 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.h +++ b/editor/plugins/gpu_particles_2d_editor_plugin.h @@ -56,6 +56,7 @@ class GPUParticles2DEditorPlugin : public EditorPlugin { }; GPUParticles2D *particles; + List<GPUParticles2D *> selected_particles; EditorFileDialog *file; EditorNode *editor; @@ -79,6 +80,7 @@ class GPUParticles2DEditorPlugin : public EditorPlugin { void _menu_callback(int p_idx); void _generate_visibility_rect(); void _generate_emission_mask(); + void _selection_changed(); protected: void _notification(int p_what); diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 903a3689b0..5ac58795d1 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -362,6 +362,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<ImageTexture> tex; tex.instantiate(); + tex->create_from_image(image); Ref<ParticlesMaterial> material = node->get_process_material(); ERR_FAIL_COND(material.is_null()); @@ -390,6 +391,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<ImageTexture> tex2; tex2.instantiate(); + tex2->create_from_image(image2); material->set_emission_normal_texture(tex2); } else { diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp index a4436525fb..f6788f80ba 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -30,11 +30,11 @@ #include "gpu_particles_collision_sdf_editor_plugin.h" -void GPUParticlesCollisionSDFEditorPlugin::_bake() { +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 +49,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 +58,50 @@ 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) { +void GPUParticlesCollisionSDF3DEditorPlugin::_notification(int p_what) { if (p_what == NOTIFICATION_PROCESS) { if (!col_sdf) { return; } + // Set information tooltip on the Bake button. This information is useful + // to optimize performance (video RAM size) and reduce collision tunneling (individual cell size). + 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; - 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 Vector3 extents = col_sdf->get_extents(); - if (bake_info->get_text() == text) { - return; + 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"); } - // 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")); + 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->add_theme_color_override("font_color", color); - bake_info->set_text(text); + bake->set_tooltip(text); } } -void GPUParticlesCollisionSDFEditorPlugin::make_visible(bool p_visible) { +void GPUParticlesCollisionSDF3DEditorPlugin::make_visible(bool p_visible) { if (p_visible) { bake_hb->show(); set_process(true); @@ -107,26 +111,26 @@ 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(); @@ -164,10 +168,10 @@ void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String } } -void GPUParticlesCollisionSDFEditorPlugin::_bind_methods() { +void GPUParticlesCollisionSDF3DEditorPlugin::_bind_methods() { } -GPUParticlesCollisionSDFEditorPlugin::GPUParticlesCollisionSDFEditorPlugin(EditorNode *p_node) { +GPUParticlesCollisionSDF3DEditorPlugin::GPUParticlesCollisionSDF3DEditorPlugin(EditorNode *p_node) { editor = p_node; bake_hb = memnew(HBoxContainer); bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -176,26 +180,22 @@ GPUParticlesCollisionSDFEditorPlugin::GPUParticlesCollisionSDFEditorPlugin(Edito bake->set_flat(true); bake->set_icon(editor->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..ce7ca18293 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.h @@ -36,13 +36,12 @@ #include "scene/3d/gpu_particles_collision_3d.h" #include "scene/resources/material.h" -class GPUParticlesCollisionSDFEditorPlugin : public EditorPlugin { - GDCLASS(GPUParticlesCollisionSDFEditorPlugin, EditorPlugin); +class GPUParticlesCollisionSDF3DEditorPlugin : public EditorPlugin { + GDCLASS(GPUParticlesCollisionSDF3DEditorPlugin, EditorPlugin); - GPUParticlesCollisionSDF *col_sdf; + GPUParticlesCollisionSDF3D *col_sdf; HBoxContainer *bake_hb; - Label *bake_info; Button *bake; EditorNode *editor; @@ -61,14 +60,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(EditorNode *p_node); + ~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..da050abc02 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -46,6 +46,8 @@ 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; } @@ -55,8 +57,10 @@ void GradientEditor::_ramp_changed() { undo_redo->create_action(TTR("Gradient Edited")); undo_redo->add_do_method(gradient.ptr(), "set_offsets", get_offsets()); undo_redo->add_do_method(gradient.ptr(), "set_colors", get_colors()); + undo_redo->add_do_method(gradient.ptr(), "set_interpolation_mode", get_interpolation_mode()); undo_redo->add_undo_method(gradient.ptr(), "set_offsets", gradient->get_offsets()); undo_redo->add_undo_method(gradient.ptr(), "set_colors", gradient->get_colors()); + undo_redo->add_undo_method(gradient.ptr(), "set_interpolation_mode", gradient->get_interpolation_mode()); undo_redo->commit_action(); editing = false; } @@ -69,6 +73,14 @@ 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() { @@ -77,6 +89,23 @@ GradientEditor::GradientEditor() { /////////////////////// +void GradientReverseButton::_notification(int p_what) { + if (p_what == NOTIFICATION_DRAW) { + Ref<Texture2D> icon = get_theme_icon(SNAME("ReverseGradient"), SNAME("EditorIcons")); + if (is_pressed()) { + draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height()), false, get_theme_color(SNAME("icon_pressed_color"), SNAME("Button"))); + } else { + draw_texture_rect(icon, Rect2(margin, margin, icon->get_width(), icon->get_height())); + } + } +} + +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,9 +114,23 @@ 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); + + 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) { diff --git a/editor/plugins/gradient_editor_plugin.h b/editor/plugins/gradient_editor_plugin.h index bcbb86e422..95b7b466c9 100644 --- a/editor/plugins/gradient_editor_plugin.h +++ b/editor/plugins/gradient_editor_plugin.h @@ -50,12 +50,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; + HBoxContainer *gradient_tools_hbox; + GradientReverseButton *reverse_btn; + + void _reverse_button_pressed(); + public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; 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/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index b4a70cd31d..d7b469cb74 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -43,11 +43,11 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { 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; } diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp index 94966d4fe6..bfbe6e26a9 100644 --- a/editor/plugins/material_editor_plugin.cpp +++ b/editor/plugins/material_editor_plugin.cpp @@ -32,6 +32,7 @@ #include "editor/editor_scale.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" @@ -68,8 +69,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,6 +122,21 @@ void MaterialEditor::_bind_methods() { } MaterialEditor::MaterialEditor() { + // canvas item + + layout_2d = memnew(HBoxContainer); + layout_2d->set_alignment(BoxContainer::ALIGNMENT_CENTER); + add_child(layout_2d); + layout_2d->set_anchors_and_offsets_preset(PRESET_WIDE); + + 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); @@ -153,12 +185,12 @@ 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_WIDE, 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); @@ -172,10 +204,10 @@ MaterialEditor::MaterialEditor() { vb_shape->add_child(box_switch); box_switch->connect("pressed", callable_mp(this, &MaterialEditor::_button_pressed), varray(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); @@ -206,8 +238,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) { @@ -278,6 +310,54 @@ Ref<Resource> StandardMaterial3DConversionPlugin::convert(const Ref<Resource> &p } 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_param_list(mat->get_shader_rid(), ¶ms); + + for (const PropertyInfo &E : params) { + // Texture parameter has to be treated specially since ORMMaterial3D saved it + // as RID but ShaderMaterial needs Texture itself + Ref<Texture2D> texture = mat->get_texture_by_name(E.name); + if (texture.is_valid()) { + smat->set_shader_param(E.name, texture); + } else { + Variant value = RS::get_singleton()->material_get_param(mat->get_rid(), E.name); + smat->set_shader_param(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; } @@ -315,6 +395,8 @@ Ref<Resource> ParticlesMaterialConversionPlugin::convert(const Ref<Resource> &p_ } 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; } @@ -352,6 +434,8 @@ Ref<Resource> CanvasItemMaterialConversionPlugin::convert(const Ref<Resource> &p } 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; } @@ -389,6 +473,8 @@ Ref<Resource> ProceduralSkyMaterialConversionPlugin::convert(const Ref<Resource> } 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; } @@ -426,6 +512,8 @@ Ref<Resource> PanoramaSkyMaterialConversionPlugin::convert(const Ref<Resource> & } 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; } @@ -463,5 +551,44 @@ Ref<Resource> PhysicalSkyMaterialConversionPlugin::convert(const Ref<Resource> & } 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_param_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_render_priority(mat->get_render_priority()); return smat; } diff --git a/editor/plugins/material_editor_plugin.h b/editor/plugins/material_editor_plugin.h index a4532b58b3..8262b8149d 100644 --- a/editor/plugins/material_editor_plugin.h +++ b/editor/plugins/material_editor_plugin.h @@ -39,6 +39,7 @@ #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" class SubViewportContainer; @@ -46,22 +47,27 @@ 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; @@ -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..4b18ac6e9f 100644 --- a/editor/plugins/mesh_editor_plugin.cpp +++ b/editor/plugins/mesh_editor_plugin.cpp @@ -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) { @@ -80,7 +80,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; diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 9a2b222f21..2ce13b717a 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -79,7 +79,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { Node *owner = node == get_tree()->get_edited_scene_root() ? node : node->get_owner(); 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_reference(body); @@ -113,7 +113,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { Node *owner = instance == get_tree()->get_edited_scene_root() ? instance : instance->get_owner(); - 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_reference(body); @@ -146,7 +146,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { 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_reference(cshape); @@ -185,7 +185,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { Node *owner = node->get_owner(); - 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_reference(cshape); @@ -202,7 +202,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.")); @@ -246,7 +247,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { 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_reference(nmi); @@ -397,7 +398,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) { @@ -425,7 +426,7 @@ void MeshInstance3DEditor::_create_outline_mesh() { 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_reference(mi); diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index b3f92c9d95..fd62f632dc 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -47,23 +47,25 @@ 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(); } @@ -108,6 +110,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; @@ -197,15 +206,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; } @@ -219,16 +229,21 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { String p = editor->get_inspector()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); - 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; } } @@ -258,16 +273,22 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { 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->get_ok_button()->set_text("Apply without Transforms"); + cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(false)); + cd_update->add_button("Apply with Transforms")->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(true)); } void MeshLibraryEditorPlugin::edit(Object *p_node) { diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 6c33c8bb9e..9e225ffb9b 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -41,23 +41,27 @@ class MeshLibraryEditor : public Control { EditorNode *editor; MenuButton *menu; - ConfirmationDialog *cd; + ConfirmationDialog *cd_remove; + ConfirmationDialog *cd_update; EditorFileDialog *file; + bool apply_xforms; int to_erase; 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; 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,7 +70,7 @@ 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); }; diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp index 5514bccabb..517aca596a 100644 --- a/editor/plugins/multimesh_editor_plugin.cpp +++ b/editor/plugins/multimesh_editor_plugin.cpp @@ -48,7 +48,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 +89,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; diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index d04e88e915..32355735cc 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -34,26 +34,27 @@ #include "core/math/geometry_2d.h" #include "core/math/geometry_3d.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/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/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 +70,7 @@ #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" #define HANDLE_HALF_SIZE 9.5 @@ -243,8 +244,10 @@ void EditorNode3DGizmo::Instance::create_instance(Node3D *p_base, bool p_hidden) RS::get_singleton()->instance_geometry_set_flag(instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); } -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; @@ -682,7 +685,7 @@ 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_axis(2).normalized(), p_camera->get_transform().origin); int vc = collision_segments.size(); const Vector3 *vptr = collision_segments.ptr(); @@ -832,7 +835,7 @@ 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); @@ -1003,7 +1006,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 { @@ -1311,7 +1316,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_axis(2), gt.origin); Vector3 inters; if (cp.intersects_ray(ray_from, ray_dir, &inters)) { @@ -1481,8 +1486,6 @@ void Light3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } -////// - //// player gizmo AudioStreamPlayer3DGizmoPlugin::AudioStreamPlayer3DGizmoPlugin() { Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/stream_player_3d", Color(0.4, 0.8, 1)); @@ -1621,6 +1624,29 @@ void AudioStreamPlayer3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { ////// +AudioListener3DGizmoPlugin::AudioListener3DGizmoPlugin() { + create_icon_material("audio_listener_3d_icon", Node3DEditor::get_singleton()->get_theme_icon("GizmoAudioListener3D", "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)); @@ -1817,47 +1843,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 +1851,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 { @@ -2047,171 +2032,6 @@ 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() { @@ -2491,30 +2311,30 @@ void VehicleWheel3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { /////////// -SoftBody3DGizmoPlugin::SoftBody3DGizmoPlugin() { +SoftDynamicBody3DGizmoPlugin::SoftDynamicBody3DGizmoPlugin() { Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/shape", Color(0.5, 0.7, 1)); 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 +2370,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) 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) 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, 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) const { + SoftDynamicBody3D *soft_body = Object::cast_to<SoftDynamicBody3D>(p_gizmo->get_spatial_node()); return soft_body->is_point_pinned(p_id); } @@ -2631,7 +2451,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; @@ -2707,7 +2527,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 +2543,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 +2581,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"); @@ -2822,7 +2642,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; @@ -2898,7 +2718,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 +2734,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")); @@ -2947,11 +2767,11 @@ int GPUParticlesCollision3DGizmoPlugin::get_priority() const { String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id) 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"; } @@ -2961,11 +2781,11 @@ String GPUParticlesCollision3DGizmoPlugin::get_handle_name(const EditorNode3DGiz Variant GPUParticlesCollision3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) 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")); } @@ -2983,7 +2803,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 +2818,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; @@ -3021,7 +2841,7 @@ 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) { 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 +2854,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,7 +2871,6 @@ 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 = @@ -3061,7 +2880,7 @@ void GPUParticlesCollision3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { 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 +2922,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 +2947,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; @@ -3824,15 +3643,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)); } @@ -4539,9 +4358,9 @@ void CollisionShape3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_handles(handles, handles_material); } - if (Object::cast_to<WorldMarginShape3D>(*s)) { - Ref<WorldMarginShape3D> ps = s; - Plane p = ps->get_plane(); + if (Object::cast_to<WorldBoundaryShape3D>(*s)) { + Ref<WorldBoundaryShape3D> wbs = s; + const Plane &p = wbs->get_plane(); Vector<Vector3> points; Vector3 n1 = p.get_any_perpendicular_normal(); @@ -4752,10 +4571,10 @@ 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); } } @@ -5455,3 +5274,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) const { + return "Extents"; +} + +Variant FogVolumeGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id) const { + return Vector3(p_gizmo->get_spatial_node()->call("get_extents")); +} + +void FogVolumeGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, Camera3D *p_camera, const Point2 &p_point) { + Node3D *sn = p_gizmo->get_spatial_node(); + + 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, 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..cf9a464b69 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.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; @@ -95,7 +95,7 @@ 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)); @@ -249,6 +249,19 @@ public: 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); @@ -319,18 +332,6 @@ public: Position3DGizmoPlugin(); }; -class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Skeleton3DGizmoPlugin, 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; - - Skeleton3DGizmoPlugin(); -}; - class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin); @@ -379,8 +380,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; @@ -394,7 +395,7 @@ public: 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; - SoftBody3DGizmoPlugin(); + SoftDynamicBody3DGizmoPlugin(); }; class VisibleOnScreenNotifier3DGizmoPlugin : public EditorNode3DGizmoPlugin { @@ -668,4 +669,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) 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; + + 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..f49b749046 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -128,7 +128,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. @@ -185,7 +185,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) { @@ -252,6 +252,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 +273,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 +295,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 +326,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) { @@ -374,8 +367,8 @@ int Node3DEditorViewport::get_selected_count() const { 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; } @@ -400,7 +393,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 { @@ -496,7 +489,7 @@ 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()); + Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); Set<Ref<EditorNode3DGizmo>> found_gizmos; Node *edited_scene = get_tree()->get_edited_scene_root(); @@ -559,7 +552,7 @@ 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()); + Vector<ObjectID> instances = RenderingServer::get_singleton()->instances_cull_ray(pos, pos + ray * camera->get_far(), get_tree()->get_root()->get_world_3d()->get_scenario()); Set<Node3D *> found_nodes; for (int i = 0; i < instances.size(); i++) { @@ -668,13 +661,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); @@ -835,7 +828,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 +860,12 @@ 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.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 +874,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 +901,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 +944,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(); @@ -989,7 +982,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b 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_axis(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1015,7 +1008,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; @@ -1027,7 +1020,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b float col_d = 1e20; for (int i = 0; i < 3; i++) { - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); Vector3 r; if (!plane.intersects_ray(ray_pos, ray, &r)) { continue; @@ -1053,7 +1046,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; @@ -1093,7 +1086,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b 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_axis(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1119,7 +1112,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; @@ -1245,7 +1238,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { 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--; } } @@ -1298,7 +1291,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 +1302,31 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; //do NONE } + EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; { EditorNode *en = editor; 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; 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,21 +1337,29 @@ 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: { - if (is_freelook_active()) { - scale_freelook_speed(zoom_factor); + case MouseButton::WHEEL_UP: { + if (b->is_alt_pressed()) { + scale_fov(-0.05); } else { - scale_cursor_distance(1.0 / zoom_factor); + if (is_freelook_active()) { + scale_freelook_speed(zoom_factor); + } else { + scale_cursor_distance(1.0 / zoom_factor); + } } } break; - case MOUSE_BUTTON_WHEEL_DOWN: { - if (is_freelook_active()) { - scale_freelook_speed(1.0 / zoom_factor); + case MouseButton::WHEEL_DOWN: { + if (b->is_alt_pressed()) { + scale_fov(0.05); } else { - scale_cursor_distance(zoom_factor); + 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()) { @@ -1391,9 +1400,9 @@ 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, true); @@ -1407,7 +1416,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } 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 +1433,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,7 +1464,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } } break; - case MOUSE_BUTTON_LEFT: { + case MouseButton::LEFT: { if (b->is_pressed()) { 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()) { @@ -1590,17 +1599,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - clicked = _select_ray(b->get_position()); + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + clicked = _select_ray(b->get_position()); - //clicking is always deferred to either move or release + //clicking is always deferred to either move or release - clicked_wants_append = b->is_shift_pressed(); + 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(); @@ -1611,14 +1622,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - if (clicked.is_valid()) { - _select_clicked(false); - } + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + 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,9 +1642,9 @@ 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); @@ -1712,7 +1725,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } - 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); } @@ -1725,7 +1738,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { String n = _edit.gizmo->get_handle_name(_edit.gizmo_handle); 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) { 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()) { @@ -1737,7 +1750,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } 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); + _compute_edit(_edit.original_mouse_pos); clicked = ObjectID(); _edit.mode = TRANSFORM_TRANSLATE; @@ -1767,33 +1780,33 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: motion_mask = Vector3(0, 0, 0); - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_YZ: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); plane_mv = true; break; case TRANSFORM_XZ: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); plane_mv = true; break; case TRANSFORM_XY: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); plane_mv = true; break; } @@ -1815,7 +1828,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } else { // Alternative planar scaling mode - if (_get_key_modifier(m) != KEY_SHIFT) { + if (_get_key_modifier(m) != Key::SHIFT) { motion = motion_mask.dot(motion) * motion_mask; } } @@ -1831,6 +1844,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { 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); @@ -1841,7 +1856,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { 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) + ")"); + 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) { @@ -1860,13 +1875,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords); if (!local_coords) { new_xform = se->original.affine_inverse() * new_xform; } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + 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); @@ -1886,30 +1901,30 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_YZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); plane_mv = true; break; case TRANSFORM_XZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); plane_mv = true; break; case TRANSFORM_XY: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); plane_mv = true; break; } @@ -1940,7 +1955,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { 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) + ")"); + 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) { @@ -1959,11 +1974,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords); new_xform = se->original.affine_inverse() * new_xform; - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + 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); @@ -1982,18 +1997,18 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); axis = Vector3(1, 0, 0); break; case TRANSFORM_Y_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); axis = Vector3(0, 1, 0); break; case TRANSFORM_Z_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); axis = Vector3(0, 0, 1); break; case TRANSFORM_YZ: @@ -2045,14 +2060,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { 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(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords); if (!local_coords) { new_xform = se->original.affine_inverse() * new_xform; } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + 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); @@ -2068,7 +2083,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } } - } 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 +2092,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 +2110,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 +2165,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,9 +2216,9 @@ 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_keycode(); + if (code >= Key::KEY_0 && code <= Key::KEY_9) { + k->set_keycode(code - Key::KEY_0 + Key::KP_0); } } @@ -2230,6 +2245,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 +2294,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; } @@ -2275,15 +2317,27 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { 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 @@ -2451,6 +2505,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); @@ -2498,8 +2562,14 @@ static bool is_shortcut_pressed(const String &p_path) { if (shortcut.is_null()) { return false; } - InputEventKey *k = Object::cast_to<InputEventKey>(shortcut->get_event().ptr()); - if (k == nullptr) { + + const Array shortcuts = shortcut->get_events(); + Ref<InputEventKey> k; + if (shortcuts.size() > 0) { + k = shortcuts.front(); + } + + if (k.is_null()) { return false; } const Input &input = *Input::get_singleton(); @@ -2616,6 +2686,9 @@ void Node3DEditorViewport::_project_settings_changed() { const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"); viewport->set_use_occlusion_culling(use_occlusion_culling); + + const float lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); + viewport->set_lod_threshold(lod_threshold); } void Node3DEditorViewport::_notification(int p_what) { @@ -2678,8 +2751,8 @@ void Node3DEditorViewport::_notification(int p_what) { 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()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); if (!sp) { continue; } @@ -2703,15 +2776,28 @@ void Node3DEditorViewport::_notification(int p_what) { 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; + { + const Vector3 offset(0.005, 0.005, 0.005); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t.translate(se->aabb.position - offset / 2); + t.basis = t.basis * aabb_s; + } + { + const Vector3 offset(0.01, 0.01, 0.01); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t_offset.translate(se->aabb.position - offset / 2); + t_offset.basis = t_offset.basis * aabb_s; + } 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)) { @@ -2802,13 +2888,13 @@ void Node3DEditorViewport::_notification(int p_what) { // 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->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(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", @@ -2901,7 +2987,7 @@ static void draw_indicator_bar(Control &surface, real_t fill, const Ref<Texture2 surface.draw_texture(icon, icon_pos); // 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); + surface.draw_string(font, Vector2(icon_pos.x, icon_pos.y + icon_size.y + 16 * EDSCALE), text, HORIZONTAL_ALIGNMENT_LEFT, -1.f, font_size); } void Node3DEditorViewport::_draw() { @@ -2941,9 +3027,9 @@ 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) { @@ -3170,7 +3256,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(); @@ -3535,7 +3621,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) { @@ -3570,7 +3656,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { 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 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; @@ -3821,8 +3907,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 +3931,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 +3939,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; } @@ -3932,7 +4022,7 @@ void Node3DEditorViewport::_remove_preview() { } 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; } @@ -3959,7 +4049,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,15 +4080,15 @@ 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 (!editor->get_edited_scene()->get_scene_file_path().is_empty()) { // cyclical instancing + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { 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); @@ -4109,8 +4215,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; @@ -4132,10 +4238,9 @@ 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. + EditorNode::get_singleton()->get_scene_tree_dock()->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.")); @@ -4194,7 +4299,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); @@ -4292,7 +4398,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 +4410,18 @@ 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); + 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); 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 +4444,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 +4455,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(); @@ -4429,7 +4535,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(); @@ -4736,9 +4842,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,8 +4873,8 @@ 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; @@ -4818,7 +4930,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 +4953,39 @@ 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_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); 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_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); return si; } @@ -4865,10 +4993,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 +5012,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"); @@ -6028,7 +6142,7 @@ void fragment() { 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"); + const Color accent_color = EditorNode::get_singleton()->get_gui_base()->get_theme_color(SNAME("accent_color"), SNAME("Editor")); context_menu_stylebox->set_bg_color(accent_color * Color(1, 1, 1, 0.1)); // Add an underline to the StyleBox, but prevent its minimum vertical size from changing. context_menu_stylebox->set_border_color(accent_color); @@ -6144,7 +6258,7 @@ void Node3DEditor::_init_grid() { if (orthogonal) { camera_distance = camera->get_size() / 2.0; Vector3 camera_direction = -camera->get_global_transform().get_basis().get_axis(2); - Plane grid_plane = Plane(Vector3(), normal); + Plane grid_plane = Plane(normal); Vector3 intersection; if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) { camera_position = intersection; @@ -6294,7 +6408,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; @@ -6442,7 +6556,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 +6572,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; @@ -6474,7 +6588,12 @@ void Node3DEditor::snap_selected_nodes_to_floor() { Vector3 to = from - Vector3(0.0, max_snap_height, 0.0); Set<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)) { snapped_to_floor = true; } } @@ -6491,7 +6610,12 @@ void Node3DEditor::snap_selected_nodes_to_floor() { Vector3 to = from - Vector3(0.0, max_snap_height, 0.0); Set<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(); @@ -6517,19 +6641,20 @@ void Node3DEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { 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); } @@ -6544,7 +6669,7 @@ void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { 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 +6682,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); } @@ -6574,7 +6699,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); @@ -6688,8 +6813,8 @@ 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; @@ -6741,6 +6866,33 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { } } +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(); + } +} + void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) { Node3D *sp = nullptr; if (p_obj) { @@ -6861,11 +7013,11 @@ 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<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin))); add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin))); @@ -6885,11 +7037,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); @@ -7011,7 +7165,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 +7201,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); @@ -7104,9 +7258,9 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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]->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.")); + tool_button[TOOL_MODE_SELECT]->set_tooltip(keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Drag: Rotate selected node around pivot.") + "\n" + TTR("Alt+RMB: Show list of all nodes at position clicked, including locked.")); hbc_menu->add_child(memnew(VSeparator)); tool_button[TOOL_MODE_MOVE] = memnew(Button); @@ -7115,7 +7269,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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]->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); @@ -7124,7 +7278,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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]->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); @@ -7133,7 +7287,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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]->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)); @@ -7153,7 +7307,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { tool_button[TOOL_LOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); tool_button[TOOL_LOCK_SELECTED]->set_tooltip(TTR("Lock selected node, preventing selection and movement.")); // 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]); @@ -7162,7 +7316,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { tool_button[TOOL_UNLOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip(TTR("Unlock selected node, allowing selection and movement.")); // 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]); @@ -7171,7 +7325,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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.")); // 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]); @@ -7180,7 +7334,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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.")); // 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)); @@ -7190,7 +7344,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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]->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); @@ -7199,7 +7353,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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]->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)); @@ -7245,19 +7399,27 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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; @@ -7268,7 +7430,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { hbc_menu->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(); @@ -7300,19 +7462,19 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { 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); + 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); @@ -7405,7 +7567,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), varray(0.0)); } /* XFORM DIALOG */ @@ -7499,7 +7661,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); @@ -7587,8 +7749,8 @@ void fragment() { 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,7 +7768,7 @@ 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); @@ -7654,8 +7816,8 @@ void fragment() { 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); @@ -7716,9 +7878,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 +7898,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 +7909,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(); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 59f3ec6fcd..8d647808ba 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -41,6 +41,7 @@ #include "scene/3d/world_environment.h" #include "scene/gui/panel_container.h" #include "scene/resources/environment.h" +#include "scene/resources/fog_material.h" #include "scene/resources/sky_material.h" class Node3DEditor; @@ -315,7 +316,7 @@ private: 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 +326,7 @@ private: x_rot = 0.5; y_rot = -0.5; distance = 4; + fov_scale = 1.0; region_select = false; } }; @@ -333,6 +335,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); @@ -349,6 +353,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(); @@ -431,7 +436,9 @@ public: bool last_xform_dirty; Node3D *sp; 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 @@ -663,6 +670,7 @@ private: Node3D *selected; 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 +682,6 @@ private: void _register_all_gizmos(); - Node3DEditor(); - void _selection_changed(); void _refresh_menu_icons(); @@ -756,7 +762,7 @@ 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(); } diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index ab88b9f00d..1e85b19a84 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -41,11 +41,11 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &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; } diff --git a/editor/plugins/ot_features_plugin.cpp b/editor/plugins/ot_features_plugin.cpp index fd42bce06e..7f3ebc01d5 100644 --- a/editor/plugins/ot_features_plugin.cpp +++ b/editor/plugins/ot_features_plugin.cpp @@ -136,6 +136,7 @@ void OpenTypeFeaturesAdd::update_property() { void OpenTypeFeaturesAdd::_features_menu() { Size2 size = get_size(); menu->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); + menu->reset_size(); menu->popup(); } @@ -185,12 +186,6 @@ 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); diff --git a/editor/plugins/ot_features_plugin.h b/editor/plugins/ot_features_plugin.h index dbafa3bbf6..add491ed48 100644 --- a/editor/plugins/ot_features_plugin.h +++ b/editor/plugins/ot_features_plugin.h @@ -86,8 +86,6 @@ class EditorInspectorPluginOpenTypeFeatures : public 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; }; diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp index 0a949c8610..53c5b8dd70 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.cpp +++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp @@ -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 { @@ -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,17 @@ 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", Vector<StringName>()); + exception_list["LineEdit"].append("text"); + exception_list.insert("TextEdit", Vector<StringName>()); + exception_list["TextEdit"].append("text"); + exception_list.insert("CodeEdit", Vector<StringName>()); + exception_list["CodeEdit"].append("text"); } diff --git a/editor/plugins/packed_scene_translation_parser_plugin.h b/editor/plugins/packed_scene_translation_parser_plugin.h index e51d65414e..af0291b69c 100644 --- a/editor/plugins/packed_scene_translation_parser_plugin.h +++ b/editor/plugins/packed_scene_translation_parser_plugin.h @@ -37,7 +37,9 @@ class PackedSceneEditorTranslationParserPlugin : public EditorTranslationParserP GDCLASS(PackedSceneEditorTranslationParserPlugin, EditorTranslationParserPlugin); // Scene Node's properties that contain translation strings. - Set<String> lookup_properties; + Set<StringName> lookup_properties; + // Properties from specific Nodes that should be ignored. + Map<StringName, Vector<StringName>> 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..79f8ce95cd 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -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); @@ -537,7 +537,7 @@ Path2DEditor::Path2DEditor(EditorNode *p_editor) { 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->set_tooltip(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD) + TTR("Click: Add Point") + "\n" + TTR("Left Click: Split Segment (in curve)") + "\n" + TTR("Right Click: Delete Point")); curve_edit->connect("pressed", callable_mp(this, &Path2DEditor::_mode_selected), varray(MODE_EDIT)); base_hb->add_child(curve_edit); curve_edit_curve = memnew(Button); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 13f7908170..e83f6481f9 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -101,7 +101,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &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)); + const Plane p = Plane(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); Vector3 inters; @@ -125,7 +125,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point 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_axis(2), gt.xform(original)); Vector3 inters; @@ -294,13 +294,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,7 +316,7 @@ 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; @@ -329,14 +329,14 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref const Vector3 *r = v3a.ptr(); 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]) { @@ -386,16 +386,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_axis(2), origin); Vector3 ray_from = p_camera->project_ray_origin(mbpos); Vector3 ray_dir = p_camera->project_ray_normal(mbpos); @@ -405,13 +405,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); @@ -425,27 +425,27 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref 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(); 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(); 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 +510,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 = editor->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) { @@ -566,7 +573,7 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { 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); @@ -607,15 +614,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..974234ba8f 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -100,7 +100,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; } diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index f92f50f826..71955cf62c 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -43,6 +43,7 @@ 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(); } } @@ -50,7 +51,7 @@ PhysicalBone3DEditor::PhysicalBone3DEditor(EditorNode *p_editor) : editor(p_editor) { 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)); diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 782152b002..affcab9e7d 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -170,7 +170,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); @@ -447,9 +447,9 @@ void Polygon2DEditor::_uv_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->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(); @@ -463,7 +463,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { 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))); + 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 +483,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 +527,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 +573,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 +584,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 +599,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 +626,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 +643,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; @@ -695,14 +695,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 +759,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) { @@ -768,9 +768,9 @@ 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()) { + } else if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { uv_zoom->set_value(uv_zoom->get_value() / (1 - (0.1 * mb->get_factor()))); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { + } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { uv_zoom->set_value(uv_zoom->get_value() * (1 - (0.1 * mb->get_factor()))); } } @@ -778,8 +778,8 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { 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); + if ((mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || Input::get_singleton()->is_key_pressed(Key::SPACE)) { + Vector2 drag = mm->get_relative(); uv_hscroll->set_value(uv_hscroll->get_value() - drag.x); uv_vscroll->set_value(uv_vscroll->get_value() - drag.y); @@ -791,7 +791,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 +870,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 +905,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(); } } diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index eae6916a92..30b7a26aab 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -110,7 +110,7 @@ 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.find("\\") != -1 || new_name.find("/") != -1 || preloader->has_resource(new_name)) { s->set_text(0, old_name); return; } @@ -147,10 +147,10 @@ void ResourcePreloaderEditor::_paste_pressed() { } 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(); } @@ -300,7 +300,7 @@ void ResourcePreloaderEditor::drop_data_fw(const Point2 &p_point, const Variant 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(); diff --git a/editor/plugins/resource_preloader_editor_plugin.h b/editor/plugins/resource_preloader_editor_plugin.h index 04ab458eb5..943765d4e0 100644 --- a/editor/plugins/resource_preloader_editor_plugin.h +++ b/editor/plugins/resource_preloader_editor_plugin.h @@ -59,7 +59,6 @@ class ResourcePreloaderEditor : public PanelContainer { ResourcePreloader *preloader; 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); diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp index ed91f174d1..d1830bf5af 100644 --- a/editor/plugins/root_motion_editor_plugin.cpp +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -89,7 +89,7 @@ void EditorPropertyRootMotion::_node_assign() { 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; @@ -271,26 +271,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..c05975b6c3 100644 --- a/editor/plugins/root_motion_editor_plugin.h +++ b/editor/plugins/root_motion_editor_plugin.h @@ -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..caaac2c7d3 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -37,6 +37,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "editor/debugger/editor_debugger_node.h" +#include "editor/debugger/script_editor_debugger.h" #include "editor/editor_node.h" #include "editor/editor_run_script.h" #include "editor/editor_scale.h" @@ -45,6 +46,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" @@ -177,7 +179,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 +189,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 +214,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,12 +227,6 @@ 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; @@ -312,10 +309,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 +321,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) { @@ -398,7 +392,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(); @@ -479,6 +473,75 @@ void ScriptEditor::_clear_execution(REF p_script) { } } +void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { + Ref<Script> script = Object::cast_to<Script>(*p_script); + if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { + // Update if open. + for (int i = 0; i < tab_container->get_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + if (se && se->get_edited_resource()->get_path() == script->get_path()) { + 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_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(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()) { @@ -670,7 +733,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); @@ -691,20 +754,24 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected); if (current) { - Ref<Script> script = current->get_edited_resource(); - if (p_save && script.is_valid()) { + RES 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 +785,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,6 +798,7 @@ 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()) { @@ -783,39 +851,35 @@ void ScriptEditor::_copy_script_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; - } - - 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; - } + for (int i = tab_container->get_child_count() - 1; i >= 0; i--) { + if (i != current_idx) { + script_close_queue.push_back(i); } - - _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_child_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_child(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), varray(), CONNECT_ONESHOT); + break; } } @@ -840,7 +904,7 @@ void ScriptEditor::_resave_scripts(const String &p_str) { RES 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 } @@ -881,7 +945,7 @@ void ScriptEditor::_reload_scripts() { RES 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 } @@ -925,10 +989,6 @@ void ScriptEditor::_res_saved_callback(const Ref<Resource> &p_res) { 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 - } - if (script == p_res) { se->tag_saved_version(); } @@ -938,6 +998,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_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + if (!se) { + continue; + } + + RES edited_res = se->get_edited_resource(); + + if (!edited_res->is_built_in()) { + continue; // 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")); @@ -967,7 +1052,7 @@ bool ScriptEditor::_test_script_times_on_disk(RES p_for_script) { 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 } @@ -1009,35 +1094,21 @@ void ScriptEditor::_file_dialog_action(String p_file) { } 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; - } - - 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(); @@ -1112,8 +1183,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); @@ -1127,6 +1202,10 @@ void ScriptEditor::_menu_option(int p_option) { 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(); file_dialog->set_title(TTR("Open File")); return; @@ -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(); + } } } } @@ -1335,8 +1415,12 @@ void ScriptEditor::_menu_option(int p_option) { } break; case SHOW_IN_FILE_SYSTEM: { const RES script = current->get_edited_resource(); - const String path = script->get_path(); + String path = script->get_path(); if (!path.is_empty()) { + if (script->is_built_in()) { + path = path.get_slice("::", 0); // Show the scene instead. + } + FileSystemDock *file_system_dock = EditorNode::get_singleton()->get_filesystem_dock(); file_system_dock->navigate_to_path(path); // Ensure that the FileSystem dock is visible. @@ -1466,6 +1550,8 @@ void ScriptEditor::_notification(int p_what) { editor->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop)); editor->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback)); editor->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback)); + editor->connect("scene_saved", callable_mp(this, &ScriptEditor::_scene_saved_callback)); + editor->get_filesystem_dock()->connect("files_moved", callable_mp(this, &ScriptEditor::_files_moved)); editor->get_filesystem_dock()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed)); script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected)); @@ -1475,6 +1561,7 @@ void ScriptEditor::_notification(int p_what) { 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: @@ -1557,8 +1644,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,6 +1665,7 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { + Set<String> loaded_scripts; for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) { @@ -1590,7 +1678,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 +1688,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) { @@ -1625,7 +1728,7 @@ 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; @@ -1728,7 +1831,7 @@ 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_ofi(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); } @@ -1849,20 +1952,7 @@ 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(); _ScriptEditorItemData sd; sd.icon = icon; @@ -1986,7 +2076,7 @@ void ScriptEditor::_update_script_names() { 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_ofi(sedata[i].name)) { sedata_filtered.push_back(sedata[i]); } } @@ -2034,7 +2124,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; } @@ -2097,9 +2187,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(); @@ -2256,6 +2347,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(); @@ -2321,7 +2416,17 @@ 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); + editor->save_scene_list(scene_to_save); + } + } else { + editor->save_resource(resource); + } if (script != nullptr) { const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); @@ -2334,6 +2439,8 @@ void ScriptEditor::save_current_script() { } void ScriptEditor::save_all_scripts() { + Vector<String> scenes_to_save; + for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) { @@ -2363,7 +2470,7 @@ void ScriptEditor::save_all_scripts() { 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; @@ -2392,9 +2499,19 @@ void ScriptEditor::save_all_scripts() { 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()) { + editor->save_scene_list(scenes_to_save); + } + _update_script_names(); EditorFileSystem::get_singleton()->update_script_classes(); } @@ -2414,6 +2531,41 @@ 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; +} + +RES 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()) { + editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return RES(); + } + + edit(scr); + return scr; + } + + Error error; + Ref<TextFile> text_file = _load_text_file(p_file, &error); + if (error != OK) { + editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return RES(); + } + + if (text_file.is_valid()) { + edit(text_file); + return text_file; + } + return RES(); +} + 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)); @@ -2448,7 +2600,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,6 +2608,20 @@ 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; @@ -2465,6 +2631,12 @@ void ScriptEditor::_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,7 +2648,7 @@ 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"); @@ -2501,6 +2673,26 @@ 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)); @@ -2512,6 +2704,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() { @@ -2639,15 +2840,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; @@ -2709,12 +2920,16 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co int num_tabs_before = tab_container->get_child_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 (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) { + continue; + } + + RES res = open_file(file); + if (res.is_valid()) { if (tab_container->get_child_count() > num_tabs_before) { tab_container->move_child(tab_container->get_child(tab_container->get_child_count() - 1), new_index); num_tabs_before = tab_container->get_child_count(); @@ -2728,6 +2943,29 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } } +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::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -2762,7 +3000,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 +3010,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: @@ -2817,8 +3055,8 @@ 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_position(get_screen_position() + get_local_mouse_position()); + context_menu->reset_size(); context_menu->popup(); } @@ -2839,6 +3077,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = true; + Set<String> loaded_scripts; List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); @@ -2851,8 +3090,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); @@ -2883,7 +3126,7 @@ 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); @@ -2897,6 +3140,26 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { script_split->set_split_offset(p_layout->get_value("ScriptEditor", "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; _update_script_names(); @@ -2914,11 +3177,8 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { 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)); @@ -2931,10 +3191,13 @@ 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()); + + // Save the cache. + script_editor_cache->save(EditorSettings::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; } @@ -3017,15 +3280,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); + 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(); @@ -3113,9 +3376,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()) { @@ -3205,6 +3469,9 @@ void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_numb shader_editor->make_visible(true); shader_editor->get_shader_editor()->goto_line_selection(line_number - 1, begin, end); return; + } else if (fpath.get_extension() == "tscn") { + editor->load_scene(fpath); + return; } else { Ref<Script> script = res; if (script.is_valid()) { @@ -3272,7 +3539,6 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_update_script_connections", &ScriptEditor::_update_script_connections); ClassDB::bind_method("_help_class_open", &ScriptEditor::_help_class_open); 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); @@ -3298,6 +3564,9 @@ void ScriptEditor::_bind_methods() { ScriptEditor::ScriptEditor(EditorNode *p_editor) { current_theme = ""; + script_editor_cache.instantiate(); + script_editor_cache->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); + completion_cache = memnew(EditorScriptCodeCompletionCache); restoring_layout = false; waiting_update_names = false; @@ -3403,11 +3672,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_unhandled_input(true); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); @@ -3418,7 +3689,7 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { 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/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 +3699,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 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,16 +3726,16 @@ 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)); script_search_menu = memnew(MenuButton); @@ -3484,6 +3755,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(); @@ -3619,7 +3892,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()) { if (ResourceLoader::get_resource_type(res_path) == "PackedScene") { if (!EditorNode::get_singleton()->is_scene_open(res_path)) { EditorNode::get_singleton()->load_scene(res_path); @@ -3715,7 +3988,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..0adeca031e 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -155,10 +155,13 @@ 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; @@ -306,6 +309,7 @@ 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); @@ -338,6 +342,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(); @@ -360,27 +365,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 _breaked(bool p_breaked, bool p_can_debug); - void _update_window_menu(); void _script_created(Ref<Script> p_script); + void _set_breakpoint(REF 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(); @@ -411,13 +421,13 @@ class ScriptEditor : public PanelContainer { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + virtual void input(const Ref<InputEvent> &p_event) override; virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; 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(); @@ -440,7 +450,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); + Set<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,6 +475,8 @@ 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 = ""); + RES open_file(const String &p_file); void ensure_select_current(); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 5f48106afc..d5af7c5b38 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -90,7 +90,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); @@ -158,7 +158,6 @@ void ScriptTextEditor::enable_editor() { editor_enabled = true; _enable_code_editor(); - _set_theme_for_script(); _validate_script(); } @@ -206,7 +205,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 +219,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); @@ -376,18 +375,21 @@ 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; @@ -434,10 +436,12 @@ void ScriptTextEditor::_validate_script() { 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(); @@ -459,6 +463,10 @@ 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) { @@ -542,7 +550,7 @@ void ScriptTextEditor::_validate_script() { 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); @@ -653,7 +661,7 @@ void ScriptEditor::_update_modified_scripts_for_external_editor(Ref<Script> p_fo 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 } @@ -694,7 +702,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); @@ -831,7 +839,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 +866,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 +885,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() { @@ -984,7 +990,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; } @@ -1131,7 +1137,7 @@ void ScriptTextEditor::_edit_option(int p_op) { if (expression.parse(line) == OK) { Variant result = expression.execute(Array(), Variant(), false); - if (expression.get_error_text() == "") { + if (expression.get_error_text().is_empty()) { results.push_back(whitespace + result.get_construct_string()); } else { results.push_back(line); @@ -1243,7 +1249,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 +1263,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; @@ -1338,8 +1344,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() { @@ -1370,6 +1374,14 @@ Array ScriptTextEditor::get_breakpoints() { return code_editor->get_text_editor()->get_breakpointed_lines(); } +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(String p_method, Object *p_obj) { code_editor->get_text_editor()->set_tooltip_request_func(p_obj, p_method, this); } @@ -1387,11 +1399,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; } @@ -1449,7 +1462,7 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data 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 += ", "; @@ -1513,7 +1526,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 +1560,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 +1611,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 +1629,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 +1688,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(); } @@ -1696,7 +1706,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)); @@ -1795,9 +1804,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); @@ -1836,6 +1845,7 @@ ScriptTextEditor::ScriptTextEditor() { 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); @@ -1943,70 +1953,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..afe9a7453d 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -197,7 +197,7 @@ 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; @@ -225,6 +225,8 @@ 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; diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 22ca5592bd..fa23ccf200 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -34,6 +34,7 @@ #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" @@ -131,9 +132,9 @@ void ShaderTextEditor::_load_theme_settings() { 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()); + 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); } } @@ -142,10 +143,10 @@ void ShaderTextEditor::_load_theme_settings() { } } - 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. @@ -178,6 +179,10 @@ 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; } @@ -199,7 +204,13 @@ void ShaderTextEditor::_code_complete_script(const String &p_code, List<ScriptCo ShaderLanguage sl; String calltip; - 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); + ShaderLanguage::ShaderCompileInfo info; + info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); + info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); + info.shader_types = ShaderTypes::get_singleton()->get_types(); + info.global_variable_type_func = _get_global_variable_type; + + sl.complete(p_code, info, r_options, calltip); get_text_editor()->set_code_hint(calltip); } @@ -211,12 +222,18 @@ void ShaderTextEditor::_validate_script() { //List<StringName> params; //shader->get_param_list(¶ms); + ShaderLanguage::ShaderCompileInfo info; + info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())); + info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())); + info.shader_types = ShaderTypes::get_singleton()->get_types(); + info.global_variable_type_func = _get_global_variable_type; + ShaderLanguage sl; sl.enable_warning_checking(saved_warnings_enabled); sl.set_warning_flags(saved_warning_flags); - Error err = sl.compile(code, 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); + Error err = sl.compile(code, info); if (err != OK) { String error_text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); @@ -380,7 +397,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) { @@ -478,8 +495,7 @@ void ShaderEditor::_check_for_external_edit() { return; } - // internal shader. - if (shader->get_path() == "" || shader->get_path().find("local://") != -1 || shader->get_path().find("::") != -1) { + if (shader->is_built_in()) { return; } @@ -526,7 +542,7 @@ void ShaderEditor::save_external_data(const String &p_str) { } apply_shaders(); - if (shader->get_path() != "" && shader->get_path().find("local://") == -1 && shader->get_path().find("::") == -1) { + if (!shader->is_built_in()) { //external shader, save it ResourceSaver::save(shader->get_path(), shader); } @@ -549,7 +565,7 @@ 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()); @@ -641,8 +657,8 @@ 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(); } diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp index 1e62261244..517de1901f 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -97,7 +97,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 +107,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 +136,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 +148,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 +182,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()) { diff --git a/editor/plugins/skeleton_2d_editor_plugin.cpp b/editor/plugins/skeleton_2d_editor_plugin.cpp index 7ef680d7ef..510e264c48 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.cpp +++ b/editor/plugins/skeleton_2d_editor_plugin.cpp @@ -52,34 +52,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 +98,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)); diff --git a/editor/plugins/skeleton_2d_editor_plugin.h b/editor/plugins/skeleton_2d_editor_plugin.h index dacd8fe43f..066888f685 100644 --- a/editor/plugins/skeleton_2d_editor_plugin.h +++ b/editor/plugins/skeleton_2d_editor_plugin.h @@ -40,8 +40,8 @@ class Skeleton2DEditor : public Control { GDCLASS(Skeleton2DEditor, Control); enum Menu { - MENU_OPTION_MAKE_REST, MENU_OPTION_SET_REST, + MENU_OPTION_MAKE_REST, }; Skeleton2D *node; diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 309821b3dc..bb5ef0f6eb 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -37,298 +37,318 @@ #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/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; } } } -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(); + + rest_matrix->set_object_and_property(skeleton, p_prop + "rest"); + rest_matrix->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); +void BoneTransformEditor::_property_keyed(const String &p_path, bool p_advance) { + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + PackedStringArray split = p_path.split("/"); + if (split.size() == 3 && split[0] == "bones") { + int bone_idx = split[1].to_int(); + if (split[2] == "position") { + te->insert_transform_key(skeleton, skeleton->get_bone_name(bone_idx), Animation::TYPE_POSITION_3D, skeleton->get(p_path)); + } + 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)); + } } } void BoneTransformEditor::_update_properties() { - if (updating) { + 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(property); - _update_transform_properties(tform); } -void BoneTransformEditor::_update_custom_pose_properties() { - if (updating) { - return; - } +Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr; - if (skeleton == nullptr) { - return; +void Skeleton3DEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + if (p_keyable) { + animation_hb->show(); + } else { + animation_hb->hide(); } +}; - 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(); - - 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); - - 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; + } + } } -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)); + } + 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() { @@ -356,7 +376,7 @@ void Skeleton3DEditor::create_physical_skeleton() { bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); - /// create physical bone on parent + // 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); @@ -370,7 +390,7 @@ void Skeleton3DEditor::create_physical_skeleton() { 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 + // Create joint between parent of parent. if (-1 != parent_parent) { bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN); } @@ -483,7 +503,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,43 +525,39 @@ 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/")) { - const int b_idx = path.get_slicec('/', 1).to_int(); - const String bone_path = "bones/" + itos(b_idx) + "/"; + if (selected) { + const String path = selected->get_metadata(0); - pose_editor->set_target(bone_path + "pose"); - rest_editor->set_target(bone_path + "rest"); - custom_pose_editor->set_target(bone_path + "custom_pose"); + if (path.begins_with("bones/")) { + const int b_idx = path.get_slicec('/', 1).to_int(); + const String bone_path = "bones/" + itos(b_idx) + "/"; - _update_properties(); - - pose_editor->set_visible(true); - rest_editor->set_visible(true); - custom_pose_editor->set_visible(true); + pose_editor->set_target(bone_path); + pose_editor->set_keyable(keyable); + selected_bone = b_idx; + } } + pose_editor->set_visible(selected); + set_bone_options_enabled(selected); + _update_properties(); + _update_gizmo_visible(); } +// May be not used with single select mode. void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { } void Skeleton3DEditor::_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; } @@ -569,7 +585,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 +603,96 @@ 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); + Node3DEditor *ne = Node3DEditor::get_singleton(); + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + + // Create Top Menu Bar. + separator = memnew(VSeparator); + ne->add_control_to_menu_panel(separator); - options->set_text(TTR("Skeleton3D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + // Create Skeleton Option in Top Menu Bar. + skeleton_options = memnew(MenuButton); + ne->add_control_to_menu_panel(skeleton_options); - options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); + skeleton_options->set_text(TTR("Skeleton3D")); + skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); - options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option)); + // 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->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), varray(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), varray(true)); + key_insert_all_button->set_tooltip(TTR("Insert key of all bone poses.")); + key_insert_all_button->set_shortcut(ED_SHORTCUT("skeleton_3d_editor/insert_key_of_all_bones", TTR("Insert Key (All Bones)"), KeyModifierMask::CMD + Key::INSERT)); + 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 +707,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 +717,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), Vector<Variant>(), 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)); #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 +756,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 +767,237 @@ 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); +} + +void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { + edit_mode = pressed; + _update_gizmo_visible(); } Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) : editor(p_editor), 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 : hint_albedo; +uniform float point_size : hint_range(0,128) = 32; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION.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 = editor->get_gui_base()->get_theme_icon("EditorBoneHandle", "EditorIcons"); + handle_material->set_shader_param("point_size", handle->get_width()); + handle_material->set_shader_param("texture_albedo", handle); + + 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 +1009,432 @@ 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, editor, skeleton)); add_custom_control(skel_editor); } Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { editor = p_node; - Ref<EditorInspectorPluginSkeleton> skeleton_plugin; - skeleton_plugin.instantiate(); + skeleton_plugin = memnew(EditorInspectorPluginSkeleton); skeleton_plugin->editor = editor; 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*INV_CAMERA_MATRIX*WORLD_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); + + // Regist 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 = 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); + } + + Vector<Transform3D> grests; + grests.resize(skeleton->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; + + 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(); + + // You have children but no parent, then you must be a root/parentless bone. + if (skeleton->get_bone_parent(current_bone_idx) < 0) { + grests.write[current_bone_idx] = skeleton->get_bone_rest(current_bone_idx); + } + + for (int i = 0; i < child_bones_size; i++) { + // Something wrong. + if (child_bones_vector[i] < 0) { + continue; + } + + int child_bone_idx = child_bones_vector[i]; + + grests.write[child_bone_idx] = grests[current_bone_idx] * skeleton->get_bone_rest(child_bone_idx); + + Vector3 v0 = grests[current_bone_idx].origin; + Vector3 v1 = grests[child_bone_idx].origin; + Vector3 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(grests[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(grests[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 + (grests[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 + (grests[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..1dd2d2281d 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -33,39 +33,39 @@ #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; + + EditorInspectorSection *rest_section = nullptr; + EditorPropertyTransform3D *rest_matrix = nullptr; Rect2 background_rects[5]; Skeleton3D *skeleton; - String property; + // String property; UndoRedo *undo_redo; - Button *key_button = nullptr; - CheckBox *enabled_checkbox = nullptr; - - bool keyable = false; bool toggle_enabled = false; bool updating = false; @@ -73,18 +73,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 +83,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,13 +96,17 @@ 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, }; struct BoneInfo { PhysicalBone3D *physical_bone = nullptr; - Transform3D relative_rest; // Relative to skeleton node + Transform3D relative_rest; // Relative to skeleton node. }; EditorNode *editor; @@ -138,15 +117,30 @@ class Skeleton3DEditor : public VBoxContainer { Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; - BoneTransformEditor *custom_pose_editor = nullptr; - MenuButton *options = nullptr; + VSeparator *separator; + MenuButton *skeleton_options = nullptr; + Button *edit_mode_button; + + bool edit_mode = false; + + HBoxContainer *animation_hb; + Button *key_loc_button; + Button *key_rot_button; + Button *key_scale_button; + Button *key_insert_button; + Button *key_insert_all_button; + EditorFileDialog *file_dialog = nullptr; - UndoRedo *undo_redo = nullptr; + bool keyable; - void _on_click_option(int p_option); + static Skeleton3DEditor *singleton; + + 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,6 +149,11 @@ 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); @@ -162,20 +161,57 @@ class Skeleton3DEditor : public VBoxContainer { 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; + 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); + 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(); @@ -186,6 +222,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { friend class Skeleton3DEditorPlugin; + Skeleton3DEditor *skel_editor; EditorNode *editor; public: @@ -196,12 +233,40 @@ public: class Skeleton3DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton3DEditorPlugin, EditorPlugin); + EditorInspectorPluginSkeleton *skeleton_plugin; EditorNode *editor; 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(EditorNode *p_node); +}; + +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/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 0f889ce33d..48b10d5a8b 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -182,7 +182,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 +209,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); } @@ -526,8 +526,6 @@ Sprite2DEditor::Sprite2DEditor() { 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)); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index 400f9f560f..316ef7b195 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -54,22 +54,46 @@ void SpriteFramesEditor::_open_sprite_sheet() { file_split_sheet->popup_file_dialog(); } +int SpriteFramesEditor::_sheet_preview_position_to_frame_index(const Point2 &p_position) { + if (p_position.x < 0 || p_position.y < 0) { + return -1; + } + + Size2i texture_size = split_sheet_preview->get_texture()->get_size(); + int h = split_sheet_h->get_value(); + int v = split_sheet_v->get_value(); + if (h > texture_size.width || v > texture_size.height) { + return -1; + } + + int x = int(p_position.x / sheet_zoom) / (texture_size.width / h); + int y = int(p_position.y / sheet_zoom) / (texture_size.height / v); + if (x >= h || y >= v) { + return -1; + } + return h * y + x; +} + void SpriteFramesEditor::_sheet_preview_draw() { - Size2i size = split_sheet_preview->get_size(); + Size2i texture_size = split_sheet_preview->get_texture()->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; + + real_t width = (texture_size.width / h) * sheet_zoom; + real_t height = (texture_size.height / v) * sheet_zoom; 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)); + + real_t y_end = v * height; + for (int i = 0; i <= h; i++) { + real_t x = i * width; + split_sheet_preview->draw_line(Point2(x, 0), Point2(x, y_end), Color(1, 1, 1, a)); + split_sheet_preview->draw_line(Point2(x + 1, 0), Point2(x + 1, y_end), Color(0, 0, 0, a)); } - 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)); + real_t x_end = h * width; + for (int i = 0; i <= v; i++) { + real_t y = i * height; + split_sheet_preview->draw_line(Point2(0, y), Point2(x_end, y), Color(1, 1, 1, a)); + split_sheet_preview->draw_line(Point2(0, y + 1), Point2(x_end, y + 1), Color(0, 0, 0, a)); } if (frames_selected.size() == 0) { @@ -83,9 +107,9 @@ void SpriteFramesEditor::_sheet_preview_draw() { 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; + int yp = idx / h; + real_t x = xp * width; + real_t y = yp * height; 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); @@ -102,66 +126,56 @@ void SpriteFramesEditor::_sheet_preview_draw() { 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,11 +200,11 @@ 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()) { + if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { _sheet_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()) { _sheet_zoom_out(); // Don't scroll down after zooming out. accept_event(); @@ -199,17 +213,17 @@ void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) { } void SpriteFramesEditor::_sheet_add_frames() { - Size2i size = split_sheet_preview->get_texture()->get_size(); + Size2i texture_size = split_sheet_preview->get_texture()->get_size(); int frame_count_x = split_sheet_h->get_value(); int frame_count_y = split_sheet_v->get_value(); - Size2 frame_size(size.width / frame_count_x, size.height / frame_count_y); + Size2 frame_size(texture_size.width / frame_count_x, texture_size.height / frame_count_y); undo_redo->create_action(TTR("Add Frame")); int fc = frames->get_frame_count(edited_anim); Point2 src_origin; - Rect2 src_region(Point2(), size); + Rect2 src_region(Point2(), texture_size); AtlasTexture *src_atlas = Object::cast_to<AtlasTexture>(*split_sheet_preview->get_texture()); if (src_atlas && src_atlas->get_atlas().is_valid()) { @@ -732,11 +746,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(); @@ -992,7 +1006,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); @@ -1224,8 +1238,6 @@ SpriteFramesEditor::SpriteFramesEditor() { 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); diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index 17e30f0cab..9732384000 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -100,7 +100,6 @@ class SpriteFramesEditor : public HSplitContainer { float min_sheet_zoom; 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(); @@ -128,13 +127,13 @@ class SpriteFramesEditor : public HSplitContainer { UndoRedo *undo_redo; - 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_preview_input(const Ref<InputEvent> &p_event); diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp index 91c5e96f08..1c7f319280 100644 --- a/editor/plugins/style_box_editor_plugin.cpp +++ b/editor/plugins/style_box_editor_plugin.cpp @@ -44,13 +44,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)); diff --git a/editor/plugins/style_box_editor_plugin.h b/editor/plugins/style_box_editor_plugin.h index 8ca348bd80..d82e5ab05e 100644 --- a/editor/plugins/style_box_editor_plugin.h +++ b/editor/plugins/style_box_editor_plugin.h @@ -61,8 +61,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 { diff --git a/editor/plugins/text_control_editor_plugin.cpp b/editor/plugins/text_control_editor_plugin.cpp new file mode 100644 index 0000000000..c878c83430 --- /dev/null +++ b/editor/plugins/text_control_editor_plugin.cpp @@ -0,0 +1,375 @@ +/*************************************************************************/ +/* text_control_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 "text_control_editor_plugin.h" + +#include "editor/editor_scale.h" + +void TextControlEditor::_notification(int p_notification) { + switch (p_notification) { + case NOTIFICATION_ENTER_TREE: { + if (!EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts))) { + EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts), make_binds("")); + } + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + clear_formatting->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); + } break; + case NOTIFICATION_EXIT_TREE: { + if (EditorFileSystem::get_singleton()->is_connected("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts))) { + EditorFileSystem::get_singleton()->disconnect("filesystem_changed", callable_mp(this, &TextControlEditor::_reload_fonts)); + } + } break; + default: + break; + } +} + +void TextControlEditor::_find_resources(EditorFileSystemDirectory *p_dir) { + for (int i = 0; i < p_dir->get_subdir_count(); i++) { + _find_resources(p_dir->get_subdir(i)); + } + + for (int i = 0; i < p_dir->get_file_count(); i++) { + if (p_dir->get_file_type(i) == "FontData") { + Ref<FontData> fd = ResourceLoader::load(p_dir->get_file_path(i)); + if (fd.is_valid()) { + String name = fd->get_font_name(); + String sty = fd->get_font_style_name(); + if (sty.is_empty()) { + sty = "Default"; + } + fonts[name][sty] = p_dir->get_file_path(i); + } + } + } +} + +void TextControlEditor::_reload_fonts(const String &p_path) { + fonts.clear(); + _find_resources(EditorFileSystem::get_singleton()->get_filesystem()); + _update_control(); +} + +void TextControlEditor::_update_fonts_menu() { + font_list->clear(); + font_list->add_item(TTR("[Theme Default]"), FONT_INFO_THEME_DEFAULT); + if (custom_font.is_valid()) { + font_list->add_item(TTR("[Custom Font]"), FONT_INFO_USER_CUSTOM); + } + + int id = FONT_INFO_ID; + for (Map<String, Map<String, String>>::Element *E = fonts.front(); E; E = E->next()) { + font_list->add_item(E->key(), id++); + } + + if (font_list->get_item_count() > 1) { + font_list->show(); + } else { + font_list->hide(); + } +} + +void TextControlEditor::_update_styles_menu() { + font_style_list->clear(); + if ((font_list->get_selected_id() >= FONT_INFO_ID)) { + const String &name = font_list->get_item_text(font_list->get_selected()); + for (Map<String, String>::Element *E = fonts[name].front(); E; E = E->next()) { + font_style_list->add_item(E->key()); + } + } else { + font_style_list->add_item("Default"); + } + + if (font_style_list->get_item_count() > 1) { + font_style_list->show(); + } else { + font_style_list->hide(); + } +} + +void TextControlEditor::_update_control() { + if (edited_control) { + // Get override names. + if (edited_control->is_class("RichTextLabel")) { + edited_color = "default_color"; + edited_font = "normal_font"; + edited_font_size = "normal_font_size"; + } else { + edited_color = "font_color"; + edited_font = "font"; + edited_font_size = "font_size"; + } + + // Get font override. + Ref<Font> font; + if (edited_control->has_theme_font_override(edited_font)) { + font = edited_control->get_theme_font(edited_font); + } + if (font.is_valid()) { + if (font->get_data_count() != 1) { + // Composite font, save it to "custom_font" to allow undoing font change. + custom_font = font; + _update_fonts_menu(); + font_list->select(FONT_INFO_USER_CUSTOM); + _update_styles_menu(); + font_style_list->select(0); + } else { + // Single face font, search for the font with matching name and style. + String name = font->get_data(0)->get_font_name(); + String style = font->get_data(0)->get_font_style_name(); + if (fonts.has(name) && fonts[name].has(style)) { + _update_fonts_menu(); + for (int i = 0; i < font_list->get_item_count(); i++) { + if (font_list->get_item_text(i) == name) { + font_list->select(i); + break; + } + } + _update_styles_menu(); + for (int i = 0; i < font_style_list->get_item_count(); i++) { + if (font_style_list->get_item_text(i) == style) { + font_style_list->select(i); + break; + } + } + } else { + // Unknown font, save it to "custom_font" to allow undoing font change. + custom_font = font; + _update_fonts_menu(); + font_list->select(FONT_INFO_USER_CUSTOM); + _update_styles_menu(); + font_style_list->select(0); + } + } + } else { + // No font override, select "Theme Default". + _update_fonts_menu(); + font_list->select(FONT_INFO_THEME_DEFAULT); + _update_styles_menu(); + font_style_list->select(0); + } + + // Get other theme overrides. + font_size_list->set_value(edited_control->get_theme_font_size(edited_font_size)); + outline_size_list->set_value(edited_control->get_theme_constant("outline_size")); + + font_color_picker->set_pick_color(edited_control->get_theme_color(edited_color)); + outline_color_picker->set_pick_color(edited_control->get_theme_color("font_outline_color")); + } +} + +void TextControlEditor::_font_selected(int p_id) { + _update_styles_menu(); + _set_font(); +} + +void TextControlEditor::_font_style_selected(int p_id) { + _set_font(); +} + +void TextControlEditor::_set_font() { + if (edited_control) { + if (font_list->get_selected_id() == FONT_INFO_THEME_DEFAULT) { + // Remove font override. + edited_control->remove_theme_font_override(edited_font); + return; + } else if (font_list->get_selected_id() == FONT_INFO_USER_CUSTOM) { + // Restore "custom_font". + edited_control->add_theme_font_override(edited_font, custom_font); + return; + } else { + // Load new font resource using selected name and style. + String name = font_list->get_item_text(font_list->get_selected()); + String sty = font_style_list->get_item_text(font_style_list->get_selected()); + if (sty.is_empty()) { + sty = "Default"; + } + if (fonts.has(name)) { + Ref<FontData> fd = ResourceLoader::load(fonts[name][sty]); + if (fd.is_valid()) { + Ref<Font> f; + f.instantiate(); + f->add_data(fd); + edited_control->add_theme_font_override(edited_font, f); + } + } + } + } +} + +void TextControlEditor::_font_size_selected(double p_size) { + if (edited_control) { + edited_control->add_theme_font_size_override(edited_font_size, p_size); + } +} + +void TextControlEditor::_outline_size_selected(double p_size) { + if (edited_control) { + edited_control->add_theme_constant_override("outline_size", p_size); + } +} + +void TextControlEditor::_font_color_changed(const Color &p_color) { + if (edited_control) { + edited_control->add_theme_color_override(edited_color, p_color); + } +} + +void TextControlEditor::_outline_color_changed(const Color &p_color) { + if (edited_control) { + edited_control->add_theme_color_override("font_outline_color", p_color); + } +} + +void TextControlEditor::_clear_formatting() { + if (edited_control) { + edited_control->begin_bulk_theme_override(); + edited_control->remove_theme_font_override(edited_font); + edited_control->remove_theme_font_size_override(edited_font_size); + edited_control->remove_theme_color_override(edited_color); + edited_control->remove_theme_color_override("font_outline_color"); + edited_control->remove_theme_constant_override("outline_size"); + edited_control->end_bulk_theme_override(); + _update_control(); + } +} + +void TextControlEditor::edit(Object *p_object) { + Control *ctrl = Object::cast_to<Control>(p_object); + if (!ctrl) { + edited_control = nullptr; + custom_font = Ref<Font>(); + } else { + edited_control = ctrl; + custom_font = Ref<Font>(); + _update_control(); + } +} + +bool TextControlEditor::handles(Object *p_object) const { + Control *ctrl = Object::cast_to<Control>(p_object); + if (!ctrl) { + return false; + } else { + bool valid = false; + ctrl->get("text", &valid); + return valid; + } +} + +TextControlEditor::TextControlEditor() { + add_child(memnew(VSeparator)); + + font_list = memnew(OptionButton); + font_list->set_flat(true); + font_list->set_tooltip(TTR("Font")); + add_child(font_list); + font_list->connect("item_selected", callable_mp(this, &TextControlEditor::_font_selected)); + + font_style_list = memnew(OptionButton); + font_style_list->set_flat(true); + font_style_list->set_tooltip(TTR("Font style")); + font_style_list->set_toggle_mode(true); + add_child(font_style_list); + font_style_list->connect("item_selected", callable_mp(this, &TextControlEditor::_font_style_selected)); + + font_size_list = memnew(SpinBox); + font_size_list->set_tooltip(TTR("Font Size")); + font_size_list->get_line_edit()->add_theme_constant_override("minimum_character_width", 2); + font_size_list->set_min(6); + font_size_list->set_step(1); + font_size_list->set_max(96); + font_size_list->get_line_edit()->set_flat(true); + add_child(font_size_list); + font_size_list->connect("value_changed", callable_mp(this, &TextControlEditor::_font_size_selected)); + + font_color_picker = memnew(ColorPickerButton); + font_color_picker->set_custom_minimum_size(Size2(20, 0) * EDSCALE); + font_color_picker->set_flat(true); + font_color_picker->set_tooltip(TTR("Text Color")); + add_child(font_color_picker); + font_color_picker->connect("color_changed", callable_mp(this, &TextControlEditor::_font_color_changed)); + + add_child(memnew(VSeparator)); + + outline_size_list = memnew(SpinBox); + outline_size_list->set_tooltip(TTR("Outline Size")); + outline_size_list->get_line_edit()->add_theme_constant_override("minimum_character_width", 2); + outline_size_list->set_min(0); + outline_size_list->set_step(1); + outline_size_list->set_max(96); + outline_size_list->get_line_edit()->set_flat(true); + add_child(outline_size_list); + outline_size_list->connect("value_changed", callable_mp(this, &TextControlEditor::_outline_size_selected)); + + outline_color_picker = memnew(ColorPickerButton); + outline_color_picker->set_custom_minimum_size(Size2(20, 0) * EDSCALE); + outline_color_picker->set_flat(true); + outline_color_picker->set_tooltip(TTR("Outline Color")); + add_child(outline_color_picker); + outline_color_picker->connect("color_changed", callable_mp(this, &TextControlEditor::_outline_color_changed)); + + add_child(memnew(VSeparator)); + + clear_formatting = memnew(Button); + clear_formatting->set_flat(true); + clear_formatting->set_tooltip(TTR("Clear Formatting")); + add_child(clear_formatting); + clear_formatting->connect("pressed", callable_mp(this, &TextControlEditor::_clear_formatting)); +} + +/*************************************************************************/ + +void TextControlEditorPlugin::edit(Object *p_object) { + text_ctl_editor->edit(p_object); +} + +bool TextControlEditorPlugin::handles(Object *p_object) const { + return text_ctl_editor->handles(p_object); +} + +void TextControlEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + text_ctl_editor->show(); + } else { + text_ctl_editor->hide(); + text_ctl_editor->edit(nullptr); + } +} + +TextControlEditorPlugin::TextControlEditorPlugin(EditorNode *p_node) { + editor = p_node; + text_ctl_editor = memnew(TextControlEditor); + CanvasItemEditor::get_singleton()->add_control_to_menu_panel(text_ctl_editor); + + text_ctl_editor->hide(); +} diff --git a/editor/plugins/text_control_editor_plugin.h b/editor/plugins/text_control_editor_plugin.h new file mode 100644 index 0000000000..7f4aa3754c --- /dev/null +++ b/editor/plugins/text_control_editor_plugin.h @@ -0,0 +1,119 @@ +/*************************************************************************/ +/* text_control_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 TEXT_CONTROL_EDITOR_PLUGIN_H +#define TEXT_CONTROL_EDITOR_PLUGIN_H + +#include "canvas_item_editor_plugin.h" +#include "editor/editor_file_system.h" +#include "editor/editor_inspector.h" +#include "editor/editor_node.h" +#include "editor/editor_plugin.h" +#include "scene/gui/color_rect.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" +#include "scene/gui/popup_menu.h" + +/*************************************************************************/ + +class TextControlEditor : public HBoxContainer { + GDCLASS(TextControlEditor, HBoxContainer); + + enum FontInfoID { + FONT_INFO_THEME_DEFAULT = 0, + FONT_INFO_USER_CUSTOM = 1, + FONT_INFO_ID = 100, + }; + + Map<String, Map<String, String>> fonts; + + OptionButton *font_list = nullptr; + SpinBox *font_size_list = nullptr; + OptionButton *font_style_list = nullptr; + ColorPickerButton *font_color_picker = nullptr; + SpinBox *outline_size_list = nullptr; + ColorPickerButton *outline_color_picker = nullptr; + Button *clear_formatting = nullptr; + + Control *edited_control = nullptr; + String edited_color; + String edited_font; + String edited_font_size; + Ref<Font> custom_font; + +protected: + void _notification(int p_notification); + static void _bind_methods(){}; + + void _find_resources(EditorFileSystemDirectory *p_dir); + void _reload_fonts(const String &p_path); + + void _update_fonts_menu(); + void _update_styles_menu(); + void _update_control(); + + void _font_selected(int p_id); + void _font_style_selected(int p_id); + void _set_font(); + + void _font_size_selected(double p_size); + void _outline_size_selected(double p_size); + + void _font_color_changed(const Color &p_color); + void _outline_color_changed(const Color &p_color); + + void _clear_formatting(); + +public: + void edit(Object *p_object); + bool handles(Object *p_object) const; + + TextControlEditor(); +}; + +/*************************************************************************/ + +class TextControlEditorPlugin : public EditorPlugin { + GDCLASS(TextControlEditorPlugin, EditorPlugin); + + TextControlEditor *text_ctl_editor; + EditorNode *editor; + +public: + virtual String get_name() const override { return "TextControlFontEditor"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + TextControlEditorPlugin(EditorNode *p_node); +}; + +#endif // TEXT_CONTROL_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 32bcc1a4e6..cbde382d67 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -65,18 +65,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; @@ -407,10 +410,6 @@ 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) { if (Object::cast_to<TextFile>(*p_resource)) { return memnew(TextEditor); @@ -426,7 +425,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,11 +507,15 @@ 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); @@ -521,6 +524,7 @@ TextEditor::TextEditor() { code_editor->connect("validate_script", callable_mp(this, &TextEditor::_validate_script)); code_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); 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..7404557f46 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -87,8 +87,6 @@ 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); @@ -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; @@ -137,6 +137,7 @@ public: virtual void set_debugger_active(bool p_active) override; virtual void set_tooltip_request_func(String p_method, Object *p_obj) 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..b4e394a1c0 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -173,7 +173,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); diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 44db06bcfd..e25b0270b4 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -58,6 +58,21 @@ void TexturePreview::_notification(int p_what) { } } +void TexturePreview::_update_metadata_label_text() { + Ref<Texture2D> texture = texture_display->get_texture(); + + String format; + if (Object::cast_to<ImageTexture>(*texture)) { + format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format()); + } else if (Object::cast_to<StreamTexture2D>(*texture)) { + format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*texture)->get_format()); + } else { + format = texture->get_class(); + } + + metadata_label->set_text(itos(texture->get_width()) + "x" + itos(texture->get_height()) + " " + format); +} + TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { checkerboard = memnew(TextureRect); checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); @@ -75,16 +90,8 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { 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")); @@ -94,7 +101,7 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { 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("shadow_outline_size", 1); metadata_label->set_h_size_flags(Control::SIZE_SHRINK_END); metadata_label->set_v_size_flags(Control::SIZE_SHRINK_END); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 36a5513ea6..60349febd7 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -44,6 +44,8 @@ private: TextureRect *checkerboard = nullptr; Label *metadata_label = nullptr; + void _update_metadata_label_text(); + protected: void _notification(int p_what); diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 424e018a47..1f536d13cf 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -38,7 +38,7 @@ 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(); @@ -249,7 +249,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); diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 1a6eb7b63b..8e1c81a876 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -50,7 +50,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 +63,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()) { @@ -284,7 +284,7 @@ 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->get_button_index() == MouseButton::LEFT) { if (mb->is_pressed()) { if (node_ninepatch || obj_styleBox.is_valid()) { edited_margin = -1; @@ -321,35 +321,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 +362,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 +372,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; } } @@ -419,15 +419,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 +446,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,9 +465,9 @@ 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()) { + } else if (mb->get_button_index() == MouseButton::WHEEL_UP && mb->is_pressed()) { _zoom_on_position(draw_zoom * ((0.95 + (0.05 * mb->get_factor())) / 0.95), mb->get_position()); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed()) { + } else if (mb->get_button_index() == MouseButton::WHEEL_DOWN && mb->is_pressed()) { _zoom_on_position(draw_zoom * (1 - (0.05 * mb->get_factor())), mb->get_position()); } } @@ -475,7 +475,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { 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)) { + if ((mm->get_button_mask() & MouseButton::MASK_MIDDLE) != MouseButton::NONE || Input::get_singleton()->is_key_pressed(Key::SPACE)) { Vector2 dragged(mm->get_relative().x / draw_zoom, mm->get_relative().y / draw_zoom); hscroll->set_value(hscroll->get_value() - dragged.x); vscroll->set_value(vscroll->get_value() - dragged.y); @@ -544,7 +544,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); @@ -674,8 +674,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 +692,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 +719,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 +727,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()) { @@ -823,8 +822,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 +851,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 +876,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,14 +897,14 @@ 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())) { + if ((node_sprite_2d && !node_sprite_2d->is_region_enabled()) || (node_sprite_3d && !node_sprite_3d->is_region_enabled())) { set_process(true); } if (!p_obj) { @@ -922,16 +921,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()) { @@ -967,7 +966,7 @@ Vector2 TextureRegionEditor::snap_point(Vector2 p_target) const { } TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { - node_sprite = nullptr; + node_sprite_2d = nullptr; node_sprite_3d = nullptr; node_ninepatch = nullptr; obj_styleBox = Ref<StyleBoxTexture>(nullptr); @@ -1122,7 +1121,9 @@ void TextureRegionEditorPlugin::_editor_visiblity_changed() { 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()); + bool is_node_configured = region_editor->is_stylebox() || region_editor->is_atlas_texture() || region_editor->is_ninepatch(); + is_node_configured |= region_editor->get_sprite_2d() && region_editor->get_sprite_2d()->is_region_enabled(); + is_node_configured |= region_editor->get_sprite_3d() && region_editor->get_sprite_3d()->is_region_enabled(); if ((is_node_configured && !manually_hidden) || texture_region_button->is_pressed()) { editor->make_bottom_panel_item_visible(region_editor); } diff --git a/editor/plugins/texture_region_editor_plugin.h b/editor/plugins/texture_region_editor_plugin.h index d3db0a08a9..c043d6ae33 100644 --- a/editor/plugins/texture_region_editor_plugin.h +++ b/editor/plugins/texture_region_editor_plugin.h @@ -83,7 +83,7 @@ class TextureRegionEditor : public VBoxContainer { Vector2 snap_step; Vector2 snap_separation; - Sprite2D *node_sprite; + Sprite2D *node_sprite_2d; Sprite3D *node_sprite_3d; NinePatchRect *node_ninepatch; Ref<StyleBoxTexture> obj_styleBox; @@ -134,8 +134,8 @@ 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); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 165a381407..b5d2b571f5 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -437,8 +437,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++; } @@ -759,7 +759,7 @@ void ThemeItemImportTree::_import_selected() { 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 +769,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(); @@ -941,7 +941,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,7 +1115,7 @@ 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); @@ -1130,7 +1130,7 @@ ThemeItemImportTree::ThemeItemImportTree() { 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)); - 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); @@ -1711,13 +1711,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; @@ -1783,7 +1783,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { set_hide_on_ok(false); // Closing may require a confirmation in some cases. tc = memnew(TabContainer); - tc->set_tab_align(TabContainer::TabAlign::ALIGN_LEFT); + tc->set_tab_alignment(TabContainer::ALIGNMENT_LEFT); add_child(tc); // Edit Items tab. @@ -1909,8 +1909,8 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { edit_items_message = memnew(Label); edit_items_message->set_anchors_and_offsets_preset(Control::PRESET_WIDE); 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_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); + edit_items_message->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER); edit_items_message->set_autowrap_mode(Label::AUTOWRAP_WORD); edit_items_tree->add_child(edit_items_message); @@ -1996,7 +1996,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) { @@ -2042,12 +2042,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 +2095,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 +2110,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,6 +2118,12 @@ 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) { @@ -2113,7 +2134,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); @@ -2581,11 +2602,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 +2624,7 @@ void ThemeTypeEditor::_list_type_selected(int p_index) { void ThemeTypeEditor::_add_type_button_cbk() { add_type_mode = ADD_THEME_TYPE; add_type_dialog->set_title(TTR("Add Item Type")); + add_type_dialog->get_ok_button()->set_text(TTR("Add Type")); add_type_dialog->set_include_own_types(false); add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); } @@ -2848,7 +2870,7 @@ 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)); } -void ThemeTypeEditor::_edit_resource_item(RES p_resource) { +void ThemeTypeEditor::_edit_resource_item(RES p_resource, bool p_edit) { EditorNode::get_singleton()->edit_resource(p_resource); } @@ -2969,7 +2991,8 @@ void ThemeTypeEditor::_type_variation_changed(const String p_value) { void ThemeTypeEditor::_add_type_variation_cbk() { add_type_mode = ADD_VARIATION_BASE; - add_type_dialog->set_title(TTR("Add Variation Base Type")); + add_type_dialog->set_title(TTR("Set Variation Base Type")); + add_type_dialog->get_ok_button()->set_text(TTR("Set Base Type")); add_type_dialog->set_include_own_types(true); add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); } @@ -3102,7 +3125,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,7 +3152,7 @@ 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_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); type_variation_locked->set_autowrap_mode(Label::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(); @@ -3204,7 +3227,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_right_button(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); @@ -3328,12 +3351,12 @@ 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_tab_alignment(TabBar::ALIGNMENT_LEFT); preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL); preview_tabbar_hb->add_child(preview_tabs); preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab)); - preview_tabs->connect("right_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab)); + preview_tabs->connect("tab_rmb_clicked", callable_mp(this, &ThemeEditor::_remove_preview_tab)); HBoxContainer *add_preview_button_hb = memnew(HBoxContainer); preview_tabbar_hb->add_child(add_preview_button_hb); diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index 5b0357e3f8..bd1bf216e1 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -31,10 +31,11 @@ #ifndef THEME_EDITOR_PLUGIN_H #define THEME_EDITOR_PLUGIN_H +#include "scene/gui/dialogs.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/resources/theme.h" #include "theme_editor_preview.h" @@ -270,8 +271,11 @@ class ThemeTypeDialog : public ConfirmationDialog { Ref<Theme> edited_theme; bool include_own_types = false; + String pre_submitted_value; + LineEdit *add_type_filter; ItemList *add_type_options; + ConfirmationDialog *add_type_confirmation; void _dialog_about_to_show(); void ok_pressed() override; @@ -283,6 +287,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(); @@ -347,7 +354,6 @@ class ThemeTypeEditor : public MarginContainer { 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,7 +369,7 @@ class ThemeTypeEditor : public MarginContainer { void _color_item_changed(Color p_value, String p_item_name); void _constant_item_changed(float p_value, String p_item_name); void _font_size_item_changed(float p_value, String p_item_name); - void _edit_resource_item(RES p_resource); + void _edit_resource_item(RES 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); @@ -391,7 +397,7 @@ class ThemeEditor : public VBoxContainer { Ref<Theme> theme; - Tabs *preview_tabs; + TabBar *preview_tabs; PanelContainer *preview_tabs_content; Button *add_preview_button; EditorFileDialog *preview_scene_dialog; diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index 801ee0eac2..9a5ecd3802 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -47,7 +47,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)); @@ -71,6 +71,9 @@ void ThemeEditorPreview::_preview_visibility_changed() { 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 +120,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, 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 +129,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 +147,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 +157,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 +168,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,6 +195,7 @@ 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(); @@ -219,9 +227,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); @@ -359,7 +365,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..73422b4fba 100644 --- a/editor/plugins/theme_editor_preview.h +++ b/editor/plugins/theme_editor_preview.h @@ -55,6 +55,7 @@ class ThemeEditorPreview : public VBoxContainer { GDCLASS(ThemeEditorPreview, VBoxContainer); + ScrollContainer *preview_container; ColorRect *preview_bg; MarginContainer *preview_overlay; Control *picker_overlay; @@ -65,6 +66,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; diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp index d54906c98c..e4a16274fe 100644 --- a/editor/plugins/tiles/atlas_merging_dialog.cpp +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -94,12 +94,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. @@ -116,6 +118,7 @@ void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atla output_image_texture.instantiate(); output_image_texture->create_from_image(output_image); + merged->set_name(p_atlas_sources[0]->get_name()); merged->set_texture(output_image_texture); merged->set_texture_region_size(new_texture_region_size); } @@ -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/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index 0add83f64d..6a3261cbee 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -46,7 +46,7 @@ void TileAtlasView::gui_input(const Ref<InputEvent> &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)); + Vector2i scroll_vec = Vector2((mb->get_button_index() == MouseButton::WHEEL_LEFT) - (mb->get_button_index() == MouseButton::WHEEL_RIGHT), (mb->get_button_index() == MouseButton::WHEEL_UP) - (mb->get_button_index() == MouseButton::WHEEL_DOWN)); if (scroll_vec != Vector2()) { if (mb->is_ctrl_pressed()) { if (mb->is_shift_pressed()) { @@ -69,7 +69,7 @@ void TileAtlasView::gui_input(const Ref<InputEvent> &p_event) { } } - if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->get_button_index() == MouseButton::MIDDLE || mb->get_button_index() == MouseButton::RIGHT) { if (mb->is_pressed()) { drag_type = DRAG_TYPE_PAN; } else { @@ -97,15 +97,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; } @@ -213,54 +204,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, frame); + + // Draw, the texture in the separation areas + if (separation.x > 0) { + Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y)); + right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect); + base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } - // Draw the tile. - TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0); + 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 +321,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 +328,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,7 +367,7 @@ 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); @@ -368,18 +377,18 @@ void TileAtlasView::_draw_alternatives() { // Update the y to max value. Vector2i offset_pos = current_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 +451,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(); @@ -511,10 +519,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, Map<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 +541,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(); @@ -609,7 +616,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); @@ -649,17 +656,11 @@ TileAtlasView::TileAtlasView() { 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..e1ca3eebee 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -93,9 +93,6 @@ private: Control *base_tiles_shape_grid; void _draw_base_tiles_shape_grid(); - Control *base_tiles_dark; - void _draw_base_tiles_dark(); - Size2i _compute_base_tiles_control_size(); // Right side. @@ -124,7 +121,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; diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index d406c2514c..5ded607ab5 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -38,8 +38,16 @@ #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("_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) { @@ -59,18 +67,20 @@ TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { } 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 +125,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 +141,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()) { @@ -199,7 +218,7 @@ void GenericTilePolygonEditor::_base_control_draw() { 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)); + 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 +232,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 +247,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 +266,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 +279,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 +371,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 +408,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 +449,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 +516,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 +554,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 +563,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 +588,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 +599,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 +676,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); @@ -620,7 +719,13 @@ void GenericTilePolygonEditor::_notification(int p_what) { 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"))); + button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->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; } } @@ -647,18 +752,21 @@ 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); @@ -666,7 +774,13 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { 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_separator(); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateRight"), SNAME("EditorIcons")), TTR("Rotate Right"), ROTATE_RIGHT); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("RotateLeft"), SNAME("EditorIcons")), TTR("Rotate Left"), ROTATE_LEFT); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorX"), SNAME("EditorIcons")), TTR("Flip Horizontally"), FLIP_HORIZONTALLY); + button_advanced_menu->get_popup()->add_icon_item(get_theme_icon(SNAME("MirrorY"), SNAME("EditorIcons")), TTR("Flip Vertically"), FLIP_VERTICALLY); button_advanced_menu->get_popup()->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 +789,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); @@ -694,6 +809,7 @@ GenericTilePolygonEditor::GenericTilePolygonEditor() { 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); @@ -743,10 +859,10 @@ Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_s } 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); + 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); } } @@ -813,7 +929,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 +1020,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 +1080,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 +1112,8 @@ 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, font_size); + p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); } } @@ -1058,7 +1175,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 +1189,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) { @@ -1177,10 +1295,10 @@ Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_ } 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); + 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); } } @@ -1262,17 +1380,21 @@ 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) { @@ -1288,12 +1410,14 @@ 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()); @@ -1303,13 +1427,16 @@ void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_so TileData *tile_data = Object::cast_to<TileData>(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()); @@ -1319,37 +1446,41 @@ Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas TileData *tile_data = Object::cast_to<TileData>(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) { 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"]); } } } @@ -1375,6 +1506,27 @@ TileDataCollisionEditor::TileDataCollisionEditor() { 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->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->update_property(); + add_child(angular_velocity_editor); + property_editors["angular_velocity"] = angular_velocity_editor; + _polygons_changed(); } @@ -1465,12 +1617,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); } } @@ -1491,7 +1644,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas TileData *tile_data = Object::cast_to<TileData>(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. @@ -1513,15 +1666,17 @@ 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) { @@ -1535,7 +1690,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 +1699,8 @@ 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, font_size); + p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); } } } @@ -1627,7 +1782,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas Vector2i coords = E->get().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); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); @@ -1663,7 +1818,7 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til TileData *tile_data = Object::cast_to<TileData>(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. @@ -1685,15 +1840,17 @@ 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++) { @@ -1709,7 +1866,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 +1875,8 @@ 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, font_size); + p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); } } } @@ -1790,7 +1947,7 @@ 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); 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)) { @@ -1809,7 +1966,7 @@ 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()); @@ -1818,7 +1975,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t TileData *tile_data = Object::cast_to<TileData>(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); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { @@ -1916,7 +2073,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t // 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); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); @@ -1970,16 +2127,16 @@ 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"]); 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]); + 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 +2147,17 @@ 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(); 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); + 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]); + 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]); } } } @@ -2049,7 +2206,7 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t 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); + Vector2i position = texture_region.get_center() + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); for (int j = 0; j < polygon.size(); j++) { @@ -2132,7 +2289,7 @@ 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); 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)) { @@ -2150,7 +2307,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()); @@ -2161,7 +2318,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi TileData *tile_data = Object::cast_to<TileData>(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); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { @@ -2236,7 +2393,7 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi // Set the terrain bit. Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); - Vector2i position = (texture_region.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); 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)) { @@ -2253,14 +2410,14 @@ 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); 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 +2427,17 @@ 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(); 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); + 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]); + 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]); } } } @@ -2321,7 +2478,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 @@ -2392,10 +2549,10 @@ Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atla } 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); + 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); } } diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 99998dc779..3fc5e738bb 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -45,7 +45,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; @@ -92,7 +94,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 = EditorNode::get_undo_redo(); // UI int hovered_polygon_index = -1; @@ -106,7 +109,7 @@ private: DRAG_TYPE_CREATE_POINT, DRAG_TYPE_PAN, }; - DragType drag_type; + DragType drag_type = DRAG_TYPE_NONE; int drag_polygon_index; int drag_point_index; Vector2 drag_last_pos; @@ -141,6 +144,10 @@ private: enum AdvancedMenuOption { RESET_TO_DEFAULT_TILE, CLEAR_TILE, + ROTATE_RIGHT, + ROTATE_LEFT, + FLIP_HORIZONTALLY, + FLIP_VERTICALLY, }; void _base_control_draw(); @@ -159,6 +166,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)); diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 77084f551a..b4d6f3659d 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -36,6 +36,7 @@ #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 +44,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) { @@ -118,15 +99,26 @@ void TileMapEditorTilesPlugin::_update_toolbar() { picker_button->show(); erase_button->show(); tools_settings_vsep_2->show(); - bucket_continuous_checkbox->show(); + bucket_contiguous_checkbox->show(); random_tile_checkbox->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() { @@ -152,22 +144,31 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { 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("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. @@ -193,10 +194,10 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { } // Synchronize - TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current()); + 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 +244,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 +336,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 +371,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 +415,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 +427,32 @@ 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() { 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() { + 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"))); + + 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,7 +480,7 @@ 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()); @@ -454,35 +543,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) { + Map<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) { + Map<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,15 +590,23 @@ 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()) { @@ -521,31 +620,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) { + Map<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 +656,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) { + Map<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 +677,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 +714,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 +726,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. + if ((tiles_bottom_panel->is_visible_in_tree() || patterns_bottom_panel->is_visible_in_tree()) && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered. Map<Vector2i, TileMapCell> preview; 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); } } } @@ -656,21 +760,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 (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); - 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 +786,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()); + for (const KeyValue<Vector2i, TileMapCell> &E : preview) { + drawn_grid_rect.expand_to(E.key); } } } @@ -734,33 +840,35 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over 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 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_checkbox->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 = Object::cast_to<TileData>(atlas_source->get_tile_data(E.value.get_atlas_coords(), E.value.alternative_tile)); // 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 +876,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 +894,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 +915,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(); @@ -859,7 +967,7 @@ 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) { +Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { return Map<Vector2i, TileMapCell>(); @@ -871,14 +979,15 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ } // 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; 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_checkbox->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,7 +1016,7 @@ 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) { +Map<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>(); @@ -923,9 +1032,11 @@ 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; + 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> err_output; ERR_FAIL_COND_V(pattern->is_empty(), err_output); @@ -936,7 +1047,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!p_erase && random_tile_checkbox->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,7 +1075,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start return output; } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous) { +Map<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>(); @@ -982,16 +1093,17 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i } // 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(); } @@ -1004,11 +1116,11 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i 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_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1035,9 +1147,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 +1162,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_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1103,14 +1215,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,60 +1240,80 @@ 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); + } + + // Creating a pattern in the pattern list. + select_last_pattern = true; + int new_pattern_index = tile_set->get_patterns_count(); + undo_redo->create_action(TTR("Add TileSet pattern")); + undo_redo->add_do_method(*tile_set, "add_pattern", selection_pattern, new_pattern_index); + undo_redo->add_undo_method(*tile_set, "remove_pattern", new_pattern_index); + undo_redo->commit_action(); + } else { + // Get the top-left cell. + Vector2i top_left; + if (!tile_map_selection.is_empty()) { + top_left = tile_map_selection.front()->get(); + } + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + top_left = top_left.min(E->get()); + } - 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); + // 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); - TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); + TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); - 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)); - } + // Build the list of cells to undo. + 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)); + } - 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 do. + 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])); + } - // 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); + // Move the tiles. + undo_redo->create_action(TTR("Move tiles")); + for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + for (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); + } + + // 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); + 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++) { @@ -1191,51 +1323,50 @@ void TileMapEditorTilesPlugin::_stop_dragging() { } } } - selection_pattern = tile_map->get_pattern(tile_map_layer, coords_array); - if (!selection_pattern->is_empty()) { + 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); + Map<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)); + Map<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 +1394,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 +1406,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 +1418,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; } @@ -1314,8 +1448,29 @@ 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; + } + + Set<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); } } @@ -1325,9 +1480,14 @@ 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()) { @@ -1336,7 +1496,7 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection( 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,7 +1511,7 @@ 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; @@ -1360,17 +1520,17 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( } 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; - 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 { @@ -1389,14 +1549,14 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( } } 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 +1569,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 +1602,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() { @@ -1452,13 +1638,25 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { 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 (int frame = 0; frame < atlas->get_tile_animation_frames_count(E->get().get_atlas_coords()); frame++) { + Color color = selection_color; + if (frame > 0) { + color.a *= 0.3; + } + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords(), frame), color, false); + } } } // 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. @@ -1539,7 +1737,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 +1752,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 +1789,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; } @@ -1697,7 +1895,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 +1909,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(); @@ -1744,8 +1942,9 @@ 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; @@ -1761,15 +1960,19 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { 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); + toolbar->set_h_size_flags(Control::SIZE_EXPAND_FILL); HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer); @@ -1779,7 +1982,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 +1990,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 +1999,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 +2007,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 +2015,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 +2031,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 +2040,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,10 +2050,11 @@ 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); @@ -1877,42 +2084,47 @@ 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); 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_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)); + sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_source_display).unbind(1)); + sources_list->connect("item_selected", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::set_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditorPlugin::get_singleton(), &TilesEditorPlugin::synchronize_sources_list), varray(sources_list)); atlas_sources_split_container->add_child(sources_list); // 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,8 +2141,8 @@ 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)); @@ -1941,30 +2153,42 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { // 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("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_pattern_selection)); + 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 +2208,342 @@ 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; -} - -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; +Vector<TileMapEditorPlugin::TabData> TileMapEditorTerrainsPlugin::get_tabs() const { + Vector<TileMapEditorPlugin::TabData> tabs; + tabs.push_back({ toolbar, main_vbox_container }); + return tabs; } -Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<Constraint> p_constraints) const { +Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TileSet::TerrainsPattern> &p_to_paint, int p_terrain_set) const { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return Set<TerrainsTilePattern>(); + return Map<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 Map<Vector2i, TileMapCell>(); } - return compatible_terrain_tile_patterns; -} + Map<Vector2i, TileMapCell> output; -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>(); - } + // Add the constraints from the added tiles. + Set<TileMap::TerrainConstraint> added_tiles_constraints_set; + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { + Vector2i coords = E_to_paint.key; + TileSet::TerrainsPattern terrains_pattern = E_to_paint.value; - Ref<TileSet> tile_set = tile_map->get_tileset(); - if (!tile_set.is_valid()) { - return Set<Constraint>(); + Set<TileMap::TerrainConstraint> cell_constraints = tile_map->get_terrain_constraints_from_added_tile(coords, p_terrain_set, terrains_pattern); + for (Set<TileMap::TerrainConstraint>::Element *E = cell_constraints.front(); E; E = E->next()) { + added_tiles_constraints_set.insert(E->get()); + } } - 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)); + // Build the list of potential tiles to replace. + Set<Vector2i> potential_to_replace; + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { + Vector2i coords = E_to_paint.key; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (tile_map->is_existing_neighbor(TileSet::CellNeighbor(i))) { + Vector2i neighbor = tile_map->get_neighbor_cell(coords, TileSet::CellNeighbor(i)); + if (!p_to_paint.has(neighbor)) { + potential_to_replace.insert(neighbor); + } } } } - // 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(); + // Set of tiles to replace + Set<Vector2i> to_replace; - Map<int, int> terrain_count; + // Add the central tiles to the one to replace. + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { + to_replace.insert(E_to_paint.key); + } - // 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]; - } + // Add the constraints from the surroundings of the modified areas. + Set<TileMap::TerrainConstraint> removed_cells_constraints_set; + bool to_replace_modified = true; + while (to_replace_modified) { + // Get the constraints from the removed cells. + removed_cells_constraints_set = tile_map->get_terrain_constraints_from_removed_cells_list(tile_map_layer, to_replace, p_terrain_set); - 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; + // Filter the sources to make sure they are in the potential_to_replace. + Map<TileMap::TerrainConstraint, Set<Vector2i>> per_constraint_tiles; + for (Set<TileMap::TerrainConstraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) { + Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits(); + for (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_source_tile_of_constraint : sources_of_constraint) { + if (potential_to_replace.has(E_source_tile_of_constraint.key)) { + per_constraint_tiles[E->get()].insert(E_source_tile_of_constraint.key); } - 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(); + to_replace_modified = false; + for (Set<TileMap::TerrainConstraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { + TileMap::TerrainConstraint c = E->get(); + // Check if we have a conflict in constraints. + if (removed_cells_constraints_set.has(c) && removed_cells_constraints_set.find(c)->get().get_terrain() != c.get_terrain()) { + // If we do, we search for a neighbor to remove. + if (per_constraint_tiles.has(c) && !per_constraint_tiles[c].is_empty()) { + // Remove it. + Vector2i to_add_to_remove = per_constraint_tiles[c].front()->get(); + potential_to_replace.erase(to_add_to_remove); + to_replace.insert(to_add_to_remove); + to_replace_modified = true; + for (KeyValue<TileMap::TerrainConstraint, Set<Vector2i>> &E_source_tiles_of_constraint : per_constraint_tiles) { + E_source_tiles_of_constraint.value.erase(to_add_to_remove); + } + break; + } } } - - // Set the adequate terrain. - if (max > 0) { - c.set_terrain(max_terrain); - constraints.insert(c); - } } - return constraints; -} + // Combine all constraints together. + Set<TileMap::TerrainConstraint> constraints = removed_cells_constraints_set; + for (Set<TileMap::TerrainConstraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { + constraints.insert(E->get()); + } -Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const { - TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); - if (!tile_map) { - return Set<TileMapEditorTerrainsPlugin::Constraint>(); + // Remove the central tiles from the ones to replace. + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { + to_replace.erase(E_to_paint.key); } - Ref<TileSet> tile_set = tile_map->get_tileset(); - if (!tile_set.is_valid()) { - return Set<TileMapEditorTerrainsPlugin::Constraint>(); + // Run WFC to fill the holes with the constraints. + Map<Vector2i, TileSet::TerrainsPattern> wfc_output = tile_map->terrain_wave_function_collapse(to_replace, p_terrain_set, constraints); + + // Actually paint the tiles. + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E_to_paint : p_to_paint) { + output[E_to_paint.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E_to_paint.value); } - // 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++; - } + // Use the WFC run for the output. + for (const KeyValue<Vector2i, TileSet::TerrainsPattern> &E : wfc_output) { + output[E.key] = tile_set->get_random_tile_from_terrains_pattern(p_terrain_set, E.value); } 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 { +Map<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 Map<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern>(); + return Map<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); + TileSet::TerrainsPattern terrains_pattern; + if (p_erase) { + terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + } else { + terrains_pattern = selected_terrains_pattern; } - // 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()); - } - - // 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); + Map<Vector2i, TileSet::TerrainsPattern> to_draw; + for (int i = 0; i < line.size(); i++) { + to_draw[line[i]] = terrains_pattern; } - return output; + return _draw_terrains(to_draw, selected_terrain_set); } -TileMapCell TileMapEditorTerrainsPlugin::_get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const { +Map<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 Map<Vector2i, TileMapCell>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return TileMapCell(); + return Map<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); - - 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; - } + TileSet::TerrainsPattern terrains_pattern; + if (p_erase) { + terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + } else { + terrains_pattern = selected_terrains_pattern; } - // 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); + 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)); - count += tile_data->get_probability(); - } else { - count += 1.0; - } - } else { - count += 1.0; - } - - if (count >= picked) { - return E->get(); + Map<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++) { + to_draw[Vector2i(x, y)] = terrains_pattern; } } - - ERR_FAIL_V(TileMapCell()); + return _draw_terrains(to_draw, selected_terrain_set); } -Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const { +Set<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 Set<Vector2i>(); } Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return Map<Vector2i, TileMapCell>(); + return Set<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 = Object::cast_to<TileData>(atlas_source->get_tile_data(source_cell.get_atlas_coords(), source_cell.alternative_tile)); + } + if (!tile_data) { + return Set<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(); + } + + Set<Vector2i> output; + if (p_contiguous) { + // Replace continuous tiles like the source. + Set<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 = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords))); + } + 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 = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_map->get_cell_atlas_coords(tile_map_layer, coords), tile_map->get_cell_alternative_tile(tile_map_layer, coords))); + } + 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()); +Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map<Vector2i, TileMapCell>(); } - // Run WFC to fill the holes with the constraints. - Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints); + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map<Vector2i, TileMapCell>(); + } - // 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()); + TileSet::TerrainsPattern terrains_pattern; + if (p_erase) { + terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + } else { + terrains_pattern = selected_terrains_pattern; } - // 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()); + Set<Vector2i> cells_to_draw = _get_cells_for_bucket_fill(p_coords, p_contiguous); + Map<Vector2i, TileSet::TerrainsPattern> to_draw; + for (const Vector2i &coords : cells_to_draw) { + to_draw[coords] = terrains_pattern; } - return output; + return _draw_terrains(to_draw, selected_terrain_set); } void TileMapEditorTerrainsPlugin::_stop_dragging() { @@ -2667,26 +2552,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; + + Ref<TileSetSource> source = tile_set->get_source(cell.source_id); + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(cell.get_atlas_coords(), cell.alternative_tile)); + } - if (terrain_tiles.has(tile)) { - Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[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 +2597,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,15 +2612,9 @@ 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; - break; - } - } - if (equals) { + TileSet::TerrainsPattern in_meta_terrains_pattern(*tile_set, new_terrain_set); + in_meta_terrains_pattern.set_terrains_from_array(metadata_dict["terrains_pattern"]); + if (in_meta_terrains_pattern == terrains_pattern) { terrains_tile_list->select(i); break; } @@ -2733,20 +2627,90 @@ 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: { + Map<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: { + Map<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_terrains_pattern = TileSet::TerrainsPattern(); + 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_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + } else if (terrains_tile_list->is_anything_selected()) { + metadata_dict = terrains_tile_list->get_item_metadata(terrains_tile_list->get_selected_items()[0]); + selected_terrains_pattern = TileSet::TerrainsPattern(*tile_set, selected_terrain_set); + selected_terrains_pattern.set_terrains_from_array(metadata_dict["terrains_pattern"]); + } + } +} + 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 +2734,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()); + Map<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 +2765,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_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); + Map<Vector2i, TileMapCell> to_draw = _draw_line(cell, cell, drag_erasing); + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + drag_modified[E.key] = tile_map->get_cell(tile_map_layer, E.key); + tile_map->set_cell(tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); + } + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && !Input::get_singleton()->is_key_pressed(Key::CTRL))) { + if (selected_terrain_set < 0 || !selected_terrains_pattern.is_valid()) { + return true; + } + drag_type = DRAG_TYPE_LINE; + drag_start_mouse_pos = mpos; + drag_modified.clear(); + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(Key::SHIFT) && Input::get_singleton()->is_key_pressed(Key::CTRL))) { + if (selected_terrain_set < 0 || !selected_terrains_pattern.is_valid()) { + return true; + } + drag_type = DRAG_TYPE_RECT; + drag_start_mouse_pos = mpos; + drag_modified.clear(); + } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { + if (selected_terrain_set < 0 || !selected_terrains_pattern.is_valid()) { + 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])) { + Map<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 +2850,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; } - 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))); + if (!tile_map->is_visible_in_tree()) { + return; + } + + 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. + Set<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(); + + Map<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), 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 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 +2990,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(); } } @@ -2956,22 +3013,22 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { TileData *tile_data = Object::cast_to<TileData>(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 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_peering_bit_terrain(terrain_set, bit)) { + int terrain = terrains_pattern.get_terrain(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 +3036,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 +3059,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 +3104,28 @@ 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)); // 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()) { + Map<int, Set<TileSet::TerrainsPattern>> sorted; + + for (Set<TileSet::TerrainsPattern>::Element *E = per_terrain_terrains_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) { // 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_peering_bit_terrain(selected_terrain_set, bit) && E->get().get_terrain(bit) == selected_terrain_id) { count++; } } sorted[count].insert(E->get()); } - 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 (Map<int, Set<TileSet::TerrainsPattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) { + for (Set<TileSet::TerrainsPattern>::Element *E = E_set->get().front(); E; E = E->next()) { + TileSet::TerrainsPattern terrains_pattern = E->get(); // Get the icon. Ref<Texture2D> icon; @@ -3090,15 +3133,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 = Object::cast_to<TileData>(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; @@ -3119,7 +3162,7 @@ void TileMapEditorTerrainsPlugin::_update_tiles_list() { 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["terrains_pattern"] = terrains_pattern.get_terrains_as_array(); terrains_tile_list->set_item_metadata(item_index, list_metadata_dict); } } @@ -3129,6 +3172,16 @@ 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"))); +} + void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_layer) { _stop_dragging(); // Avoids staying in a wrong drag state. @@ -3141,15 +3194,18 @@ void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id, int p_tile_map_la } TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { - set_name("Terrains"); + 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,7 +3214,7 @@ 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); @@ -3177,10 +3233,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 +3274,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 +3282,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() { @@ -3225,7 +3316,7 @@ void TileMapEditor::_notification(int p_what) { 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; } @@ -3271,7 +3362,11 @@ void TileMapEditor::_layers_selection_button_draw() { clr = get_theme_color(SNAME("font_disabled_color")); break; default: - clr = get_theme_color(SNAME("font_color")); + if (layers_selection_button->has_focus()) { + clr = get_theme_color(SNAME("font_focus_color")); + } else { + clr = get_theme_color(SNAME("font_color")); + } } } @@ -3349,14 +3444,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 +3531,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(); } @@ -3546,33 +3636,79 @@ void TileMapEditor::_update_layers_selection() { 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 (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(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 +3722,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 +3779,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]); @@ -3700,10 +3838,12 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { 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 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 +3857,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,7 +3891,7 @@ 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(); } @@ -3760,32 +3900,40 @@ TileMapEditor::TileMapEditor() { 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. @@ -3836,23 +3984,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..f462119727 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -33,17 +33,24 @@ #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" +#include "scene/gui/tab_bar.h" -class TileMapEditorPlugin : public VBoxContainer { +class TileMapEditorPlugin : public Object { public: - virtual Control *get_toolbar() const { - return memnew(Control); + struct TabData { + Control *toolbar; + Control *panel; }; + + 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(){}; @@ -68,14 +75,15 @@ private: Button *line_tool_button; Button *rect_tool_button; Button *bucket_tool_button; - Button *picker_button; HBoxContainer *tools_settings; + VSeparator *tools_settings_vsep; + Button *picker_button; Button *erase_button; - CheckBox *bucket_continuous_checkbox; VSeparator *tools_settings_vsep_2; + CheckBox *bucket_contiguous_checkbox; CheckBox *random_tile_checkbox; float scattering = 0.0; Label *scatter_label; @@ -101,31 +109,38 @@ 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; - 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); + Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos, bool p_erase); + Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); + Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase); void _stop_dragging(); ///// Selection system. ///// Set<Vector2i> tile_map_selection; - TileMapPattern *tile_map_clipboard = memnew(TileMapPattern); - TileMapPattern *selection_pattern = memnew(TileMapPattern); + 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; 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(); - ///// Bottom panel. ////. + ///// Bottom panel common //// + void _tab_changed(); + + ///// Bottom panel tiles //// + VBoxContainer *tiles_bottom_panel; Label *missing_source_label; Label *invalid_source_label; @@ -134,7 +149,7 @@ private: 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; @@ -164,15 +179,26 @@ private: void _scenes_list_multi_selected(int p_index, bool p_selected); void _scenes_list_nothing_selected(); + ///// Bottom panel patterns //// + VBoxContainer *patterns_bottom_panel; + ItemList *patterns_item_list; + Label *patterns_help_label; + 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; @@ -194,102 +220,73 @@ private: Ref<ButtonGroup> tool_buttons_group; Button *paint_tool_button; + Button *line_tool_button; + Button *rect_tool_button; + Button *bucket_tool_button; HBoxContainer *tools_settings; + VSeparator *tools_settings_vsep; Button *picker_button; Button *erase_button; + VSeparator *tools_settings_vsep_2; + CheckBox *bucket_contiguous_checkbox; void _update_toolbar(); + // Main vbox. + VBoxContainer *main_vbox_container; + // 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; // 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; + Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TileSet::TerrainsPattern> &p_to_paint, int p_terrain_set) const; + Map<Vector2i, TileMapCell> _draw_line(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); + Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell, bool p_erase); + Set<Vector2i> _get_cells_for_bucket_fill(Vector2i p_coords, bool p_contiguous); + Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous, bool p_erase); 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; + int selected_terrain_set = -1; + TileSet::TerrainsPattern selected_terrains_pattern; + void _update_selection(); // Bottom panel. Tree *terrains_tree; ItemList *terrains_tile_list; + // Cache. + LocalVector<LocalVector<Set<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(); @@ -325,7 +322,9 @@ private: // Bottom panel. Label *missing_tileset_label; - Tabs *tabs; + TabBar *tabs_bar; + LocalVector<TileMapEditorPlugin::TabData> tabs_data; + LocalVector<TileMapEditorPlugin *> tabs_plugins; void _update_bottom_panel(); // TileMap. @@ -341,7 +340,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 +351,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(); diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp index 9e47a44b34..60a66ab954 100644 --- a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -34,7 +34,7 @@ void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list) { ItemList *item_list = Object::cast_to<ItemList>(p_item_list); - popup_menu->set_size(Vector2(1, 1)); + popup_menu->reset_size(); popup_menu->set_position(get_position() + item_list->get_global_mouse_position()); popup_menu->popup(); } diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 432f48fa85..660eb94cf5 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -66,10 +66,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 +83,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, "texture_region_size", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::BOOL, "use_texture_padding", PROPERTY_HINT_NONE, "")); } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { @@ -106,6 +118,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,52 +147,127 @@ 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); - - 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)); + 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); - 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; @@ -202,19 +293,63 @@ 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 (alternative == 0 && p_name == "size_in_atlas") { - r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords); + } 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 == "alternative_id") { - r_ret = alternative; + } else if (p_name == "animation_speed") { + r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); return true; + } 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; + } } } @@ -242,6 +377,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 +387,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, "")); + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); + // Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does. + if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); + } 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, "")); + } + } + } + // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit). struct PropertyId { int occurence_id = 0; @@ -374,9 +536,6 @@ 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() { @@ -598,9 +757,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 +776,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 +803,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 +842,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")); + } } } @@ -762,7 +929,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 +980,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 +1013,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 +1036,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 +1075,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 +1115,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 +1142,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 +1223,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 +1236,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 +1288,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; @@ -1260,7 +1427,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 })) { @@ -1454,9 +1621,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; @@ -1511,37 +1675,45 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { TileSelection selected = E->get(); 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); + 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->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); + } } } } @@ -1550,7 +1722,9 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { if (drag_type == DRAG_TYPE_REMOVE_TILES) { // Draw the tiles to be removed. for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) { - tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get()), Color(0.0, 0.0, 0.0), false); + for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(E->get()); frame++) { + tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get(), frame), Color(0.0, 0.0, 0.0), false); + } } } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) { // Draw tiles to be removed. @@ -1617,7 +1791,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,7 +1818,7 @@ 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); @@ -1661,7 +1841,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() { 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); + Vector2i position = texture_region.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(E->get().tile, 0); Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); xform.translate(position); @@ -1712,7 +1892,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,7 +1908,7 @@ 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 Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos); @@ -1805,7 +1985,7 @@ 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); @@ -1829,7 +2009,7 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { 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; + Vector2 position = rect.get_center(); Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform(); xform.translate(position); @@ -1850,8 +2030,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 +2051,68 @@ 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"); 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])); + 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 +2127,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 +2140,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. @@ -2061,7 +2282,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons")); break; case NOTIFICATION_INTERNAL_PROCESS: - if (tile_set_atlas_source_changed_needs_update) { + if (tile_set_changed_needs_update) { // Update everything. _update_source_inspector(); @@ -2074,7 +2295,7 @@ 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: @@ -2100,7 +2321,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); @@ -2114,18 +2335,18 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { 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_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER); tile_inspector_no_tile_selected_label->set_text(TTR("No tile selected.")); middle_vbox_container->add_child(tile_inspector_no_tile_selected_label); @@ -2164,7 +2385,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); @@ -2233,14 +2454,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 +2481,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); @@ -2290,7 +2509,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { 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); @@ -2308,15 +2527,202 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { 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("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 = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + generic_tile_polygon_editor->set_background(tile_set_atlas_source->get_texture(), tile_set_atlas_source->get_tile_texture_region(coords), tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); + + // 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..bd1fd2e7d0 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -43,7 +43,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 +78,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 -- @@ -98,6 +99,9 @@ private: static void _bind_methods(); public: + TileSetAtlasSource *get_edited_tile_set_atlas_source() const { return tile_set_atlas_source; }; + Set<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>()); @@ -106,13 +110,14 @@ private: } }; +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(); - bool tile_set_atlas_source_changed_needs_update = false; + bool tile_set_changed_needs_update = false; // -- Properties painting -- VBoxContainer *tile_data_painting_editor_container; @@ -189,7 +194,6 @@ private: TILE_CREATE_ALTERNATIVE, TILE_DELETE, - ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE, ADVANCED_AUTO_CREATE_TILES, ADVANCED_AUTO_REMOVE_TILES, }; @@ -263,7 +267,8 @@ private: void _auto_remove_tiles(); AcceptDialog *confirm_auto_create_tiles; - 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 +285,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; + 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..b7495dbea9 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -41,10 +41,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 +81,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) { @@ -145,14 +145,21 @@ void TileSetEditor::_update_sources_list(int force_selected_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,12 +167,14 @@ 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; @@ -200,7 +209,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) { @@ -318,6 +327,7 @@ void TileSetEditor::_notification(int p_what) { tile_set->set_edited(true); } _update_sources_list(); + _update_patterns_list(); tile_set_changed_needs_update = false; } break; @@ -326,15 +336,242 @@ void TileSetEditor::_notification(int p_what) { } } +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; + } + } +} + +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 (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(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 = Object::cast_to<TileData>(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_peering_bit_terrain(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_peering_bit_terrain(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); @@ -350,30 +587,7 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p TileData *tile_data = Object::cast_to<TileData>(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"); for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); @@ -381,22 +595,6 @@ void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p 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 +608,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 +629,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(); @@ -443,8 +642,21 @@ TileSetEditor::TileSetEditor() { set_process_internal(true); + // TabBar. + tabs_bar = memnew(TabBar); + 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); @@ -463,14 +675,14 @@ TileSetEditor::TileSetEditor() { 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), varray(sources_list)); 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); @@ -510,8 +722,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 +742,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..58312ce3df 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -46,13 +46,22 @@ class TileSetEditor : public VBoxContainer { private: Ref<TileSet> tile_set; bool tile_set_changed_needs_update = false; + HSplitContainer *split_container; + // TabBar. + HBoxContainer *tile_set_toolbar; + TabBar *tabs_bar; + + // Tiles. Label *no_source_selected_label; TileSetAtlasSourceEditor *tile_set_atlas_source_editor; TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; UndoRedo *undo_redo = EditorNode::get_undo_redo(); + 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. @@ -69,8 +78,18 @@ private: AtlasMergingDialog *atlas_merging_dialog; TileProxiesManagerDialog *tile_proxies_manager_dialog; + // Patterns. + ItemList *patterns_item_list; + Label *patterns_help_label; + 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,8 +100,6 @@ 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(); 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..035fe57d95 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -56,10 +56,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 +73,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 +103,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 +192,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; @@ -363,8 +385,8 @@ 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; } @@ -390,7 +412,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,8 +447,8 @@ 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() { @@ -437,7 +459,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 +477,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 +493,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..4e33128be5 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -51,6 +51,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: @@ -124,14 +125,15 @@ 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(); }; diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 79b869b511..47dfc57b0f 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -30,99 +30,184 @@ #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 = (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 lasst moment to avoid rendering too early. + EditorNode::get_singleton()->add_child(viewport); -void TilesEditor::_notification(int p_what) { + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<TilesEditorPlugin *>(this), &TilesEditorPlugin::_preview_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); + + pattern_preview_done.wait(); + + Ref<Image> image = viewport->get_texture()->get_image(); + Ref<ImageTexture> image_texture; + image_texture.instantiate(); + image_texture->create_from_image(image); + + // Find the index for the given pattern. TODO: optimize. + Variant args[] = { item.pattern, image_texture }; + const Variant *args_ptr[] = { &args[0], &args[1] }; + Variant r; + Callable::CallError error; + item.callback.call(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(); +} + +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 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 { +void TilesEditorPlugin::make_visible(bool p_visible) { + if (p_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) { + editor_node->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.")); + editor_node->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(); + editor_node->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) { +void TilesEditorPlugin::synchronize_sources_list(Object *p_current) { ItemList *item_list = Object::cast_to<ItemList>(p_current); ERR_FAIL_COND(!item_list); @@ -136,25 +221,25 @@ void TilesEditor::synchronize_sources_list(Object *p_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::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. @@ -164,114 +249,75 @@ void TilesEditor::edit(Object *p_object) { tile_map_id = p_object->get_instance_id(); tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); tile_set = tile_map->get_tileset(); + editor_node->make_bottom_panel_item_visible(tilemap_editor); } 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); + editor_node->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(EditorNode *p_node) { 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); + editor_node = p_node; - // 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 = p_node->add_bottom_panel_item(TTR("TileSet"), tileset_editor); + tileset_editor_button->hide(); + tilemap_editor_button = p_node->add_bottom_panel_item(TTR("TileMap"), tilemap_editor); + tilemap_editor_button->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..33493040f6 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -38,24 +38,24 @@ #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; private: + EditorNode *editor_node; + bool tile_map_changed_needs_update = false; ObjectID tile_map_id; Ref<TileSet> tile_set; - Button *tileset_tilemap_switch_button; - - Control *tilemap_toolbar; + Button *tilemap_editor_button; TileMapEditor *tilemap_editor; + Button *tileset_editor_button; TileSetEditor *tileset_editor; - void _update_switch_button(); void _update_editors(); // For synchronization. @@ -65,15 +65,35 @@ private: void _tile_map_changed(); + // 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; } - 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); } + 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); } + + // 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); @@ -82,27 +102,6 @@ public: 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); } - virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index aaa29bcb7a..eea0d2789d 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -49,6 +49,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 +76,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 +266,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 +299,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) { @@ -374,6 +383,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 +416,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 +447,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 +521,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 +530,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 +555,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..2782c1d9dc 100644 --- a/editor/plugins/version_control_editor_plugin.h +++ b/editor/plugins/version_control_editor_plugin.h @@ -57,6 +57,8 @@ private: List<StringName> available_addons; PopupMenu *version_control_actions; + ConfirmationDialog *metadata_dialog; + OptionButton *metadata_selection; AcceptDialog *set_up_dialog; VBoxContainer *set_up_vbc; HBoxContainer *set_up_hbc; @@ -98,6 +100,7 @@ private: RichTextLabel *diff; 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 +124,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; } diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 5b1da11f12..365cfd9232 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -31,6 +31,7 @@ #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" @@ -103,7 +104,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); @@ -123,16 +123,16 @@ void VisualShaderGraphPlugin::set_connections(List<VisualShader::Connection> &p_ 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; } @@ -212,19 +212,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 +256,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 +272,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 +291,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; } @@ -491,6 +493,35 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { bool is_curve = curve.is_valid() || curve_xyz.is_valid(); if (is_curve) { + // a default value handling + { + Variant default_value; + bool port_left_used = false; + + for (const VisualShader::Connection &E : connections) { + if (E.to_node == p_id && E.to_port == 0) { + port_left_used = true; + break; + } + } + + if (!port_left_used) { + default_value = vsnode->get_input_port_default_value(0); + } + + Button *button = memnew(Button); + custom_editor->add_child(button); + register_default_input_button(p_id, 0, button); + custom_editor->move_child(button, 0); + + button->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_edit_port_default_input), varray(button, p_id, 0)); + if (default_value.get_type() != Variant::NIL) { + set_input_port_default_value(p_type, p_id, 0, default_value); + } else { + button->hide(); + } + } + VisualShaderEditor::get_singleton()->graph->add_child(node); VisualShaderEditor::get_singleton()->_update_created_node(node); @@ -560,7 +591,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (vsnode->is_use_prop_slots()) { String error = vsnode->get_warning(visual_shader->get_mode(), p_type); - if (error != String()) { + if (!error.is_empty()) { Label *error_label = memnew(Label); error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_label->set_text(error); @@ -643,6 +674,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; } } } @@ -777,7 +809,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expand->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(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"))); @@ -845,7 +877,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { node->add_child(offset); String error = vsnode->get_warning(visual_shader->get_mode(), p_type); - if (error != String()) { + if (!error.is_empty()) { Label *error_label = memnew(Label); error_label->add_theme_color_override("font_color", VisualShaderEditor::get_singleton()->get_theme_color(SNAME("error_color"), SNAME("Editor"))); error_label->set_text(error); @@ -1008,7 +1040,6 @@ void VisualShaderEditor::edit(VisualShader *p_visual_shader) { hide(); } else { if (changed) { // to avoid tree collapse - _clear_buffer(); _update_options_menu(); _update_preview(); _update_graph(); @@ -1030,7 +1061,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--; } } @@ -1160,7 +1191,7 @@ void VisualShaderEditor::update_custom_nodes() { category = category.rstrip("/"); category = category.lstrip("/"); category = "Addons/" + category; - if (subcategory != "") { + if (!subcategory.is_empty()) { category += "/" + subcategory; } @@ -1207,7 +1238,7 @@ 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; @@ -1387,13 +1418,23 @@ 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); 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); + 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); @@ -1405,6 +1446,7 @@ void VisualShaderEditor::_set_mode(int p_which) { 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); mode = MODE_FLAGS_SPATIAL_CANVASITEM; @@ -1562,6 +1604,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 +1687,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; } @@ -1671,7 +1715,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; } @@ -1932,7 +1976,7 @@ 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; @@ -1945,8 +1989,8 @@ void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p 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 +2038,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 +2078,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() { @@ -2365,7 +2410,7 @@ 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); @@ -2547,6 +2592,7 @@ void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_pa } } } + _member_cancel(); VisualShaderNodeUniform *uniform = Object::cast_to<VisualShaderNodeUniform>(vsnode.ptr()); if (uniform) { @@ -2712,9 +2758,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) { @@ -3009,7 +3052,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 +3093,15 @@ void VisualShaderEditor::_graph_gui_input(const Ref<InputEvent> &p_event) { selected_float_constant = -1; } - if (to_change.is_empty() && copy_nodes_buffer.is_empty()) { + if (to_change.is_empty() && copy_items_buffer.is_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_items_buffer.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::DELETE, to_change.is_empty()); popup_menu->set_item_disabled(NodeMenuOptions::DUPLICATE, to_change.is_empty()); + popup_menu->set_item_disabled(NodeMenuOptions::CLEAR_COPY_BUFFER, copy_items_buffer.is_empty()); int temp = popup_menu->get_item_index(NodeMenuOptions::SEPARATOR2); if (temp != -1) { @@ -3121,9 +3166,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,37 +3185,27 @@ 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::_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(); } @@ -3279,69 +3314,88 @@ 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; + Set<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 { + 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; + Set<int> added_set; - for (int &E : r_nodes) { - connection_remap[E] = id_from; - Ref<VisualShaderNode> node = visual_shader->get_node(pasted_type, E); - + for (CopyItem &item : r_items) { bool unsupported = false; for (int i = 0; i < add_options.size(); i++) { - if (add_options[i].type == node->get_class_name()) { + if (add_options[i].type == item.node->get_class_name()) { if (!_is_available(add_options[i].mode)) { unsupported = true; } @@ -3349,48 +3403,47 @@ void VisualShaderEditor::_dup_paste_nodes(int p_type, int p_pasted_type, List<in } } if (unsupported) { - unsupported_set.insert(E); + 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 (int i = 0; i < r_items.size(); i++) { 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 +3451,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(); + + _dup_copy_nodes(get_current_shader_type(), copy_items_buffer, copy_connections_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); + } - _clear_buffer(); + _delete_nodes(get_current_shader_type(), ids); - _dup_copy_nodes(copy_type, copy_nodes_buffer, copy_nodes_excluded_buffer); + 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 +3520,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 +3539,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_graph(); + + graph->grab_focus(); } void VisualShaderEditor::_custom_mode_toggled(bool p_enabled) { @@ -3690,8 +3750,11 @@ 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); @@ -3702,6 +3765,9 @@ void VisualShaderEditor::_node_menu_id_pressed(int p_idx) { 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 +3775,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; @@ -3877,9 +3943,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_variable_type_func = _get_global_variable_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)); @@ -3916,7 +3988,7 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_input_select_item", &VisualShaderEditor::_input_select_item); ClassDB::bind_method("_uniform_select_item", &VisualShaderEditor::_uniform_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("_set_mode", &VisualShaderEditor::_set_mode); ClassDB::bind_method("_nodes_dragged", &VisualShaderEditor::_nodes_dragged); @@ -3970,7 +4042,7 @@ VisualShaderEditor::VisualShaderEditor() { 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("copy_nodes_request", callable_mp(this, &VisualShaderEditor::_copy_nodes), varray(false)); graph->connect("paste_nodes_request", callable_mp(this, &VisualShaderEditor::_paste_nodes), varray(false, Point2())); graph->connect("delete_nodes_request", callable_mp(this, &VisualShaderEditor::_delete_nodes_request)); graph->connect("gui_input", callable_mp(this, &VisualShaderEditor::_graph_gui_input)); @@ -4025,6 +4097,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 +4112,15 @@ 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()->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), varray(false, VisualShaderNode::PORT_TYPE_MAX, VisualShaderNode::PORT_TYPE_MAX)); preview_shader = memnew(Button); preview_shader->set_flat(true); @@ -4089,10 +4168,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)); /////////////////////////////////////// @@ -4166,8 +4247,8 @@ VisualShaderEditor::VisualShaderEditor() { 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_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 +4258,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 +4271,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); @@ -4303,6 +4384,7 @@ VisualShaderEditor::VisualShaderEditor() { const String input_param_for_fragment_and_light_shader_modes = TTR("'%s' input parameter for fragment and light shader modes."); const String input_param_for_fragment_shader_mode = TTR("'%s' input parameter for fragment shader mode."); const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode."); + const String input_param_for_fog_shader_mode = TTR("'%s' input parameter for fog shader mode."); const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode."); const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode."); const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode."); @@ -4422,6 +4504,16 @@ VisualShaderEditor::VisualShaderEditor() { 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)); + // FOG INPUTS + + add_options.push_back(AddOption("WorldPosition", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "world_position"), "world_position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("ObjectPosition", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "object_position"), "object_position", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("UVW", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "uvw"), "uvw", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("Extents", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "extents"), "extents", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("Transform", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("SDF", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "sdf"), "sdf", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + add_options.push_back(AddOption("Time", "Input", "Fog", "VisualShaderNodeInput", vformat(input_param_for_fog_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FOG, Shader::MODE_FOG)); + // 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)); @@ -4435,6 +4527,7 @@ VisualShaderEditor::VisualShaderEditor() { 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("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("MeshEmitter", "Particles", "Emitters", "VisualShaderNodeParticleMeshEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); @@ -4463,6 +4556,7 @@ VisualShaderEditor::VisualShaderEditor() { 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)); @@ -4502,6 +4596,11 @@ VisualShaderEditor::VisualShaderEditor() { 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)); @@ -4885,7 +4984,7 @@ public: } } - void setup(Ref<Resource> p_parent_resource, Vector<EditorProperty *> p_properties, const Vector<StringName> &p_names, Ref<VisualShaderNode> p_node) { + void setup(Ref<Resource> p_parent_resource, Vector<EditorProperty *> p_properties, const Vector<StringName> &p_names, const Map<StringName, String> &p_overrided_names, Ref<VisualShaderNode> p_node) { parent_resource = p_parent_resource; updating = false; node = p_node; @@ -4901,7 +5000,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); @@ -4993,7 +5096,7 @@ 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(p_parent_resource, editors, properties, p_node->get_editable_properties_names(), p_node); return editor; } @@ -5090,11 +5193,7 @@ 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) { @@ -5107,11 +5206,7 @@ bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, const Var 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 +5223,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; diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 9f24c5af72..74ccda3c9a 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -111,7 +111,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(); @@ -145,6 +144,7 @@ class VisualShaderEditor : public VBoxContainer { OptionButton *edit_type_standard; OptionButton *edit_type_particles; OptionButton *edit_type_sky; + OptionButton *edit_type_fog; CheckBox *custom_mode_box; bool custom_mode_enabled = false; @@ -180,7 +180,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 +204,10 @@ class VisualShaderEditor : public VBoxContainer { TYPE_FLAGS_SKY = 1, }; + enum FogTypeFlags { + TYPE_FLAGS_FOG = 1, + }; + enum ToolsMenuOptions { EXPAND_ALL, COLLAPSE_ALL @@ -211,10 +216,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, @@ -335,8 +342,6 @@ class VisualShaderEditor : public VBoxContainer { void _delete_node_request(int p_type, int p_node); void _delete_nodes_request(); - void _removed_from_graph(); - void _node_changed(int p_id); void _edit_port_default_input(Object *p_button, int p_node, int p_port); @@ -376,19 +381,27 @@ 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; + }; + + 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; + List<CopyItem> copy_items_buffer; + 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; @@ -510,9 +523,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..4e81fc4e77 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -33,8 +33,8 @@ 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(); @@ -67,31 +67,36 @@ void VoxelGIEditorPlugin::_notification(int p_what) { return; } + // Set information tooltip on the Bake button. This information is useful + // to optimize performance (video RAM size) and reduce light leaking (individual cell size). + const Vector3i size = voxel_gi->get_estimated_cell_size(); - String text = vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z); + + 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); - text += " - " + vformat(TTR("VRAM Size: %s MB"), String::num(size_mb, 2)); - - if (bake_info->get_text() == text) { - return; + // 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"); } - // 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")); + 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->add_theme_color_override("font_color", color); - bake_info->set_text(text); + bake->set_tooltip(text); } } @@ -147,10 +152,6 @@ VoxelGIEditorPlugin::VoxelGIEditorPlugin(EditorNode *p_node) { bake->set_text(TTR("Bake GI Probe")); 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..ed66728557 100644 --- a/editor/plugins/voxel_gi_editor_plugin.h +++ b/editor/plugins/voxel_gi_editor_plugin.h @@ -42,7 +42,6 @@ class VoxelGIEditorPlugin : public EditorPlugin { VoxelGI *voxel_gi; HBoxContainer *bake_hb; - Label *bake_info; Button *bake; EditorNode *editor; |