diff options
Diffstat (limited to 'editor/plugins')
47 files changed, 2263 insertions, 1222 deletions
diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index 49fcac512b..686a35e442 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -129,8 +129,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven add_point_pos += blend_space->get_min_space(); if (snap->is_pressed()) { - add_point_pos.x = Math::snapped(add_point_pos.x, blend_space->get_snap().x); - add_point_pos.y = Math::snapped(add_point_pos.y, blend_space->get_snap().y); + add_point_pos = add_point_pos.snapped(blend_space->get_snap()); } } @@ -215,8 +214,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_gui_input(const Ref<InputEven Vector2 point = blend_space->get_blend_point_position(selected_point); point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } updating = true; @@ -467,8 +465,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { if (dragging_selected && selected_point == point_idx) { point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -503,8 +500,7 @@ void AnimationNodeBlendSpace2DEditor::_blend_space_draw() { if (dragging_selected && selected_point == i) { point += drag_ofs; if (snap->is_pressed()) { - point.x = Math::snapped(point.x, blend_space->get_snap().x); - point.y = Math::snapped(point.y, blend_space->get_snap().y); + point = point.snapped(blend_space->get_snap()); } } point = (point - blend_space->get_min_space()) / (blend_space->get_max_space() - blend_space->get_min_space()); @@ -702,8 +698,7 @@ void AnimationNodeBlendSpace2DEditor::_update_edited_point_pos() { if (dragging_selected) { pos += drag_ofs; if (snap->is_pressed()) { - pos.x = Math::snapped(pos.x, blend_space->get_snap().x); - pos.y = Math::snapped(pos.y, blend_space->get_snap().y); + pos = pos.snapped(blend_space->get_snap()); } } updating = true; diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 24cb660f7a..55ffbf9477 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -776,16 +776,16 @@ void AnimationNodeBlendTreeEditor::_notification(int p_what) { } if (player) { - for (Map<StringName, ProgressBar *>::Element *E = animations.front(); E; E = E->next()) { - Ref<AnimationNodeAnimation> an = blend_tree->get_node(E->key()); + for (const KeyValue<StringName, ProgressBar *> &E : animations) { + Ref<AnimationNodeAnimation> an = blend_tree->get_node(E.key); if (an.is_valid()) { if (player->has_animation(an->get_animation())) { Ref<Animation> anim = player->get_animation(an->get_animation()); if (anim.is_valid()) { - E->get()->set_max(anim->get_length()); + E.value->set_max(anim->get_length()); //StringName path = AnimationTreeEditor::get_singleton()->get_base_path() + E.input_node; - StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E->key()) + "/time"; - E->get()->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); + StringName time_path = AnimationTreeEditor::get_singleton()->get_base_path() + String(E.key) + "/time"; + E.value->set_value(AnimationTreeEditor::get_singleton()->get_tree()->get(time_path)); } } } diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 68b143358a..ea025dad3e 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -298,7 +298,7 @@ void AnimationPlayerEditor::_animation_selected(int p_which) { autoplay->set_pressed(current == player->get_autoplay()); - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); _animation_key_editor_seek(timeline_position, false); } @@ -826,7 +826,7 @@ void AnimationPlayerEditor::_update_player() { pin->set_disabled(player == nullptr); if (!player) { - AnimationPlayerEditor::singleton->get_track_editor()->update_keying(); + AnimationPlayerEditor::get_singleton()->get_track_editor()->update_keying(); EditorNode::get_singleton()->update_keying(); return; } @@ -1466,15 +1466,15 @@ void AnimationPlayerEditor::_prepare_onion_layers_2() { } void AnimationPlayerEditor::_start_onion_skinning() { - // FIXME: Using "idle_frame" makes onion layers update one frame behind the current. - if (!get_tree()->is_connected("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->connect("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + // FIXME: Using "process_frame" makes onion layers update one frame behind the current. + if (!get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->connect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); } } void AnimationPlayerEditor::_stop_onion_skinning() { - if (get_tree()->is_connected("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { - get_tree()->disconnect("idle_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); + if (get_tree()->is_connected("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred))) { + get_tree()->disconnect("process_frame", callable_mp(this, &AnimationPlayerEditor::_prepare_onion_layers_1_deferred)); _free_onion_layers(); diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 0a514d3ff1..eb8db2eaba 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -128,6 +128,7 @@ class AnimationPlayerEditor : public VBoxContainer { bool updating_blends; AnimationTrackEditor *track_editor; + static AnimationPlayerEditor *singleton; // Onion skinning. struct { @@ -226,7 +227,8 @@ protected: public: AnimationPlayer *get_player() const; - static AnimationPlayerEditor *singleton; + + static AnimationPlayerEditor *get_singleton() { return singleton; } bool is_pinned() const { return pin->is_pressed(); } void unpin() { pin->set_pressed(false); } diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index cd84be0c25..6c5606fbfd 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -55,7 +55,7 @@ void AnimationTreeEditor::edit(AnimationTree *p_tree) { tree = p_tree; Vector<String> path; - if (tree->has_meta("_tree_edit_path")) { + if (tree && tree->has_meta("_tree_edit_path")) { path = tree->get_meta("_tree_edit_path"); edit_path(path); } else { diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index 664b2f521e..aacfc3e305 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -830,9 +830,9 @@ void EditorAssetLibrary::_update_image_queue() { int current_images = 0; List<int> to_delete; - for (Map<int, ImageQueue>::Element *E = image_queue.front(); E; E = E->next()) { - if (!E->get().active && current_images < max_images) { - String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text()); + for (KeyValue<int, ImageQueue> &E : image_queue) { + if (!E.value.active && current_images < max_images) { + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E.value.image_url.md5_text()); Vector<String> headers; if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) { @@ -844,14 +844,14 @@ void EditorAssetLibrary::_update_image_queue() { } } - Error err = E->get().request->request(E->get().image_url, headers); + Error err = E.value.request->request(E.value.image_url, headers); if (err != OK) { - to_delete.push_back(E->key()); + to_delete.push_back(E.key); } else { - E->get().active = true; + E.value.active = true; } current_images++; - } else if (E->get().active) { + } else if (E.value.active) { current_images++; } } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index faa3b58dbb..8935f715e6 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -513,7 +513,7 @@ Object *CanvasItemEditor::_get_editor_data(Object *p_what) { } void CanvasItemEditor::_keying_changed() { - if (AnimationPlayerEditor::singleton->get_track_editor()->is_visible_in_tree()) { + if (AnimationPlayerEditor::get_singleton()->get_track_editor()->is_visible_in_tree()) { animation_hb->show(); } else { animation_hb->hide(); @@ -748,8 +748,8 @@ bool CanvasItemEditor::_select_click_on_item(CanvasItem *item, Point2 p_click_po List<CanvasItem *> CanvasItemEditor::_get_edited_canvas_items(bool retreive_locked, bool remove_canvas_item_if_parent_in_selection) { List<CanvasItem *> selection; - for (Map<Node *, Object *>::Element *E = editor_selection->get_selection().front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : editor_selection->get_selection()) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (canvas_item && canvas_item->is_visible_in_tree() && canvas_item->get_viewport() == EditorNode::get_singleton()->get_scene_root() && (retreive_locked || !_is_node_locked(canvas_item))) { CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); if (se) { @@ -1174,7 +1174,6 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo if (!panning) { if (b->is_pressed() && (b->get_button_index() == MOUSE_BUTTON_MIDDLE || - b->get_button_index() == MOUSE_BUTTON_RIGHT || (b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_PAN) || (b->get_button_index() == MOUSE_BUTTON_LEFT && !EditorSettings::get_singleton()->get("editors/2d/simple_panning") && pan_pressed))) { // Pan the viewport @@ -1232,8 +1231,9 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } } - if (is_pan_key) { + if (is_pan_key && pan_pressed != k->is_pressed()) { pan_pressed = k->is_pressed(); + _update_cursor(); } } @@ -1460,8 +1460,8 @@ bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEven List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; - if (canvas_item->get_filename() != "" && canvas_item != editor->get_edited_scene()) { - editor->open_request(canvas_item->get_filename()); + if (canvas_item->get_scene_file_path() != "" && canvas_item != editor->get_edited_scene()) { + editor->open_request(canvas_item->get_scene_file_path()); return true; } } @@ -2615,7 +2615,19 @@ void CanvasItemEditor::_update_cursor() { c = CURSOR_HSIZE; } - viewport->set_default_cursor_shape(c); + if (pan_pressed) { + c = CURSOR_DRAG; + } + + if (c != viewport->get_default_cursor_shape()) { + viewport->set_default_cursor_shape(c); + + // Force refresh cursor if it's over the viewport. + if (viewport->get_global_rect().has_point(get_global_mouse_position())) { + DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); + DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); + } + } } void CanvasItemEditor::_draw_text_at_position(Point2 p_position, String p_string, Side p_side) { @@ -3782,8 +3794,8 @@ void CanvasItemEditor::_notification(int p_what) { } // Update the viewport if bones changes - for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { - Object *b = ObjectDB::get_instance(E->key().from); + for (KeyValue<BoneKey, BoneList> &E : bone_list) { + Object *b = ObjectDB::get_instance(E.key.from); if (!b) { viewport->update(); break; @@ -3796,14 +3808,14 @@ void CanvasItemEditor::_notification(int p_what) { Transform2D global_xform = b2->get_global_transform(); - if (global_xform != E->get().xform) { - E->get().xform = global_xform; + if (global_xform != E.value.xform) { + E.value.xform = global_xform; viewport->update(); } Bone2D *bone = Object::cast_to<Bone2D>(b); - if (bone && bone->get_length() != E->get().length) { - E->get().length = bone->get_length(); + if (bone && bone->get_length() != E.value.length) { + E.value.length = bone->get_length(); viewport->update(); } } @@ -3816,7 +3828,7 @@ void CanvasItemEditor::_notification(int p_what) { select_sb->set_default_margin(Side(i), 4); } - AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); + AnimationPlayerEditor::get_singleton()->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); _keying_changed(); } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { @@ -4254,17 +4266,13 @@ void CanvasItemEditor::_button_tool_select(int p_index) { viewport->update(); _update_cursor(); - - // Request immediate refresh of cursor when using hot-keys to switch between tools - DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)viewport->get_default_cursor_shape(); - DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape); } void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, bool p_scale, bool p_on_existing) { Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item || !canvas_item->is_visible_in_tree()) { continue; } @@ -4277,13 +4285,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Node2D *n2d = Object::cast_to<Node2D>(canvas_item); if (key_pos && p_location) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "position", n2d->get_position(), p_on_existing); } if (key_rot && p_rotation) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "rotation", n2d->get_rotation(), p_on_existing); } if (key_scale && p_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(n2d, "scale", n2d->get_scale(), p_on_existing); } if (n2d->has_meta("_edit_bone_") && n2d->get_parent_item()) { @@ -4309,13 +4317,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, if (has_chain && ik_chain.size()) { for (Node2D *&F : ik_chain) { if (key_pos) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "position", F->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "rotation", F->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(F, "scale", F->get_scale(), p_on_existing); } } } @@ -4325,13 +4333,13 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, Control *ctrl = Object::cast_to<Control>(canvas_item); if (key_pos) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_position", ctrl->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_rotation", ctrl->get_rotation(), p_on_existing); } if (key_scale) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl, "rect_size", ctrl->get_size(), p_on_existing); } } } @@ -4695,8 +4703,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item || !canvas_item->is_visible_in_tree()) { continue; } @@ -4741,8 +4749,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { case ANIM_CLEAR_POSE: { Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item || !canvas_item->is_visible_in_tree()) { continue; } @@ -4771,7 +4779,7 @@ void CanvasItemEditor::_popup_callback(int p_op) { } /* if (key_scale) - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); + AnimationPlayerEditor::get_singleton()->get_track_editor()->insert_node_value_key(ctrl,"rect/size",ctrl->get_size()); */ } } @@ -4816,8 +4824,8 @@ void CanvasItemEditor::_popup_callback(int p_op) { Node *editor_root = EditorNode::get_singleton()->get_edited_scene()->get_tree()->get_edited_scene_root(); undo_redo->create_action(TTR("Create Custom Bone2D(s) from Node(s)")); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node2D *n2d = Object::cast_to<Node2D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node2D *n2d = Object::cast_to<Node2D>(E.key); Bone2D *new_bone = memnew(Bone2D); String new_bone_name = n2d->get_name(); @@ -4861,8 +4869,8 @@ void CanvasItemEditor::_focus_selection(int p_op) { int count = 0; Map<Node *, Object *> &selection = editor_selection->get_selection(); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E.key); if (!canvas_item) { continue; } @@ -4898,8 +4906,7 @@ void CanvasItemEditor::_focus_selection(int p_op) { if (p_op == VIEW_CENTER_TO_SELECTION) { center = rect.get_center(); Vector2 offset = viewport->get_size() / 2 - editor->get_scene_root()->get_global_canvas_transform().xform(center); - view_offset.x -= Math::round(offset.x / zoom); - view_offset.y -= Math::round(offset.y / zoom); + view_offset -= (offset / zoom).round(); update_viewport(); } else { // VIEW_FRAME_TO_SELECTION @@ -5385,7 +5392,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { pan_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_tool_select), make_binds(TOOL_PAN)); pan_button->set_shortcut(ED_SHORTCUT("canvas_item_editor/pan_mode", TTR("Pan Mode"), KEY_G)); pan_button->set_shortcut_context(this); - pan_button->set_tooltip(TTR("Pan Mode")); + pan_button->set_tooltip(TTR("You can also use Pan View shortcut (Space by default) to pan in any mode.")); ruler_button = memnew(Button); ruler_button->set_flat(true); @@ -5801,7 +5808,7 @@ void CanvasItemEditorViewport::_remove_preview() { } bool CanvasItemEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { - if (p_desired_node->get_filename() == p_target_scene_path) { + if (p_desired_node->get_scene_file_path() == p_target_scene_path) { return true; } @@ -5898,14 +5905,14 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons return false; } - if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + if (editor->get_edited_scene()->get_scene_file_path() != "") { // cyclical instancing + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } } - instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp index 8b354c33a1..53314db1e9 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp @@ -103,16 +103,16 @@ void CollisionPolygon3DEditor::_wip_close() { undo_redo->commit_action(); } -bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!node) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Transform3D gt = node->get_global_transform(); Transform3D gi = gt.affine_inverse(); float depth = _get_depth() * 0.5; Vector3 n = gt.basis.get_axis(2).normalized(); - Plane p(gt.origin + n * depth, n); + Plane p(n, gt.origin + n * depth); Ref<InputEventMouseButton> mb = p_event; @@ -124,7 +124,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); @@ -151,19 +151,19 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con snap_ignore = false; _polygon_draw(); edited_point = 1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { if (wip.size() > 1 && p_camera->unproject_position(gt.xform(Vector3(wip[0].x, wip[0].y, depth))).distance_to(gpoint) < grab_threshold) { //wip closed _wip_close(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { wip.push_back(cpoint); edited_point = wip.size(); snap_ignore = false; _polygon_draw(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT && mb->is_pressed() && wip_active) { @@ -184,7 +184,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //search edges @@ -219,7 +219,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con _polygon_draw(); snap_ignore = true; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } else { //look for points to move @@ -244,7 +244,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con edited_point_pos = poly[closest_idx]; _polygon_draw(); snap_ignore = false; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } else { @@ -253,7 +253,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con if (edited_point != -1) { //apply - ERR_FAIL_INDEX_V(edited_point, poly.size(), false); + ERR_FAIL_INDEX_V(edited_point, poly.size(), EditorPlugin::AFTER_GUI_INPUT_PASS); poly.write[edited_point] = edited_point_pos; undo_redo->create_action(TTR("Edit Poly")); undo_redo->add_do_method(node, "set_polygon", poly); @@ -263,7 +263,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->commit_action(); edited_point = -1; - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } @@ -290,7 +290,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con undo_redo->add_do_method(this, "_polygon_draw"); undo_redo->add_undo_method(this, "_polygon_draw"); undo_redo->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } @@ -310,7 +310,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector3 spoint; if (!p.intersects_ray(ray_from, ray_dir, &spoint)) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } spoint = gi.xform(spoint); @@ -332,7 +332,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } float CollisionPolygon3DEditor::_get_depth() { diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/collision_polygon_3d_editor_plugin.h index 5db0f7308a..10b0adf76c 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.h +++ b/editor/plugins/collision_polygon_3d_editor_plugin.h @@ -88,7 +88,7 @@ protected: static void _bind_methods(); public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event); void edit(Node *p_collision_polygon); CollisionPolygon3DEditor(EditorNode *p_editor); ~CollisionPolygon3DEditor(); @@ -101,7 +101,7 @@ class Polygon3DEditorPlugin : public EditorPlugin { EditorNode *editor; public: - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override { return collision_polygon_editor->forward_spatial_gui_input(p_camera, p_event); } virtual String get_name() const override { return "Polygon3DEditor"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 903a3689b0..5ac58795d1 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -362,6 +362,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<ImageTexture> tex; tex.instantiate(); + tex->create_from_image(image); Ref<ParticlesMaterial> material = node->get_process_material(); ERR_FAIL_COND(material.is_null()); @@ -390,6 +391,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<ImageTexture> tex2; tex2.instantiate(); + tex2->create_from_image(image2); material->set_emission_normal_texture(tex2); } else { diff --git a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp index a4436525fb..6df2e34ceb 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -33,7 +33,7 @@ void GPUParticlesCollisionSDFEditorPlugin::_bake() { if (col_sdf) { if (col_sdf->get_texture().is_null() || !col_sdf->get_texture()->get_path().is_resource_file()) { - String path = get_tree()->get_edited_scene_root()->get_filename(); + String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); if (path == String()) { path = "res://" + col_sdf->get_name() + "_data.exr"; } else { diff --git a/editor/plugins/item_list_editor_plugin.cpp b/editor/plugins/item_list_editor_plugin.cpp index 3207a989bd..16cafda899 100644 --- a/editor/plugins/item_list_editor_plugin.cpp +++ b/editor/plugins/item_list_editor_plugin.cpp @@ -244,6 +244,7 @@ void ItemListEditor::_node_removed(Node *p_node) { 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)); @@ -258,6 +259,12 @@ void ItemListEditor::_add_pressed() { 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; @@ -350,6 +357,11 @@ ItemListEditor::ItemListEditor() { 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); diff --git a/editor/plugins/item_list_editor_plugin.h b/editor/plugins/item_list_editor_plugin.h index 8c77f3d952..8f61aef083 100644 --- a/editor/plugins/item_list_editor_plugin.h +++ b/editor/plugins/item_list_editor_plugin.h @@ -204,6 +204,7 @@ class ItemListEditor : public HBoxContainer { Tree *tree; Button *add_button; Button *del_button; + Button *clear_button; int selected_idx; @@ -213,6 +214,7 @@ class ItemListEditor : public HBoxContainer { void _add_pressed(); void _delete_pressed(); + void _clear_pressed(); void _node_removed(Node *p_node); diff --git a/editor/plugins/lightmap_gi_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index b4a70cd31d..123087446c 100644 --- a/editor/plugins/lightmap_gi_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -43,9 +43,9 @@ void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { switch (err) { case LightmapGI::BAKE_ERROR_NO_SAVE_PATH: { - String scene_path = lightmap->get_filename(); + String scene_path = lightmap->get_scene_file_path(); if (scene_path == String()) { - scene_path = lightmap->get_owner()->get_filename(); + scene_path = lightmap->get_owner()->get_scene_file_path(); } if (scene_path == String()) { EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for lightmap images.\nSave your scene and try again.")); diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index eb771f2bc4..b28f3e134c 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -43,13 +43,13 @@ #include "scene/3d/decal.h" #include "scene/3d/gpu_particles_3d.h" #include "scene/3d/gpu_particles_collision_3d.h" +#include "scene/3d/joint_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/lightmap_gi.h" #include "scene/3d/lightmap_probe.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/occluder_instance_3d.h" -#include "scene/3d/physics_joint_3d.h" #include "scene/3d/position_3d.h" #include "scene/3d/ray_cast_3d.h" #include "scene/3d/reflection_probe.h" @@ -682,7 +682,7 @@ bool EditorNode3DGizmo::intersect_ray(Camera3D *p_camera, const Point2 &p_point, } if (collision_segments.size()) { - Plane camp(p_camera->get_transform().origin, (-p_camera->get_transform().basis.get_axis(2)).normalized()); + Plane camp(-p_camera->get_transform().basis.get_axis(2).normalized(), p_camera->get_transform().origin); int vc = collision_segments.size(); const Vector3 *vptr = collision_segments.ptr(); @@ -832,7 +832,7 @@ void EditorNode3DGizmo::_bind_methods() { ClassDB::bind_method(D_METHOD("get_plugin"), &EditorNode3DGizmo::get_plugin); ClassDB::bind_method(D_METHOD("clear"), &EditorNode3DGizmo::clear); ClassDB::bind_method(D_METHOD("set_hidden", "hidden"), &EditorNode3DGizmo::set_hidden); - ClassDB::bind_method(D_METHOD("is_subgizmo_selected"), &EditorNode3DGizmo::is_subgizmo_selected); + ClassDB::bind_method(D_METHOD("is_subgizmo_selected", "id"), &EditorNode3DGizmo::is_subgizmo_selected); ClassDB::bind_method(D_METHOD("get_subgizmo_selection"), &EditorNode3DGizmo::get_subgizmo_selection); GDVIRTUAL_BIND(_redraw); @@ -1313,7 +1313,7 @@ void Light3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, light->set_param(Light3D::PARAM_RANGE, d); } else if (Object::cast_to<OmniLight3D>(light)) { - Plane cp = Plane(gt.origin, p_camera->get_transform().basis.get_axis(2)); + Plane cp = Plane(p_camera->get_transform().basis.get_axis(2), gt.origin); Vector3 inters; if (cp.intersects_ray(ray_from, ray_dir, &inters)) { @@ -1840,47 +1840,6 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_lines(lines, material); p_gizmo->add_handles(handles, get_material("handles")); - - ClippedCamera3D *clipcam = Object::cast_to<ClippedCamera3D>(camera); - if (clipcam) { - Node3D *parent = Object::cast_to<Node3D>(camera->get_parent()); - if (!parent) { - return; - } - Vector3 cam_normal = -camera->get_global_transform().basis.get_axis(Vector3::AXIS_Z).normalized(); - Vector3 cam_x = camera->get_global_transform().basis.get_axis(Vector3::AXIS_X).normalized(); - Vector3 cam_y = camera->get_global_transform().basis.get_axis(Vector3::AXIS_Y).normalized(); - Vector3 cam_pos = camera->get_global_transform().origin; - Vector3 parent_pos = parent->get_global_transform().origin; - - Plane parent_plane(parent_pos, cam_normal); - Vector3 ray_from = parent_plane.project(cam_pos); - - lines.clear(); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5); - - lines.push_back(ray_from + cam_x * 0.5 + cam_y * -0.5); - lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5); - - lines.push_back(ray_from + cam_x * -0.5 + cam_y * -0.5); - lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5); - - lines.push_back(ray_from + cam_x * -0.5 + cam_y * 0.5); - lines.push_back(ray_from + cam_x * 0.5 + cam_y * 0.5); - - if (parent_plane.distance_to(cam_pos) < 0) { - lines.push_back(ray_from); - lines.push_back(cam_pos); - } - - Transform3D local = camera->get_global_transform().affine_inverse(); - for (int i = 0; i < lines.size(); i++) { - lines.write[i] = local.xform(lines[i]); - } - - p_gizmo->add_lines(lines, material); - } } ////// @@ -2070,169 +2029,6 @@ void Position3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { p_gizmo->add_collision_segments(cursor_points); } -///// - -Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { - Color gizmo_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); - create_material("skeleton_material", gizmo_color); -} - -bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; -} - -String Skeleton3DGizmoPlugin::get_gizmo_name() const { - return "Skeleton3D"; -} - -int Skeleton3DGizmoPlugin::get_priority() const { - return -1; -} - -void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - Skeleton3D *skel = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); - - p_gizmo->clear(); - - Ref<Material> material = get_material("skeleton_material", p_gizmo); - - Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); - - surface_tool->begin(Mesh::PRIMITIVE_LINES); - surface_tool->set_material(material); - LocalVector<Transform3D> grests; - grests.resize(skel->get_bone_count()); - - LocalVector<int> bones; - LocalVector<float> weights; - bones.resize(4); - weights.resize(4); - - for (int i = 0; i < 4; i++) { - bones[i] = 0; - weights[i] = 0; - } - - weights[0] = 1; - - AABB aabb; - - Color bonecolor = Color(1.0, 0.4, 0.4, 0.3); - Color rootcolor = Color(0.4, 1.0, 0.4, 0.1); - - LocalVector<int> bones_to_process; - bones_to_process = skel->get_parentless_bones(); - - while (bones_to_process.size() > 0) { - int current_bone_idx = bones_to_process[0]; - bones_to_process.erase(current_bone_idx); - - LocalVector<int> child_bones_vector; - child_bones_vector = skel->get_bone_children(current_bone_idx); - int child_bones_size = child_bones_vector.size(); - - if (skel->get_bone_parent(current_bone_idx) < 0) { - grests[current_bone_idx] = skel->get_bone_rest(current_bone_idx); - } - - for (int i = 0; i < child_bones_size; i++) { - int child_bone_idx = child_bones_vector[i]; - - grests[child_bone_idx] = grests[current_bone_idx] * skel->get_bone_rest(child_bone_idx); - Vector3 v0 = grests[current_bone_idx].origin; - Vector3 v1 = grests[child_bone_idx].origin; - Vector3 d = (v1 - v0).normalized(); - real_t dist = v0.distance_to(v1); - - // Find closest axis. - int closest = -1; - real_t closest_d = 0.0; - for (int j = 0; j < 3; j++) { - real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); - if (j == 0 || dp > closest_d) { - closest = j; - } - } - - // Find closest other. - Vector3 first; - Vector3 points[4]; - int point_idx = 0; - for (int j = 0; j < 3; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 - grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(rootcolor); - surface_tool->add_vertex(v0 + grests[current_bone_idx].basis[j].normalized() * dist * 0.05); - - if (j == closest) { - continue; - } - - Vector3 axis; - if (first == Vector3()) { - axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); - first = axis; - } else { - axis = d.cross(first).normalized(); - } - - for (int k = 0; k < 2; k++) { - if (k == 1) { - axis = -axis; - } - Vector3 point = v0 + d * dist * 0.2; - point += axis * dist * 0.1; - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v0); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(point); - bones[0] = child_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(v1); - points[point_idx++] = point; - } - } - SWAP(points[1], points[2]); - for (int j = 0; j < 4; j++) { - bones[0] = current_bone_idx; - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[j]); - surface_tool->set_bones(bones); - surface_tool->set_weights(weights); - surface_tool->set_color(bonecolor); - surface_tool->add_vertex(points[(j + 1) % 4]); - } - - // Add the bone's children to the list of bones to be processed. - bones_to_process.push_back(child_bones_vector[i]); - } - } - - Ref<ArrayMesh> m = surface_tool->commit(); - p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skel->register_skin(Ref<Skin>())); -} - //// PhysicalBone3DGizmoPlugin::PhysicalBone3DGizmoPlugin() { @@ -4773,10 +4569,10 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } Vector<Vector3> lines; - for (Map<_EdgeKey, bool>::Element *E = edge_map.front(); E; E = E->next()) { - if (E->get()) { - lines.push_back(E->key().from); - lines.push_back(E->key().to); + for (const KeyValue<_EdgeKey, bool> &E : edge_map) { + if (E.value) { + lines.push_back(E.key.from); + lines.push_back(E.key.to); } } diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 24b4a23d4b..d1aca4d92e 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -332,18 +332,6 @@ public: Position3DGizmoPlugin(); }; -class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { - GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); - -public: - bool has_gizmo(Node3D *p_spatial) override; - String get_gizmo_name() const override; - int get_priority() const override; - void redraw(EditorNode3DGizmo *p_gizmo) override; - - Skeleton3DGizmoPlugin(); -}; - class PhysicalBone3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(PhysicalBone3DGizmoPlugin, EditorNode3DGizmoPlugin); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index d52a633fed..32a8bbf406 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -357,8 +357,8 @@ int Node3DEditorViewport::get_selected_count() const { int count = 0; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); if (!sp) { continue; } @@ -651,13 +651,13 @@ void Node3DEditorViewport::_select_region() { Vector3 a = _get_screen_to_space(box[i]); Vector3 b = _get_screen_to_space(box[(i + 1) % 4]); if (orthogonal) { - frustum.push_back(Plane(a, (a - b).normalized())); + frustum.push_back(Plane((a - b).normalized(), a)); } else { frustum.push_back(Plane(a, b, cam_pos)); } } - Plane near(cam_pos, -_get_camera_normal()); + Plane near(-_get_camera_normal(), cam_pos); near.d -= get_znear(); frustum.push_back(near); @@ -864,8 +864,8 @@ void Node3DEditorViewport::_compute_edit(const Point2 &p_point) { Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; if (se && se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - int subgizmo_id = E->key(); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + int subgizmo_id = E.key; se->subgizmos[subgizmo_id] = se->gizmo->get_subgizmo_transform(subgizmo_id); } se->original_local = selected->get_transform(); @@ -972,7 +972,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667); Vector3 r; - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1010,7 +1010,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b float col_d = 1e20; for (int i = 0; i < 3; i++) { - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); Vector3 r; if (!plane.intersects_ray(ray_pos, ray, &r)) { continue; @@ -1076,7 +1076,7 @@ bool Node3DEditorViewport::_transform_gizmo_select(const Vector2 &p_screenpos, b const Vector3 grabber_pos = gt.origin + (ivec2 + ivec3) * gizmo_scale * (GIZMO_PLANE_SIZE + GIZMO_PLANE_DST * 0.6667); Vector3 r; - Plane plane(gt.origin, gt.basis.get_axis(i).normalized()); + Plane plane(gt.basis.get_axis(i).normalized(), gt.origin); if (plane.intersects_ray(ray_pos, ray, &r)) { const real_t dist = r.distance_to(grabber_pos); @@ -1291,24 +1291,31 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; //do NONE } + EditorPlugin::AfterGUIInput after = EditorPlugin::AFTER_GUI_INPUT_PASS; { EditorNode *en = editor; EditorPluginList *force_input_forwarding_list = en->get_editor_plugins_force_input_forwarding(); if (!force_input_forwarding_list->is_empty()) { - bool discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); - if (discard) { + EditorPlugin::AfterGUIInput discard = force_input_forwarding_list->forward_spatial_gui_input(camera, p_event, true); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } { EditorNode *en = editor; EditorPluginList *over_plugin_list = en->get_editor_plugins_over(); if (!over_plugin_list->is_empty()) { - bool discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); - if (discard) { + EditorPlugin::AfterGUIInput discard = over_plugin_list->forward_spatial_gui_input(camera, p_event, false); + if (discard == EditorPlugin::AFTER_GUI_INPUT_STOP) { return; } + if (discard == EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + after = EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } } } @@ -1374,9 +1381,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector<int> ids; Vector<Transform3D> restore; - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - ids.push_back(GE->key()); - restore.push_back(GE->value()); + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + ids.push_back(GE.key); + restore.push_back(GE.value); } se->gizmo->commit_subgizmos(ids, restore, true); @@ -1573,17 +1580,19 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - clicked = _select_ray(b->get_position()); + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + clicked = _select_ray(b->get_position()); - //clicking is always deferred to either move or release + //clicking is always deferred to either move or release - clicked_wants_append = b->is_shift_pressed(); + clicked_wants_append = b->is_shift_pressed(); - if (clicked.is_null()) { - //default to regionselect - cursor.region_select = true; - cursor.region_begin = b->get_position(); - cursor.region_end = b->get_position(); + if (clicked.is_null()) { + //default to regionselect + cursor.region_select = true; + cursor.region_begin = b->get_position(); + cursor.region_end = b->get_position(); + } } surface->update(); @@ -1594,14 +1603,16 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { break; } - if (clicked.is_valid()) { - _select_clicked(false); - } + if (after != EditorPlugin::AFTER_GUI_INPUT_DESELECT) { + if (clicked.is_valid()) { + _select_clicked(false); + } - if (cursor.region_select) { - _select_region(); - cursor.region_select = false; - surface->update(); + if (cursor.region_select) { + _select_region(); + cursor.region_select = false; + surface->update(); + } } if (_edit.mode != TRANSFORM_NONE) { @@ -1612,9 +1623,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector<int> ids; Vector<Transform3D> restore; - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - ids.push_back(GE->key()); - restore.push_back(GE->value()); + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + ids.push_back(GE.key); + restore.push_back(GE.value); } se->gizmo->commit_subgizmos(ids, restore, false); @@ -1750,33 +1761,33 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: motion_mask = Vector3(0, 0, 0); - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_YZ: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); plane_mv = true; break; case TRANSFORM_XZ: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2) + spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); plane_mv = true; break; case TRANSFORM_XY: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0) + spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); plane_mv = true; break; } @@ -1845,13 +1856,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original * xform, xform, motion, snap, local_coords); if (!local_coords) { new_xform = se->original.affine_inverse() * new_xform; } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + se->gizmo->set_subgizmo_transform(GE.key, new_xform); } } else { Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords); @@ -1871,30 +1882,30 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(0); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Y_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(1); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_Z_AXIS: motion_mask = spatial_editor->get_gizmo_transform().basis.get_axis(2); - plane = Plane(_edit.center, motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized()); + plane = Plane(motion_mask.cross(motion_mask.cross(_get_camera_normal())).normalized(), _edit.center); break; case TRANSFORM_YZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); plane_mv = true; break; case TRANSFORM_XZ: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); plane_mv = true; break; case TRANSFORM_XY: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); plane_mv = true; break; } @@ -1944,11 +1955,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original * xform, xform, motion, snap, local_coords); new_xform = se->original.affine_inverse() * new_xform; - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + se->gizmo->set_subgizmo_transform(GE.key, new_xform); } } else { Transform3D new_xform = _compute_transform(TRANSFORM_TRANSLATE, se->original, se->original_local, motion, snap, local_coords); @@ -1967,18 +1978,18 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { switch (_edit.plane) { case TRANSFORM_VIEW: - plane = Plane(_edit.center, _get_camera_normal()); + plane = Plane(_get_camera_normal(), _edit.center); break; case TRANSFORM_X_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(0)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(0), _edit.center); axis = Vector3(1, 0, 0); break; case TRANSFORM_Y_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(1)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(1), _edit.center); axis = Vector3(0, 1, 0); break; case TRANSFORM_Z_AXIS: - plane = Plane(_edit.center, spatial_editor->get_gizmo_transform().basis.get_axis(2)); + plane = Plane(spatial_editor->get_gizmo_transform().basis.get_axis(2), _edit.center); axis = Vector3(0, 0, 1); break; case TRANSFORM_YZ: @@ -2030,14 +2041,14 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { Vector3 compute_axis = local_coords ? axis : plane.normal; if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - Transform3D xform = GE->get(); + for (KeyValue<int, Transform3D> &GE : se->subgizmos) { + Transform3D xform = GE.value; Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original * xform, xform, compute_axis, angle, local_coords); if (!local_coords) { new_xform = se->original.affine_inverse() * new_xform; } - se->gizmo->set_subgizmo_transform(GE->key(), new_xform); + se->gizmo->set_subgizmo_transform(GE.key, new_xform); } } else { Transform3D new_xform = _compute_transform(TRANSFORM_ROTATE, se->original, se->original_local, compute_axis, angle, local_coords); @@ -2215,6 +2226,21 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { if (ED_IS_SHORTCUT("spatial_editor/right_view", p_event)) { _menu_option(VIEW_RIGHT); } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_down", p_event)) { + cursor.x_rot -= Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_up", p_event)) { + cursor.x_rot += Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_right", p_event)) { + cursor.y_rot -= Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_left", p_event)) { + cursor.y_rot += Math_PI / 12.0; + } + if (ED_IS_SHORTCUT("spatial_editor/orbit_view_180", p_event)) { + cursor.y_rot += Math_PI; + } if (ED_IS_SHORTCUT("spatial_editor/focus_origin", p_event)) { _menu_option(VIEW_CENTER_TO_ORIGIN); } @@ -2237,7 +2263,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } - if (!AnimationPlayerEditor::singleton->get_track_editor()->has_keying()) { + if (!AnimationPlayerEditor::get_singleton()->get_track_editor()->has_keying()) { set_message(TTR("Keying is disabled (no key inserted).")); return; } @@ -2483,8 +2509,14 @@ static bool is_shortcut_pressed(const String &p_path) { if (shortcut.is_null()) { return false; } - InputEventKey *k = Object::cast_to<InputEventKey>(shortcut->get_event().ptr()); - if (k == nullptr) { + + const Array shortcuts = shortcut->get_events(); + Ref<InputEventKey> k; + if (shortcuts.size() > 0) { + k = shortcuts.front(); + } + + if (k.is_null()) { return false; } const Input &input = *Input::get_singleton(); @@ -2601,6 +2633,9 @@ void Node3DEditorViewport::_project_settings_changed() { const bool use_occlusion_culling = GLOBAL_GET("rendering/occlusion_culling/use_occlusion_culling"); viewport->set_use_occlusion_culling(use_occlusion_culling); + + const float lod_threshold = GLOBAL_GET("rendering/mesh_lod/lod_change/threshold_pixels"); + viewport->set_lod_threshold(lod_threshold); } void Node3DEditorViewport::_notification(int p_what) { @@ -2663,8 +2698,8 @@ void Node3DEditorViewport::_notification(int p_what) { bool changed = false; bool exist = false; - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node3D *sp = Object::cast_to<Node3D>(E->key()); + for (const KeyValue<Node *, Object *> &E : selection) { + Node3D *sp = Object::cast_to<Node3D>(E.key); if (!sp) { continue; } @@ -2688,15 +2723,28 @@ void Node3DEditorViewport::_notification(int p_what) { se->aabb = new_aabb; - t.translate(se->aabb.position); + Transform3D t_offset = t; // apply AABB scaling before item's global transform - Basis aabb_s; - aabb_s.scale(se->aabb.size); - t.basis = t.basis * aabb_s; + { + const Vector3 offset(0.005, 0.005, 0.005); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t.translate(se->aabb.position - offset / 2); + t.basis = t.basis * aabb_s; + } + { + const Vector3 offset(0.01, 0.01, 0.01); + Basis aabb_s; + aabb_s.scale(se->aabb.size + offset); + t_offset.translate(se->aabb.position - offset / 2); + t_offset.basis = t_offset.basis * aabb_s; + } RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_offset, t_offset); RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray, t); + RenderingServer::get_singleton()->instance_set_transform(se->sbox_instance_xray_offset, t_offset); } if (changed || (spatial_editor->is_gizmo_visible() && !exist)) { @@ -3555,7 +3603,7 @@ void Node3DEditorViewport::update_transform_gizmo_view() { const Vector3 camz = -camera_xform.get_basis().get_axis(2).normalized(); const Vector3 camy = -camera_xform.get_basis().get_axis(1).normalized(); - const Plane p(camera_xform.origin, camz); + const Plane p = Plane(camz, camera_xform.origin); const real_t gizmo_d = MAX(Math::abs(p.distance_to(xform.origin)), CMP_EPSILON); const real_t d0 = camera->unproject_position(camera_xform.origin + camz * gizmo_d).y; const real_t d1 = camera->unproject_position(camera_xform.origin + camz * gizmo_d + camy).y; @@ -3806,8 +3854,8 @@ void Node3DEditorViewport::focus_selection() { } if (se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *GE = se->subgizmos.front(); GE; GE = GE->next()) { - center += se->gizmo->get_subgizmo_transform(GE->key()).origin; + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + center += se->gizmo->get_subgizmo_transform(GE.key).origin; count++; } } @@ -3830,7 +3878,7 @@ void Node3DEditorViewport::assign_pending_data_pointers(Node3D *p_preview_node, } Vector3 Node3DEditorViewport::_get_instance_position(const Point2 &p_pos) const { - const float MAX_DISTANCE = 10; + const float MAX_DISTANCE = 50.0; Vector3 world_ray = _get_ray(p_pos); Vector3 world_pos = _get_ray_pos(p_pos); @@ -3917,7 +3965,7 @@ void Node3DEditorViewport::_remove_preview() { } bool Node3DEditorViewport::_cyclical_dependency_exists(const String &p_target_scene_path, Node *p_desired_node) { - if (p_desired_node->get_filename() == p_target_scene_path) { + if (p_desired_node->get_scene_file_path() == p_target_scene_path) { return true; } @@ -3959,15 +4007,15 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po return false; } - if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + if (editor->get_edited_scene()->get_scene_file_path() != "") { // cyclical instancing + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_scene_file_path(), instantiated_scene)) { memdelete(instantiated_scene); return false; } } if (scene != nullptr) { - instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_scene_file_path(ProjectSettings::get_singleton()->localize_path(path)); } editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); @@ -4722,9 +4770,15 @@ Node3DEditorSelectedItem::~Node3DEditorSelectedItem() { if (sbox_instance.is_valid()) { RenderingServer::get_singleton()->free(sbox_instance); } + if (sbox_instance_offset.is_valid()) { + RenderingServer::get_singleton()->free(sbox_instance_offset); + } if (sbox_instance_xray.is_valid()) { RenderingServer::get_singleton()->free(sbox_instance_xray); } + if (sbox_instance_xray_offset.is_valid()) { + RenderingServer::get_singleton()->free(sbox_instance_xray_offset); + } } void Node3DEditor::select_gizmo_highlight_axis(int p_axis) { @@ -4747,8 +4801,8 @@ void Node3DEditor::update_transform_gizmo() { Node3DEditorSelectedItem *se = selected ? editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(selected) : nullptr; if (se && se->gizmo.is_valid()) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E->key()); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + Transform3D xf = se->sp->get_global_transform() * se->gizmo->get_subgizmo_transform(E.key); gizmo_center += xf.origin; if (count == 0 && local_gizmo_coords) { gizmo_basis = xf.basis; @@ -4827,23 +4881,39 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { si->sbox_instance = RenderingServer::get_singleton()->instance_create2( selection_box->get_rid(), sp->get_world_3d()->get_scenario()); + si->sbox_instance_offset = RenderingServer::get_singleton()->instance_create2( + selection_box->get_rid(), + sp->get_world_3d()->get_scenario()); RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting( + si->sbox_instance_offset, + RS::SHADOW_CASTING_SETTING_OFF); // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting. // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position. RS::get_singleton()->instance_set_layer_mask(si->sbox_instance, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); + RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); si->sbox_instance_xray = RenderingServer::get_singleton()->instance_create2( selection_box_xray->get_rid(), sp->get_world_3d()->get_scenario()); + si->sbox_instance_xray_offset = RenderingServer::get_singleton()->instance_create2( + selection_box_xray->get_rid(), + sp->get_world_3d()->get_scenario()); RS::get_singleton()->instance_geometry_set_cast_shadows_setting( si->sbox_instance_xray, RS::SHADOW_CASTING_SETTING_OFF); + RS::get_singleton()->instance_geometry_set_cast_shadows_setting( + si->sbox_instance_xray_offset, + RS::SHADOW_CASTING_SETTING_OFF); // Use the Edit layer to hide the selection box when View Gizmos is disabled, since it is a bit distracting. // It's still possible to approximately guess what is selected by looking at the manipulation gizmo position. RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); - RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_set_layer_mask(si->sbox_instance_xray_offset, 1 << Node3DEditorViewport::GIZMO_EDIT_LAYER); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); + RS::get_singleton()->instance_geometry_set_flag(si->sbox_instance_xray_offset, RS::INSTANCE_FLAG_IGNORE_OCCLUSION_CULLING, true); return si; } @@ -4851,10 +4921,6 @@ Object *Node3DEditor::_get_editor_data(Object *p_what) { void Node3DEditor::_generate_selection_boxes() { // Use two AABBs to create the illusion of a slightly thicker line. AABB aabb(Vector3(), Vector3(1, 1, 1)); - AABB aabb_offset(Vector3(), Vector3(1, 1, 1)); - // Grow the bounding boxes slightly to avoid Z-fighting with the mesh's edges. - aabb.grow_by(0.005); - aabb_offset.grow_by(0.01); // Create a x-ray (visible through solid surfaces) and standard version of the selection box. // Both will be drawn at the same position, but with different opacity. @@ -4874,16 +4940,6 @@ void Node3DEditor::_generate_selection_boxes() { st_xray->add_vertex(b); } - for (int i = 0; i < 12; i++) { - Vector3 a, b; - aabb_offset.get_edge(i, a, b); - - st->add_vertex(a); - st->add_vertex(b); - st_xray->add_vertex(a); - st_xray->add_vertex(b); - } - Ref<StandardMaterial3D> mat = memnew(StandardMaterial3D); mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); const Color selection_box_color = EDITOR_GET("editors/3d/selection_box_color"); @@ -6130,7 +6186,7 @@ void Node3DEditor::_init_grid() { if (orthogonal) { camera_distance = camera->get_size() / 2.0; Vector3 camera_direction = -camera->get_global_transform().get_basis().get_axis(2); - Plane grid_plane = Plane(Vector3(), normal); + Plane grid_plane = Plane(normal); Vector3 intersection; if (grid_plane.intersects_ray(camera_position, camera_direction, &intersection)) { camera_position = intersection; @@ -6280,7 +6336,7 @@ void Node3DEditor::update_grid() { // Gets a orthogonal or perspective position correctly (for the grid comparison) const Vector3 camera_position = get_editor_viewport(0)->camera->get_position(); - if (!grid_init_draw || (camera_position - grid_camera_last_update_position).length() >= 10.0f) { + if (!grid_init_draw || grid_camera_last_update_position.distance_squared_to(camera_position) >= 100.0f) { _finish_grid(); _init_grid(); grid_init_draw = true; @@ -6428,7 +6484,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { // We add a bit of margin to the from position to avoid it from snapping // when the spatial is already on a floor and there's another floor under // it - from = from + Vector3(0.0, 0.2, 0.0); + from = from + Vector3(0.0, 1, 0.0); Dictionary d; @@ -6444,7 +6500,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { Array keys = snap_data.keys(); // The maximum height an object can travel to be snapped - const float max_snap_height = 20.0; + const float max_snap_height = 500.0; // Will be set to `true` if at least one node from the selection was successfully snapped bool snapped_to_floor = false; @@ -6674,8 +6730,8 @@ Vector<int> Node3DEditor::get_subgizmo_selection() { Vector<int> ret; if (se) { - for (Map<int, Transform3D>::Element *E = se->subgizmos.front(); E; E = E->next()) { - ret.push_back(E->key()); + for (const KeyValue<int, Transform3D> &E : se->subgizmos) { + ret.push_back(E.key); } } return ret; @@ -6727,6 +6783,33 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { } } +void Node3DEditor::_set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform) { + if (p_id == -1) { + _clear_subgizmo_selection(p_obj); + return; + } + + Node3D *sp = nullptr; + if (p_obj) { + sp = Object::cast_to<Node3D>(p_obj); + } else { + sp = selected; + } + + if (!sp) { + return; + } + + Node3DEditorSelectedItem *se = editor_selection->get_node_editor_data<Node3DEditorSelectedItem>(sp); + if (se) { + se->subgizmos.clear(); + se->subgizmos.insert(p_id, p_transform); + se->gizmo = p_gizmo; + sp->update_gizmos(); + update_transform_gizmo(); + } +} + void Node3DEditor::_clear_subgizmo_selection(Object *p_obj) { Node3D *sp = nullptr; if (p_obj) { @@ -6852,7 +6935,6 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<OccluderInstance3DGizmoPlugin>(memnew(OccluderInstance3DGizmoPlugin))); add_gizmo_plugin(Ref<SoftDynamicBody3DGizmoPlugin>(memnew(SoftDynamicBody3DGizmoPlugin))); add_gizmo_plugin(Ref<Sprite3DGizmoPlugin>(memnew(Sprite3DGizmoPlugin))); - add_gizmo_plugin(Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin))); add_gizmo_plugin(Ref<Position3DGizmoPlugin>(memnew(Position3DGizmoPlugin))); add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin))); @@ -6877,6 +6959,7 @@ void Node3DEditor::_register_all_gizmos() { void Node3DEditor::_bind_methods() { ClassDB::bind_method("_get_editor_data", &Node3DEditor::_get_editor_data); ClassDB::bind_method("_request_gizmo", &Node3DEditor::_request_gizmo); + ClassDB::bind_method("_set_subgizmo_selection", &Node3DEditor::_set_subgizmo_selection); ClassDB::bind_method("_clear_subgizmo_selection", &Node3DEditor::_clear_subgizmo_selection); ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons); @@ -7238,6 +7321,11 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { ED_SHORTCUT("spatial_editor/front_view", TTR("Front View"), KEY_KP_1); ED_SHORTCUT("spatial_editor/left_view", TTR("Left View"), KEY_MASK_ALT + KEY_KP_3); ED_SHORTCUT("spatial_editor/right_view", TTR("Right View"), KEY_KP_3); + ED_SHORTCUT("spatial_editor/orbit_view_down", TTR("Orbit View Down"), KEY_KP_2); + ED_SHORTCUT("spatial_editor/orbit_view_left", TTR("Orbit View Left"), KEY_KP_4); + ED_SHORTCUT("spatial_editor/orbit_view_right", TTR("Orbit View Right"), KEY_KP_6); + ED_SHORTCUT("spatial_editor/orbit_view_up", TTR("Orbit View Up"), KEY_KP_8); + ED_SHORTCUT("spatial_editor/orbit_view_180", TTR("Orbit View 180"), KEY_KP_9); ED_SHORTCUT("spatial_editor/switch_perspective_orthogonal", TTR("Switch Perspective/Orthogonal View"), KEY_KP_5); ED_SHORTCUT("spatial_editor/insert_anim_key", TTR("Insert Animation Key"), KEY_K); ED_SHORTCUT("spatial_editor/focus_origin", TTR("Focus Origin"), KEY_O); @@ -7703,6 +7791,13 @@ Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const { return p_target; } +bool Node3DEditor::is_gizmo_visible() const { + if (selected) { + return gizmo.visible && selected->is_transform_gizmo_visible(); + } + return gizmo.visible; +} + double Node3DEditor::get_translate_snap() const { double snap_value; if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 59f3ec6fcd..2d5aeaa981 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -431,7 +431,9 @@ public: bool last_xform_dirty; Node3D *sp; RID sbox_instance; + RID sbox_instance_offset; RID sbox_instance_xray; + RID sbox_instance_xray_offset; Ref<EditorNode3DGizmo> gizmo; Map<int, Transform3D> subgizmos; // map ID -> initial transform @@ -663,6 +665,7 @@ private: Node3D *selected; void _request_gizmo(Object *p_obj); + void _set_subgizmo_selection(Object *p_obj, Ref<Node3DGizmo> p_gizmo, int p_id, Transform3D p_transform = Transform3D()); void _clear_subgizmo_selection(Object *p_obj = nullptr); static Node3DEditor *singleton; @@ -756,7 +759,7 @@ public: float get_fov() const { return settings_fov->get_value(); } Transform3D get_gizmo_transform() const { return gizmo.transform; } - bool is_gizmo_visible() const { return gizmo.visible; } + bool is_gizmo_visible() const; ToolMode get_tool_mode() const { return tool_mode; } bool are_local_coords_enabled() const { return tool_option_button[Node3DEditor::TOOL_OPT_LOCAL_COORDS]->is_pressed(); } diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index ab88b9f00d..0328b1bea6 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -41,9 +41,9 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { switch (err) { case OccluderInstance3D::BAKE_ERROR_NO_SAVE_PATH: { - String scene_path = occluder_instance->get_filename(); + String scene_path = occluder_instance->get_scene_file_path(); if (scene_path == String()) { - scene_path = occluder_instance->get_owner()->get_filename(); + scene_path = occluder_instance->get_owner()->get_scene_file_path(); } if (scene_path == String()) { EditorNode::get_singleton()->show_warning(TTR("Can't determine a save path for the occluder.\nSave your scene and try again.")); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 13f7908170..e2902feba1 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -101,7 +101,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point // Setting curve point positions if (p_id < c->get_point_count()) { - Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2)); + const Plane p = Plane(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); Vector3 inters; @@ -125,7 +125,7 @@ void Path3DGizmo::set_handle(int p_id, Camera3D *p_camera, const Point2 &p_point Vector3 base = c->get_point_position(idx); - Plane p(gt.xform(original), p_camera->get_transform().basis.get_axis(2)); + Plane p(p_camera->get_transform().basis.get_axis(2), gt.xform(original)); Vector3 inters; @@ -294,13 +294,13 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path) { orig_out_length = 0; } -bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { +EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { if (!path) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Ref<Curve3D> c = path->get_curve(); if (c.is_null()) { - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } Transform3D gt = path->get_global_transform(); Transform3D it = gt.affine_inverse(); @@ -329,14 +329,14 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref const Vector3 *r = v3a.ptr(); if (p_camera->unproject_position(gt.xform(c->get_point_position(0))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } for (int i = 0; i < c->get_point_count() - 1; i++) { //find the offset and point index of the place to break up int j = idx; if (p_camera->unproject_position(gt.xform(c->get_point_position(i + 1))).distance_to(mbpos) < click_dist) { - return false; //nope, existing + return EditorPlugin::AFTER_GUI_INPUT_PASS; //nope, existing } while (j < rc && c->get_point_position(i + 1) != r[j]) { @@ -386,16 +386,16 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", closest_seg_point, Vector3(), Vector3(), closest_seg + 1); ur->add_undo_method(c.ptr(), "remove_point", closest_seg + 1); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else { - Vector3 org; + Vector3 origin; if (c->get_point_count() == 0) { - org = path->get_transform().get_origin(); + origin = path->get_transform().get_origin(); } else { - org = gt.xform(c->get_point_position(c->get_point_count() - 1)); + origin = gt.xform(c->get_point_position(c->get_point_count() - 1)); } - Plane p(org, p_camera->get_transform().basis.get_axis(2)); + Plane p(p_camera->get_transform().basis.get_axis(2), origin); Vector3 ray_from = p_camera->project_ray_origin(mbpos); Vector3 ray_dir = p_camera->project_ray_normal(mbpos); @@ -405,7 +405,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "add_point", it.xform(inters), Vector3(), Vector3(), -1); ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } //add new at pos @@ -425,27 +425,27 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref ur->add_do_method(c.ptr(), "remove_point", i); ur->add_undo_method(c.ptr(), "add_point", c->get_point_position(i), c->get_point_in(i), c->get_point_out(i), i); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_out < click_dist) { UndoRedo *ur = editor->get_undo_redo(); ur->create_action(TTR("Remove Out-Control Point")); ur->add_do_method(c.ptr(), "set_point_out", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_out", i, c->get_point_out(i)); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } else if (dist_to_p_in < click_dist) { UndoRedo *ur = editor->get_undo_redo(); ur->create_action(TTR("Remove In-Control Point")); ur->add_do_method(c.ptr(), "set_point_in", i, Vector3()); ur->add_undo_method(c.ptr(), "set_point_in", i, c->get_point_in(i)); ur->commit_action(); - return true; + return EditorPlugin::AFTER_GUI_INPUT_STOP; } } } } - return false; + return EditorPlugin::AFTER_GUI_INPUT_PASS; } void Path3DEditorPlugin::edit(Object *p_object) { @@ -510,7 +510,14 @@ void Path3DEditorPlugin::_close_curve() { if (c->get_point_count() < 2) { return; } - c->add_point(c->get_point_position(0), c->get_point_in(0), c->get_point_out(0)); + if (c->get_point_position(0) == c->get_point_position(c->get_point_count() - 1)) { + return; + } + UndoRedo *ur = editor->get_undo_redo(); + ur->create_action(TTR("Close Curve")); + ur->add_do_method(c.ptr(), "add_point", c->get_point_position(0), c->get_point_in(0), c->get_point_out(0), -1); + ur->add_undo_method(c.ptr(), "remove_point", c->get_point_count()); + ur->commit_action(); } void Path3DEditorPlugin::_handle_option_pressed(int p_option) { diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index b74d7cc59e..974234ba8f 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -100,7 +100,7 @@ public: Path3D *get_edited_path() { return path; } static Path3DEditorPlugin *singleton; - virtual bool forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; virtual String get_name() const override { return "Path3D"; } bool has_main_screen() const override { return false; } diff --git a/editor/plugins/physical_bone_3d_editor_plugin.cpp b/editor/plugins/physical_bone_3d_editor_plugin.cpp index f92f50f826..b1e104e680 100644 --- a/editor/plugins/physical_bone_3d_editor_plugin.cpp +++ b/editor/plugins/physical_bone_3d_editor_plugin.cpp @@ -43,6 +43,7 @@ void PhysicalBone3DEditor::_on_toggle_button_transform_joint(bool p_is_pressed) void PhysicalBone3DEditor::_set_move_joint() { if (selected) { selected->_set_gizmo_move_joint(button_transform_joint->is_pressed()); + Node3DEditor::get_singleton()->update_transform_gizmo(); } } diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index ac18ff9f1c..5afe9ed60c 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -779,7 +779,7 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { if (mm.is_valid()) { if ((mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) || Input::get_singleton()->is_key_pressed(KEY_SPACE)) { - Vector2 drag(mm->get_relative().x, mm->get_relative().y); + Vector2 drag = mm->get_relative(); uv_hscroll->set_value(uv_hscroll->get_value() - drag.x); uv_vscroll->set_value(uv_vscroll->get_value() - drag.y); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 07e4d4ebf0..677a5f1f2c 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -214,6 +214,7 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { void ScriptEditorBase::_bind_methods() { ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor); + ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptEditorBase::add_syntax_highlighter); ADD_SIGNAL(MethodInfo("name_changed")); ADD_SIGNAL(MethodInfo("edited_script_changed")); @@ -484,14 +485,32 @@ void ScriptEditor::_clear_execution(REF p_script) { void ScriptEditor::_set_breakpoint(REF p_script, int p_line, bool p_enabled) { Ref<Script> script = Object::cast_to<Script>(*p_script); if (script.is_valid() && (script->has_source_code() || script->get_path().is_resource_file())) { - if (edit(p_script, p_line, 0, false)) { - editor->push_item(p_script.ptr()); - - ScriptEditorBase *se = _get_current_editor(); - if (se) { + // Update if open. + for (int i = 0; i < tab_container->get_child_count(); i++) { + ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); + if (se && se->get_edited_resource()->get_path() == script->get_path()) { se->set_breakpoint(p_line, p_enabled); + return; + } + } + + // Handle closed. + Dictionary state = script_editor_cache->get_value(script->get_path(), "state"); + Array breakpoints; + if (state.has("breakpoints")) { + breakpoints = state["breakpoints"]; + } + + if (breakpoints.has(p_line)) { + if (!p_enabled) { + breakpoints.erase(p_line); } + } else if (p_enabled) { + breakpoints.push_back(p_line); } + state["breakpoints"] = breakpoints; + script_editor_cache->set_value(script->get_path(), "state", state); + EditorDebuggerNode::get_singleton()->set_breakpoint(script->get_path(), p_line + 1, false); } } @@ -502,6 +521,34 @@ void ScriptEditor::_clear_breakpoints() { se->clear_breakpoints(); } } + + // Clear from closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, false); + } + + if (breakpoints.size() > 0) { + Dictionary state = script_editor_cache->get_value(E, "state"); + state["breakpoints"] = Array(); + script_editor_cache->set_value(E, "state", state); + } + } +} + +Array ScriptEditor::_get_cached_breakpoints_for_script(const String &p_path) const { + if (!ResourceLoader::exists(p_path, "Script") || p_path.begins_with("local://") || !script_editor_cache->has_section_key(p_path, "state")) { + return Array(); + } + + Dictionary state = script_editor_cache->get_value(p_path, "state"); + if (!state.has("breakpoints")) { + return Array(); + } + return state["breakpoints"]; } ScriptEditorBase *ScriptEditor::_get_current_editor() const { @@ -716,20 +763,24 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected); if (current) { - Ref<Script> script = current->get_edited_resource(); - if (p_save && script.is_valid()) { + RES file = current->get_edited_resource(); + if (p_save && file.is_valid()) { // Do not try to save internal scripts, but prompt to save in-memory // scripts which are not saved to disk yet (have empty path). - if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) { + if (file->get_path().find("local://") == -1 && file->get_path().find("::") == -1) { save_current_script(); } } - if (script.is_valid()) { - if (!script->get_path().is_empty()) { + if (file.is_valid()) { + if (!file->get_path().is_empty()) { // Only saved scripts can be restored. - previous_scripts.push_back(script->get_path()); + previous_scripts.push_back(file->get_path()); + } + + Ref<Script> script = file; + if (script.is_valid()) { + notify_script_close(script); } - notify_script_close(script); } } @@ -756,6 +807,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { int idx = tab_container->get_current_tab(); if (current) { current->clear_edit_menu(); + _save_editor_state(current); } memdelete(tselected); if (idx >= tab_container->get_child_count()) { @@ -1030,35 +1082,21 @@ void ScriptEditor::_file_dialog_action(String p_file) { } file->close(); memdelete(file); - [[fallthrough]]; - } - case FILE_OPEN: { - List<String> extensions; - ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); - if (extensions.find(p_file.get_extension())) { - Ref<Script> scr = ResourceLoader::load(p_file); - if (!scr.is_valid()) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); - file_dialog_option = -1; - return; - } - - edit(scr); - file_dialog_option = -1; - return; - } - Error error; - Ref<TextFile> text_file = _load_text_file(p_file, &error); - if (error != OK) { - editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + if (EditorFileSystem::get_singleton()) { + if (textfile_extensions.has(p_file.get_extension())) { + EditorFileSystem::get_singleton()->update_file(p_file); + } } - if (text_file.is_valid()) { - edit(text_file); - file_dialog_option = -1; + if (!open_textfile_after_create) { return; } + [[fallthrough]]; + } + case FILE_OPEN: { + open_file(p_file); + file_dialog_option = -1; } break; case FILE_SAVE_AS: { ScriptEditorBase *current = _get_current_editor(); @@ -1133,8 +1171,12 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog_option = FILE_NEW_TEXTFILE; file_dialog->clear_filters(); + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); + } file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("New Text File...")); + open_textfile_after_create = true; } break; case FILE_OPEN: { file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); @@ -1148,6 +1190,10 @@ void ScriptEditor::_menu_option(int p_option) { file_dialog->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } + for (const String &E : textfile_extensions) { + file_dialog->add_filter("*." + E + " ; " + E.to_upper()); + } + file_dialog->popup_file_dialog(); file_dialog->set_title(TTR("Open File")); return; @@ -1488,6 +1534,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->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)); @@ -1497,6 +1544,7 @@ void ScriptEditor::_notification(int p_what) { EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed)); EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed)); + _editor_settings_changed(); [[fallthrough]]; } case NOTIFICATION_TRANSLATION_CHANGED: @@ -1600,6 +1648,7 @@ void ScriptEditor::notify_script_changed(const Ref<Script> &p_script) { } void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { + Set<String> loaded_scripts; for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); if (!se) { @@ -1612,6 +1661,7 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { } String base = script->get_path(); + loaded_scripts.insert(base); if (base.begins_with("local://") || base == "") { continue; } @@ -1621,6 +1671,20 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) { p_breakpoints->push_back(base + ":" + itos((int)bpoints[j] + 1)); } } + + // Load breakpoints that are in closed scripts. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + p_breakpoints->push_back(E + ":" + itos((int)breakpoints[i] + 1)); + } + } } void ScriptEditor::_members_overview_selected(int p_idx) { @@ -2056,7 +2120,7 @@ void ScriptEditor::_update_script_connections() { } } -Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) { +Ref<TextFile> ScriptEditor::_load_text_file(const String &p_path, Error *r_error) const { if (r_error) { *r_error = ERR_FILE_CANT_OPEN; } @@ -2278,6 +2342,10 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra _add_recent_script(p_resource->get_path()); } + if (script_editor_cache->has_section(p_resource->get_path())) { + se->set_edit_state(script_editor_cache->get_value(p_resource->get_path(), "state")); + } + _sort_list_on_update = true; _update_script_names(); _save_layout(); @@ -2436,6 +2504,41 @@ void ScriptEditor::open_script_create_dialog(const String &p_base_name, const St script_create_dialog->config(p_base_name, p_base_path); } +void ScriptEditor::open_text_file_create_dialog(const String &p_base_path, const String &p_base_name) { + file_dialog->set_current_file(p_base_name); + file_dialog->set_current_dir(p_base_path); + _menu_option(FILE_NEW_TEXTFILE); + open_textfile_after_create = false; +} + +RES ScriptEditor::open_file(const String &p_file) { + List<String> extensions; + ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); + if (extensions.find(p_file.get_extension())) { + Ref<Script> scr = ResourceLoader::load(p_file); + if (!scr.is_valid()) { + editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return RES(); + } + + edit(scr); + return scr; + } + + Error error; + Ref<TextFile> text_file = _load_text_file(p_file, &error); + if (error != OK) { + editor->show_warning(TTR("Could not load file at:") + "\n\n" + p_file, TTR("Error!")); + return RES(); + } + + if (text_file.is_valid()) { + edit(text_file); + return text_file; + } + return RES(); +} + void ScriptEditor::_editor_stop() { for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -2478,6 +2581,20 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const } } +void ScriptEditor::_save_editor_state(ScriptEditorBase *p_editor) { + if (restoring_layout) { + return; + } + + const String &path = p_editor->get_edited_resource()->get_path(); + if (!path.is_resource_file()) { + return; + } + + script_editor_cache->set_value(path, "state", p_editor->get_edit_state()); + // This is saved later when we save the editor layout. +} + void ScriptEditor::_save_layout() { if (restoring_layout) { return; @@ -2487,6 +2604,12 @@ void ScriptEditor::_save_layout() { } void ScriptEditor::_editor_settings_changed() { + textfile_extensions.clear(); + const Vector<String> textfile_ext = ((String)(EditorSettings::get_singleton()->get("docks/filesystem/textfile_extensions"))).split(",", false); + for (const String &E : textfile_ext) { + textfile_extensions.insert(E); + } + trim_trailing_whitespace_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/trim_trailing_whitespace_on_save"); convert_indent_on_save = EditorSettings::get_singleton()->get("text_editor/behavior/files/convert_indent_on_save"); use_space_indentation = EditorSettings::get_singleton()->get("text_editor/behavior/indent/type"); @@ -2523,6 +2646,26 @@ void ScriptEditor::_filesystem_changed() { _update_script_names(); } +void ScriptEditor::_files_moved(const String &p_old_file, const String &p_new_file) { + if (!script_editor_cache->has_section(p_old_file)) { + return; + } + Variant state = script_editor_cache->get_value(p_old_file, "state"); + script_editor_cache->erase_section(p_old_file); + script_editor_cache->set_value(p_new_file, "state", state); + + // If Script, update breakpoints with debugger. + Array breakpoints = _get_cached_breakpoints_for_script(p_new_file); + for (int i = 0; i < breakpoints.size(); i++) { + int line = (int)breakpoints[i] + 1; + EditorDebuggerNode::get_singleton()->set_breakpoint(p_old_file, line, false); + if (!p_new_file.begins_with("local://") && ResourceLoader::exists(p_new_file, "Script")) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_new_file, line, true); + } + } + // This is saved later when we save the editor layout. +} + void ScriptEditor::_file_removed(const String &p_removed_file) { for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -2534,6 +2677,15 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { _close_tab(i, false, false); } } + + // Check closed. + if (script_editor_cache->has_section(p_removed_file)) { + Array breakpoints = _get_cached_breakpoints_for_script(p_removed_file); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(p_removed_file, (int)breakpoints[i] + 1, false); + } + script_editor_cache->erase_section(p_removed_file); + } } void ScriptEditor::_update_find_replace_bar() { @@ -2664,12 +2816,22 @@ bool ScriptEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - return true; + if (ResourceLoader::exists(file, "Script")) { + Ref<Script> scr = ResourceLoader::load(file); + if (scr.is_valid()) { + return true; + } + } + + if (textfile_extensions.has(file.get_extension())) { + Error err; + Ref<TextFile> text_file = _load_text_file(file, &err); + if (text_file.is_valid() && err == OK) { + return true; + } } } - return true; + return false; } return false; @@ -2734,9 +2896,13 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co if (file == "" || !FileAccess::exists(file)) { continue; } - Ref<Script> scr = ResourceLoader::load(file); - if (scr.is_valid()) { - edit(scr); + + if (!ResourceLoader::exists(file, "Script") && !textfile_extensions.has(file.get_extension())) { + continue; + } + + RES res = open_file(file); + if (res.is_valid()) { if (tab_container->get_child_count() > num_tabs_before) { tab_container->move_child(tab_container->get_child(tab_container->get_child_count() - 1), new_index); num_tabs_before = tab_container->get_child_count(); @@ -2750,6 +2916,29 @@ void ScriptEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Co } } +void ScriptEditor::input(const Ref<InputEvent> &p_event) { + // This is implemented in `input()` rather than `unhandled_input()` to allow + // the shortcut to be used regardless of the click location. + // This feature can be disabled to avoid interfering with other uses of the additional + // mouse buttons, such as push-to-talk in a VoIP program. + if (EDITOR_GET("interface/editor/mouse_extra_buttons_navigate_history")) { + const Ref<InputEventMouseButton> mb = p_event; + + // Navigate the script history using additional mouse buttons present on some mice. + // This must be hardcoded as the editor shortcuts dialog doesn't allow assigning + // more than one shortcut per action. + if (mb.is_valid() && mb->is_pressed() && is_visible_in_tree()) { + if (mb->get_button_index() == MOUSE_BUTTON_XBUTTON1) { + _history_back(); + } + + if (mb->get_button_index() == MOUSE_BUTTON_XBUTTON2) { + _history_forward(); + } + } + } +} + void ScriptEditor::unhandled_key_input(const Ref<InputEvent> &p_event) { ERR_FAIL_COND(p_event.is_null()); @@ -2861,6 +3050,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { restoring_layout = true; + Set<String> loaded_scripts; List<String> extensions; ResourceLoader::get_recognized_extensions_for_type("Script", &extensions); @@ -2873,8 +3063,12 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { } if (!FileAccess::exists(path)) { + if (script_editor_cache->has_section(path)) { + script_editor_cache->erase_section(path); + } continue; } + loaded_scripts.insert(path); if (extensions.find(path.get_extension())) { Ref<Script> scr = ResourceLoader::load(path); @@ -2919,6 +3113,26 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) { script_split->set_split_offset(p_layout->get_value("ScriptEditor", "split_offset")); } + // Remove any deleted editors that have been removed between launches. + // and if a Script, register breakpoints with the debugger. + List<String> cached_editors; + script_editor_cache->get_sections(&cached_editors); + for (const String &E : cached_editors) { + if (loaded_scripts.has(E)) { + continue; + } + + if (!FileAccess::exists(E)) { + script_editor_cache->erase_section(E); + continue; + } + + Array breakpoints = _get_cached_breakpoints_for_script(E); + for (int i = 0; i < breakpoints.size(); i++) { + EditorDebuggerNode::get_singleton()->set_breakpoint(E, (int)breakpoints[i] + 1, true); + } + } + restoring_layout = false; _update_script_names(); @@ -2936,11 +3150,8 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { continue; } - Dictionary script_info; - script_info["path"] = path; - script_info["state"] = se->get_edit_state(); - - scripts.push_back(script_info); + _save_editor_state(se); + scripts.push_back(path); } EditorHelp *eh = Object::cast_to<EditorHelp>(tab_container->get_child(i)); @@ -2953,6 +3164,9 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) { p_layout->set_value("ScriptEditor", "open_scripts", scripts); p_layout->set_value("ScriptEditor", "open_help", helps); p_layout->set_value("ScriptEditor", "split_offset", script_split->get_split_offset()); + + // Save the cache. + script_editor_cache->save(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); } void ScriptEditor::_help_class_open(const String &p_class) { @@ -3294,7 +3508,6 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method("_update_script_connections", &ScriptEditor::_update_script_connections); ClassDB::bind_method("_help_class_open", &ScriptEditor::_help_class_open); ClassDB::bind_method("_live_auto_reload_running_scripts", &ScriptEditor::_live_auto_reload_running_scripts); - ClassDB::bind_method("_update_members_overview", &ScriptEditor::_update_members_overview); ClassDB::bind_method("_update_recent_scripts", &ScriptEditor::_update_recent_scripts); @@ -3320,6 +3533,9 @@ void ScriptEditor::_bind_methods() { ScriptEditor::ScriptEditor(EditorNode *p_editor) { current_theme = ""; + script_editor_cache.instantiate(); + script_editor_cache->load(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg")); + completion_cache = memnew(EditorScriptCodeCompletionCache); restoring_layout = false; waiting_update_names = false; @@ -3427,9 +3643,11 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { ED_SHORTCUT("script_editor/window_sort", TTR("Sort")); ED_SHORTCUT("script_editor/window_move_up", TTR("Move Up"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_UP); ED_SHORTCUT("script_editor/window_move_down", TTR("Move Down"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_DOWN); - ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_PERIOD); // these should be KEY_GREATER and KEY_LESS but those don't work + // FIXME: These should be `KEY_GREATER` and `KEY_LESS` but those don't work. + ED_SHORTCUT("script_editor/next_script", TTR("Next Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_PERIOD); ED_SHORTCUT("script_editor/prev_script", TTR("Previous Script"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_COMMA); - set_process_unhandled_key_input(true); + set_process_input(true); + set_process_unhandled_input(true); file_menu = memnew(MenuButton); file_menu->set_text(TTR("File")); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index 7620605570..8caebc1c8c 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -366,6 +366,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); + bool open_textfile_after_create = true; bool trim_trailing_whitespace_on_save; bool use_space_indentation; bool convert_indent_on_save; @@ -381,13 +382,17 @@ class ScriptEditor : public PanelContainer { void _script_created(Ref<Script> p_script); void _set_breakpoint(REF p_scrpt, int p_line, bool p_enabled); void _clear_breakpoints(); + Array _get_cached_breakpoints_for_script(const String &p_path) const; ScriptEditorBase *_get_current_editor() const; Array _get_open_script_editors() const; + Ref<ConfigFile> script_editor_cache; + void _save_editor_state(ScriptEditorBase *p_editor); void _save_layout(); void _editor_settings_changed(); void _filesystem_changed(); + void _files_moved(const String &p_old_file, const String &p_new_file); void _file_removed(const String &p_file); void _autosave_scripts(); void _update_autosave_timer(); @@ -418,6 +423,7 @@ class ScriptEditor : public PanelContainer { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + virtual void input(const Ref<InputEvent> &p_event) override; virtual void unhandled_key_input(const Ref<InputEvent> &p_event) override; void _script_list_gui_input(const Ref<InputEvent> &ev); @@ -447,7 +453,8 @@ class ScriptEditor : public PanelContainer { Ref<Script> _get_current_script(); Array _get_open_scripts() const; - Ref<TextFile> _load_text_file(const String &p_path, Error *r_error); + Set<String> textfile_extensions; + Ref<TextFile> _load_text_file(const String &p_path, Error *r_error) const; Error _save_text_file(Ref<TextFile> p_text_file, const String &p_path); void _on_find_in_files_requested(String text); @@ -471,6 +478,8 @@ public: bool is_scripts_panel_toggled(); void apply_scripts() const; void open_script_create_dialog(const String &p_base_name, const String &p_base_path); + void open_text_file_create_dialog(const String &p_base_path, const String &p_base_name = ""); + RES open_file(const String &p_file); void ensure_select_current(); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index 2b1ca068ee..2c02389db2 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -433,10 +433,12 @@ void ScriptTextEditor::_validate_script() { int warning_nb = warnings.size(); warnings_panel->clear(); + bool has_connections_table = false; // Add missing connections. if (GLOBAL_GET("debug/gdscript/warnings/enable").booleanize()) { Node *base = get_tree()->get_edited_scene_root(); if (base && missing_connections.size() > 0) { + has_connections_table = true; warnings_panel->push_table(1); for (const Connection &connection : missing_connections) { String base_path = base->get_name(); @@ -458,6 +460,10 @@ void ScriptTextEditor::_validate_script() { code_editor->set_error_count(errors.size()); code_editor->set_warning_count(warning_nb); + if (has_connections_table) { + warnings_panel->add_newline(); + } + // Add script warnings. warnings_panel->push_table(3); for (const ScriptLanguage::Warning &w : warnings) { @@ -1335,8 +1341,6 @@ void ScriptTextEditor::_bind_methods() { ClassDB::bind_method("_get_drag_data_fw", &ScriptTextEditor::get_drag_data_fw); ClassDB::bind_method("_can_drop_data_fw", &ScriptTextEditor::can_drop_data_fw); ClassDB::bind_method("_drop_data_fw", &ScriptTextEditor::drop_data_fw); - - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &ScriptTextEditor::add_syntax_highlighter); } Control *ScriptTextEditor::get_edit_menu() { @@ -1701,7 +1705,6 @@ void ScriptTextEditor::_enable_code_editor() { code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel)); code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script)); code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings)); - code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); code_editor->get_text_editor()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol)); code_editor->get_text_editor()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol)); code_editor->get_text_editor()->connect("gutter_added", callable_mp(this, &ScriptTextEditor::_update_gutter_indexes)); @@ -1841,6 +1844,7 @@ ScriptTextEditor::ScriptTextEditor() { code_editor->get_text_editor()->set_draw_breakpoints_gutter(true); code_editor->get_text_editor()->set_draw_executing_lines_gutter(true); + code_editor->get_text_editor()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled)); connection_gutter = 1; code_editor->get_text_editor()->add_gutter(connection_gutter); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index 22ca5592bd..a88e24c0d0 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -131,9 +131,9 @@ void ShaderTextEditor::_load_theme_settings() { List<String> built_ins; if (shader.is_valid()) { - for (const Map<StringName, ShaderLanguage::FunctionInfo>::Element *E = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())).front(); E; E = E->next()) { - for (const Map<StringName, ShaderLanguage::BuiltInInfo>::Element *F = E->get().built_ins.front(); F; F = F->next()) { - built_ins.push_back(F->key()); + for (const KeyValue<StringName, ShaderLanguage::FunctionInfo> &E : ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode()))) { + for (const KeyValue<StringName, ShaderLanguage::BuiltInInfo> &F : E.value.built_ins) { + built_ins.push_back(F.key); } } diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 309821b3dc..708eaf2c46 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -37,298 +37,251 @@ #include "editor/plugins/animation_player_editor_plugin.h" #include "node_3d_editor_plugin.h" #include "scene/3d/collision_shape_3d.h" +#include "scene/3d/joint_3d.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/physics_body_3d.h" -#include "scene/3d/physics_joint_3d.h" #include "scene/resources/capsule_shape_3d.h" #include "scene/resources/sphere_shape_3d.h" +#include "scene/resources/surface_tool.h" void BoneTransformEditor::create_editors() { const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); section = memnew(EditorInspectorSection); section->setup("trf_properties", label, this, section_color, true); + section->unfold(); add_child(section); - key_button = memnew(Button); - key_button->set_text(TTR("Key Transform")); - key_button->set_visible(keyable); - key_button->set_icon(get_theme_icon(SNAME("Key"), SNAME("EditorIcons"))); - key_button->set_flat(true); - section->get_vbox()->add_child(key_button); - - enabled_checkbox = memnew(CheckBox(TTR("Pose Enabled"))); - enabled_checkbox->set_flat(true); - enabled_checkbox->set_visible(toggle_enabled); + enabled_checkbox = memnew(EditorPropertyCheck()); + enabled_checkbox->set_label("Pose Enabled"); + enabled_checkbox->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(enabled_checkbox); - // Translation property - translation_property = memnew(EditorPropertyVector3()); - translation_property->setup(-10000, 10000, 0.001f, true); - translation_property->set_label("Translation"); - translation_property->set_use_folding(true); - translation_property->set_read_only(false); - translation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); - section->get_vbox()->add_child(translation_property); - - // Rotation property - rotation_property = memnew(EditorPropertyVector3()); + // Position property. + position_property = memnew(EditorPropertyVector3()); + position_property->setup(-10000, 10000, 0.001f, true); + position_property->set_label("Position"); + position_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); + section->get_vbox()->add_child(position_property); + + // Rotation property. + rotation_property = memnew(EditorPropertyQuaternion()); rotation_property->setup(-10000, 10000, 0.001f, true); - rotation_property->set_label("Rotation Degrees"); - rotation_property->set_use_folding(true); - rotation_property->set_read_only(false); - rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + rotation_property->set_label("Rotation"); + rotation_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(rotation_property); - // Scale property + // Scale property. scale_property = memnew(EditorPropertyVector3()); scale_property->setup(-10000, 10000, 0.001f, true); scale_property->set_label("Scale"); - scale_property->set_use_folding(true); - scale_property->set_read_only(false); - scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_vector3)); + scale_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed)); section->get_vbox()->add_child(scale_property); - // Transform/Matrix section - transform_section = memnew(EditorInspectorSection); - transform_section->setup("trf_properties_transform", "Matrix", this, section_color, true); - section->get_vbox()->add_child(transform_section); + // Transform/Matrix section. + rest_section = memnew(EditorInspectorSection); + rest_section->setup("trf_properties_transform", "Rest", this, section_color, true); + section->get_vbox()->add_child(rest_section); - // Transform/Matrix property - transform_property = memnew(EditorPropertyTransform3D()); - transform_property->setup(-10000, 10000, 0.001f, true); - transform_property->set_label("Transform"); - transform_property->set_use_folding(true); - transform_property->set_read_only(false); - transform_property->connect("property_changed", callable_mp(this, &BoneTransformEditor::_value_changed_transform)); - transform_section->get_vbox()->add_child(transform_property); + // Transform/Matrix property. + rest_matrix = memnew(EditorPropertyTransform3D()); + rest_matrix->setup(-10000, 10000, 0.001f, true); + rest_matrix->set_label("Transform"); + rest_section->get_vbox()->add_child(rest_matrix); } void BoneTransformEditor::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { create_editors(); - key_button->connect("pressed", callable_mp(this, &BoneTransformEditor::_key_button_pressed)); - enabled_checkbox->connect("toggled", callable_mp(this, &BoneTransformEditor::_checkbox_toggled)); - [[fallthrough]]; - } - case NOTIFICATION_SORT_CHILDREN: { - const Ref<Font> font = get_theme_font(SNAME("font"), SNAME("Tree")); - int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); - - Point2 buffer; - buffer.x += get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - buffer.y += font->get_height(font_size); - buffer.y += get_theme_constant(SNAME("vseparation"), SNAME("Tree")); - - const float vector_height = translation_property->get_size().y; - const float transform_height = transform_property->get_size().y; - const float button_height = key_button->get_size().y; - - const float width = get_size().x - get_theme_constant(SNAME("inspector_margin"), SNAME("Editor")); - Vector<Rect2> input_rects; - if (keyable && section->get_vbox()->is_visible()) { - input_rects.push_back(Rect2(key_button->get_position() + buffer, Size2(width, button_height))); - } else { - input_rects.push_back(Rect2(0, 0, 0, 0)); - } - - if (section->get_vbox()->is_visible()) { - input_rects.push_back(Rect2(translation_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(rotation_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(scale_property->get_position() + buffer, Size2(width, vector_height))); - input_rects.push_back(Rect2(transform_property->get_position() + buffer, Size2(width, transform_height))); - } else { - const int32_t start = input_rects.size(); - const int32_t empty_input_rect_elements = 4; - const int32_t end = start + empty_input_rect_elements; - for (int i = start; i < end; ++i) { - input_rects.push_back(Rect2(0, 0, 0, 0)); - } - } - - for (int32_t i = 0; i < input_rects.size(); i++) { - background_rects[i] = input_rects[i]; - } - - update(); - break; - } - case NOTIFICATION_DRAW: { - const Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); - - for (int i = 0; i < 5; ++i) { - draw_rect(background_rects[i], dark_color); - } - break; } } } -void BoneTransformEditor::_value_changed(const double p_value) { +void BoneTransformEditor::_value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing) { if (updating) { return; } - - Transform3D tform = compute_transform_from_vector3s(); - _change_transform(tform); + if (skeleton) { + undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + undo_redo->add_undo_property(skeleton, p_property, skeleton->get(p_property)); + undo_redo->add_do_property(skeleton, p_property, p_value); + undo_redo->commit_action(); + } } -void BoneTransformEditor::_value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) { - return; - } - Transform3D tform = compute_transform_from_vector3s(); - _change_transform(tform); +BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : + skeleton(p_skeleton) { + undo_redo = EditorNode::get_undo_redo(); } -Transform3D BoneTransformEditor::compute_transform_from_vector3s() const { - // Convert rotation from degrees to radians. - Vector3 prop_rotation = rotation_property->get_vector(); - prop_rotation.x = Math::deg2rad(prop_rotation.x); - prop_rotation.y = Math::deg2rad(prop_rotation.y); - prop_rotation.z = Math::deg2rad(prop_rotation.z); +void BoneTransformEditor::set_target(const String &p_prop) { + enabled_checkbox->set_object_and_property(skeleton, p_prop + "enabled"); + enabled_checkbox->update_property(); - return Transform3D( - Basis(prop_rotation, scale_property->get_vector()), - translation_property->get_vector()); -} + position_property->set_object_and_property(skeleton, p_prop + "position"); + position_property->update_property(); -void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean) { - if (updating) { - return; - } - _change_transform(p_transform); -} + rotation_property->set_object_and_property(skeleton, p_prop + "rotation"); + rotation_property->update_property(); -void BoneTransformEditor::_change_transform(Transform3D p_new_transform) { - if (property.get_slicec('/', 0) == "bones" && property.get_slicec('/', 2) == "custom_pose") { - undo_redo->create_action(TTR("Set Custom Bone Pose Transform"), UndoRedo::MERGE_ENDS); - undo_redo->add_undo_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), skeleton->get_bone_custom_pose(property.get_slicec('/', 1).to_int())); - undo_redo->add_do_method(skeleton, "set_bone_custom_pose", property.get_slicec('/', 1).to_int(), p_new_transform); - undo_redo->commit_action(); - } else if (property.get_slicec('/', 0) == "bones") { - undo_redo->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); - undo_redo->add_undo_property(skeleton, property, skeleton->get(property)); - undo_redo->add_do_property(skeleton, property, p_new_transform); - undo_redo->commit_action(); - } -} + scale_property->set_object_and_property(skeleton, p_prop + "scale"); + scale_property->update_property(); -void BoneTransformEditor::update_enabled_checkbox() { - if (enabled_checkbox) { - const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; - const bool is_enabled = skeleton->get(path); - enabled_checkbox->set_pressed(is_enabled); - } + rest_matrix->set_object_and_property(skeleton, p_prop + "rest"); + rest_matrix->update_property(); } void BoneTransformEditor::_update_properties() { - if (updating) { + if (!skeleton) { return; } - - if (skeleton == nullptr) { - return; + int selected = Skeleton3DEditor::get_singleton()->get_selected_bone(); + List<PropertyInfo> props; + skeleton->get_property_list(&props); + for (const PropertyInfo &E : props) { + PackedStringArray spr = E.name.split("/"); + if (spr.size() == 3 && spr[0] == "bones") { + if (spr[1].to_int() == selected) { + if (spr[2] == "enabled") { + enabled_checkbox->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + enabled_checkbox->update_property(); + enabled_checkbox->update(); + } + if (spr[2] == "position") { + position_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + position_property->update_property(); + position_property->update(); + } + if (spr[2] == "rotation") { + rotation_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rotation_property->update_property(); + rotation_property->update(); + } + if (spr[2] == "scale") { + scale_property->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + scale_property->update_property(); + scale_property->update(); + } + if (spr[2] == "rest") { + rest_matrix->set_read_only(E.usage & PROPERTY_USAGE_READ_ONLY); + rest_matrix->update_property(); + rest_matrix->update(); + } + } + } } +} - updating = true; +Skeleton3DEditor *Skeleton3DEditor::singleton = nullptr; - Transform3D tform = skeleton->get(property); - _update_transform_properties(tform); -} +void Skeleton3DEditor::set_keyable(const bool p_keyable) { + keyable = p_keyable; + skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS, !p_keyable); + skeleton_options->get_popup()->set_item_disabled(SKELETON_OPTION_INSERT_KEYS_EXISTED, !p_keyable); +}; -void BoneTransformEditor::_update_custom_pose_properties() { - if (updating) { - return; - } +void Skeleton3DEditor::set_rest_options_enabled(const bool p_rest_options_enabled) { + rest_options->get_popup()->set_item_disabled(REST_OPTION_POSE_TO_REST, !p_rest_options_enabled); +}; - if (skeleton == nullptr) { +void Skeleton3DEditor::_on_click_skeleton_option(int p_skeleton_option) { + if (!skeleton) { return; } - updating = true; - - Transform3D tform = skeleton->get_bone_custom_pose(property.to_int()); - _update_transform_properties(tform); -} - -void BoneTransformEditor::_update_transform_properties(Transform3D tform) { - Basis rotation_basis = tform.get_basis(); - Vector3 rotation_radians = rotation_basis.get_rotation_euler(); - Vector3 rotation_degrees = Vector3(Math::rad2deg(rotation_radians.x), Math::rad2deg(rotation_radians.y), Math::rad2deg(rotation_radians.z)); - Vector3 translation = tform.get_origin(); - Vector3 scale = tform.basis.get_scale(); - - translation_property->update_using_vector(translation); - rotation_property->update_using_vector(rotation_degrees); - scale_property->update_using_vector(scale); - transform_property->update_using_transform(tform); - - update_enabled_checkbox(); - updating = false; -} - -BoneTransformEditor::BoneTransformEditor(Skeleton3D *p_skeleton) : - skeleton(p_skeleton) { - undo_redo = EditorNode::get_undo_redo(); + switch (p_skeleton_option) { + case SKELETON_OPTION_CREATE_PHYSICAL_SKELETON: { + create_physical_skeleton(); + break; + } + case SKELETON_OPTION_INIT_POSE: { + init_pose(); + break; + } + case SKELETON_OPTION_INSERT_KEYS: { + insert_keys(true); + break; + } + case SKELETON_OPTION_INSERT_KEYS_EXISTED: { + insert_keys(false); + break; + } + } } -void BoneTransformEditor::set_target(const String &p_prop) { - property = p_prop; -} +void Skeleton3DEditor::_on_click_rest_option(int p_rest_option) { + if (!skeleton) { + return; + } -void BoneTransformEditor::set_keyable(const bool p_keyable) { - keyable = p_keyable; - if (key_button) { - key_button->set_visible(p_keyable); + switch (p_rest_option) { + case REST_OPTION_POSE_TO_REST: { + pose_to_rest(); + break; + } } } -void BoneTransformEditor::set_toggle_enabled(const bool p_enabled) { - toggle_enabled = p_enabled; - if (enabled_checkbox) { - enabled_checkbox->set_visible(p_enabled); +void Skeleton3DEditor::init_pose() { + const int bone_len = skeleton->get_bone_count(); + if (!bone_len) { + return; + } + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Transform"), UndoRedo::MERGE_ENDS); + for (int i = 0; i < bone_len; i++) { + Transform3D rest = skeleton->get_bone_rest(i); + ur->add_do_method(skeleton, "set_bone_pose_position", i, rest.origin); + ur->add_do_method(skeleton, "set_bone_pose_rotation", i, rest.basis.get_rotation_quaternion()); + ur->add_do_method(skeleton, "set_bone_pose_scale", i, rest.basis.get_scale()); + ur->add_undo_method(skeleton, "set_bone_pose_position", i, skeleton->get_bone_pose_position(i)); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", i, skeleton->get_bone_pose_rotation(i)); + ur->add_undo_method(skeleton, "set_bone_pose_scale", i, skeleton->get_bone_pose_scale(i)); } + ur->commit_action(); } -void BoneTransformEditor::_key_button_pressed() { - if (skeleton == nullptr) { +void Skeleton3DEditor::insert_keys(bool p_all_bones) { + if (!skeleton) { return; } - const BoneId bone_id = property.get_slicec('/', 1).to_int(); - const String name = skeleton->get_bone_name(bone_id); + int bone_len = skeleton->get_bone_count(); + Node *root = EditorNode::get_singleton()->get_tree()->get_root(); + String path = root->get_path_to(skeleton); - if (name.is_empty()) { - return; - } + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + te->make_insert_queue(); + for (int i = 0; i < bone_len; i++) { + const String name = skeleton->get_bone_name(i); - // Need to normalize the basis before you key it - Transform3D tform = compute_transform_from_vector3s(); - tform.orthonormalize(); - AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); -} + if (name.is_empty()) { + continue; + } + + if (!p_all_bones && !te->has_transform_track(skeleton, name)) { + continue; + } -void BoneTransformEditor::_checkbox_toggled(const bool p_toggled) { - if (enabled_checkbox) { - const String path = "bones/" + property.get_slicec('/', 1) + "/enabled"; - skeleton->set(path, p_toggled); + Transform3D tform = skeleton->get_bone_pose(i); + te->insert_transform_key(skeleton, name, tform); } + te->commit_insert_queue(); } -void Skeleton3DEditor::_on_click_option(int p_option) { +void Skeleton3DEditor::pose_to_rest() { if (!skeleton) { return; } - switch (p_option) { - case MENU_OPTION_CREATE_PHYSICAL_SKELETON: { - create_physical_skeleton(); - break; - } - } + // Todo: Do method with multiple bone selection. + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Rest"), UndoRedo::MERGE_ENDS); + ur->add_do_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_pose(selected_bone)); + ur->add_undo_method(skeleton, "set_bone_rest", selected_bone, skeleton->get_bone_rest(selected_bone)); + ur->commit_action(); } void Skeleton3DEditor::create_physical_skeleton() { @@ -356,7 +309,7 @@ void Skeleton3DEditor::create_physical_skeleton() { bones_infos.write[bone_id].relative_rest = bones_infos[parent].relative_rest * skeleton->get_bone_rest(bone_id); - /// create physical bone on parent + // Create physical bone on parent. if (!bones_infos[parent].physical_bone) { bones_infos.write[parent].physical_bone = create_physical_bone(parent, bone_id, bones_infos); @@ -370,7 +323,7 @@ void Skeleton3DEditor::create_physical_skeleton() { bones_infos[parent].physical_bone->set_owner(owner); bones_infos[parent].physical_bone->get_child(0)->set_owner(owner); // set shape owner - /// Create joint between parent of parent + // Create joint between parent of parent. if (-1 != parent_parent) { bones_infos[parent].physical_bone->set_joint_type(PhysicalBone3D::JOINT_TYPE_PIN); } @@ -483,7 +436,7 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se ERR_FAIL_NULL(skeleton); UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); ur->create_action(TTR("Set Bone Parentage")); - // If the target is a child of ourselves, we move only *us* and not our children + // If the target is a child of ourselves, we move only *us* and not our children. if (skeleton->is_bone_parent_of(p_target_boneidx, p_selected_boneidx)) { const BoneId parent_idx = skeleton->get_bone_parent(p_selected_boneidx); const int bone_count = skeleton->get_bone_count(); @@ -505,43 +458,38 @@ void Skeleton3DEditor::move_skeleton_bone(NodePath p_skeleton_path, int32_t p_se void Skeleton3DEditor::_joint_tree_selection_changed() { TreeItem *selected = joint_tree->get_selected(); - const String path = selected->get_metadata(0); - - if (path.begins_with("bones/")) { - const int b_idx = path.get_slicec('/', 1).to_int(); - const String bone_path = "bones/" + itos(b_idx) + "/"; + if (selected) { + const String path = selected->get_metadata(0); - pose_editor->set_target(bone_path + "pose"); - rest_editor->set_target(bone_path + "rest"); - custom_pose_editor->set_target(bone_path + "custom_pose"); + if (path.begins_with("bones/")) { + const int b_idx = path.get_slicec('/', 1).to_int(); + const String bone_path = "bones/" + itos(b_idx) + "/"; - _update_properties(); - - pose_editor->set_visible(true); - rest_editor->set_visible(true); - custom_pose_editor->set_visible(true); + pose_editor->set_target(bone_path); + selected_bone = b_idx; + } } + pose_editor->set_visible(selected); + set_rest_options_enabled(selected); + _update_properties(); + _update_gizmo_visible(); } +// May be not used with single select mode. void Skeleton3DEditor::_joint_tree_rmb_select(const Vector2 &p_pos) { } void Skeleton3DEditor::_update_properties() { - if (rest_editor) { - rest_editor->_update_properties(); - } if (pose_editor) { pose_editor->_update_properties(); } - if (custom_pose_editor) { - custom_pose_editor->_update_custom_pose_properties(); - } + Node3DEditor::get_singleton()->update_transform_gizmo(); } void Skeleton3DEditor::update_joint_tree() { joint_tree->clear(); - if (skeleton == nullptr) { + if (!skeleton) { return; } @@ -569,7 +517,7 @@ void Skeleton3DEditor::update_joint_tree() { joint_item->set_selectable(0, true); joint_item->set_metadata(0, "bones/" + itos(current_bone_idx)); - // Add the bone's children to the list of bones to be processed + // Add the bone's children to the list of bones to be processed. Vector<int> current_bone_child_bones = skeleton->get_bone_children(current_bone_idx); int child_bone_size = current_bone_child_bones.size(); for (int i = 0; i < child_bone_size; i++) { @@ -587,16 +535,56 @@ void Skeleton3DEditor::create_editors() { set_focus_mode(FOCUS_ALL); - // Create Top Menu Bar - options = memnew(MenuButton); - Node3DEditor::get_singleton()->add_control_to_menu_panel(options); + Node3DEditor *ne = Node3DEditor::get_singleton(); + AnimationTrackEditor *te = AnimationPlayerEditor::get_singleton()->get_track_editor(); + + // Create Top Menu Bar. + separator = memnew(VSeparator); + ne->add_control_to_menu_panel(separator); + + // Create Skeleton Option in Top Menu Bar. + skeleton_options = memnew(MenuButton); + ne->add_control_to_menu_panel(skeleton_options); + + skeleton_options->set_text(TTR("Skeleton3D")); + skeleton_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + + skeleton_options->get_popup()->add_item(TTR("Init pose"), SKELETON_OPTION_INIT_POSE); + skeleton_options->get_popup()->add_item(TTR("Insert key of all bone poses"), SKELETON_OPTION_INSERT_KEYS); + skeleton_options->get_popup()->add_item(TTR("Insert key of bone poses already exist track"), SKELETON_OPTION_INSERT_KEYS_EXISTED); + skeleton_options->get_popup()->add_item(TTR("Create physical skeleton"), SKELETON_OPTION_CREATE_PHYSICAL_SKELETON); + + skeleton_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_skeleton_option)); + + // Create Rest Option in Top Menu Bar. + rest_options = memnew(MenuButton); + ne->add_control_to_menu_panel(rest_options); + + rest_options->set_text(TTR("Edit Rest")); + rest_options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("BoneAttachment3D"), SNAME("EditorIcons"))); + + rest_options->get_popup()->add_item(TTR("Apply current pose to rest"), REST_OPTION_POSE_TO_REST); + rest_options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_rest_option)); + set_rest_options_enabled(false); - options->set_text(TTR("Skeleton3D")); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Skeleton3D"), SNAME("EditorIcons"))); + Vector<Variant> button_binds; + button_binds.resize(1); - options->get_popup()->add_item(TTR("Create physical skeleton"), MENU_OPTION_CREATE_PHYSICAL_SKELETON); + edit_mode_button = memnew(Button); + ne->add_control_to_menu_panel(edit_mode_button); + edit_mode_button->set_tooltip(TTR("Edit Mode\nShow buttons on joints.")); + edit_mode_button->set_toggle_mode(true); + edit_mode_button->set_flat(true); + edit_mode_button->connect("toggled", callable_mp(this, &Skeleton3DEditor::edit_mode_toggled)); - options->get_popup()->connect("id_pressed", callable_mp(this, &Skeleton3DEditor::_on_click_option)); + edit_mode = false; + + set_keyable(te->has_keying()); + + if (skeleton) { + skeleton->add_child(handles_mesh_instance); + handles_mesh_instance->set_skeleton_path(NodePath("")); + } const Color section_color = get_theme_color(SNAME("prop_subsection"), SNAME("Editor")); @@ -612,7 +600,7 @@ void Skeleton3DEditor::create_editors() { joint_tree = memnew(Tree); joint_tree->set_columns(1); - joint_tree->set_focus_mode(Control::FocusMode::FOCUS_NONE); + joint_tree->set_focus_mode(Control::FOCUS_NONE); joint_tree->set_select_mode(Tree::SELECT_SINGLE); joint_tree->set_hide_root(true); joint_tree->set_v_size_flags(SIZE_EXPAND_FILL); @@ -622,37 +610,30 @@ void Skeleton3DEditor::create_editors() { s_con->add_child(joint_tree); pose_editor = memnew(BoneTransformEditor(skeleton)); - pose_editor->set_label(TTR("Bone Pose")); - pose_editor->set_keyable(AnimationPlayerEditor::singleton->get_track_editor()->has_keying()); - pose_editor->set_toggle_enabled(true); + pose_editor->set_label(TTR("Bone Transform")); pose_editor->set_visible(false); add_child(pose_editor); - - rest_editor = memnew(BoneTransformEditor(skeleton)); - rest_editor->set_label(TTR("Bone Rest")); - rest_editor->set_visible(false); - add_child(rest_editor); - - custom_pose_editor = memnew(BoneTransformEditor(skeleton)); - custom_pose_editor->set_label(TTR("Bone Custom Pose")); - custom_pose_editor->set_visible(false); - add_child(custom_pose_editor); } void Skeleton3DEditor::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_READY: { + edit_mode_button->set_icon(get_theme_icon("ToolBoneSelect", "EditorIcons")); + get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); + break; + } case NOTIFICATION_ENTER_TREE: { create_editors(); update_joint_tree(); update_editors(); - - get_tree()->connect("node_removed", callable_mp(this, &Skeleton3DEditor::_node_removed), Vector<Variant>(), Object::CONNECT_ONESHOT); joint_tree->connect("item_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_selection_changed)); joint_tree->connect("item_rmb_selected", callable_mp(this, &Skeleton3DEditor::_joint_tree_rmb_select)); #ifdef TOOLS_ENABLED + skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); skeleton->connect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); -#endif // TOOLS_ENABLED - + skeleton->connect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); + skeleton->connect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); +#endif break; } } @@ -661,7 +642,8 @@ void Skeleton3DEditor::_notification(int p_what) { void Skeleton3DEditor::_node_removed(Node *p_node) { if (skeleton && p_node == skeleton) { skeleton = nullptr; - options->hide(); + skeleton_options->hide(); + rest_options->hide(); } _update_properties(); @@ -672,23 +654,236 @@ void Skeleton3DEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_joint_tree_selection_changed"), &Skeleton3DEditor::_joint_tree_selection_changed); ClassDB::bind_method(D_METHOD("_joint_tree_rmb_select"), &Skeleton3DEditor::_joint_tree_rmb_select); ClassDB::bind_method(D_METHOD("_update_properties"), &Skeleton3DEditor::_update_properties); - ClassDB::bind_method(D_METHOD("_on_click_option"), &Skeleton3DEditor::_on_click_option); + ClassDB::bind_method(D_METHOD("_on_click_skeleton_option"), &Skeleton3DEditor::_on_click_skeleton_option); + ClassDB::bind_method(D_METHOD("_on_click_rest_option"), &Skeleton3DEditor::_on_click_rest_option); + + ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &Skeleton3DEditor::drop_data_fw); - ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &Skeleton3DEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Skeleton3DEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Skeleton3DEditor::drop_data_fw); ClassDB::bind_method(D_METHOD("move_skeleton_bone"), &Skeleton3DEditor::move_skeleton_bone); + + ClassDB::bind_method(D_METHOD("_draw_gizmo"), &Skeleton3DEditor::_draw_gizmo); +} + +void Skeleton3DEditor::edit_mode_toggled(const bool pressed) { + edit_mode = pressed; + _update_gizmo_visible(); } Skeleton3DEditor::Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *p_skeleton) : editor(p_editor), editor_plugin(e_plugin), skeleton(p_skeleton) { + singleton = this; + + // Handle. + handle_material = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + handle_shader = Ref<Shader>(memnew(Shader)); + handle_shader->set_code(R"( +// Skeleton 3D gizmo handle shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled, depth_draw_always; +uniform sampler2D texture_albedo : hint_albedo; +uniform float point_size : hint_range(0,128) = 32; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION.z = mix(POSITION.z, 0, 0.999); + POINT_SIZE = point_size; +} +void fragment() { + vec4 albedo_tex = texture(texture_albedo,POINT_COORD); + vec3 col = albedo_tex.rgb + COLOR.rgb; + col = vec3(min(col.r,1.0),min(col.g,1.0),min(col.b,1.0)); + ALBEDO = col; + if (albedo_tex.a < 0.5) { discard; } + ALPHA = albedo_tex.a; +} +)"); + handle_material->set_shader(handle_shader); + Ref<Texture2D> handle = editor->get_gui_base()->get_theme_icon("EditorBoneHandle", "EditorIcons"); + handle_material->set_shader_param("point_size", handle->get_width()); + handle_material->set_shader_param("texture_albedo", handle); + + handles_mesh_instance = memnew(MeshInstance3D); + handles_mesh_instance->set_cast_shadows_setting(GeometryInstance3D::SHADOW_CASTING_SETTING_OFF); + handles_mesh.instantiate(); + handles_mesh_instance->set_mesh(handles_mesh); +} + +void Skeleton3DEditor::update_bone_original() { + if (!skeleton) { + return; + } + if (skeleton->get_bone_count() == 0 || selected_bone == -1) { + return; + } + bone_original_position = skeleton->get_bone_pose_position(selected_bone); + bone_original_rotation = skeleton->get_bone_pose_rotation(selected_bone); + bone_original_scale = skeleton->get_bone_pose_scale(selected_bone); +} + +void Skeleton3DEditor::_hide_handles() { + handles_mesh_instance->hide(); +} + +void Skeleton3DEditor::_draw_gizmo() { + if (!skeleton) { + return; + } + + // If you call get_bone_global_pose() while drawing the surface, such as toggle rest mode, + // the skeleton update will be done first and + // the drawing surface will be interrupted once and an error will occur. + skeleton->force_update_all_dirty_bones(); + + // Handles. + if (edit_mode) { + _draw_handles(); + } else { + _hide_handles(); + } +} + +void Skeleton3DEditor::_draw_handles() { + handles_mesh_instance->show(); + + const int bone_len = skeleton->get_bone_count(); + handles_mesh->clear_surfaces(); + handles_mesh->surface_begin(Mesh::PRIMITIVE_POINTS); + + for (int i = 0; i < bone_len; i++) { + Color c; + if (i == selected_bone) { + c = Color(1, 1, 0); + } else { + c = Color(0.1, 0.25, 0.8); + } + Vector3 point = skeleton->get_bone_global_pose(i).origin; + handles_mesh->surface_set_color(c); + handles_mesh->surface_add_vertex(point); + } + handles_mesh->surface_end(); + handles_mesh->surface_set_material(0, handle_material); +} + +TreeItem *Skeleton3DEditor::_find(TreeItem *p_node, const NodePath &p_path) { + if (!p_node) { + return nullptr; + } + + NodePath np = p_node->get_metadata(0); + if (np == p_path) { + return p_node; + } + + TreeItem *children = p_node->get_first_child(); + while (children) { + TreeItem *n = _find(children, p_path); + if (n) { + return n; + } + children = children->get_next(); + } + + return nullptr; +} + +void Skeleton3DEditor::_subgizmo_selection_change() { + if (!skeleton) { + return; + } + + // Once validated by subgizmos_intersect_ray, but required if through inspector's bones tree. + if (!edit_mode) { + skeleton->clear_subgizmo_selection(); + return; + } + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + if (selected >= 0) { + Vector<Ref<Node3DGizmo>> gizmos = skeleton->get_gizmos(); + for (int i = 0; i < gizmos.size(); i++) { + Ref<EditorNode3DGizmo> gizmo = gizmos[i]; + if (!gizmo.is_valid()) { + continue; + } + Ref<Skeleton3DGizmoPlugin> plugin = gizmo->get_plugin(); + if (!plugin.is_valid()) { + continue; + } + skeleton->set_subgizmo_selection(gizmo, selected, skeleton->get_bone_global_pose(selected)); + break; + } + } else { + skeleton->clear_subgizmo_selection(); + } +} + +void Skeleton3DEditor::select_bone(int p_idx) { + if (p_idx >= 0) { + TreeItem *ti = _find(joint_tree->get_root(), "bones/" + itos(p_idx)); + if (ti) { + // Make visible when it's collapsed. + TreeItem *node = ti->get_parent(); + while (node && node != joint_tree->get_root()) { + node->set_collapsed(false); + node = node->get_parent(); + } + ti->select(0); + joint_tree->scroll_to_item(ti); + } + } else { + selected_bone = -1; + joint_tree->deselect_all(); + _joint_tree_selection_changed(); + } } Skeleton3DEditor::~Skeleton3DEditor() { - if (options) { - Node3DEditor::get_singleton()->remove_control_from_menu_panel(options); + if (skeleton) { +#ifdef TOOLS_ENABLED + skeleton->disconnect("show_rest_only_changed", callable_mp(this, &Skeleton3DEditor::_update_gizmo_visible)); + skeleton->disconnect("bone_enabled_changed", callable_mp(this, &Skeleton3DEditor::_bone_enabled_changed)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_draw_gizmo)); + skeleton->disconnect("pose_updated", callable_mp(this, &Skeleton3DEditor::_update_properties)); + skeleton->set_transform_gizmo_visible(true); +#endif + handles_mesh_instance->get_parent()->remove_child(handles_mesh_instance); + } + + handles_mesh_instance->queue_delete(); + + Node3DEditor *ne = Node3DEditor::get_singleton(); + + if (separator) { + ne->remove_control_from_menu_panel(separator); + memdelete(separator); + } + + if (skeleton_options) { + ne->remove_control_from_menu_panel(skeleton_options); + memdelete(skeleton_options); + } + + if (rest_options) { + ne->remove_control_from_menu_panel(rest_options); + memdelete(rest_options); + } + + if (edit_mode_button) { + ne->remove_control_from_menu_panel(edit_mode_button); + memdelete(edit_mode_button); } } @@ -700,16 +895,432 @@ void EditorInspectorPluginSkeleton::parse_begin(Object *p_object) { Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_object); ERR_FAIL_COND(!skeleton); - Skeleton3DEditor *skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); + skel_editor = memnew(Skeleton3DEditor(this, editor, skeleton)); add_custom_control(skel_editor); } Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { editor = p_node; - Ref<EditorInspectorPluginSkeleton> skeleton_plugin; - skeleton_plugin.instantiate(); + skeleton_plugin = memnew(EditorInspectorPluginSkeleton); skeleton_plugin->editor = editor; EditorInspector::add_inspector_plugin(skeleton_plugin); + + Ref<Skeleton3DGizmoPlugin> gizmo_plugin = Ref<Skeleton3DGizmoPlugin>(memnew(Skeleton3DGizmoPlugin)); + Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); +} + +EditorPlugin::AfterGUIInput Skeleton3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); + if (se->is_edit_mode()) { + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (ne->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + if (!ne->is_gizmo_visible()) { + return EditorPlugin::AFTER_GUI_INPUT_STOP; + } + } + if (mb->is_pressed()) { + se->update_bone_original(); + } + } + return EditorPlugin::AFTER_GUI_INPUT_DESELECT; + } + return EditorPlugin::AFTER_GUI_INPUT_PASS; +} + +bool Skeleton3DEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("Skeleton3D"); +} + +void Skeleton3DEditor::_bone_enabled_changed(const int p_bone_id) { + _update_gizmo_visible(); +} + +void Skeleton3DEditor::_update_gizmo_visible() { + _subgizmo_selection_change(); + if (edit_mode) { + if (selected_bone == -1) { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(false); +#endif + } else { +#ifdef TOOLS_ENABLED + if (skeleton->is_bone_enabled(selected_bone) && !skeleton->is_show_rest_only()) { + skeleton->set_transform_gizmo_visible(true); + } else { + skeleton->set_transform_gizmo_visible(false); + } +#endif + } + } else { +#ifdef TOOLS_ENABLED + skeleton->set_transform_gizmo_visible(true); +#endif + } + _draw_gizmo(); +} + +int Skeleton3DEditor::get_selected_bone() const { + return selected_bone; +} + +Skeleton3DGizmoPlugin::Skeleton3DGizmoPlugin() { + unselected_mat = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); + unselected_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + unselected_mat->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + unselected_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); + unselected_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); + + selected_mat = Ref<ShaderMaterial>(memnew(ShaderMaterial)); + selected_sh = Ref<Shader>(memnew(Shader)); + selected_sh->set_code(R"( +// Skeleton 3D gizmo bones shader. + +shader_type spatial; +render_mode unshaded, shadows_disabled; +void vertex() { + if (!OUTPUT_IS_SRGB) { + COLOR.rgb = mix( pow((COLOR.rgb + vec3(0.055)) * (1.0 / (1.0 + 0.055)), vec3(2.4)), COLOR.rgb* (1.0 / 12.92), lessThan(COLOR.rgb,vec3(0.04045)) ); + } + VERTEX = VERTEX; + POSITION=PROJECTION_MATRIX*INV_CAMERA_MATRIX*WORLD_MATRIX*vec4(VERTEX.xyz,1.0); + POSITION.z = mix(POSITION.z, 0, 0.998); +} +void fragment() { + ALBEDO = COLOR.rgb; + ALPHA = COLOR.a; +} +)"); + selected_mat->set_shader(selected_sh); + + // Regist properties in editor settings. + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/skeleton", Color(1, 0.8, 0.4)); + EDITOR_DEF("editors/3d_gizmos/gizmo_colors/selected_bone", Color(0.8, 0.3, 0.0)); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_axis_length", (float)0.1); + EDITOR_DEF("editors/3d_gizmos/gizmo_settings/bone_shape", 1); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/3d_gizmos/gizmo_settings/bone_shape", PROPERTY_HINT_ENUM, "Wire,Octahedron")); +} + +bool Skeleton3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Skeleton3D>(p_spatial) != nullptr; +} + +String Skeleton3DGizmoPlugin::get_gizmo_name() const { + return "Skeleton3D"; +} + +int Skeleton3DGizmoPlugin::get_priority() const { + return -1; +} + +int Skeleton3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, -1); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + + if (!se->is_edit_mode()) { + return -1; + } + + if (Node3DEditor::get_singleton()->get_tool_mode() != Node3DEditor::TOOL_MODE_SELECT) { + return -1; + } + + // Select bone. + real_t grab_threshold = 4 * EDSCALE; + Vector3 ray_from = p_camera->get_global_transform().origin; + Transform3D gt = skeleton->get_global_transform(); + int closest_idx = -1; + real_t closest_dist = 1e10; + const int bone_len = skeleton->get_bone_count(); + for (int i = 0; i < bone_len; i++) { + Vector3 joint_pos_3d = gt.xform(skeleton->get_bone_global_pose(i).origin); + Vector2 joint_pos_2d = p_camera->unproject_position(joint_pos_3d); + real_t dist_3d = ray_from.distance_to(joint_pos_3d); + real_t dist_2d = p_point.distance_to(joint_pos_2d); + if (dist_2d < grab_threshold && dist_3d < closest_dist) { + closest_dist = dist_3d; + closest_idx = i; + } + } + + if (closest_idx >= 0) { + WARN_PRINT("ray:"); + WARN_PRINT(itos(closest_idx)); + se->select_bone(closest_idx); + return closest_idx; + } + + se->select_bone(-1); + return -1; +} + +Transform3D Skeleton3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND_V(!skeleton, Transform3D()); + + return skeleton->get_bone_global_pose(p_id); +} + +void Skeleton3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + // Prepare for global to local. + Transform3D original_to_local = Transform3D(); + int parent_idx = skeleton->get_bone_parent(p_id); + if (parent_idx >= 0) { + original_to_local = original_to_local * skeleton->get_bone_global_pose(parent_idx); + } + Basis to_local = original_to_local.get_basis().inverse(); + + // Prepare transform. + Transform3D t = Transform3D(); + + // Basis. + t.basis = to_local * p_transform.get_basis(); + + // Origin. + Vector3 orig = Vector3(); + orig = skeleton->get_bone_pose(p_id).origin; + Vector3 sub = p_transform.origin - skeleton->get_bone_global_pose(p_id).origin; + t.origin = orig + to_local.xform(sub); + + // Apply transform. + skeleton->set_bone_pose_position(p_id, t.origin); + skeleton->set_bone_pose_rotation(p_id, t.basis.get_rotation_quaternion()); + skeleton->set_bone_pose_scale(p_id, t.basis.get_scale()); +} + +void Skeleton3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + ERR_FAIL_COND(!skeleton); + + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + Node3DEditor *ne = Node3DEditor::get_singleton(); + + UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); + ur->create_action(TTR("Set Bone Transform")); + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { + for (int i = 0; i < p_ids.size(); i++) { + ur->add_do_method(skeleton, "set_bone_pose_position", p_ids[i], skeleton->get_bone_pose_position(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_position", p_ids[i], se->get_bone_original_position()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || ne->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { + for (int i = 0; i < p_ids.size(); i++) { + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + } + } + if (ne->get_tool_mode() == Node3DEditor::TOOL_MODE_SCALE) { + for (int i = 0; i < p_ids.size(); i++) { + // If the axis is swapped by scaling, the rotation can be changed. + ur->add_do_method(skeleton, "set_bone_pose_rotation", p_ids[i], skeleton->get_bone_pose_rotation(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_rotation", p_ids[i], se->get_bone_original_rotation()); + ur->add_do_method(skeleton, "set_bone_pose_scale", p_ids[i], skeleton->get_bone_pose_scale(p_ids[i])); + ur->add_undo_method(skeleton, "set_bone_pose_scale", p_ids[i], se->get_bone_original_scale()); + } + } + ur->commit_action(); +} + +void Skeleton3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Skeleton3D *skeleton = Object::cast_to<Skeleton3D>(p_gizmo->get_spatial_node()); + p_gizmo->clear(); + + int selected = -1; + Skeleton3DEditor *se = Skeleton3DEditor::get_singleton(); + if (se) { + selected = se->get_selected_bone(); + } + + Color bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/skeleton"); + Color selected_bone_color = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_colors/selected_bone"); + real_t bone_axis_length = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_axis_length"); + int bone_shape = EditorSettings::get_singleton()->get("editors/3d_gizmos/gizmo_settings/bone_shape"); + + LocalVector<Color> axis_colors; + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_x_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_y_color"), SNAME("Editor"))); + axis_colors.push_back(Node3DEditor::get_singleton()->get_theme_color(SNAME("axis_z_color"), SNAME("Editor"))); + + Ref<SurfaceTool> surface_tool(memnew(SurfaceTool)); + surface_tool->begin(Mesh::PRIMITIVE_LINES); + + if (p_gizmo->is_selected()) { + surface_tool->set_material(selected_mat); + } else { + unselected_mat->set_albedo(bone_color); + surface_tool->set_material(unselected_mat); + } + + Vector<Transform3D> grests; + grests.resize(skeleton->get_bone_count()); + + LocalVector<int> bones; + LocalVector<float> weights; + bones.resize(4); + weights.resize(4); + for (int i = 0; i < 4; i++) { + bones[i] = 0; + weights[i] = 0; + } + weights[0] = 1; + + int current_bone_index = 0; + Vector<int> bones_to_process = skeleton->get_parentless_bones(); + + while (bones_to_process.size() > current_bone_index) { + int current_bone_idx = bones_to_process[current_bone_index]; + current_bone_index++; + + Color current_bone_color = (current_bone_idx == selected) ? selected_bone_color : bone_color; + + Vector<int> child_bones_vector; + child_bones_vector = skeleton->get_bone_children(current_bone_idx); + int child_bones_size = child_bones_vector.size(); + + // You have children but no parent, then you must be a root/parentless bone. + if (skeleton->get_bone_parent(current_bone_idx) < 0) { + grests.write[current_bone_idx] = skeleton->get_bone_rest(current_bone_idx); + } + + for (int i = 0; i < child_bones_size; i++) { + // Something wrong. + if (child_bones_vector[i] < 0) { + continue; + } + + int child_bone_idx = child_bones_vector[i]; + + grests.write[child_bone_idx] = grests[current_bone_idx] * skeleton->get_bone_rest(child_bone_idx); + + Vector3 v0 = grests[current_bone_idx].origin; + Vector3 v1 = grests[child_bone_idx].origin; + Vector3 d = (v1 - v0).normalized(); + real_t dist = v0.distance_to(v1); + + // Find closest axis. + int closest = -1; + real_t closest_d = 0.0; + for (int j = 0; j < 3; j++) { + real_t dp = Math::abs(grests[current_bone_idx].basis[j].normalized().dot(d)); + if (j == 0 || dp > closest_d) { + closest = j; + } + } + + // Draw bone. + switch (bone_shape) { + case 0: { // Wire shape. + surface_tool->set_color(current_bone_color); + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + } break; + + case 1: { // Octahedron shape. + Vector3 first; + Vector3 points[6]; + int point_idx = 0; + for (int j = 0; j < 3; j++) { + Vector3 axis; + if (first == Vector3()) { + axis = d.cross(d.cross(grests[current_bone_idx].basis[j])).normalized(); + first = axis; + } else { + axis = d.cross(first).normalized(); + } + + surface_tool->set_color(current_bone_color); + for (int k = 0; k < 2; k++) { + if (k == 1) { + axis = -axis; + } + Vector3 point = v0 + d * dist * 0.2; + point += axis * dist * 0.1; + + bones[0] = current_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(point); + bones[0] = child_bone_idx; + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + points[point_idx++] = point; + } + } + surface_tool->set_color(current_bone_color); + SWAP(points[1], points[2]); + bones[0] = current_bone_idx; + for (int j = 0; j < 6; j++) { + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(points[(j + 1) % 6]); + } + } break; + } + + // Axis as root of the bone. + for (int j = 0; j < 3; j++) { + bones[0] = current_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v0 + (grests[current_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + + // Axis at the end of the bone children. + if (i == child_bones_size - 1) { + for (int j = 0; j < 3; j++) { + bones[0] = child_bone_idx; + surface_tool->set_color(axis_colors[j]); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1); + surface_tool->set_bones(bones); + surface_tool->set_weights(weights); + surface_tool->add_vertex(v1 + (grests[child_bone_idx].basis.inverse())[j].normalized() * dist * bone_axis_length); + + if (j == closest) { + continue; + } + } + } + + // Add the bone's children to the list of bones to be processed. + bones_to_process.push_back(child_bones_vector[i]); + } + } + + Ref<ArrayMesh> m = surface_tool->commit(); + p_gizmo->add_mesh(m, Ref<Material>(), Transform3D(), skeleton->register_skin(skeleton->create_skin_from_rest_transforms())); } diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 9de52c6fa8..3b4dd362fb 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -33,7 +33,12 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/editor_properties.h" +#include "node_3d_editor_plugin.h" +#include "scene/3d/camera_3d.h" +#include "scene/3d/mesh_instance_3d.h" #include "scene/3d/skeleton_3d.h" +#include "scene/resources/immediate_mesh.h" class EditorInspectorPluginSkeleton; class Joint; @@ -41,29 +46,28 @@ class PhysicalBone3D; class Skeleton3DEditorPlugin; class Button; class CheckBox; -class EditorPropertyTransform3D; -class EditorPropertyVector3; class BoneTransformEditor : public VBoxContainer { GDCLASS(BoneTransformEditor, VBoxContainer); EditorInspectorSection *section = nullptr; - EditorPropertyVector3 *translation_property = nullptr; - EditorPropertyVector3 *rotation_property = nullptr; + EditorPropertyCheck *enabled_checkbox = nullptr; + EditorPropertyVector3 *position_property = nullptr; + EditorPropertyQuaternion *rotation_property = nullptr; EditorPropertyVector3 *scale_property = nullptr; - EditorInspectorSection *transform_section = nullptr; - EditorPropertyTransform3D *transform_property = nullptr; + + EditorInspectorSection *rest_section = nullptr; + EditorPropertyTransform3D *rest_matrix = nullptr; Rect2 background_rects[5]; Skeleton3D *skeleton; - String property; + // String property; UndoRedo *undo_redo; - Button *key_button = nullptr; - CheckBox *enabled_checkbox = nullptr; + // Button *key_button = nullptr; bool keyable = false; bool toggle_enabled = false; @@ -73,18 +77,7 @@ class BoneTransformEditor : public VBoxContainer { void create_editors(); - // Called when one of the EditorSpinSliders are changed. - void _value_changed(const double p_value); - // Called when the one of the EditorPropertyVector3 are updated. - void _value_changed_vector3(const String p_property_name, const Vector3 p_vector, const StringName p_edited_property_name, const bool p_boolean); - // Called when the transform_property is updated. - void _value_changed_transform(const String p_property_name, const Transform3D p_transform, const StringName p_edited_property_name, const bool p_boolean); - // Changes the transform to the given transform and updates the UI accordingly. - void _change_transform(Transform3D p_new_transform); - // Creates a Transform using the EditorPropertyVector3 properties. - Transform3D compute_transform_from_vector3s() const; - - void update_enabled_checkbox(); + void _value_changed(const String &p_property, Variant p_value, const String &p_name, bool p_changing); protected: void _notification(int p_what); @@ -92,28 +85,11 @@ protected: public: BoneTransformEditor(Skeleton3D *p_skeleton); - // Which transform target to modify + // Which transform target to modify. void set_target(const String &p_prop); void set_label(const String &p_label) { label = p_label; } void _update_properties(); - void _update_custom_pose_properties(); - void _update_transform_properties(Transform3D p_transform); - - // Can/cannot modify the spinner values for the Transform - void set_read_only(const bool p_read_only); - - // Transform can be keyed, whether or not to show the button - void set_keyable(const bool p_keyable); - - // Bone can be toggled enabled or disabled, whether or not to show the checkbox - void set_toggle_enabled(const bool p_enabled); - - // Key Transform Button pressed - void _key_button_pressed(); - - // Bone Enabled Checkbox toggled - void _checkbox_toggled(const bool p_toggled); }; class Skeleton3DEditor : public VBoxContainer { @@ -121,13 +97,20 @@ class Skeleton3DEditor : public VBoxContainer { friend class Skeleton3DEditorPlugin; - enum Menu { - MENU_OPTION_CREATE_PHYSICAL_SKELETON + enum SkeletonOption { + SKELETON_OPTION_INIT_POSE, + SKELETON_OPTION_INSERT_KEYS, + SKELETON_OPTION_INSERT_KEYS_EXISTED, + SKELETON_OPTION_CREATE_PHYSICAL_SKELETON + }; + + enum RestOption { + REST_OPTION_POSE_TO_REST }; struct BoneInfo { PhysicalBone3D *physical_bone = nullptr; - Transform3D relative_rest; // Relative to skeleton node + Transform3D relative_rest; // Relative to skeleton node. }; EditorNode *editor; @@ -138,15 +121,25 @@ class Skeleton3DEditor : public VBoxContainer { Tree *joint_tree = nullptr; BoneTransformEditor *rest_editor = nullptr; BoneTransformEditor *pose_editor = nullptr; - BoneTransformEditor *custom_pose_editor = nullptr; - MenuButton *options = nullptr; + VSeparator *separator; + MenuButton *skeleton_options = nullptr; + MenuButton *rest_options = nullptr; + Button *edit_mode_button; + + bool edit_mode = false; + EditorFileDialog *file_dialog = nullptr; - UndoRedo *undo_redo = nullptr; + bool keyable; - void _on_click_option(int p_option); + static Skeleton3DEditor *singleton; + + void _on_click_skeleton_option(int p_skeleton_option); + void _on_click_rest_option(int p_rest_option); void _file_selected(const String &p_file); + TreeItem *_find(TreeItem *p_node, const NodePath &p_path); + void edit_mode_toggled(const bool pressed); EditorFileDialog *file_export_lib = nullptr; @@ -155,6 +148,10 @@ class Skeleton3DEditor : public VBoxContainer { void create_editors(); + void init_pose(); + void insert_keys(bool p_all_bones); + void pose_to_rest(); + void create_physical_skeleton(); PhysicalBone3D *create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos); @@ -162,20 +159,57 @@ class Skeleton3DEditor : public VBoxContainer { bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + void set_keyable(const bool p_keyable); + void set_rest_options_enabled(const bool p_rest_options_enabled); + + // Handle. + MeshInstance3D *handles_mesh_instance; + Ref<ImmediateMesh> handles_mesh; + Ref<ShaderMaterial> handle_material; + Ref<Shader> handle_shader; + + Vector3 bone_original_position; + Quaternion bone_original_rotation; + Vector3 bone_original_scale; + + void _update_gizmo_visible(); + void _bone_enabled_changed(const int p_bone_id); + + void _hide_handles(); + + void _draw_gizmo(); + void _draw_handles(); + + void _joint_tree_selection_changed(); + void _joint_tree_rmb_select(const Vector2 &p_pos); + void _update_properties(); + + void _subgizmo_selection_change(); + + int selected_bone = -1; + protected: void _notification(int p_what); void _node_removed(Node *p_node); static void _bind_methods(); public: + static Skeleton3DEditor *get_singleton() { return singleton; } + + void select_bone(int p_idx); + + int get_selected_bone() const; + void move_skeleton_bone(NodePath p_skeleton_path, int32_t p_selected_boneidx, int32_t p_target_boneidx); Skeleton3D *get_skeleton() const { return skeleton; }; - void _joint_tree_selection_changed(); - void _joint_tree_rmb_select(const Vector2 &p_pos); + bool is_edit_mode() const { return edit_mode; } - void _update_properties(); + void update_bone_original(); + Vector3 get_bone_original_position() const { return bone_original_position; }; + Quaternion get_bone_original_rotation() const { return bone_original_rotation; }; + Vector3 get_bone_original_scale() const { return bone_original_scale; }; Skeleton3DEditor(EditorInspectorPluginSkeleton *e_plugin, EditorNode *p_editor, Skeleton3D *skeleton); ~Skeleton3DEditor(); @@ -186,6 +220,7 @@ class EditorInspectorPluginSkeleton : public EditorInspectorPlugin { friend class Skeleton3DEditorPlugin; + Skeleton3DEditor *skel_editor; EditorNode *editor; public: @@ -196,12 +231,40 @@ public: class Skeleton3DEditorPlugin : public EditorPlugin { GDCLASS(Skeleton3DEditorPlugin, EditorPlugin); + EditorInspectorPluginSkeleton *skeleton_plugin; EditorNode *editor; public: - Skeleton3DEditorPlugin(EditorNode *p_node); + virtual EditorPlugin::AfterGUIInput forward_spatial_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) override; + + bool has_main_screen() const override { return false; } + virtual bool handles(Object *p_object) const override; virtual String get_name() const override { return "Skeleton3D"; } + + Skeleton3DEditorPlugin(EditorNode *p_node); +}; + +class Skeleton3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(Skeleton3DGizmoPlugin, EditorNode3DGizmoPlugin); + + Ref<StandardMaterial3D> unselected_mat; + Ref<ShaderMaterial> selected_mat; + Ref<Shader> selected_sh; + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + + int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override; + Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override; + void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override; + void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) override; + + void redraw(EditorNode3DGizmo *p_gizmo) override; + + Skeleton3DGizmoPlugin(); }; #endif // SKELETON_3D_EDITOR_PLUGIN_H diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 06ba8a6168..1fc7eb98e0 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -407,10 +407,6 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) { code_editor->convert_case(p_case); } -void TextEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("add_syntax_highlighter", "highlighter"), &TextEditor::add_syntax_highlighter); -} - static ScriptEditorBase *create_editor(const RES &p_resource) { if (Object::cast_to<TextFile>(*p_resource)) { return memnew(TextEditor); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index 9308fec210..7404557f46 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -87,8 +87,6 @@ private: }; protected: - static void _bind_methods(); - void _edit_option(int p_op); void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position); void _text_edit_gui_input(const Ref<InputEvent> &ev); diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index 44db06bcfd..b9ec6bf5ab 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -58,6 +58,21 @@ void TexturePreview::_notification(int p_what) { } } +void TexturePreview::_update_metadata_label_text() { + Ref<Texture2D> texture = texture_display->get_texture(); + + String format; + if (Object::cast_to<ImageTexture>(*texture)) { + format = Image::get_format_name(Object::cast_to<ImageTexture>(*texture)->get_format()); + } else if (Object::cast_to<StreamTexture2D>(*texture)) { + format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*texture)->get_format()); + } else { + format = texture->get_class(); + } + + metadata_label->set_text(itos(texture->get_width()) + "x" + itos(texture->get_height()) + " " + format); +} + TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { checkerboard = memnew(TextureRect); checkerboard->set_stretch_mode(TextureRect::STRETCH_TILE); @@ -75,16 +90,8 @@ TexturePreview::TexturePreview(Ref<Texture2D> p_texture, bool p_show_metadata) { if (p_show_metadata) { metadata_label = memnew(Label); - String format; - if (Object::cast_to<ImageTexture>(*p_texture)) { - format = Image::get_format_name(Object::cast_to<ImageTexture>(*p_texture)->get_format()); - } else if (Object::cast_to<StreamTexture2D>(*p_texture)) { - format = Image::get_format_name(Object::cast_to<StreamTexture2D>(*p_texture)->get_format()); - } else { - format = p_texture->get_class(); - } - - metadata_label->set_text(itos(p_texture->get_width()) + "x" + itos(p_texture->get_height()) + " " + format); + _update_metadata_label_text(); + p_texture->connect("changed", callable_mp(this, &TexturePreview::_update_metadata_label_text)); // It's okay that these colors are static since the grid color is static too. metadata_label->add_theme_color_override("font_color", Color::named("white")); diff --git a/editor/plugins/texture_editor_plugin.h b/editor/plugins/texture_editor_plugin.h index 36a5513ea6..60349febd7 100644 --- a/editor/plugins/texture_editor_plugin.h +++ b/editor/plugins/texture_editor_plugin.h @@ -44,6 +44,8 @@ private: TextureRect *checkerboard = nullptr; Label *metadata_label = nullptr; + void _update_metadata_label_text(); + protected: void _notification(int p_what); diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index cfb2d63bc7..ce90d61616 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -50,7 +50,7 @@ void draw_margin_line(Control *edit_draw, Vector2 from, Vector2 to) { EditorNode::get_singleton()->get_theme_base()->get_theme_color(SNAME("mono_color"), SNAME("Editor")).inverted() * Color(1, 1, 1, 0.5), Math::round(2 * EDSCALE)); - while ((to - from).length_squared() > 200) { + while (from.distance_squared_to(to) > 200) { edit_draw->draw_line( from, from + line, @@ -674,8 +674,7 @@ void TextureRegionEditor::_zoom_on_position(float p_zoom, Point2 p_position) { draw_zoom = p_zoom; Point2 ofs = p_position; ofs = ofs / prev_zoom - ofs / draw_zoom; - draw_ofs.x = Math::round(draw_ofs.x + ofs.x); - draw_ofs.y = Math::round(draw_ofs.y + ofs.y); + draw_ofs = (draw_ofs + ofs).round(); edit_draw->update(); } diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index 165a381407..19e1b40a0d 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -437,8 +437,8 @@ void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) { } int count = 0; - for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) { - ThemeItem ti = E->key(); + for (const KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) { + ThemeItem ti = E.key; if (ti.data_type == p_data_type) { count++; } @@ -759,7 +759,7 @@ void ThemeItemImportTree::_import_selected() { ProgressDialog::get_singleton()->add_task("import_theme_items", TTR("Importing Theme Items"), selected_items.size() + 2); int idx = 0; - for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) { + for (KeyValue<ThemeItem, ItemCheckedState> &E : selected_items) { // Arbitrary number of items to skip from reporting. // Reduces the number of UI updates that this causes when copying large themes. if (idx % 10 == 0) { @@ -769,8 +769,8 @@ void ThemeItemImportTree::_import_selected() { ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Importing items {n}/{n}").format(arr, "{n}"), idx); } - ItemCheckedState cs = E->get(); - ThemeItem ti = E->key(); + ItemCheckedState cs = E.value; + ThemeItem ti = E.key; if (cs == SELECT_IMPORT_DEFINITION || cs == SELECT_IMPORT_FULL) { Variant item_value = Variant(); @@ -3333,7 +3333,7 @@ ThemeEditor::ThemeEditor() { preview_tabs->set_h_size_flags(SIZE_EXPAND_FILL); preview_tabbar_hb->add_child(preview_tabs); preview_tabs->connect("tab_changed", callable_mp(this, &ThemeEditor::_change_preview_tab)); - preview_tabs->connect("right_button_pressed", callable_mp(this, &ThemeEditor::_remove_preview_tab)); + preview_tabs->connect("tab_rmb_clicked", callable_mp(this, &ThemeEditor::_remove_preview_tab)); HBoxContainer *add_preview_button_hb = memnew(HBoxContainer); preview_tabbar_hb->add_child(add_preview_button_hb); diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp index 801ee0eac2..d26d0ec39d 100644 --- a/editor/plugins/theme_editor_preview.cpp +++ b/editor/plugins/theme_editor_preview.cpp @@ -126,8 +126,8 @@ void ThemeEditorPreview::_draw_picker_overlay() { highlight_label_rect.size.x += margin_left + margin_right; highlight_label_rect.size.y += margin_top + margin_bottom; - highlight_label_rect.position.x = CLAMP(highlight_label_rect.position.x, 0.0, picker_overlay->get_size().width); - highlight_label_rect.position.y = CLAMP(highlight_label_rect.position.y, 0.0, picker_overlay->get_size().height); + highlight_label_rect.position = highlight_label_rect.position.clamp(Vector2(), picker_overlay->get_size()); + picker_overlay->draw_style_box(theme_cache.preview_picker_label, highlight_label_rect); Point2 label_pos = highlight_label_rect.position; diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp index e98bd74b62..c064073b77 100644 --- a/editor/plugins/tiles/tile_atlas_view.cpp +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -97,15 +97,6 @@ Size2i TileAtlasView::_compute_base_tiles_control_size() { if (texture.is_valid()) { size = texture->get_size(); } - - // Extend the size to all existing tiles. - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { - Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); - grid_size = grid_size.max(tile_id + Vector2i(1, 1)); - } - size = size.max(grid_size * (tile_set_atlas_source->get_texture_region_size() + tile_set_atlas_source->get_separation()) + tile_set_atlas_source->get_margins()); - return size; } @@ -213,43 +204,56 @@ void TileAtlasView::_draw_base_tiles() { Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); if (texture.is_valid()) { Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - // Draw the texture, square by square. Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + + // Draw the texture where there is no tile. for (int x = 0; x < grid_size.x; x++) { for (int y = 0; y < grid_size.y; y++) { Vector2i coords = Vector2i(x, y); if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { - Rect2i rect = Rect2i(texture_region_size * coords + margins, texture_region_size); - base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + Rect2i rect = Rect2i((texture_region_size + separation) * coords + margins, texture_region_size + separation); + rect = rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (rect.size.x > 0 && rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + } } } } // Draw the texture around the grid. Rect2i rect; + // Top. rect.position = Vector2i(); rect.set_end(Vector2i(texture->get_size().x, margins.y)); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Bottom - int bottom_border = margins.y + (grid_size.y * texture_region_size.y); + int bottom_border = margins.y + (grid_size.y * (texture_region_size.y + separation.y)); if (bottom_border < texture->get_size().y) { rect.position = Vector2i(0, bottom_border); rect.set_end(texture->get_size()); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } + // Left rect.position = Vector2i(0, margins.y); - rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); + // Right. - int right_border = margins.x + (grid_size.x * texture_region_size.x); + int right_border = margins.x + (grid_size.x * (texture_region_size.x + separation.x)); if (right_border < texture->get_size().x) { rect.position = Vector2i(right_border, margins.y); - rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * texture_region_size.y))); + rect.set_end(Vector2i(texture->get_size().x, margins.y + (grid_size.y * (texture_region_size.y + separation.y)))); base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + base_tiles_draw->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.5)); } // Draw actual tiles, using their properties (modulation, etc...) @@ -258,12 +262,30 @@ void TileAtlasView::_draw_base_tiles() { for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) { // Update the y to max value. - int animation_columns = tile_set_atlas_source->get_tile_animation_columns(atlas_coords); - Vector2i frame_coords = atlas_coords + (tile_set_atlas_source->get_tile_size_in_atlas(atlas_coords) + tile_set_atlas_source->get_tile_animation_separation(atlas_coords)) * ((animation_columns > 0) ? Vector2i(frame % animation_columns, frame / animation_columns) : Vector2i(frame, 0)); - Vector2i offset_pos = (margins + (frame_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0)); + Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame); + Vector2i offset_pos = base_frame_rect.get_center() + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0); // Draw the tile. TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame); + + // Draw, the texture in the separation areas + if (separation.x > 0) { + Rect2i right_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(base_frame_rect.size.x, 0), Vector2i(separation.x, base_frame_rect.size.y)); + right_sep_rect = right_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (right_sep_rect.size.x > 0 && right_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, right_sep_rect, right_sep_rect); + base_tiles_draw->draw_rect(right_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } + + if (separation.y > 0) { + Rect2i bottom_sep_rect = Rect2i(base_frame_rect.get_position() + Vector2i(0, base_frame_rect.size.y), Vector2i(base_frame_rect.size.x + separation.x, separation.y)); + bottom_sep_rect = bottom_sep_rect.intersection(Rect2i(Vector2(), texture->get_size())); + if (bottom_sep_rect.size.x > 0 && bottom_sep_rect.size.y > 0) { + base_tiles_draw->draw_texture_rect_region(texture, bottom_sep_rect, bottom_sep_rect); + base_tiles_draw->draw_rect(bottom_sep_rect, Color(0.0, 0.0, 0.0, 0.5)); + } + } } } } @@ -299,30 +321,6 @@ void TileAtlasView::_draw_base_tiles_texture_grid() { } } -void TileAtlasView::_draw_base_tiles_dark() { - Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); - if (texture.is_valid()) { - Vector2i margins = tile_set_atlas_source->get_margins(); - Vector2i separation = tile_set_atlas_source->get_separation(); - Vector2i texture_region_size = tile_set_atlas_source->get_texture_region_size(); - - Size2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); - - // Draw each tile texture region. - for (int x = 0; x < grid_size.x; x++) { - for (int y = 0; y < grid_size.y; y++) { - Vector2i origin = margins + (Vector2i(x, y) * (texture_region_size + separation)); - Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); - - if (base_tile_coords == TileSetSource::INVALID_ATLAS_COORDS) { - // Draw the grid. - base_tiles_dark->draw_rect(Rect2i(origin, texture_region_size), Color(0.0, 0.0, 0.0, 0.5), true); - } - } - } - } -} - void TileAtlasView::_draw_base_tiles_shape_grid() { // Draw the shapes. Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); @@ -453,7 +451,6 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_ base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -522,10 +519,10 @@ void TileAtlasView::_update_alternative_tiles_rect_cache() { } Vector3i TileAtlasView::get_alternative_tile_at_pos(const Vector2 p_pos) const { - for (Map<Vector2, Map<int, Rect2i>>::Element *E_coords = alternative_tiles_rect_cache.front(); E_coords; E_coords = E_coords->next()) { - for (Map<int, Rect2i>::Element *E_alternative = E_coords->value().front(); E_alternative; E_alternative = E_alternative->next()) { - if (E_alternative->value().has_point(p_pos)) { - return Vector3i(E_coords->key().x, E_coords->key().y, E_alternative->key()); + for (const KeyValue<Vector2, Map<int, Rect2i>> &E_coords : alternative_tiles_rect_cache) { + for (const KeyValue<int, Rect2i> &E_alternative : E_coords.value) { + if (E_alternative.value.has_point(p_pos)) { + return Vector3i(E_coords.key.x, E_coords.key.y, E_alternative.key); } } } @@ -544,7 +541,6 @@ void TileAtlasView::update() { base_tiles_draw->update(); base_tiles_texture_grid->update(); base_tiles_shape_grid->update(); - base_tiles_dark->update(); alternatives_draw->update(); background_left->update(); background_right->update(); @@ -660,12 +656,6 @@ TileAtlasView::TileAtlasView() { base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid)); base_tiles_drawing_root->add_child(base_tiles_shape_grid); - base_tiles_dark = memnew(Control); - base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE); - base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark)); - base_tiles_drawing_root->add_child(base_tiles_dark); - // Alternative tiles. Label *alternative_tiles_label = memnew(Label); alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index 5b0df366ae..e1ca3eebee 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -93,9 +93,6 @@ private: Control *base_tiles_shape_grid; void _draw_base_tiles_shape_grid(); - Control *base_tiles_dark; - void _draw_base_tiles_dark(); - Size2i _compute_base_tiles_control_size(); // Right side. @@ -124,7 +121,6 @@ public: // Left side. void set_texture_grid_visible(bool p_visible) { base_tiles_texture_grid->set_visible(p_visible); }; - void set_dark_visible(bool p_visible) { base_tiles_dark->set_visible(p_visible); }; void set_tile_shape_grid_visible(bool p_visible) { base_tiles_shape_grid->set_visible(p_visible); }; Vector2i get_atlas_tile_coords_at_pos(const Vector2 p_pos) const; diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp index ea7ca787c8..104f46f773 100644 --- a/editor/plugins/tiles/tile_data_editors.cpp +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -38,8 +38,16 @@ #include "editor/editor_properties.h" #include "editor/editor_scale.h" -void TileDataEditor::_call_tile_set_changed() { - _tile_set_changed(); +void TileDataEditor::_tile_set_changed_plan_update() { + _tile_set_changed_update_needed = true; + call_deferred("_tile_set_changed_deferred_update"); +} + +void TileDataEditor::_tile_set_changed_deferred_update() { + if (_tile_set_changed_update_needed) { + _tile_set_changed(); + _tile_set_changed_update_needed = false; + } } TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { @@ -59,18 +67,20 @@ TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { } void TileDataEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_tile_set_changed_deferred_update"), &TileDataEditor::_tile_set_changed_deferred_update); + ADD_SIGNAL(MethodInfo("needs_redraw")); } void TileDataEditor::set_tile_set(Ref<TileSet> p_tile_set) { if (tile_set.is_valid()) { - tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->disconnect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } tile_set = p_tile_set; if (tile_set.is_valid()) { - tile_set->connect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + tile_set->connect("changed", callable_mp(this, &TileDataEditor::_tile_set_changed_plan_update)); } - _call_tile_set_changed(); + _tile_set_changed_plan_update(); } bool DummyObject::_set(const StringName &p_name, const Variant &p_value) { @@ -231,10 +241,14 @@ void GenericTilePolygonEditor::_zoom_changed() { void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { switch (p_item_pressed) { - case RESET_TO_DEFAULT_TILE: + case RESET_TO_DEFAULT_TILE: { undo_redo->create_action(TTR("Edit Polygons")); undo_redo->add_do_method(this, "clear_polygons"); - undo_redo->add_do_method(this, "add_polygon", tile_set->get_tile_shape_polygon()); + Vector<Vector2> polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } + undo_redo->add_do_method(this, "add_polygon", polygon); undo_redo->add_do_method(base_control, "update"); undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); undo_redo->add_undo_method(this, "clear_polygons"); @@ -244,8 +258,8 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; - case CLEAR_TILE: + } break; + case CLEAR_TILE: { undo_redo->create_action(TTR("Edit Polygons")); undo_redo->add_do_method(this, "clear_polygons"); undo_redo->add_do_method(base_control, "update"); @@ -257,7 +271,7 @@ void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { undo_redo->add_undo_method(base_control, "update"); undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); undo_redo->commit_action(true); - break; + } break; default: break; } @@ -308,6 +322,9 @@ void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_cur ERR_FAIL_COND(!tile_set.is_valid()); Vector<Point2> polygon = tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * tile_set->get_tile_size(); + } Point2 snapped_point = r_point; // Snap to polygon vertices. @@ -539,7 +556,11 @@ void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) { // Set the default tile shape clear_polygons(); if (p_tile_set.is_valid()) { - add_polygon(p_tile_set->get_tile_shape_polygon()); + Vector<Vector2> polygon = p_tile_set->get_tile_shape_polygon(); + for (int i = 0; i < polygon.size(); i++) { + polygon.write[i] = polygon[i] * p_tile_set->get_tile_size(); + } + add_polygon(polygon); } } tile_set = p_tile_set; @@ -622,7 +643,7 @@ void GenericTilePolygonEditor::_notification(int p_what) { button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CurveDelete"), SNAME("EditorIcons"))); button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("CenterView"), SNAME("EditorIcons"))); button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("Snap"), SNAME("EditorIcons"))); - button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenu"), SNAME("EditorIcons"))); + button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); break; } } @@ -745,10 +766,10 @@ Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_s } void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), E->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), p_new_value); + for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E.key.alternative_tile, property), E.value); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E.key.alternative_tile, property), p_new_value); } } @@ -1180,10 +1201,10 @@ Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_ } void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), E->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), p_new_value); + for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, occlusion_layer), E.value); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, occlusion_layer), p_new_value); } } @@ -1265,17 +1286,21 @@ void TileDataCollisionEditor::_polygons_changed() { } Variant TileDataCollisionEditor::_get_painted_value() { + Dictionary dict; + dict["linear_velocity"] = dummy_object->get("linear_velocity"); + dict["angular_velocity"] = dummy_object->get("angular_velocity"); Array array; for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { ERR_FAIL_COND_V(polygon_editor->get_polygon(i).size() < 3, Variant()); - Dictionary dict; - dict["points"] = polygon_editor->get_polygon(i); - dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); - dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = polygon_editor->get_polygon(i); + polygon_dict["one_way"] = dummy_object->get(vformat("polygon_%d_one_way", i)); + polygon_dict["one_way_margin"] = dummy_object->get(vformat("polygon_%d_one_way_margin", i)); + array.push_back(polygon_dict); } + dict["polygons"] = array; - return array; + return dict; } void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { @@ -1291,12 +1316,14 @@ void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_ } _polygons_changed(); + dummy_object->set("linear_velocity", tile_data->get_constant_linear_velocity(physics_layer)); + dummy_object->set("angular_velocity", tile_data->get_constant_angular_velocity(physics_layer)); for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { dummy_object->set(vformat("polygon_%d_one_way", i), tile_data->is_collision_polygon_one_way(physics_layer, i)); dummy_object->set(vformat("polygon_%d_one_way_margin", i), tile_data->get_collision_polygon_one_way_margin(physics_layer, i)); } - for (Map<StringName, EditorProperty *>::Element *E = property_editors.front(); E; E = E->next()) { - E->get()->update_property(); + for (const KeyValue<StringName, EditorProperty *> &E : property_editors) { + E.value->update_property(); } polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); @@ -1306,13 +1333,16 @@ void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_so TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); ERR_FAIL_COND(!tile_data); - Array array = p_value; + Dictionary dict = p_value; + tile_data->set_constant_linear_velocity(physics_layer, dict["linear_velocity"]); + tile_data->set_constant_angular_velocity(physics_layer, dict["angular_velocity"]); + Array array = dict["polygons"]; tile_data->set_collision_polygons_count(physics_layer, array.size()); for (int i = 0; i < array.size(); i++) { - Dictionary dict = array[i]; - tile_data->set_collision_polygon_points(physics_layer, i, dict["points"]); - tile_data->set_collision_polygon_one_way(physics_layer, i, dict["one_way"]); - tile_data->set_collision_polygon_one_way_margin(physics_layer, i, dict["one_way_margin"]); + Dictionary polygon_dict = array[i]; + tile_data->set_collision_polygon_points(physics_layer, i, polygon_dict["points"]); + tile_data->set_collision_polygon_one_way(physics_layer, i, polygon_dict["one_way"]); + tile_data->set_collision_polygon_one_way_margin(physics_layer, i, polygon_dict["one_way_margin"]); } polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); @@ -1322,37 +1352,41 @@ Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); ERR_FAIL_COND_V(!tile_data, Variant()); + Dictionary dict; + dict["linear_velocity"] = tile_data->get_constant_linear_velocity(physics_layer); + dict["angular_velocity"] = tile_data->get_constant_angular_velocity(physics_layer); Array array; for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { - Dictionary dict; - dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); - dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); - dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); - array.push_back(dict); + Dictionary polygon_dict; + polygon_dict["points"] = tile_data->get_collision_polygon_points(physics_layer, i); + polygon_dict["one_way"] = tile_data->is_collision_polygon_one_way(physics_layer, i); + polygon_dict["one_way_margin"] = tile_data->get_collision_polygon_one_way_margin(physics_layer, i); + array.push_back(polygon_dict); } - return array; + dict["polygons"] = array; + return dict; } void TileDataCollisionEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { Array new_array = p_new_value; - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Array old_array = E->get(); + for (KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Array old_array = E.value; - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), old_array.size()); + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), old_array.size()); for (int i = 0; i < old_array.size(); i++) { Dictionary dict = old_array[i]; - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); } - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E->key().alternative_tile, physics_layer), new_array.size()); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygons_count", coords.x, coords.y, E.key.alternative_tile, physics_layer), new_array.size()); for (int i = 0; i < new_array.size(); i++) { Dictionary dict = new_array[i]; - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["points"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E->key().alternative_tile, physics_layer, i), dict["one_way_margin"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/points", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["points"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/physics_layer_%d/polygon_%d/one_way_margin", coords.x, coords.y, E.key.alternative_tile, physics_layer, i), dict["one_way_margin"]); } } } @@ -1378,6 +1412,27 @@ TileDataCollisionEditor::TileDataCollisionEditor() { polygon_editor->connect("polygons_changed", callable_mp(this, &TileDataCollisionEditor::_polygons_changed)); add_child(polygon_editor); + dummy_object->add_dummy_property("linear_velocity"); + dummy_object->set("linear_velocity", Vector2()); + dummy_object->add_dummy_property("angular_velocity"); + dummy_object->set("angular_velocity", 0.0); + + EditorProperty *linear_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::VECTOR2, "linear_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + linear_velocity_editor->set_object_and_property(dummy_object, "linear_velocity"); + linear_velocity_editor->set_label("linear_velocity"); + linear_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + linear_velocity_editor->update_property(); + add_child(linear_velocity_editor); + property_editors["linear_velocity"] = linear_velocity_editor; + + EditorProperty *angular_velocity_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::FLOAT, "angular_velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + angular_velocity_editor->set_object_and_property(dummy_object, "angular_velocity"); + angular_velocity_editor->set_label("angular_velocity"); + angular_velocity_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + angular_velocity_editor->update_property(); + add_child(angular_velocity_editor); + property_editors["angular_velocity"] = linear_velocity_editor; + _polygons_changed(); } @@ -1976,16 +2031,16 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t drag_type = DRAG_TYPE_NONE; } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Terrain Set")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } @@ -1996,17 +2051,17 @@ void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_t int terrain_set = int(painted["terrain_set"]); int terrain = int(painted["terrain"]); undo_redo->create_action(TTR("Painting Terrain")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), terrain); } if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } @@ -2259,14 +2314,14 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi } else { if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { undo_redo->create_action(TTR("Painting Tiles Property")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), dict["terrain_set"]); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->key().alternative_tile), drag_painted_value); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), dict["terrain_set"]); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E.key.alternative_tile), drag_painted_value); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } undo_redo->commit_action(false); @@ -2276,17 +2331,17 @@ void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasVi int terrain_set = int(painted["terrain_set"]); int terrain = int(painted["terrain"]); undo_redo->create_action(TTR("Painting Terrain")); - for (Map<TileMapCell, Variant>::Element *E = drag_modified.front(); E; E = E->next()) { - Dictionary dict = E->get(); - Vector2i coords = E->key().get_atlas_coords(); + for (KeyValue<TileMapCell, Variant> &E : drag_modified) { + Dictionary dict = E.value; + Vector2i coords = E.key.get_atlas_coords(); Array array = dict["terrain_peering_bits"]; for (int i = 0; i < array.size(); i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), terrain); } if (tile_set->is_valid_peering_bit_terrain(dict["terrain_set"], bit)) { - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), array[i]); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E.key.alternative_tile), array[i]); } } } @@ -2398,10 +2453,10 @@ Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atla } void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { - for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { - Vector2i coords = E->key().get_atlas_coords(); - undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), E->get()); - undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), p_new_value); + for (const KeyValue<TileMapCell, Variant> &E : p_previous_values) { + Vector2i coords = E.key.get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, navigation_layer), E.value); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E.key.alternative_tile, navigation_layer), p_new_value); } } diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h index 99998dc779..acb4c5882d 100644 --- a/editor/plugins/tiles/tile_data_editors.h +++ b/editor/plugins/tiles/tile_data_editors.h @@ -45,7 +45,9 @@ class TileDataEditor : public VBoxContainer { GDCLASS(TileDataEditor, VBoxContainer); private: - void _call_tile_set_changed(); + bool _tile_set_changed_update_needed = false; + void _tile_set_changed_plan_update(); + void _tile_set_changed_deferred_update(); protected: Ref<TileSet> tile_set; diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 8b5b576369..5ca39b82b2 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -455,34 +455,36 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p switch (drag_type) { case DRAG_TYPE_PAINT: { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } + _fix_invalid_tiles_in_tile_map_selection(); } break; case DRAG_TYPE_BUCKET: { Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); for (int i = 0; i < line.size(); i++) { if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } + _fix_invalid_tiles_in_tile_map_selection(); } break; default: break; @@ -499,8 +501,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->get_button_index() == 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 (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { // Do nothing. @@ -508,6 +514,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p drag_start_mouse_pos = mpos; if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { // Move the selection + _update_selection_pattern_from_tilemap_selection(); // Make sure the pattern is up to date before moving. drag_type = DRAG_TYPE_MOVE; drag_modified.clear(); for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { @@ -521,31 +528,32 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p } } else { // Check if we are picking a tile. - if (picker_button->is_pressed()) { + if (picker_button->is_pressed() || (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { drag_type = DRAG_TYPE_PICK; drag_start_mouse_pos = mpos; } else { // Paint otherwise. - if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + if (tool_buttons_group->get_pressed_button() == paint_tool_button && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { drag_type = DRAG_TYPE_PAINT; drag_start_mouse_pos = mpos; drag_modified.clear(); Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + _fix_invalid_tiles_in_tile_map_selection(); + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(KEY_SHIFT) && !Input::get_singleton()->is_key_pressed(KEY_CTRL))) { drag_type = DRAG_TYPE_LINE; drag_start_mouse_pos = mpos; drag_modified.clear(); - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button || (tool_buttons_group->get_pressed_button() == paint_tool_button && Input::get_singleton()->is_key_pressed(KEY_SHIFT) && Input::get_singleton()->is_key_pressed(KEY_CTRL))) { drag_type = DRAG_TYPE_RECT; drag_start_mouse_pos = mpos; drag_modified.clear(); @@ -557,18 +565,19 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p for (int i = 0; i < line.size(); i++) { if (!drag_modified.has(line[i])) { Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - Vector2i coords = E->key(); + Vector2i coords = E.key; if (!drag_modified.has(coords)) { drag_modified.insert(coords, tile_map->get_cell(tile_map_layer, coords)); } - tile_map->set_cell(tile_map_layer, coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, coords, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } + _fix_invalid_tiles_in_tile_map_selection(); } } } @@ -576,6 +585,9 @@ 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; + } } CanvasItemEditor::get_singleton()->update_viewport(); @@ -682,13 +694,13 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(drag_last_mouse_pos - mouse_offset), clipboard_used_cells[i], tile_map_clipboard); preview[coords] = TileMapCell(tile_map_clipboard->get_cell_source_id(clipboard_used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(clipboard_used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(clipboard_used_cells[i])); } - } else if (!picker_button->is_pressed()) { + } else if (!picker_button->is_pressed() && !(drag_type == DRAG_TYPE_NONE && Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { bool expand_grid = false; if (tool_buttons_group->get_pressed_button() == paint_tool_button && drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); expand_grid = true; - } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == line_tool_button || drag_type == DRAG_TYPE_LINE) { if (drag_type == DRAG_TYPE_NONE) { // Preview for a single pattern. preview = _draw_line(drag_last_mouse_pos, drag_last_mouse_pos, drag_last_mouse_pos); @@ -698,20 +710,20 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos); expand_grid = true; } - } else if (tool_buttons_group->get_pressed_button() == rect_tool_button && drag_type == DRAG_TYPE_RECT) { - // Preview for a line pattern. + } 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)); expand_grid = true; } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { - // Preview for a line pattern. + // Preview for a fill pattern. preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed()); } // Expand the grid if needed if (expand_grid && !preview.is_empty()) { drawn_grid_rect = Rect2i(preview.front()->key(), Vector2i(1, 1)); - for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { - drawn_grid_rect.expand_to(E->key()); + for (const KeyValue<Vector2i, TileMapCell> &E : preview) { + drawn_grid_rect.expand_to(E.key); } } } @@ -748,23 +760,23 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over } // Draw the preview. - for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { + for (const KeyValue<Vector2i, TileMapCell> &E : preview) { Transform2D tile_xform; - tile_xform.set_origin(tile_map->map_to_world(E->key())); + tile_xform.set_origin(tile_map->map_to_world(E.key)); tile_xform.set_scale(tile_set->get_tile_size()); - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { tile_set->draw_tile_shape(p_overlay, xform * tile_xform, Color(1.0, 1.0, 1.0, 0.5), true); } else { - if (tile_set->has_source(E->get().source_id)) { - TileSetSource *source = *tile_set->get_source(E->get().source_id); + if (tile_set->has_source(E.value.source_id)) { + TileSetSource *source = *tile_set->get_source(E.value.source_id); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Get tile data. - TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E.value.get_atlas_coords(), E.value.alternative_tile)); // Compute the offset - Rect2i source_rect = atlas_source->get_tile_texture_region(E->get().get_atlas_coords()); - Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E->get().get_atlas_coords(), E->get().alternative_tile); + Rect2i source_rect = atlas_source->get_tile_texture_region(E.value.get_atlas_coords()); + Vector2i tile_offset = atlas_source->get_tile_effective_texture_offset(E.value.get_atlas_coords(), E.value.alternative_tile); // Compute the destination rectangle in the CanvasItem. Rect2 dest_rect; @@ -772,9 +784,9 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over bool transpose = tile_data->get_transpose(); if (transpose) { - dest_rect.position = (tile_map->map_to_world(E->key()) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_world(E.key) - Vector2(dest_rect.size.y, dest_rect.size.x) / 2 - tile_offset); } else { - dest_rect.position = (tile_map->map_to_world(E->key()) - dest_rect.size / 2 - tile_offset); + dest_rect.position = (tile_map->map_to_world(E.key) - dest_rect.size / 2 - tile_offset); } dest_rect = xform.xform(dest_rect); @@ -877,12 +889,12 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { // Paint the tiles on the tile map. - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && 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++) { @@ -929,7 +941,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> err_output; ERR_FAIL_COND_V(pattern->is_empty(), err_output); @@ -940,7 +952,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start Map<Vector2i, TileMapCell> output; if (!pattern->is_empty()) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && 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++) { @@ -988,7 +1000,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // Get or create the pattern. TileMapPattern erase_pattern; erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); - TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + TileMapPattern *pattern = _is_erasing() ? &erase_pattern : selection_pattern; if (!pattern->is_empty()) { TileMapCell source = tile_map->get_cell(tile_map_layer, p_coords); @@ -1012,7 +1024,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1041,7 +1053,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i TypedArray<Vector2i> to_check; if (source.source_id == TileSet::INVALID_SOURCE) { Rect2i rect = tile_map->get_used_rect(); - if (rect.size.x <= 0 || rect.size.y <= 0) { + if (rect.has_no_area()) { rect = Rect2i(p_coords, Vector2i(1, 1)); } for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) { @@ -1058,7 +1070,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i source.get_atlas_coords() == tile_map->get_cell_atlas_coords(tile_map_layer, coords) && source.alternative_tile == tile_map->get_cell_alternative_tile(tile_map_layer, coords) && (source.source_id != TileSet::INVALID_SOURCE || boundaries.has_point(coords))) { - if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + if (!_is_erasing() && random_tile_checkbox->is_pressed()) { // Paint a random tile. output.insert(coords, _pick_random_tile(pattern)); } else { @@ -1165,11 +1177,11 @@ void TileMapEditorTilesPlugin::_stop_dragging() { } undo_redo->create_action(TTR("Move tiles")); // Move the tiles. - for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : cells_do) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } - for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) { - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : cells_undo) { + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } // Update the selection. @@ -1205,41 +1217,41 @@ void TileMapEditorTilesPlugin::_stop_dragging() { } break; case DRAG_TYPE_PAINT: { undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } undo_redo->commit_action(false); } break; case DRAG_TYPE_LINE: { Map<Vector2i, TileMapCell> to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos); undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); } undo_redo->commit_action(); } break; case DRAG_TYPE_RECT: { Map<Vector2i, TileMapCell> to_draw = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos)); undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { + for (const KeyValue<Vector2i, TileMapCell> &E : to_draw) { + if (!_is_erasing() && E.value.source_id == TileSet::INVALID_SOURCE) { continue; } - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); } undo_redo->commit_action(); } break; case DRAG_TYPE_BUCKET: { undo_redo->create_action(TTR("Paint tiles")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } undo_redo->commit_action(false); } break; @@ -1323,6 +1335,29 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { } } +void TileMapEditorTilesPlugin::_fix_invalid_tiles_in_tile_map_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Set<Vector2i> to_remove; + for (Vector2i selected : tile_map_selection) { + TileMapCell cell = tile_map->get_cell(tile_map_layer, selected); + if (cell.source_id == TileSet::INVALID_SOURCE && cell.get_atlas_coords() == TileSetSource::INVALID_ATLAS_COORDS && cell.alternative_tile == TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + to_remove.insert(selected); + } + } + + for (Vector2i cell : to_remove) { + tile_map_selection.erase(cell); + } +} + +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) { @@ -1364,17 +1399,17 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( } int vertical_offset = 0; - for (Map<int, List<const TileMapCell *>>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) { + for (const KeyValue<int, List<const TileMapCell *>> &E_source : per_source) { // Per source. List<const TileMapCell *> unorganized; Rect2i encompassing_rect_coords; Map<Vector2i, const TileMapCell *> organized_pattern; - TileSetSource *source = *tile_set->get_source(E_source->key()); + TileSetSource *source = *tile_set->get_source(E_source.key); TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); if (atlas_source) { // Organize using coordinates. - for (const TileMapCell *current : E_source->get()) { + for (const TileMapCell *current : E_source.value) { if (current->alternative_tile == 0) { organized_pattern[current->get_atlas_coords()] = current; } else { @@ -1393,14 +1428,14 @@ void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection( } } else { // Add everything unorganized. - for (const TileMapCell *cell : E_source->get()) { + for (const TileMapCell *cell : E_source.value) { unorganized.push_back(cell); } } // Now add everything to the output pattern. - for (Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); E_cell; E_cell = E_cell->next()) { - selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + for (const KeyValue<Vector2i, const TileMapCell *> &E_cell : organized_pattern) { + selection_pattern->set_cell(E_cell.key - encompassing_rect_coords.position + Vector2i(0, vertical_offset), E_cell.value->source_id, E_cell.value->get_atlas_coords(), E_cell.value->alternative_tile); } Vector2i organized_size = selection_pattern->get_size(); int unorganized_index = 0; @@ -1423,6 +1458,8 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( } } _update_bottom_panel(); + tile_atlas_control->update(); + alternative_tiles_control->update(); } void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { @@ -1804,6 +1841,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { paint_tool_button->set_toggle_mode(true); paint_tool_button->set_button_group(tool_buttons_group); paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_D)); + paint_tool_button->set_tooltip(TTR("Shift: Draw line.") + "\n" + TTR("Shift+Ctrl: Draw rectangle.")); paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); tilemap_tiles_tools_buttons->add_child(paint_tool_button); @@ -1844,6 +1882,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { picker_button->set_flat(true); picker_button->set_toggle_mode(true); picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->set_tooltip(TTR("Alternatively hold Ctrl with other tools to pick tile.")); picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(picker_button); @@ -1852,6 +1891,7 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { erase_button->set_flat(true); erase_button->set_toggle_mode(true); erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + erase_button->set_tooltip(TTR("Alternatively use RMB to erase tiles.")); erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); tools_settings->add_child(erase_button); @@ -2308,14 +2348,14 @@ Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugi // Returns all tiles compatible with the given constraints. Set<TerrainsTilePattern> compatible_terrain_tile_patterns; - for (Map<TerrainsTilePattern, Set<TileMapCell>>::Element *E = per_terrain_terrains_tile_patterns_tiles[p_terrain_set].front(); E; E = E->next()) { + 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]); + 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()) { @@ -2327,7 +2367,7 @@ Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugi } if (valid) { - compatible_terrain_tile_patterns.insert(E->key()); + compatible_terrain_tile_patterns.insert(E.key); } } @@ -2368,15 +2408,15 @@ Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_c // Count the number of occurrences per terrain. Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); - for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_overlapping = overlapping_terrain_bits.front(); E_overlapping; E_overlapping = E_overlapping->next()) { - if (!p_to_replace.has(E_overlapping->key())) { - TileMapCell neighbor_cell = tile_map->get_cell(tile_map_layer, E_overlapping->key()); + 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]; } - int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping->get())) : -1; + 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; } @@ -2387,10 +2427,10 @@ Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_c // Get the terrain with the max number of occurrences. int max = 0; int max_terrain = -1; - for (Map<int, int>::Element *E_terrain_count = terrain_count.front(); E_terrain_count; E_terrain_count = E_terrain_count->next()) { - if (E_terrain_count->get() > max) { - max = E_terrain_count->get(); - max_terrain = E_terrain_count->key(); + 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; } } @@ -2458,15 +2498,15 @@ Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTer while (!to_replace.is_empty()) { // Compute the minimum number of tile possibilities for each cell. int min_nb_possibilities = 100000000; - for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { - min_nb_possibilities = MIN(min_nb_possibilities, E->get().size()); + 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 (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { - if (E->get().size() == min_nb_possibilities) { - to_choose_from.push_back(E->key()); + 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); } } @@ -2584,9 +2624,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map // Add the constraints from the added tiles. Set<TileMapEditorTerrainsPlugin::Constraint> added_tiles_constraints_set; - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - Vector2i coords = E_to_paint->key(); - TerrainsTilePattern terrains_tile_pattern = E_to_paint->get(); + 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; 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()) { @@ -2596,8 +2636,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map // Build the list of potential tiles to replace. Set<Vector2i> potential_to_replace; - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - Vector2i coords = E_to_paint->key(); + for (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)); @@ -2612,8 +2652,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map Set<Vector2i> to_replace; // Add the central tiles to the one to replace. TODO: maybe change that. - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - to_replace.insert(E_to_paint->key()); + 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. @@ -2627,9 +2667,9 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map Map<Constraint, Set<Vector2i>> source_tiles_of_constraint; for (Set<Constraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) { Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits(); - for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_source_tile_of_constraint = sources_of_constraint.front(); E_source_tile_of_constraint; E_source_tile_of_constraint = E_source_tile_of_constraint->next()) { - if (potential_to_replace.has(E_source_tile_of_constraint->key())) { - source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint->key()); + 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); } } } @@ -2646,8 +2686,8 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map potential_to_replace.erase(to_add_to_remove); to_replace.insert(to_add_to_remove); to_replace_modified = true; - for (Map<Constraint, Set<Vector2i>>::Element *E_source_tiles_of_constraint = source_tiles_of_constraint.front(); E_source_tiles_of_constraint; E_source_tiles_of_constraint = E_source_tiles_of_constraint->next()) { - E_source_tiles_of_constraint->get().erase(to_add_to_remove); + 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; } @@ -2665,13 +2705,13 @@ Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints); // Use the WFC run for the output. - for (Map<Vector2i, TerrainsTilePattern>::Element *E = wfc_output.front(); E; E = E->next()) { - output[E->key()] = _get_random_tile_from_pattern(p_terrain_set, E->get()); + for (const KeyValue<Vector2i, TerrainsTilePattern> &E : wfc_output) { + output[E.key] = _get_random_tile_from_pattern(p_terrain_set, E.value); } // Override the WFC results to make sure at least the painted tiles are actually painted. - for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { - output[E_to_paint->key()] = _get_random_tile_from_pattern(p_terrain_set, E_to_paint->get()); + 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); } return output; @@ -2749,9 +2789,9 @@ void TileMapEditorTerrainsPlugin::_stop_dragging() { } break; case DRAG_TYPE_PAINT: { undo_redo->create_action(TTR("Paint terrain")); - for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { - undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E->key(), tile_map->get_cell_source_id(tile_map_layer, E->key()), tile_map->get_cell_atlas_coords(tile_map_layer, E->key()), tile_map->get_cell_alternative_tile(tile_map_layer, E->key())); - undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + for (const KeyValue<Vector2i, TileMapCell> &E : drag_modified) { + undo_redo->add_do_method(tile_map, "set_cell", tile_map_layer, E.key, tile_map->get_cell_source_id(tile_map_layer, E.key), tile_map->get_cell_atlas_coords(tile_map_layer, E.key), tile_map->get_cell_alternative_tile(tile_map_layer, E.key)); + undo_redo->add_undo_method(tile_map, "set_cell", tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } undo_redo->commit_action(false); } break; @@ -2825,11 +2865,11 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> to_draw[line[i]] = selected_terrains_tile_pattern; } Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set); - for (Map<Vector2i, TileMapCell>::Element *E = modified.front(); E; E = E->next()) { - if (!drag_modified.has(E->key())) { - drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key()); + for (const KeyValue<Vector2i, TileMapCell> &E : modified) { + if (!drag_modified.has(E.key)) { + drag_modified[E.key] = tile_map->get_cell(tile_map_layer, E.key); } - tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + tile_map->set_cell(tile_map_layer, E.key, E.value.source_id, E.value.get_atlas_coords(), E.value.alternative_tile); } } } break; @@ -2864,9 +2904,9 @@ bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern; Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set); - for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { - drag_modified[E->key()] = tile_map->get_cell(tile_map_layer, E->key()); - tile_map->set_cell(tile_map_layer, E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + 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); } } } diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index 6126db59e9..5fbd9cada8 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -104,12 +104,14 @@ private: Vector2 drag_start_mouse_pos; Vector2 drag_last_mouse_pos; Map<Vector2i, TileMapCell> drag_modified; + bool rmb_erasing = false; TileMapCell _pick_random_tile(const TileMapPattern *p_pattern); Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos); Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell); Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous); void _stop_dragging(); + bool _is_erasing() const; ///// Selection system. ///// Set<Vector2i> tile_map_selection; @@ -124,6 +126,7 @@ private: void _update_selection_pattern_from_tileset_selection(); void _update_tileset_selection_from_selection_pattern(); void _update_fix_selected_and_hovered(); + void _fix_invalid_tiles_in_tile_map_selection(); ///// Bottom panel. ////. Label *missing_source_label; diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 54185d1d2a..c43a854d9a 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -87,7 +87,7 @@ void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list p_list->push_back(PropertyInfo(Variant::OBJECT, "texture", PROPERTY_HINT_RESOURCE_TYPE, "Texture2D")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "margins", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "separation", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "tile_size", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, "texture_region_size", PROPERTY_HINT_NONE, "")); } void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { @@ -131,6 +131,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na return false; } + // ID and size related properties. if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; @@ -160,36 +161,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i); emit_signal(SNAME("changed"), "size_in_atlas"); return true; - } else if (p_name == "animation_columns") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), p_value, tile_set_atlas_source->get_tile_animation_separation(coords), tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_columns(coords, p_value); - emit_signal(SNAME("changed"), "animation_columns"); - return true; - } else if (p_name == "animation_separation") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), p_value, tile_set_atlas_source->get_tile_animation_frames_count(coords), coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_separation(coords, p_value); - emit_signal(SNAME("changed"), "animation_separation"); - return true; - } else if (p_name == "animation_speed") { - tile_set_atlas_source->set_tile_animation_speed(coords, p_value); - emit_signal(SNAME("changed"), "animation_speed"); - return true; - } else if (p_name == "animation_frames_count") { - bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(coords, tile_set_atlas_source->get_tile_size_in_atlas(coords), tile_set_atlas_source->get_tile_animation_columns(coords), tile_set_atlas_source->get_tile_animation_separation(coords), p_value, coords); - ERR_FAIL_COND_V(!has_room_for_tile, false); - tile_set_atlas_source->set_tile_animation_frames_count(coords, p_value); - notify_property_list_changed(); - emit_signal(SNAME("changed"), "animation_separation"); - return true; - } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[0].trim_prefix("animation_frame_").to_int(); - ERR_FAIL_INDEX_V(frame, tile_set_atlas_source->get_tile_animation_frames_count(coords), false); - if (components[1] == "duration") { - tile_set_atlas_source->set_tile_animation_frame_duration(coords, frame, p_value); - return true; - } } } else if (alternative > 0) { if (p_name == "alternative_id") { @@ -213,6 +184,74 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_na } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_separation(tile.tile), tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_columns(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_columns"); + return true; + } else if (p_name == "animation_separation") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), p_value, tile_set_atlas_source->get_tile_animation_frames_count(tile.tile), tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_separation(tile.tile, p_value); + } + } + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (p_name == "animation_speed") { + for (TileSelection tile : tiles) { + tile_set_atlas_source->set_tile_animation_speed(tile.tile, p_value); + } + emit_signal(SNAME("changed"), "animation_speed"); + return true; + } else if (p_name == "animation_frames_count") { + for (TileSelection tile : tiles) { + bool has_room_for_tile = tile_set_atlas_source->has_room_for_tile(tile.tile, tile_set_atlas_source->get_tile_size_in_atlas(tile.tile), tile_set_atlas_source->get_tile_animation_columns(tile.tile), tile_set_atlas_source->get_tile_animation_separation(tile.tile), p_value, tile.tile); + if (!has_room_for_tile) { + ERR_PRINT("No room for tile"); + } else { + tile_set_atlas_source->set_tile_animation_frames_count(tile.tile, p_value); + } + } + notify_property_list_changed(); + emit_signal(SNAME("changed"), "animation_separation"); + return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + for (TileSelection tile : tiles) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(tile.tile)) { + ERR_PRINT(vformat("No tile animation frame with index %d", frame)); + } else { + if (components[1] == "duration") { + tile_set_atlas_source->set_tile_animation_frame_duration(tile.tile, frame, p_value); + return true; + } + } + } + } + } + + // Other properties. bool any_valid = false; for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { const Vector2i &coords = E->get().tile; @@ -238,6 +277,7 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na return false; } + // ID and size related properties.s if (tiles.size() == 1) { const Vector2i &coords = tiles.front()->get().tile; const int &alternative = tiles.front()->get().alternative; @@ -250,27 +290,6 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na } else if (p_name == "size_in_atlas") { r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords); return true; - } else if (p_name == "animation_columns") { - r_ret = tile_set_atlas_source->get_tile_animation_columns(coords); - return true; - } else if (p_name == "animation_separation") { - r_ret = tile_set_atlas_source->get_tile_animation_separation(coords); - return true; - } else if (p_name == "animation_speed") { - r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); - return true; - } else if (p_name == "animation_frames_count") { - r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords); - return true; - } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { - int frame = components[0].trim_prefix("animation_frame_").to_int(); - if (components[1] == "duration") { - if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) { - return false; - } - r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame); - return true; - } } } else if (alternative > 0) { if (p_name == "alternative_id") { @@ -280,6 +299,44 @@ bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_na } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + const Vector2i &coords = tiles.front()->get().tile; + + Vector<String> components = String(p_name).split("/", true, 2); + if (p_name == "animation_columns") { + r_ret = tile_set_atlas_source->get_tile_animation_columns(coords); + return true; + } else if (p_name == "animation_separation") { + r_ret = tile_set_atlas_source->get_tile_animation_separation(coords); + return true; + } else if (p_name == "animation_speed") { + r_ret = tile_set_atlas_source->get_tile_animation_speed(coords); + return true; + } else if (p_name == "animation_frames_count") { + r_ret = tile_set_atlas_source->get_tile_animation_frames_count(coords); + return true; + } else if (components.size() == 2 && components[0].begins_with("animation_frame_") && components[0].trim_prefix("animation_frame_").is_valid_int()) { + int frame = components[0].trim_prefix("animation_frame_").to_int(); + if (components[1] == "duration") { + if (frame < 0 || frame >= tile_set_atlas_source->get_tile_animation_frames_count(coords)) { + return false; + } + r_ret = tile_set_atlas_source->get_tile_animation_frame_duration(coords, frame); + return true; + } + } + } + for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { // Return the first tile with a property matching the name. // Note: It's a little bit annoying, but the behavior is the same the one in MultiNodeEdit. @@ -304,29 +361,42 @@ void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<Pro return; } + // ID and size related properties. if (tiles.size() == 1) { if (tiles.front()->get().alternative == 0) { p_list->push_back(PropertyInfo(Variant::VECTOR2I, "atlas_coords", PROPERTY_HINT_NONE, "")); p_list->push_back(PropertyInfo(Variant::VECTOR2I, "size_in_atlas", PROPERTY_HINT_NONE, "")); - - // Animation. - p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); - p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); - p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); - if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { - p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); - } else { - for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) { - p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "")); - } - } } else { p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", PROPERTY_HINT_NONE, "")); } } + // Animation. + // Check if all tiles have an alternative_id of 0. + bool all_alternatve_id_zero = true; + for (TileSelection tile : tiles) { + if (tile.alternative != 0) { + all_alternatve_id_zero = false; + break; + } + } + + if (all_alternatve_id_zero) { + p_list->push_back(PropertyInfo(Variant::NIL, "Animation", PROPERTY_HINT_NONE, "animation_", PROPERTY_USAGE_GROUP)); + p_list->push_back(PropertyInfo(Variant::INT, "animation_columns", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::VECTOR2I, "animation_separation", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_speed", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::INT, "animation_frames_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Frames,animation_frame_")); + // Not optimal, but returns value for the first tile. This is similar to what MultiNodeEdit does. + if (tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile) == 1) { + p_list->push_back(PropertyInfo(Variant::FLOAT, "animation_frame_0/duration", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_READ_ONLY)); + } else { + for (int i = 0; i < tile_set_atlas_source->get_tile_animation_frames_count(tiles.front()->get().tile); i++) { + p_list->push_back(PropertyInfo(Variant::FLOAT, vformat("animation_frame_%d/duration", i), PROPERTY_HINT_NONE, "")); + } + } + } + // Get the list of properties common to all tiles (similar to what's done in MultiNodeEdit). struct PropertyId { int occurence_id = 0; @@ -450,9 +520,6 @@ void TileSetAtlasSourceEditor::_update_tile_id_label() { void TileSetAtlasSourceEditor::_update_source_inspector() { // Update the proxy object. atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id); - - // Update the "clear outside texture" button. - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture()); } void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() { @@ -674,9 +741,9 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { #undef ADD_TILE_DATA_EDITOR // Add tile data editors as children. - for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { + for (KeyValue<String, TileDataEditor *> &E : tile_data_editors) { // Tile Data Editor. - TileDataEditor *tile_data_editor = E->get(); + TileDataEditor *tile_data_editor = E.value; if (!tile_data_editor->is_inside_tree()) { tile_data_painting_editor_container->add_child(tile_data_editor); } @@ -693,7 +760,11 @@ void TileSetAtlasSourceEditor::_update_tile_data_editors() { // Update visibility. bool is_visible = tools_button_group->get_pressed_button() == tool_paint_button; tile_data_editor_dropdown_button->set_visible(is_visible); - tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + if (tile_data_editors_tree->get_selected()) { + tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0)); + } else { + tile_data_editor_dropdown_button->set_text(TTR("Select a property editor")); + } tile_data_editors_label->set_visible(is_visible); } @@ -716,9 +787,9 @@ void TileSetAtlasSourceEditor::_update_current_tile_data_editor() { } // Hide all editors but the current one. - for (Map<String, TileDataEditor *>::Element *E = tile_data_editors.front(); E; E = E->next()) { - E->get()->hide(); - E->get()->get_toolbar()->hide(); + for (const KeyValue<String, TileDataEditor *> &E : tile_data_editors) { + E.value->hide(); + E.value->get_toolbar()->hide(); } if (tile_data_editors.has(property)) { current_tile_data_editor = tile_data_editors[property]; @@ -889,7 +960,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven current_tile_data_editor->forward_painting_atlas_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); } // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; tile_atlas_control->update(); tile_atlas_control_unscaled->update(); @@ -926,11 +997,11 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; } @@ -991,7 +1062,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_current_tile = coords; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -1031,7 +1102,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_current_tile = new_rect.position; // Update only what's needed. - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; _update_tile_inspector(); _update_atlas_view(); _update_tile_id_label(); @@ -1136,7 +1207,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4] && Rect2(pos, zoomed_size).has_point(mouse_local_pos)) { drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP_LEFT + i * 2); drag_start_mouse_pos = mouse_local_pos; @@ -1145,7 +1216,7 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEven drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); cursor_shape = (i % 2) ? CURSOR_BDIAGSIZE : CURSOR_FDIAGSIZE; } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i] && Rect2((pos + next_pos) / 2.0, zoomed_size).has_point(mouse_local_pos)) { drag_type = (DragType)((int)DRAG_TYPE_RESIZE_TOP + i * 2); drag_start_mouse_pos = mouse_local_pos; @@ -1530,9 +1601,6 @@ void TileSetAtlasSourceEditor::_menu_option(int p_option) { undo_redo->commit_action(); _update_tile_id_label(); } break; - case ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE: { - tile_set_atlas_source->clear_tiles_outside_texture(); - } break; case ADVANCED_AUTO_CREATE_TILES: { _auto_create_tiles(); } break; @@ -1614,13 +1682,13 @@ void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; } for (int i = 0; i < 4; i++) { - Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + Vector2 pos = rect.position + rect.size * coords[i]; if (can_grow[i] && can_grow[(i + 3) % 4]) { tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false); } else { tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false); } - Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + Vector2 next_pos = rect.position + rect.size * coords[(i + 1) % 4]; if (can_grow[i]) { tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false); } else { @@ -1942,8 +2010,13 @@ void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { } } -void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() { - tile_set_atlas_source_changed_needs_update = true; +void TileSetAtlasSourceEditor::_tile_set_changed() { + tile_set_changed_needs_update = true; +} + +void TileSetAtlasSourceEditor::_tile_proxy_object_changed(String p_what) { + tile_set_changed_needs_update = false; // Avoid updating too many things. + _update_atlas_view(); } void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) { @@ -1960,30 +2033,66 @@ void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo #define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, obj->get(property)); - AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); - if (tile_data) { + undo_redo->start_force_keep_in_merge_ends(); + AtlasTileProxyObject *tile_data_proxy = Object::cast_to<AtlasTileProxyObject>(p_edited); + if (tile_data_proxy) { Vector<String> components = String(p_property).split("/", true, 2); if (components.size() == 2 && components[1] == "polygons_count") { int layer_index = components[0].trim_prefix("physics_layer_").to_int(); int new_polygons_count = p_new_value; - int old_polygons_count = tile_data->get(vformat("physics_layer_%d/polygons_count", layer_index)); + int old_polygons_count = tile_data_proxy->get(vformat("physics_layer_%d/polygons_count", layer_index)); if (new_polygons_count < old_polygons_count) { for (int i = new_polygons_count - 1; i < old_polygons_count; i++) { - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); - ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/points", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way", layer_index, i)); + ADD_UNDO(tile_data_proxy, vformat("physics_layer_%d/polygon_%d/one_way_margin", layer_index, i)); } } } else if (p_property == "terrain_set") { - int current_terrain_set = tile_data->get("terrain_set"); + int current_terrain_set = tile_data_proxy->get("terrain_set"); for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); if (tile_set->is_valid_peering_bit_terrain(current_terrain_set, bit)) { - ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); + ADD_UNDO(tile_data_proxy, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i])); + } + } + } + } + + TileSetAtlasSourceProxyObject *atlas_source_proxy = Object::cast_to<TileSetAtlasSourceProxyObject>(p_edited); + if (atlas_source_proxy) { + TileSetAtlasSource *atlas_source = atlas_source_proxy->get_edited(); + ERR_FAIL_COND(!atlas_source); + + PackedVector2Array arr; + if (p_property == "texture") { + arr = atlas_source->get_tiles_to_be_removed_on_change(p_new_value, atlas_source->get_margins(), atlas_source->get_separation(), atlas_source->get_texture_region_size()); + } else if (p_property == "margins") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), p_new_value, atlas_source->get_separation(), atlas_source->get_texture_region_size()); + } else if (p_property == "separation") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), p_new_value, atlas_source->get_texture_region_size()); + } else if (p_property == "texture_region_size") { + arr = atlas_source->get_tiles_to_be_removed_on_change(atlas_source->get_texture(), atlas_source->get_margins(), atlas_source->get_separation(), p_new_value); + } + + if (!arr.is_empty()) { + // Get all properties assigned to a tile. + List<PropertyInfo> properties; + atlas_source->get_property_list(&properties); + + for (int i = 0; i < arr.size(); i++) { + Vector2i coords = arr[i]; + String prefix = vformat("%d:%d/", coords.x, coords.y); + for (PropertyInfo pi : properties) { + if (pi.name.begins_with(prefix)) { + ADD_UNDO(atlas_source, pi.name); + } } } } } + undo_redo->end_force_keep_in_merge_ends(); + #undef ADD_UNDO } @@ -1998,8 +2107,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource } // Remove listener for old objects. - if (tile_set_atlas_source) { - tile_set_atlas_source->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Clear the selection. @@ -2011,8 +2120,8 @@ void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource tile_set_atlas_source_id = p_source_id; // Add the listener again. - if (tile_set_atlas_source) { - tile_set_atlas_source->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_atlas_source_changed)); + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_set_changed)); } // Update everything. @@ -2153,7 +2262,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { resize_handle_disabled = get_theme_icon(SNAME("EditorHandleDisabled"), SNAME("EditorIcons")); break; case NOTIFICATION_INTERNAL_PROCESS: - if (tile_set_atlas_source_changed_needs_update) { + if (tile_set_changed_needs_update) { // Update everything. _update_source_inspector(); @@ -2166,7 +2275,7 @@ void TileSetAtlasSourceEditor::_notification(int p_what) { _update_tile_data_editors(); _update_current_tile_data_editor(); - tile_set_atlas_source_changed_needs_update = false; + tile_set_changed_needs_update = false; } break; default: @@ -2206,7 +2315,7 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { middle_vbox_container->add_child(tile_inspector_label); tile_proxy_object = memnew(AtlasTileProxyObject(this)); - tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); + tile_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_proxy_object_changed)); tile_inspector = memnew(EditorInspector); tile_inspector->set_undo_redo(undo_redo); @@ -2331,8 +2440,6 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tool_advanced_menu_buttom = memnew(MenuButton); tool_advanced_menu_buttom->set_flat(true); - tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE); - tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); @@ -2406,6 +2513,8 @@ TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); tile_atlas_view_missing_source_label->hide(); right_panel->add_child(tile_atlas_view_missing_source_label); + + EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetAtlasSourceEditor::_undo_redo_inspector_callback)); } TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() { diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index 501416c340..ea6f2847ae 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -78,6 +78,7 @@ private: int get_id(); void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + TileSetAtlasSource *get_edited() { return tile_set_atlas_source; }; }; // -- Proxy object for a tile, needed by the inspector -- @@ -112,7 +113,7 @@ private: UndoRedo *undo_redo = EditorNode::get_undo_redo(); - bool tile_set_atlas_source_changed_needs_update = false; + bool tile_set_changed_needs_update = false; // -- Properties painting -- VBoxContainer *tile_data_painting_editor_container; @@ -189,7 +190,6 @@ private: TILE_CREATE_ALTERNATIVE, TILE_DELETE, - ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE, ADVANCED_AUTO_CREATE_TILES, ADVANCED_AUTO_REMOVE_TILES, }; @@ -263,7 +263,8 @@ private: void _auto_remove_tiles(); AcceptDialog *confirm_auto_create_tiles; - void _tile_set_atlas_source_changed(); + void _tile_set_changed(); + void _tile_proxy_object_changed(String p_what); void _atlas_source_proxy_object_changed(String p_what); void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index f51f4625a9..d0d01a8d49 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -246,11 +246,9 @@ void TilesEditorPlugin::make_visible(bool p_visible) { if (p_visible) { tiles_editor_button->show(); editor_node->make_bottom_panel_item_visible(tiles_editor); - //get_tree()->connect_compat("idle_frame", tileset_editor, "_on_workspace_process"); } else { editor_node->hide_bottom_panel(); tiles_editor_button->hide(); - //get_tree()->disconnect_compat("idle_frame", tileset_editor, "_on_workspace_process"); } } diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index 27f30479cf..0a067dfc17 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -123,9 +123,9 @@ void VisualShaderGraphPlugin::set_connections(List<VisualShader::Connection> &p_ void VisualShaderGraphPlugin::show_port_preview(VisualShader::Type p_type, int p_node_id, int p_port_id) { if (visual_shader->get_shader_type() == p_type && links.has(p_node_id) && links[p_node_id].output_ports.has(p_port_id)) { - for (Map<int, Port>::Element *E = links[p_node_id].output_ports.front(); E; E = E->next()) { - if (E->value().preview_button != nullptr) { - E->value().preview_button->set_pressed(false); + for (const KeyValue<int, Port> &E : links[p_node_id].output_ports) { + if (E.value.preview_button != nullptr) { + E.value.preview_button->set_pressed(false); } } @@ -264,11 +264,11 @@ void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, int p_index, } void VisualShaderGraphPlugin::update_uniform_refs() { - for (Map<int, Link>::Element *E = links.front(); E; E = E->next()) { - VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E->get().visual_node); + for (KeyValue<int, Link> &E : links) { + VisualShaderNodeUniformRef *ref = Object::cast_to<VisualShaderNodeUniformRef>(E.value.visual_node); if (ref) { - remove_node(E->get().type, E->key()); - add_node(E->get().type, E->key()); + remove_node(E.value.type, E.key); + add_node(E.value.type, E.key); } } } @@ -491,6 +491,35 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { bool is_curve = curve.is_valid() || curve_xyz.is_valid(); if (is_curve) { + // a default value handling + { + Variant default_value; + bool port_left_used = false; + + for (const VisualShader::Connection &E : connections) { + if (E.to_node == p_id && E.to_port == 0) { + port_left_used = true; + break; + } + } + + if (!port_left_used) { + default_value = vsnode->get_input_port_default_value(0); + } + + Button *button = memnew(Button); + custom_editor->add_child(button); + register_default_input_button(p_id, 0, button); + custom_editor->move_child(button, 0); + + button->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_edit_port_default_input), varray(button, p_id, 0)); + if (default_value.get_type() != Variant::NIL) { + set_input_port_default_value(p_type, p_id, 0, default_value); + } else { + button->hide(); + } + } + VisualShaderEditor::get_singleton()->graph->add_child(node); VisualShaderEditor::get_singleton()->_update_created_node(node); @@ -643,6 +672,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { for (const VisualShader::Connection &E : connections) { if (E.to_node == p_id && E.to_port == j) { port_left_used = true; + break; } } } @@ -777,7 +807,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expand->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); hb->add_child(expand); } - if (visual_shader->get_shader_type() == VisualShader::TYPE_FRAGMENT && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { + if (vsnode->has_output_port_preview(i) && port_right != VisualShaderNode::PORT_TYPE_TRANSFORM && port_right != VisualShaderNode::PORT_TYPE_SAMPLER) { TextureButton *preview = memnew(TextureButton); preview->set_toggle_mode(true); preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon(SNAME("GuiVisibilityHidden"), SNAME("EditorIcons"))); diff --git a/editor/plugins/voxel_gi_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index 5bbc0c9dd5..9a44d40dcb 100644 --- a/editor/plugins/voxel_gi_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -33,7 +33,7 @@ void VoxelGIEditorPlugin::_bake() { if (voxel_gi) { if (voxel_gi->get_probe_data().is_null()) { - String path = get_tree()->get_edited_scene_root()->get_filename(); + String path = get_tree()->get_edited_scene_root()->get_scene_file_path(); if (path == String()) { path = "res://" + voxel_gi->get_name() + "_data.res"; } else { |