diff options
Diffstat (limited to 'editor/plugins')
44 files changed, 1900 insertions, 1841 deletions
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index ea025dad3e..65238446f9 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -41,6 +41,7 @@ #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 "servers/rendering_server.h" void AnimationPlayerEditor::_node_removed(Node *p_node) { @@ -72,7 +73,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()); } } } @@ -289,7 +290,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>()); @@ -1014,7 +1015,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()); } @@ -1424,7 +1425,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); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 8935f715e6..061483decf 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -2264,9 +2264,9 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { } } - 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())); + if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT) { add_node_menu->set_size(Vector2(1, 1)); + add_node_menu->set_position(get_screen_position() + b->get_position()); add_node_menu->popup(); node_create_position = transform.affine_inverse().xform((get_local_mouse_position())); return true; @@ -2983,18 +2983,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); @@ -3565,7 +3563,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); @@ -4777,10 +4775,6 @@ void CanvasItemEditor::_popup_callback(int p_op) { if (key_pos) { ctrl->set_position(Point2()); } - /* - if (key_scale) - AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); - */ } } @@ -5842,7 +5836,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); @@ -6198,14 +6192,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/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index 4a22dc5b62..43eb6a7ce9 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -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); diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp index 1512e1817a..cc916aad8b 100644 --- a/editor/plugins/debugger_editor_plugin.cpp +++ b/editor/plugins/debugger_editor_plugin.cpp @@ -34,6 +34,7 @@ #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" @@ -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 4cb2c0a76b..9702c7e734 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()); @@ -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 { @@ -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 { @@ -857,13 +853,9 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, font->draw_string(canvas_item, pos, sample, HALIGN_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 091feae5fb..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; + 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; @@ -177,12 +171,10 @@ public: class EditorTileMapPatternPreviewPlugin : public EditorResourcePreviewGenerator { GDCLASS(EditorTileMapPatternPreviewPlugin, EditorResourcePreviewGenerator); - 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; 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 16cafda899..0000000000 --- a/editor/plugins/item_list_editor_plugin.cpp +++ /dev/null @@ -1,410 +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"))); - clear_button->set_icon(get_theme_icon(SNAME("Remove"), 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::_clear_pressed() { - for (int i = item_plugins[selected_idx]->get_item_count() - 1; i >= 0; i--) { - item_plugins[selected_idx]->erase(i); - } -} - -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(); - - clear_button = memnew(Button); - clear_button->set_text(TTR("Delete All")); - hbc->add_child(clear_button); - clear_button->connect("pressed", callable_mp(this, &ItemListEditor::_clear_pressed)); - - 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 8f61aef083..0000000000 --- a/editor/plugins/item_list_editor_plugin.h +++ /dev/null @@ -1,250 +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; - Button *clear_button; - - int selected_idx; - - Vector<ItemListPlugin *> item_plugins; - - void _edit_items(); - - void _add_pressed(); - void _delete_pressed(); - void _clear_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/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp index 30945826bb..140d2952dd 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" @@ -283,6 +284,52 @@ Ref<Resource> StandardMaterial3DConversionPlugin::convert(const Ref<Resource> &p 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; +} + String ParticlesMaterialConversionPlugin::converts_to() const { return "ShaderMaterial"; } @@ -477,3 +524,40 @@ Ref<Resource> PhysicalSkyMaterialConversionPlugin::convert(const Ref<Resource> & 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..62549843f7 100644 --- a/editor/plugins/material_editor_plugin.h +++ b/editor/plugins/material_editor_plugin.h @@ -107,6 +107,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 +161,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_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 574d3ef27e..7a85c5167b 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); @@ -247,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); @@ -426,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/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index b28f3e134c..74fbef3caf 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -41,6 +41,7 @@ #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" @@ -3641,15 +3642,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)); } @@ -5272,3 +5273,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 d1aca4d92e..56e4ad5518 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -669,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 e8086e2fea..5b1cbe6f00 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -489,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(); @@ -552,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++) { @@ -1855,7 +1855,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) { @@ -1954,7 +1954,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) { @@ -2245,12 +2245,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { _menu_option(VIEW_RIGHT); } if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", p_event)) { - cursor.x_rot -= Math_PI / 12.0; + // 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)) { - cursor.x_rot += Math_PI / 12.0; + // 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(); } @@ -2885,13 +2887,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", @@ -3253,7 +3255,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(); @@ -3936,9 +3938,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; } @@ -4376,7 +4382,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); @@ -6566,7 +6572,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; } } @@ -6583,7 +6594,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(); @@ -6636,7 +6652,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); @@ -6666,7 +6682,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); @@ -7004,6 +7020,7 @@ 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() { @@ -7131,7 +7148,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(); } diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index e1318f52a8..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; diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index e2902feba1..f9a5f429d2 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -614,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/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 677a5f1f2c..000dfe8bda 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -227,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; @@ -315,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(); } @@ -767,7 +758,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { if (p_save && file.is_valid()) { // Do not try to save internal scripts, but prompt to save in-memory // scripts which are not saved to disk yet (have empty path). - if (file->get_path().find("local://") == -1 && file->get_path().find("::") == -1) { + if (file->is_built_in()) { save_current_script(); } } @@ -913,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 } @@ -954,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 } @@ -998,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(); } @@ -1011,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")); @@ -1040,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 } @@ -1534,6 +1546,7 @@ 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)); @@ -1627,8 +1640,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--; } } @@ -1935,20 +1948,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; @@ -2183,9 +2183,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(); @@ -2411,7 +2412,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(); @@ -2424,6 +2435,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) { @@ -2453,7 +2466,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; @@ -2482,9 +2495,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(); } @@ -2573,7 +2596,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(); } @@ -3349,9 +3372,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()) { @@ -3861,7 +3885,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); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 2b0bdfd109..0adeca031e 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -365,6 +365,7 @@ 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; diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 2c02389db2..a655420d27 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -375,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 != "") { + // 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; @@ -658,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 } @@ -1396,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; } diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index a88e24c0d0..9d2b4c88c5 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -178,6 +178,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; } @@ -478,8 +482,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 +529,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); } 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 1fc7eb98e0..8186c2c84c 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 != "") { + // 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; 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 b9ec6bf5ab..e25b0270b4 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -101,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_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 424e018a47..ee62138d12 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -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/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 127219546d..b1ef85b4f4 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -2848,7 +2848,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); } diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index b6becbb1c7..f5ad577aff 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -362,7 +362,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); diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index d26d0ec39d..e13a10fe3f 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -117,7 +117,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); @@ -133,7 +133,7 @@ void ThemeEditorPreview::_draw_picker_overlay() { 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, HALIGN_LEFT, -1, theme_cache.font_size); } } @@ -188,6 +188,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(); diff --git a/editor/plugins/theme_editor_preview.h b/editor/plugins/theme_editor_preview.h index 4e1b149e70..f973119257 100644 --- a/editor/plugins/theme_editor_preview.h +++ b/editor/plugins/theme_editor_preview.h @@ -65,6 +65,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/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index 104f46f773..e9d80bb4b8 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -987,6 +987,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: @@ -1018,8 +1019,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, HALIGN_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); } } @@ -1582,6 +1583,7 @@ void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas // 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) { @@ -1604,8 +1606,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, HALIGN_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); } } } @@ -1755,6 +1757,7 @@ void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_til // 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++) { @@ -1779,8 +1782,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, HALIGN_CENTER, string_size.x, font_size, color, 1, Color(0, 0, 0, 1)); } } } diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 5dfa44a353..39fbd86f77 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -99,7 +99,7 @@ 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(); @@ -543,9 +543,9 @@ 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); + 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 (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -560,9 +560,9 @@ 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()); + 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 (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -592,11 +592,11 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p if (mb->get_button_index() == MOUSE_BUTTON_LEFT || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { if (mb->is_pressed()) { - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - rmb_erasing = true; + // Pressed + if (erase_button->is_pressed() || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + drag_erasing = true; } - // Pressed if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { // Do nothing. } else if (tool_buttons_group->get_pressed_button() == select_tool_button) { @@ -626,9 +626,9 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_type = DRAG_TYPE_PAINT; drag_start_mouse_pos = mpos; drag_modified.clear(); - Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos); + Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos, drag_erasing); for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -653,9 +653,9 @@ 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()); + 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 (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { + if (!drag_erasing && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } Vector2i coords = E.key; @@ -674,9 +674,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } else { // Released _stop_dragging(); - if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - rmb_erasing = false; - } + drag_erasing = false; } CanvasItemEditor::get_singleton()->update_viewport(); @@ -730,7 +728,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over 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++) { @@ -789,25 +787,25 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over 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 || 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 (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)); + 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 fill pattern. - preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed()); + 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 @@ -855,7 +853,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Transform2D tile_xform; tile_xform.set_origin(tile_map->map_to_world(E.key)); tile_xform.set_scale(tile_set->get_tile_size()); - if (!_is_erasing() && random_tile_checkbox->is_pressed()) { + 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.value.source_id)) { @@ -966,7 +964,7 @@ TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(Ref<TileMapPattern> p_pa return TileMapCell(); } -Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos) { +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>(); @@ -981,12 +979,12 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ 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 = _is_erasing() ? erase_pattern : selection_pattern; + 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 (!_is_erasing() && 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++) { @@ -1015,7 +1013,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>(); @@ -1034,7 +1032,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start 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 = _is_erasing() ? erase_pattern : selection_pattern; + Ref<TileMapPattern> pattern = p_erase ? erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> err_output; ERR_FAIL_COND_V(pattern->is_empty(), err_output); @@ -1046,7 +1044,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { - if (!_is_erasing() && 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++) { @@ -1074,7 +1072,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>(); @@ -1095,14 +1093,14 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i 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 = _is_erasing() ? erase_pattern : selection_pattern; + 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(); } @@ -1115,11 +1113,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 (!_is_erasing() && 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 { @@ -1146,7 +1144,7 @@ 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.has_no_area()) { rect = Rect2i(p_coords, Vector2i(1, 1)); @@ -1161,11 +1159,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 (!_is_erasing() && 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 { @@ -1338,10 +1336,10 @@ void TileMapEditorTilesPlugin::_stop_dragging() { undo_redo->commit_action(false); } break; case DRAG_TYPE_LINE: { - Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos); + 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 (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { + 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); @@ -1350,10 +1348,10 @@ void TileMapEditorTilesPlugin::_stop_dragging() { undo_redo->commit_action(); } break; case DRAG_TYPE_RECT: { - Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos)); + 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 (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { - if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { + 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); @@ -1473,10 +1471,6 @@ void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { } } -bool TileMapEditorTilesPlugin::_is_erasing() const { - return erase_button->is_pressed() || rmb_erasing; -} - void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { @@ -2053,10 +2047,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); @@ -2210,6 +2205,26 @@ 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(); } } @@ -2219,674 +2234,313 @@ Vector<TileMapEditorPlugin::TabData> TileMapEditorTerrainsPlugin::get_tabs() con return tabs; } -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; -} - -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 (const KeyValue<TerrainsTilePattern, Set<TileMapCell>> &E : per_terrain_terrains_tile_patterns_tiles[p_terrain_set]) { - 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 (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_overlapping : overlapping_terrain_bits) { - 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.value)) : -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 (const KeyValue<int, int> &E_terrain_count : terrain_count) { - if (E_terrain_count.value > max) { - max = E_terrain_count.value; - 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); - } + // 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()); } - return constraints; -} + // 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); + } -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>(); + // 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); } - Ref<TileSet> tile_set = tile_map->get_tileset(); - if (!tile_set.is_valid()) { - return Set<TileMapEditorTerrainsPlugin::Constraint>(); - } - - // 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 (const KeyValue<Vector2i, Set<TerrainsTilePattern>> &E : per_cell_acceptable_tiles) { - min_nb_possibilities = MIN(min_nb_possibilities, E.value.size()); - } - - // Get the set of possible cells to fill. - LocalVector<Vector2i> to_choose_from; - for (const KeyValue<Vector2i, Set<TerrainsTilePattern>> &E : per_cell_acceptable_tiles) { - if (E.value.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 (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) { - Vector2i coords = E_to_paint.key; - TerrainsTilePattern terrains_tile_pattern = E_to_paint.value; + 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 (const KeyValue<Vector2i, TerrainsTilePattern> &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); + // 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 (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) { - 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 (const KeyValue<Vector2i, TileSet::CellNeighbor> &E_source_tile_of_constraint : sources_of_constraint) { - 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)); } } + } else { + to_check = tile_map->get_used_cells(tile_map_layer); } - - 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 (KeyValue<Constraint, Set<Vector2i>> &E_source_tiles_of_constraint : source_tiles_of_constraint) { - E_source_tiles_of_constraint.value.erase(to_add_to_remove); - } - break; + 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 (const KeyValue<Vector2i, TerrainsTilePattern> &E : wfc_output) { - output[E.key] = _get_random_tile_from_pattern(p_terrain_set, E.value); + 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 (const KeyValue<Vector2i, TerrainsTilePattern> &E_to_paint : p_to_paint) { - output[E_to_paint.key] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint.value); + 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() { @@ -2895,26 +2549,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; } } @@ -2926,8 +2594,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; @@ -2940,15 +2609,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; } @@ -2967,12 +2630,82 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { } undo_redo->commit_action(false); } break; + case DRAG_TYPE_LINE: { + Map<Vector2i, TileMapCell> to_draw = _draw_line(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos), drag_erasing); + 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 (!main_vbox_container->is_visible_in_tree()) { // If the bottom editor is not visible, we ignore inputs. @@ -2998,46 +2731,19 @@ 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 (const KeyValue<Vector2i, TileMapCell> &E : modified) { + 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); } @@ -3056,35 +2762,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() == MOUSE_BUTTON_LEFT || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { if (mb->is_pressed()) { // Pressed + if (erase_button->is_pressed() || mb->get_button_index() == MOUSE_BUTTON_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); + 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(); @@ -3097,24 +2847,133 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> return false; } -TileMapEditorTerrainsPlugin::TerrainsTilePattern TileMapEditorTerrainsPlugin::_build_terrains_tile_pattern(TileData *p_tile_data) { +void TileMapEditorTerrainsPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) { TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); if (!tile_map) { - return TerrainsTilePattern(); + return; } + if (tile_map_layer < 0) { + return; + } + ERR_FAIL_INDEX(tile_map_layer, tile_map->get_layers_count()); + Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - return TerrainsTilePattern(); + return; + } + + if (!tile_map->is_visible_in_tree()) { + return; } - TerrainsTilePattern output; - for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { - if (tile_set->is_valid_peering_bit_terrain(p_tile_data->get_terrain_set(), TileSet::CellNeighbor(i))) { - output.push_back(p_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(i))); + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Vector2i tile_shape_size = tile_set->get_tile_size(); + + // Handle the preview of the tiles to be placed. + if (main_vbox_container->is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered. + 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() { @@ -3128,45 +2987,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(); } } @@ -3184,22 +3010,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); + } } } } @@ -3207,22 +3033,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() { @@ -3291,26 +3101,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; @@ -3318,15 +3130,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; @@ -3347,7 +3159,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); } } @@ -3359,6 +3171,10 @@ 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"))); } @@ -3418,6 +3234,30 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { 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 -- @@ -3442,6 +3282,17 @@ TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { 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() { @@ -3508,7 +3359,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")); + } } } diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 1f1a560113..f462119727 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -75,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; @@ -108,17 +109,16 @@ 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; - bool rmb_erasing = false; 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); - 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); + 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(); - bool _is_erasing() const; ///// Selection system. ///// Set<Vector2i> tile_map_selection; @@ -220,89 +220,60 @@ 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(); @@ -315,7 +286,7 @@ private: public: 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(); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index c8892bceaa..ae744f697b 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -841,7 +841,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")); + } } } diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index 0fbb9a98c7..915ce50836 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) { @@ -608,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) { diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index cda38760cf..58312ce3df 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -59,6 +59,9 @@ private: 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. @@ -98,9 +101,6 @@ public: 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 dc26d380b8..d687d9651d 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -385,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; } @@ -412,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; @@ -447,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() { 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 3be7bee714..4e33128be5 100644 --- a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -125,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 f1918073fb..47dfc57b0f 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -47,8 +47,12 @@ TilesEditorPlugin *TilesEditorPlugin::singleton = nullptr; -void TilesEditorPlugin::_pattern_preview_done(const Variant &p_udata) { - pattern_preview_done.set(); +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) { @@ -112,12 +116,9 @@ void TilesEditorPlugin::_thread() { // Add the viewport at the lasst moment to avoid rendering too early. EditorNode::get_singleton()->add_child(viewport); - pattern_preview_done.clear(); - RS::get_singleton()->request_frame_drawn_callback(const_cast<TilesEditorPlugin *>(this), "_pattern_preview_done", Variant()); + RS::get_singleton()->connect(SNAME("frame_pre_draw"), callable_mp(const_cast<TilesEditorPlugin *>(this), &TilesEditorPlugin::_preview_frame_started), Vector<Variant>(), Object::CONNECT_ONESHOT); - while (!pattern_preview_done.is_set()) { - OS::get_singleton()->delay_usec(10); - } + pattern_preview_done.wait(); Ref<Image> image = viewport->get_texture()->get_image(); Ref<ImageTexture> image_texture; @@ -274,10 +275,6 @@ bool TilesEditorPlugin::handles(Object *p_object) const { return p_object->is_class("TileMap") || p_object->is_class("TileSet"); } -void TilesEditorPlugin::_bind_methods() { - ClassDB::bind_method(D_METHOD("_pattern_preview_done", "pattern"), &TilesEditorPlugin::_pattern_preview_done); -} - TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) { set_process_internal(true); diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index dd52bdc31a..33493040f6 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -77,14 +77,14 @@ private: Thread pattern_preview_thread; SafeFlag pattern_thread_exit; SafeFlag pattern_thread_exited; - mutable SafeFlag pattern_preview_done; - void _pattern_preview_done(const Variant &p_udata); + 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 TilesEditorPlugin *get_singleton() { return singleton; } diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 09ca1f7608..10a9b2bb10 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -104,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); @@ -292,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; } @@ -1047,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(); @@ -1246,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; @@ -1426,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); @@ -1444,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; @@ -1601,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()); } @@ -2752,9 +2757,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) { @@ -3090,13 +3092,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) { @@ -3207,10 +3211,7 @@ void VisualShaderEditor::_show_members_dialog(bool at_mouse_pos, VisualShaderNod 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(); } @@ -3319,69 +3320,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; } @@ -3389,48 +3409,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++; @@ -3438,54 +3457,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); - _clear_buffer(); + if (p_cut) { + undo_redo->create_action(TTR("Cut VisualShader Node(s)")); - _dup_copy_nodes(copy_type, copy_nodes_buffer, copy_nodes_excluded_buffer); + List<int> ids; + for (const CopyItem &E : copy_items_buffer) { + ids.push_back(E.id); + } + + _delete_nodes(get_current_shader_type(), ids); + + undo_redo->commit_action(); + } } void VisualShaderEditor::_paste_nodes(bool p_use_custom_position, const Vector2 &p_custom_position) { - if (copy_nodes_buffer.is_empty()) { + if (copy_items_buffer.is_empty()) { return; } @@ -3500,11 +3526,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) { @@ -3523,11 +3545,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) { @@ -3730,8 +3756,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); @@ -3742,6 +3771,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; @@ -3956,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); @@ -4010,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)); @@ -4065,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); @@ -4075,6 +4112,8 @@ 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); @@ -4129,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)); /////////////////////////////////////// @@ -4343,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."); @@ -4462,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)); @@ -4925,7 +4977,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; @@ -4941,7 +4993,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); @@ -5033,7 +5089,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; } diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 5788959a77..c4a392469b 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, @@ -374,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; |