diff options
Diffstat (limited to 'editor/plugins')
94 files changed, 17573 insertions, 9233 deletions
diff --git a/editor/plugins/SCsub b/editor/plugins/SCsub index 359d04e5df..10a65b427e 100644 --- a/editor/plugins/SCsub +++ b/editor/plugins/SCsub @@ -3,3 +3,5 @@ Import("env") env.add_source_files(env.editor_sources, "*.cpp") + +SConscript("tiles/SCsub") diff --git a/editor/plugins/abstract_polygon_2d_editor.cpp b/editor/plugins/abstract_polygon_2d_editor.cpp index 80d0a7db60..e6f7ec1fbf 100644 --- a/editor/plugins/abstract_polygon_2d_editor.cpp +++ b/editor/plugins/abstract_polygon_2d_editor.cpp @@ -266,7 +266,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) if (mode == MODE_EDIT || (_is_line() && mode == MODE_CREATE)) { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { - if (mb->get_control() || mb->get_shift() || mb->get_alt()) { + if (mb->is_ctrl_pressed() || mb->is_shift_pressed() || mb->is_alt_pressed()) { return false; } @@ -399,7 +399,7 @@ bool AbstractPolygon2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) Vector2 cpoint = _get_node()->get_global_transform().affine_inverse().xform(canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(gpoint))); //Move the point in a single axis. Should only work when editing a polygon and while holding shift. - if (mode == MODE_EDIT && mm->get_shift()) { + if (mode == MODE_EDIT && mm->is_shift_pressed()) { Vector2 old_point = pre_move_edit.get(selected_point.vertex); if (ABS(cpoint.x - old_point.x) > ABS(cpoint.y - old_point.y)) { cpoint.y = old_point.y; diff --git a/editor/plugins/animation_blend_space_1d_editor.cpp b/editor/plugins/animation_blend_space_1d_editor.cpp index f7c0ebcfaf..b6dd9474d3 100644 --- a/editor/plugins/animation_blend_space_1d_editor.cpp +++ b/editor/plugins/animation_blend_space_1d_editor.cpp @@ -386,7 +386,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) { } else { String type = menu->get_item_metadata(p_index); - Object *obj = ClassDB::instance(type); + Object *obj = ClassDB::instantiate(type); ERR_FAIL_COND(!obj); AnimationNode *an = Object::cast_to<AnimationNode>(obj); ERR_FAIL_COND(!an); @@ -413,7 +413,7 @@ void AnimationNodeBlendSpace1DEditor::_add_menu_type(int p_index) { void AnimationNodeBlendSpace1DEditor::_add_animation_type(int p_index) { Ref<AnimationNodeAnimation> anim; - anim.instance(); + anim.instantiate(); anim->set_animation(animations_to_add[p_index]); @@ -594,7 +594,7 @@ AnimationNodeBlendSpace1DEditor::AnimationNodeBlendSpace1DEditor() { add_child(top_hb); Ref<ButtonGroup> bg; - bg.instance(); + bg.instantiate(); tool_blend = memnew(Button); tool_blend->set_flat(true); diff --git a/editor/plugins/animation_blend_space_2d_editor.cpp b/editor/plugins/animation_blend_space_2d_editor.cpp index e719df53d5..359df95bce 100644 --- a/editor/plugins/animation_blend_space_2d_editor.cpp +++ b/editor/plugins/animation_blend_space_2d_editor.cpp @@ -308,7 +308,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) { } else { String type = menu->get_item_metadata(p_index); - Object *obj = ClassDB::instance(type); + Object *obj = ClassDB::instantiate(type); ERR_FAIL_COND(!obj); AnimationNode *an = Object::cast_to<AnimationNode>(obj); ERR_FAIL_COND(!an); @@ -335,7 +335,7 @@ void AnimationNodeBlendSpace2DEditor::_add_menu_type(int p_index) { void AnimationNodeBlendSpace2DEditor::_add_animation_type(int p_index) { Ref<AnimationNodeAnimation> anim; - anim.instance(); + anim.instantiate(); anim->set_animation(animations_to_add[p_index]); @@ -818,7 +818,7 @@ AnimationNodeBlendSpace2DEditor::AnimationNodeBlendSpace2DEditor() { add_child(top_hb); Ref<ButtonGroup> bg; - bg.instance(); + bg.instantiate(); tool_blend = memnew(Button); tool_blend->set_flat(true); diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp index 48fb507bb1..dcde89f177 100644 --- a/editor/plugins/animation_blend_tree_editor_plugin.cpp +++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp @@ -139,7 +139,7 @@ void AnimationNodeBlendTreeEditor::_update_graph() { name->set_expand_to_text_length_enabled(true); node->add_child(name); node->set_slot(0, false, 0, Color(), true, 0, get_theme_color("font_color", "Label")); - name->connect("text_entered", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed), varray(agnode), CONNECT_DEFERRED); + name->connect("text_submitted", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed), varray(agnode), CONNECT_DEFERRED); name->connect("focus_exited", callable_mp(this, &AnimationNodeBlendTreeEditor::_node_renamed_focus_out), varray(name, agnode), CONNECT_DEFERRED); base = 1; node->set_show_close_button(true); @@ -231,18 +231,16 @@ void AnimationNodeBlendTreeEditor::_update_graph() { mb->get_popup()->connect("index_pressed", callable_mp(this, &AnimationNodeBlendTreeEditor::_anim_selected), varray(options, E->get()), CONNECT_DEFERRED); } - if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) { - Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode"); - Color c = sb->get_border_color(); - Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0); - mono_color.a = 0.85; - c = mono_color; - - node->add_theme_color_override("title_color", c); - c.a = 0.7; - node->add_theme_color_override("close_color", c); - node->add_theme_color_override("resizer_color", c); - } + Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode"); + Color c = sb->get_border_color(); + Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0) : Color(0.0, 0.0, 0.0); + mono_color.a = 0.85; + c = mono_color; + + node->add_theme_color_override("title_color", c); + c.a = 0.7; + node->add_theme_color_override("close_color", c); + node->add_theme_color_override("resizer_color", c); } List<AnimationNodeBlendTree::NodeConnection> connections; @@ -290,14 +288,14 @@ void AnimationNodeBlendTreeEditor::_add_node(int p_idx) { ERR_FAIL_COND(!anode.is_valid()); base_name = anode->get_class(); } else if (add_options[p_idx].type != String()) { - AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(add_options[p_idx].type)); + AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(add_options[p_idx].type)); ERR_FAIL_COND(!an); anode = Ref<AnimationNode>(an); base_name = add_options[p_idx].name; } else { ERR_FAIL_COND(add_options[p_idx].script.is_null()); String base_type = add_options[p_idx].script->get_instance_base_type(); - AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instance(base_type)); + AnimationNode *an = Object::cast_to<AnimationNode>(ClassDB::instantiate(base_type)); ERR_FAIL_COND(!an); anode = Ref<AnimationNode>(an); anode->set_script(add_options[p_idx].script); diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp index 612a8f30a4..2b92943f7e 100644 --- a/editor/plugins/animation_player_editor_plugin.cpp +++ b/editor/plugins/animation_player_editor_plugin.cpp @@ -121,11 +121,11 @@ void AnimationPlayerEditor::_notification(int p_what) { Ref<Image> reset_img = reset_icon->get_image(); Ref<Image> autoplay_reset_img; Size2 icon_size = Size2(autoplay_img->get_width(), autoplay_img->get_height()); - autoplay_reset_img.instance(); + autoplay_reset_img.instantiate(); autoplay_reset_img->create(icon_size.x * 2, icon_size.y, false, autoplay_img->get_format()); autoplay_reset_img->blit_rect(autoplay_img, Rect2(Point2(), icon_size), Point2()); autoplay_reset_img->blit_rect(reset_img, Rect2(Point2(), icon_size), Point2(icon_size.x, 0)); - autoplay_reset_icon.instance(); + autoplay_reset_icon.instantiate(); autoplay_reset_icon->create_from_image(autoplay_reset_img); } stop->set_icon(get_theme_icon("Stop", "EditorIcons")); @@ -569,8 +569,10 @@ void AnimationPlayerEditor::_animation_blend() { blend_editor.dialog->popup_centered(Size2(400, 400) * EDSCALE); blend_editor.tree->set_hide_root(true); - blend_editor.tree->set_column_min_width(0, 10); - blend_editor.tree->set_column_min_width(1, 3); + blend_editor.tree->set_column_expand_ratio(0, 10); + blend_editor.tree->set_column_clip_content(0, true); + blend_editor.tree->set_column_expand_ratio(1, 3); + blend_editor.tree->set_column_clip_content(1, true); List<StringName> anims; player->get_animation_list(&anims); @@ -994,7 +996,7 @@ void AnimationPlayerEditor::_animation_duplicate() { } } -void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) { +void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set, bool p_timeline_only) { if (updating || !player || player->is_playing()) { return; }; @@ -1015,18 +1017,18 @@ void AnimationPlayerEditor::_seek_value_changed(float p_value, bool p_set) { pos = Math::snapped(pos, _get_editor_step()); } - if (player->is_valid() && !p_set) { - float cpos = player->get_current_animation_position(); + if (!p_timeline_only) { + if (player->is_valid() && !p_set) { + float cpos = player->get_current_animation_position(); - player->seek_delta(pos, pos - cpos); - } else { - player->stop(true); - player->seek(pos, true); + player->seek_delta(pos, pos - cpos); + } else { + player->stop(true); + player->seek(pos, true); + } } track_editor->set_anim_pos(pos); - - updating = true; }; void AnimationPlayerEditor::_animation_player_changed(Object *p_pl) { @@ -1048,7 +1050,7 @@ void AnimationPlayerEditor::_animation_key_editor_anim_len_changed(float p_len) frame->set_max(p_len); } -void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag) { +void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only) { timeline_position = p_pos; if (!is_visible_in_tree()) { @@ -1070,7 +1072,7 @@ void AnimationPlayerEditor::_animation_key_editor_seek(float p_pos, bool p_drag) updating = true; frame->set_value(Math::snapped(p_pos, _get_editor_step())); updating = false; - _seek_value_changed(p_pos, !p_drag); + _seek_value_changed(p_pos, !p_drag, p_timeline_only); } void AnimationPlayerEditor::_animation_tool_menu(int p_option) { @@ -1222,10 +1224,10 @@ void AnimationPlayerEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) { ERR_FAIL_COND(p_ev.is_null()); Ref<InputEventKey> k = p_ev; - if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->get_alt() && !k->get_control() && !k->get_metakey()) { + if (is_visible_in_tree() && k.is_valid() && k->is_pressed() && !k->is_echo() && !k->is_alt_pressed() && !k->is_ctrl_pressed() && !k->is_meta_pressed()) { switch (k->get_keycode()) { case KEY_A: { - if (!k->get_shift()) { + if (!k->is_shift_pressed()) { _play_bw_from_pressed(); } else { _play_bw_pressed(); @@ -1237,7 +1239,7 @@ void AnimationPlayerEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) { accept_event(); } break; case KEY_D: { - if (!k->get_shift()) { + if (!k->is_shift_pressed()) { _play_from_pressed(); } else { _play_pressed(); @@ -1693,8 +1695,8 @@ AnimationPlayerEditor::AnimationPlayerEditor(EditorNode *p_editor, AnimationPlay animation->connect("item_selected", callable_mp(this, &AnimationPlayerEditor::_animation_selected)); file->connect("file_selected", callable_mp(this, &AnimationPlayerEditor::_dialog_action)); - frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true)); - scale->connect("text_entered", callable_mp(this, &AnimationPlayerEditor::_scale_changed)); + frame->connect("value_changed", callable_mp(this, &AnimationPlayerEditor::_seek_value_changed), make_binds(true, false)); + scale->connect("text_submitted", callable_mp(this, &AnimationPlayerEditor::_scale_changed)); renaming = false; last_active = false; diff --git a/editor/plugins/animation_player_editor_plugin.h b/editor/plugins/animation_player_editor_plugin.h index 2f6bf55e4c..5c2348f86b 100644 --- a/editor/plugins/animation_player_editor_plugin.h +++ b/editor/plugins/animation_player_editor_plugin.h @@ -187,7 +187,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _scale_changed(const String &p_scale); void _dialog_action(String p_file); void _seek_frame_changed(const String &p_frame); - void _seek_value_changed(float p_value, bool p_set = false); + void _seek_value_changed(float p_value, bool p_set = false, bool p_timeline_only = false); void _blend_editor_next_changed(const int p_idx); void _list_changed(); @@ -197,7 +197,7 @@ class AnimationPlayerEditor : public VBoxContainer { void _animation_player_changed(Object *p_pl); - void _animation_key_editor_seek(float p_pos, bool p_drag); + void _animation_key_editor_seek(float p_pos, bool p_drag, bool p_timeline_only = false); void _animation_key_editor_anim_len_changed(float p_len); void _unhandled_key_input(const Ref<InputEvent> &p_ev); diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp index a9709bbb16..94e526922d 100644 --- a/editor/plugins/animation_state_machine_editor.cpp +++ b/editor/plugins/animation_state_machine_editor.cpp @@ -124,7 +124,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } // select node or push a field inside - if (mb.is_valid() && !mb->get_shift() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb.is_valid() && !mb->is_shift_pressed() && mb->is_pressed() && tool_select->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { selected_transition_from = StringName(); selected_transition_to = StringName(); selected_node = StringName(); @@ -237,7 +237,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } //connect nodes - if (mb.is_valid() && ((tool_select->is_pressed() && mb->get_shift()) || tool_connect->is_pressed()) && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { + if (mb.is_valid() && ((tool_select->is_pressed() && mb->is_shift_pressed()) || tool_connect->is_pressed()) && mb->get_button_index() == MOUSE_BUTTON_LEFT && mb->is_pressed()) { for (int i = node_rects.size() - 1; i >= 0; i--) { //inverse to draw order if (node_rects[i].node.has_point(mb->get_position())) { //select node since nothing else was selected connecting = true; @@ -257,7 +257,7 @@ void AnimationNodeStateMachineEditor::_state_machine_gui_input(const Ref<InputEv } else { Ref<AnimationNodeStateMachineTransition> tr; - tr.instance(); + tr.instantiate(); tr->set_switch_mode(AnimationNodeStateMachineTransition::SwitchMode(transition_mode->get_selected())); updating = true; @@ -423,7 +423,7 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) { } else { String type = menu->get_item_metadata(p_index); - Object *obj = ClassDB::instance(type); + Object *obj = ClassDB::instantiate(type); ERR_FAIL_COND(!obj); AnimationNode *an = Object::cast_to<AnimationNode>(obj); ERR_FAIL_COND(!an); @@ -462,7 +462,7 @@ void AnimationNodeStateMachineEditor::_add_menu_type(int p_index) { void AnimationNodeStateMachineEditor::_add_animation_type(int p_index) { Ref<AnimationNodeAnimation> anim; - anim.instance(); + anim.instantiate(); anim->set_animation(animations_to_add[p_index]); @@ -1213,7 +1213,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { add_child(top_hb); Ref<ButtonGroup> bg; - bg.instance(); + bg.instantiate(); tool_select = memnew(Button); tool_select->set_flat(true); @@ -1329,7 +1329,7 @@ AnimationNodeStateMachineEditor::AnimationNodeStateMachineEditor() { name_edit = memnew(LineEdit); name_edit_popup->add_child(name_edit); name_edit->set_anchors_and_offsets_preset(PRESET_WIDE); - name_edit->connect("text_entered", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited)); + name_edit->connect("text_submitted", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited)); name_edit->connect("focus_exited", callable_mp(this, &AnimationNodeStateMachineEditor::_name_edited_focus_out)); open_file = memnew(EditorFileDialog); diff --git a/editor/plugins/animation_tree_editor_plugin.cpp b/editor/plugins/animation_tree_editor_plugin.cpp index c33b06ff32..e90665f84d 100644 --- a/editor/plugins/animation_tree_editor_plugin.cpp +++ b/editor/plugins/animation_tree_editor_plugin.cpp @@ -76,7 +76,7 @@ void AnimationTreeEditor::_update_path() { } Ref<ButtonGroup> group; - group.instance(); + group.instantiate(); Button *b = memnew(Button); b->set_text("Root"); diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp index fd47d9964e..cd61ebd418 100644 --- a/editor/plugins/asset_library_editor_plugin.cpp +++ b/editor/plugins/asset_library_editor_plugin.cpp @@ -86,7 +86,7 @@ void EditorAssetLibraryItem::_bind_methods() { EditorAssetLibraryItem::EditorAssetLibraryItem() { Ref<StyleBoxEmpty> border; - border.instance(); + border.instantiate(); border->set_default_margin(SIDE_LEFT, 5 * EDSCALE); border->set_default_margin(SIDE_RIGHT, 5 * EDSCALE); border->set_default_margin(SIDE_BOTTOM, 5 * EDSCALE); @@ -155,7 +155,7 @@ void EditorAssetLibraryItemDescription::set_image(int p_type, int p_index, const thumbnail->blend_rect(overlay, overlay->get_used_rect(), overlay_pos); Ref<ImageTexture> tex; - tex.instance(); + tex.instantiate(); tex->create_from_image(thumbnail); preview_images[i].button->set_icon(tex); @@ -240,7 +240,6 @@ void EditorAssetLibraryItemDescription::add_preview(int p_id, bool p_video, cons preview.video_link = p_url; preview.is_video = p_video; preview.button = memnew(Button); - preview.button->set_flat(true); preview.button->set_icon(previews->get_theme_icon("ThumbnailWait", "EditorIcons")); preview.button->set_toggle_mode(true); preview.button->connect("pressed", callable_mp(this, &EditorAssetLibraryItemDescription::_preview_click), varray(p_id)); @@ -465,7 +464,7 @@ void EditorAssetLibraryItemDownload::_make_request() { retry->hide(); download->cancel_request(); - download->set_download_file(EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip"); + download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip"); Error err = download->request(host); if (err != OK) { @@ -703,7 +702,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB PackedByteArray image_data = p_data; if (use_cache) { - String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); FileAccess *file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ); @@ -762,7 +761,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB } Ref<ImageTexture> tex; - tex.instance(); + tex.instantiate(); tex->create_from_image(image); obj->call("set_image", image_queue[p_queue_id].image_type, image_queue[p_queue_id].image_index, tex); @@ -782,7 +781,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons if (p_code != HTTPClient::RESPONSE_NOT_MODIFIED) { for (int i = 0; i < headers.size(); i++) { if (headers[i].findn("ETag:") == 0) { // Save etag - String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text()); String new_etag = headers[i].substr(headers[i].find(":") + 1, headers[i].length()).strip_edges(); FileAccess *file; @@ -830,7 +829,7 @@ void EditorAssetLibrary::_update_image_queue() { 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 = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text()); + String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text()); Vector<String> headers; if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) { @@ -1099,11 +1098,9 @@ void EditorAssetLibrary::_http_request_completed(int p_status, int p_code, const Dictionary d; { - Variant js; - String errs; - int errl; - JSON::parse(str, js, errs, errl); - d = js; + JSON json; + json.parse(str); + d = json.get_data(); } RequestType requested = requesting; @@ -1438,7 +1435,7 @@ EditorAssetLibrary::EditorAssetLibrary(bool p_templates_only) { library_scroll_bg->add_child(library_scroll); Ref<StyleBoxEmpty> border2; - border2.instance(); + border2.instantiate(); border2->set_default_margin(SIDE_LEFT, 15 * EDSCALE); border2->set_default_margin(SIDE_RIGHT, 35 * EDSCALE); border2->set_default_margin(SIDE_BOTTOM, 15 * EDSCALE); diff --git a/editor/plugins/asset_library_editor_plugin.h b/editor/plugins/asset_library_editor_plugin.h index 11eae9e041..c6ca1ecd4f 100644 --- a/editor/plugins/asset_library_editor_plugin.h +++ b/editor/plugins/asset_library_editor_plugin.h @@ -282,7 +282,7 @@ class EditorAssetLibrary : public PanelContainer { void _search(int p_page = 0); void _rerun_search(int p_ignore); void _search_text_changed(const String &p_text = ""); - void _search_text_entered(const String &p_text = ""); + void _search_text_submitted(const String &p_text = ""); void _api_request(const String &p_request, RequestType p_request_type, const String &p_arguments = ""); void _http_request_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data); void _http_download_completed(int p_status, int p_code, const PackedStringArray &headers, const PackedByteArray &p_data); diff --git a/editor/plugins/audio_stream_editor_plugin.cpp b/editor/plugins/audio_stream_editor_plugin.cpp index 3553450672..3b54b30b54 100644 --- a/editor/plugins/audio_stream_editor_plugin.cpp +++ b/editor/plugins/audio_stream_editor_plugin.cpp @@ -32,6 +32,7 @@ #include "core/config/project_settings.h" #include "core/io/resource_loader.h" +#include "core/os/keyboard.h" #include "editor/audio_stream_preview.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -144,23 +145,26 @@ void AudioStreamEditor::_draw_indicator() { Rect2 rect = _preview->get_rect(); float len = stream->get_length(); float ofs_x = _current / len * rect.size.width; - _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), get_theme_color("accent_color", "Editor"), 1); + const Color color = get_theme_color("accent_color", "Editor"); + _indicator->draw_line(Point2(ofs_x, 0), Point2(ofs_x, rect.size.height), color, Math::round(2 * EDSCALE)); + _indicator->draw_texture( + get_theme_icon("TimelineIndicator", "EditorIcons"), + Point2(ofs_x - get_theme_icon("TimelineIndicator", "EditorIcons")->get_width() * 0.5, 0), + color); _current_label->set_text(String::num(_current, 2).pad_decimals(2) + " /"); } void AudioStreamEditor::_on_input_indicator(Ref<InputEvent> p_event) { - Ref<InputEventMouseButton> mb = p_event; - - if (mb.is_valid()) { + const Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { _seek_to(mb->get_position().x); } _dragging = mb->is_pressed(); } - Ref<InputEventMouseMotion> mm = p_event; - + const Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid()) { if (_dragging) { _seek_to(mm->get_position().x); @@ -228,6 +232,7 @@ AudioStreamEditor::AudioStreamEditor() { hbox->add_child(_play_button); _play_button->set_focus_mode(Control::FOCUS_NONE); _play_button->connect("pressed", callable_mp(this, &AudioStreamEditor::_play)); + _play_button->set_shortcut(ED_SHORTCUT("inspector/audio_preview_play_pause", TTR("Audio Preview Play/Pause"), KEY_SPACE)); _stop_button = memnew(Button); _stop_button->set_flat(true); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 6ac47595dc..7282475ddf 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -332,7 +332,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; - bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL); + bool is_snap_active = smart_snap_active ^ Input::get_singleton()->is_key_pressed(KEY_CTRL); // Smart snap using the canvas position Vector2 output = p_target; @@ -460,7 +460,7 @@ Point2 CanvasItemEditor::snap_point(Point2 p_target, unsigned int p_modes, unsig } float CanvasItemEditor::snap_angle(float p_target, float p_start) const { - if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(KEY_CONTROL)) && snap_rotation_step != 0) { + if (((smart_snap_active || snap_rotation) ^ Input::get_singleton()->is_key_pressed(KEY_CTRL)) && snap_rotation_step != 0) { if (snap_relative) { return Math::snapped(p_target - snap_rotation_offset, snap_rotation_step) + snap_rotation_offset + (p_start - (int)(p_start / snap_rotation_step) * snap_rotation_step); } else { @@ -481,11 +481,11 @@ void CanvasItemEditor::_unhandled_key_input(const Ref<InputEvent> &p_ev) { } if (k.is_valid()) { - if (k->get_keycode() == KEY_CONTROL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT) { + if (k->get_keycode() == KEY_CTRL || k->get_keycode() == KEY_ALT || k->get_keycode() == KEY_SHIFT) { viewport->update(); } - if (k->is_pressed() && !k->get_control() && !k->is_echo()) { + if (k->is_pressed() && !k->is_ctrl_pressed() && !k->is_echo()) { if ((grid_snap_active || show_grid) && multiply_grid_step_shortcut.is_valid() && multiply_grid_step_shortcut->is_shortcut(p_ev)) { // Multiply the grid size grid_step_multiplier = MIN(grid_step_multiplier + 1, 12); @@ -666,93 +666,6 @@ void CanvasItemEditor::_get_canvas_items_at_pos(const Point2 &p_pos, Vector<_Sel } } -void CanvasItemEditor::_get_bones_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items) { - Point2 screen_pos = transform.xform(p_pos); - - for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { - Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().from)); - - Vector<Vector2> bone_shape; - if (!_get_bone_shape(&bone_shape, nullptr, E)) { - continue; - } - - // Check if the point is inside the Polygon2D - if (Geometry2D::is_point_in_polygon(screen_pos, bone_shape)) { - // Check if the item is already in the list - bool duplicate = false; - for (int i = 0; i < r_items.size(); i++) { - if (r_items[i].item == from_node) { - duplicate = true; - break; - } - } - if (duplicate) { - continue; - } - - // Else, add it - _SelectResult res; - res.item = from_node; - res.z_index = from_node ? from_node->get_z_index() : 0; - res.has_z = from_node; - r_items.push_back(res); - } - } -} - -bool CanvasItemEditor::_get_bone_shape(Vector<Vector2> *shape, Vector<Vector2> *outline_shape, Map<BoneKey, BoneList>::Element *bone) { - int bone_width = EditorSettings::get_singleton()->get("editors/2d/bone_width"); - int bone_outline_width = EditorSettings::get_singleton()->get("editors/2d/bone_outline_size"); - - Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(bone->key().from)); - Node2D *to_node = Object::cast_to<Node2D>(ObjectDB::get_instance(bone->key().to)); - - if (!from_node) { - return false; - } - if (!from_node->is_inside_tree()) { - return false; //may have been removed - } - - if (!to_node && bone->get().length == 0) { - return false; - } - - Vector2 from = transform.xform(from_node->get_global_position()); - Vector2 to; - - if (to_node) { - to = transform.xform(to_node->get_global_position()); - } else { - to = transform.xform(from_node->get_global_transform().xform(Vector2(bone->get().length, 0))); - } - - Vector2 rel = to - from; - Vector2 relt = rel.orthogonal().normalized() * bone_width; - Vector2 reln = rel.normalized(); - Vector2 reltn = relt.normalized(); - - if (shape) { - shape->clear(); - shape->push_back(from); - shape->push_back(from + rel * 0.2 + relt); - shape->push_back(to); - shape->push_back(from + rel * 0.2 - relt); - } - - if (outline_shape) { - outline_shape->clear(); - outline_shape->push_back(from + (-reln - reltn) * bone_outline_width); - outline_shape->push_back(from + (-reln + reltn) * bone_outline_width); - outline_shape->push_back(from + rel * 0.2 + relt + reltn * bone_outline_width); - outline_shape->push_back(to + (reln + reltn) * bone_outline_width); - outline_shape->push_back(to + (reln - reltn) * bone_outline_width); - outline_shape->push_back(from + rel * 0.2 - relt - reltn * bone_outline_width); - } - return true; -} - void CanvasItemEditor::_find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { if (!p_node) { return; @@ -886,50 +799,6 @@ Vector2 CanvasItemEditor::_position_to_anchor(const Control *p_control, Vector2 return output; } -void CanvasItemEditor::_save_canvas_item_ik_chain(const CanvasItem *p_canvas_item, List<float> *p_bones_length, List<Dictionary> *p_bones_state) { - if (p_bones_length) { - *p_bones_length = List<float>(); - } - if (p_bones_state) { - *p_bones_state = List<Dictionary>(); - } - - const Node2D *bone = Object::cast_to<Node2D>(p_canvas_item); - if (bone && bone->has_meta("_edit_bone_")) { - // Check if we have an IK chain - List<const Node2D *> bone_ik_list; - bool ik_found = false; - bone = Object::cast_to<Node2D>(bone->get_parent()); - while (bone) { - bone_ik_list.push_back(bone); - if (bone->has_meta("_edit_ik_")) { - ik_found = true; - break; - } else if (!bone->has_meta("_edit_bone_")) { - break; - } - bone = Object::cast_to<Node2D>(bone->get_parent()); - } - - //Save the bone state and length if we have an IK chain - if (ik_found) { - bone = Object::cast_to<Node2D>(p_canvas_item); - Transform2D bone_xform = bone->get_global_transform(); - for (List<const Node2D *>::Element *bone_E = bone_ik_list.front(); bone_E; bone_E = bone_E->next()) { - bone_xform = bone_xform * bone->get_transform().affine_inverse(); - const Node2D *parent_bone = bone_E->get(); - if (p_bones_length) { - p_bones_length->push_back(parent_bone->get_global_transform().get_origin().distance_to(bone->get_global_position())); - } - if (p_bones_state) { - p_bones_state->push_back(parent_bone->_edit_get_state()); - } - bone = parent_bone; - } - } - } -} - void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones) { for (List<CanvasItem *>::Element *E = p_canvas_items.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); @@ -942,31 +811,15 @@ void CanvasItemEditor::_save_canvas_item_state(List<CanvasItem *> p_canvas_items } else { se->pre_drag_rect = Rect2(); } - - // If we have a bone, save the state of all nodes in the IK chain - _save_canvas_item_ik_chain(canvas_item, &(se->pre_drag_bones_length), &(se->pre_drag_bones_undo_state)); } } } -void CanvasItemEditor::_restore_canvas_item_ik_chain(CanvasItem *p_canvas_item, const List<Dictionary> *p_bones_state) { - CanvasItem *canvas_item = p_canvas_item; - for (const List<Dictionary>::Element *E = p_bones_state->front(); E; E = E->next()) { - canvas_item = Object::cast_to<CanvasItem>(canvas_item->get_parent()); - canvas_item->_edit_set_state(E->get()); - } -} - void CanvasItemEditor::_restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones) { for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - if (se) { - canvas_item->_edit_set_state(se->undo_state); - if (restore_bones) { - _restore_canvas_item_ik_chain(canvas_item, &(se->pre_drag_bones_undo_state)); - } - } + canvas_item->_edit_set_state(se->undo_state); } } @@ -1269,12 +1122,12 @@ bool CanvasItemEditor::_gui_input_rulers_and_guides(const Ref<InputEvent> &p_eve bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bool p_already_accepted) { Ref<InputEventMouseButton> b = p_event; if (b.is_valid() && !p_already_accepted) { - const bool pan_on_scroll = bool(EditorSettings::get_singleton()->get("editors/2d/scroll_to_pan")) && !b->get_control(); + const bool pan_on_scroll = bool(EditorSettings::get_singleton()->get("editors/2d/scroll_to_pan")) && !b->is_ctrl_pressed(); if (pan_on_scroll) { // Perform horizontal scrolling first so we can check for Shift being held. if (b->is_pressed() && - (b->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || (b->get_shift() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP))) { + (b->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT || (b->is_shift_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP))) { // Pan left view_offset.x -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); @@ -1282,7 +1135,7 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo } if (b->is_pressed() && - (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->get_shift() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN))) { + (b->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT || (b->is_shift_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN))) { // Pan right view_offset.x += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); @@ -1296,11 +1149,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo view_offset.y += int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); } else { - float new_zoom = _get_next_zoom_value(-1); + zoom_widget->set_zoom_by_increments(-1); if (b->get_factor() != 1.f) { - new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f); + zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f)); } - _zoom_on_position(new_zoom, b->get_position()); + _zoom_on_position(zoom_widget->get_zoom(), b->get_position()); } return true; } @@ -1311,11 +1164,11 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo view_offset.y -= int(EditorSettings::get_singleton()->get("editors/2d/pan_speed")) / zoom * b->get_factor(); update_viewport(); } else { - float new_zoom = _get_next_zoom_value(1); + zoom_widget->set_zoom_by_increments(1); if (b->get_factor() != 1.f) { - new_zoom = zoom * ((new_zoom / zoom - 1.f) * b->get_factor() + 1.f); + zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * b->get_factor() + 1.f)); } - _zoom_on_position(new_zoom, b->get_position()); + _zoom_on_position(zoom_widget->get_zoom(), b->get_position()); } return true; } @@ -1388,15 +1241,16 @@ bool CanvasItemEditor::_gui_input_zoom_or_pan(const Ref<InputEvent> &p_event, bo Ref<InputEventPanGesture> pan_gesture = p_event; if (pan_gesture.is_valid() && !p_already_accepted) { - // If control key pressed, then zoom instead of pan - if (pan_gesture->get_control()) { + // If ctrl key pressed, then zoom instead of pan. + if (pan_gesture->is_ctrl_pressed()) { const float factor = pan_gesture->get_delta().y; - float new_zoom = _get_next_zoom_value(-1); + zoom_widget->set_zoom_by_increments(1); if (factor != 1.f) { - new_zoom = zoom * ((new_zoom / zoom - 1.f) * factor + 1.f); + zoom_widget->set_zoom(zoom * ((zoom_widget->get_zoom() / zoom - 1.f) * factor + 1.f)); } - _zoom_on_position(new_zoom, pan_gesture->get_position()); + _zoom_on_position(zoom_widget->get_zoom(), pan_gesture->get_position()); + return true; } @@ -1496,76 +1350,6 @@ bool CanvasItemEditor::_gui_input_pivot(const Ref<InputEvent> &p_event) { return false; } -void CanvasItemEditor::_solve_IK(Node2D *leaf_node, Point2 target_position) { - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(leaf_node); - if (se) { - int nb_bones = se->pre_drag_bones_undo_state.size(); - if (nb_bones > 0) { - // Build the node list - Point2 leaf_pos = target_position; - - List<Node2D *> joints_list; - List<Point2> joints_pos; - Node2D *joint = leaf_node; - Transform2D joint_transform = leaf_node->get_global_transform_with_canvas(); - for (int i = 0; i < nb_bones + 1; i++) { - joints_list.push_back(joint); - joints_pos.push_back(joint_transform.get_origin()); - joint_transform = joint_transform * joint->get_transform().affine_inverse(); - joint = Object::cast_to<Node2D>(joint->get_parent()); - } - Point2 root_pos = joints_list.back()->get()->get_global_transform_with_canvas().get_origin(); - - // Restraints the node to a maximum distance is necessary - float total_len = 0; - for (List<float>::Element *E = se->pre_drag_bones_length.front(); E; E = E->next()) { - total_len += E->get(); - } - if ((root_pos.distance_to(leaf_pos)) > total_len) { - Vector2 rel = leaf_pos - root_pos; - rel = rel.normalized() * total_len; - leaf_pos = root_pos + rel; - } - joints_pos[0] = leaf_pos; - - // Run the solver - int solver_iterations = 64; - float solver_k = 0.3; - - // Build the position list - for (int i = 0; i < solver_iterations; i++) { - // Handle the leaf joint - int node_id = 0; - for (List<float>::Element *E = se->pre_drag_bones_length.front(); E; E = E->next()) { - Vector2 direction = (joints_pos[node_id + 1] - joints_pos[node_id]).normalized(); - int len = E->get(); - if (E == se->pre_drag_bones_length.front()) { - joints_pos[1] = joints_pos[1].lerp(joints_pos[0] + len * direction, solver_k); - } else if (E == se->pre_drag_bones_length.back()) { - joints_pos[node_id] = joints_pos[node_id].lerp(joints_pos[node_id + 1] - len * direction, solver_k); - } else { - Vector2 center = (joints_pos[node_id + 1] + joints_pos[node_id]) / 2.0; - joints_pos[node_id] = joints_pos[node_id].lerp(center - (direction * len) / 2.0, solver_k); - joints_pos[node_id + 1] = joints_pos[node_id + 1].lerp(center + (direction * len) / 2.0, solver_k); - } - node_id++; - } - } - - // Set the position - for (int node_id = joints_list.size() - 1; node_id > 0; node_id--) { - Point2 current = (joints_list[node_id - 1]->get_global_position() - joints_list[node_id]->get_global_position()).normalized(); - Point2 target = (joints_pos[node_id - 1] - joints_list[node_id]->get_global_position()).normalized(); - float rot = current.angle_to(target); - if (joints_list[node_id]->get_global_transform().basis_determinant() < 0) { - rot = -rot; - } - joints_list[node_id]->rotate(rot); - } - } - } -} - bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { Ref<InputEventMouseButton> b = p_event; Ref<InputEventMouseMotion> m = p_event; @@ -1573,7 +1357,7 @@ bool CanvasItemEditor::_gui_input_rotate(const Ref<InputEvent> &p_event) { // Start rotation if (drag_type == DRAG_NONE) { if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { - if ((b->get_command() && !b->get_alt() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) { + if ((b->is_command_pressed() && !b->is_alt_pressed() && tool == TOOL_SELECT) || tool == TOOL_ROTATE) { List<CanvasItem *> selection = _get_edited_canvas_items(); // Remove not movable nodes @@ -1654,7 +1438,7 @@ bool CanvasItemEditor::_gui_input_open_scene_on_double_click(const Ref<InputEven Ref<InputEventMouseButton> b = p_event; // Open a sub-scene on double-click - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && b->is_doubleclick() && tool == TOOL_SELECT) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && b->is_double_click() && tool == TOOL_SELECT) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; @@ -1739,7 +1523,7 @@ bool CanvasItemEditor::_gui_input_anchors(const Ref<InputEvent> &p_event) { Vector2 new_anchor = xform.xform(snap_point(previous_anchor + (drag_to - drag_from), SNAP_GRID | SNAP_OTHER_NODES, SNAP_NODE_PARENT | SNAP_NODE_SIDES | SNAP_NODE_CENTER, control)); new_anchor = _position_to_anchor(control, new_anchor).snapped(Vector2(0.001, 0.001)); - bool use_single_axis = m->get_shift(); + bool use_single_axis = m->is_shift_pressed(); Vector2 drag_vector = xform.xform(drag_to) - xform.xform(drag_from); bool use_y = Math::abs(drag_vector.y) > Math::abs(drag_vector.x); @@ -1887,8 +1671,8 @@ bool CanvasItemEditor::_gui_input_resize(const Ref<InputEvent> &p_event) { //Reset state canvas_item->_edit_set_state(se->undo_state); - bool uniform = m->get_shift(); - bool symmetric = m->get_alt(); + bool uniform = m->is_shift_pressed(); + bool symmetric = m->is_alt_pressed(); Rect2 local_rect = canvas_item->_edit_get_rect(); float aspect = local_rect.get_size().y / local_rect.get_size().x; @@ -2027,7 +1811,7 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { // Drag resize handles if (drag_type == DRAG_NONE) { - if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && ((b->get_alt() && b->get_control()) || tool == TOOL_SCALE)) { + if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_ctrl_pressed()) || tool == TOOL_SCALE)) { List<CanvasItem *> selection = _get_edited_canvas_items(); if (selection.size() == 1) { CanvasItem *canvas_item = selection[0]; @@ -2073,8 +1857,8 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) { Transform2D unscaled_transform = (transform * parent_xform * canvas_item->_edit_get_transform()).orthonormalized(); Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform; - bool uniform = m->get_shift(); - bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL); + bool uniform = m->is_shift_pressed(); + bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); Point2 drag_from_local = simple_xform.xform(drag_from); Point2 drag_to_local = simple_xform.xform(drag_to); @@ -2166,7 +1950,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_NONE) { //Start moving the nodes if (b.is_valid() && b->get_button_index() == MOUSE_BUTTON_LEFT && b->is_pressed()) { - if ((b->get_alt() && !b->get_control()) || tool == TOOL_MOVE) { + if ((b->is_alt_pressed() && !b->is_ctrl_pressed()) || tool == TOOL_MOVE) { List<CanvasItem *> selection = _get_edited_canvas_items(); drag_selection.clear(); @@ -2207,14 +1991,6 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_MOVE || drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) { // Move the nodes if (m.is_valid()) { - // Save the ik chain for reapplying before IK solve - Vector<List<Dictionary>> all_bones_ik_states; - for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { - List<Dictionary> bones_ik_states; - _save_canvas_item_ik_chain(E->get(), nullptr, &bones_ik_states); - all_bones_ik_states.push_back(bones_ik_states); - } - _restore_canvas_item_state(drag_selection, true); drag_to = transform.affine_inverse().xform(m->get_position()); @@ -2234,7 +2010,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { new_pos.x = previous_pos.x; } - bool single_axis = m->get_shift(); + bool single_axis = m->is_shift_pressed(); if (single_axis) { if (ABS(new_pos.x - previous_pos.x) > ABS(new_pos.y - previous_pos.y)) { new_pos.y = previous_pos.y; @@ -2243,25 +2019,12 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } } - bool force_no_IK = m->get_alt(); int index = 0; for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - if (se) { - Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); - - Node2D *node2d = Object::cast_to<Node2D>(canvas_item); - if (node2d && se->pre_drag_bones_undo_state.size() > 0 && !force_no_IK) { - real_t initial_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation(); - _restore_canvas_item_ik_chain(node2d, &(all_bones_ik_states[index])); - real_t final_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation(); - node2d->rotate(initial_leaf_node_rotation - final_leaf_node_rotation); - _solve_IK(node2d, new_pos); - } else { - canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); - } - } + Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); + + canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); index++; } return true; @@ -2324,18 +2087,10 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } if (drag_selection.size() > 0) { - // Save the ik chain for reapplying before IK solve - Vector<List<Dictionary>> all_bones_ik_states; - for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { - List<Dictionary> bones_ik_states; - _save_canvas_item_ik_chain(E->get(), nullptr, &bones_ik_states); - all_bones_ik_states.push_back(bones_ik_states); - } - _restore_canvas_item_state(drag_selection, true); - bool move_local_base = k->get_alt(); - bool move_local_base_rotated = k->get_control() || k->get_metakey(); + bool move_local_base = k->is_alt_pressed(); + bool move_local_base_rotated = k->is_ctrl_pressed() || k->is_meta_pressed(); Vector2 dir; if (k->get_keycode() == KEY_UP) { @@ -2347,12 +2102,12 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { } else if (k->get_keycode() == KEY_RIGHT) { dir += Vector2(1, 0); } - if (k->get_shift()) { + if (k->is_shift_pressed()) { dir *= grid_step * Math::pow(2.0, grid_step_multiplier); } drag_to += dir; - if (k->get_shift()) { + if (k->is_shift_pressed()) { drag_to = drag_to.snapped(grid_step * Math::pow(2.0, grid_step_multiplier)); } @@ -2383,21 +2138,9 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) { int index = 0; for (List<CanvasItem *>::Element *E = drag_selection.front(); E; E = E->next()) { CanvasItem *canvas_item = E->get(); - CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(canvas_item); - if (se) { - Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); - - Node2D *node2d = Object::cast_to<Node2D>(canvas_item); - if (node2d && se->pre_drag_bones_undo_state.size() > 0) { - real_t initial_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation(); - _restore_canvas_item_ik_chain(node2d, &(all_bones_ik_states[index])); - real_t final_leaf_node_rotation = node2d->get_global_transform_with_canvas().get_rotation(); - node2d->rotate(initial_leaf_node_rotation - final_leaf_node_rotation); - _solve_IK(node2d, new_pos); - } else { - canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); - } - } + Transform2D xform = canvas_item->get_global_transform_with_canvas().affine_inverse() * canvas_item->get_transform(); + + canvas_item->_edit_set_position(canvas_item->_edit_get_position() + xform.xform(new_pos) - xform.xform(previous_pos)); index++; } } @@ -2441,18 +2184,18 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { if (drag_type == DRAG_NONE) { if (b.is_valid() && - ((b->get_button_index() == MOUSE_BUTTON_RIGHT && b->get_alt() && tool == TOOL_SELECT) || + ((b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_alt_pressed() && tool == TOOL_SELECT) || (b->get_button_index() == MOUSE_BUTTON_LEFT && tool == TOOL_LIST_SELECT))) { // Popup the selection menu list Point2 click = transform.affine_inverse().xform(b->get_position()); - _get_canvas_items_at_pos(click, selection_results, b->get_alt() && tool != TOOL_LIST_SELECT); + _get_canvas_items_at_pos(click, selection_results, b->is_alt_pressed() && tool != TOOL_LIST_SELECT); if (selection_results.size() == 1) { CanvasItem *item = selection_results[0].item; selection_results.clear(); - _select_click_on_item(item, click, b->get_shift()); + _select_click_on_item(item, click, b->is_shift_pressed()); return true; } else if (!selection_results.is_empty()) { @@ -2496,14 +2239,14 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { selection_menu->set_item_tooltip(i, String(item->get_name()) + "\nType: " + item->get_class() + "\nPath: " + node_path); } - selection_menu_additive_selection = b->get_shift(); + selection_menu_additive_selection = b->is_shift_pressed(); selection_menu->set_position(get_screen_transform().xform(b->get_position())); selection_menu->popup(); return true; } } - if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->get_control()) { + if (b.is_valid() && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_RIGHT && b->is_ctrl_pressed()) { add_node_menu->set_position(get_global_transform().xform(get_local_mouse_position())); add_node_menu->set_size(Vector2(1, 1)); add_node_menu->popup(); @@ -2523,23 +2266,17 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { // Find the item to select CanvasItem *canvas_item = nullptr; - // Retrieve the bones Vector<_SelectResult> selection = Vector<_SelectResult>(); - _get_bones_at_pos(click, selection); + // Retrieve the canvas items + selection = Vector<_SelectResult>(); + _get_canvas_items_at_pos(click, selection); if (!selection.is_empty()) { canvas_item = selection[0].item; - } else { - // Retrieve the canvas items - selection = Vector<_SelectResult>(); - _get_canvas_items_at_pos(click, selection); - if (!selection.is_empty()) { - canvas_item = selection[0].item; - } } if (!canvas_item) { // Start a box selection - if (!b->get_shift()) { + if (!b->is_shift_pressed()) { // Clear the selection if not additive editor_selection->clear(); viewport->update(); @@ -2551,26 +2288,42 @@ bool CanvasItemEditor::_gui_input_select(const Ref<InputEvent> &p_event) { box_selecting_to = drag_from; return true; } else { - bool still_selected = _select_click_on_item(canvas_item, click, b->get_shift()); + bool still_selected = _select_click_on_item(canvas_item, click, b->is_shift_pressed()); // Start dragging if (still_selected) { // Drag the node(s) if requested - List<CanvasItem *> selection2 = _get_edited_canvas_items(); + drag_start_origin = click; + drag_type = DRAG_QUEUED; + } + // Select the item + return true; + } + } + } - drag_selection.clear(); - for (int i = 0; i < selection2.size(); i++) { - if (_is_node_movable(selection2[i], true)) { - drag_selection.push_back(selection2[i]); - } - } + if (drag_type == DRAG_QUEUED) { + if (b.is_valid() && !b->is_pressed()) { + drag_type = DRAG_NONE; + return true; + } + if (m.is_valid()) { + Point2 click = transform.affine_inverse().xform(m->get_position()); + bool movement_threshold_passed = drag_start_origin.distance_to(click) > 10 * EDSCALE; + if (m.is_valid() && movement_threshold_passed) { + List<CanvasItem *> selection2 = _get_edited_canvas_items(); - if (selection2.size() > 0) { - drag_type = DRAG_MOVE; - drag_from = click; - _save_canvas_item_state(drag_selection); + drag_selection.clear(); + for (int i = 0; i < selection2.size(); i++) { + if (_is_node_movable(selection2[i], true)) { + drag_selection.push_back(selection2[i]); } } - // Select the item + + if (selection2.size() > 0) { + drag_type = DRAG_MOVE; + drag_from = click; + _save_canvas_item_state(drag_selection); + } return true; } } @@ -3573,7 +3326,7 @@ void CanvasItemEditor::_draw_selection() { } // Draw the move handles - bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL); + bool is_ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); bool is_alt = Input::get_singleton()->is_key_pressed(KEY_ALT); if (tool == TOOL_MOVE && show_transformation_gizmos) { if (_is_node_movable(canvas_item)) { @@ -3733,65 +3486,6 @@ void CanvasItemEditor::_draw_axis() { } } -void CanvasItemEditor::_draw_bones() { - RID ci = viewport->get_canvas_item(); - - if (skeleton_show_bones) { - Color bone_color1 = EditorSettings::get_singleton()->get("editors/2d/bone_color1"); - Color bone_color2 = EditorSettings::get_singleton()->get("editors/2d/bone_color2"); - Color bone_ik_color = EditorSettings::get_singleton()->get("editors/2d/bone_ik_color"); - Color bone_outline_color = EditorSettings::get_singleton()->get("editors/2d/bone_outline_color"); - Color bone_selected_color = EditorSettings::get_singleton()->get("editors/2d/bone_selected_color"); - - for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { - Vector<Vector2> bone_shape; - Vector<Vector2> bone_shape_outline; - if (!_get_bone_shape(&bone_shape, &bone_shape_outline, E)) { - continue; - } - - Node2D *from_node = Object::cast_to<Node2D>(ObjectDB::get_instance(E->key().from)); - if (!from_node->is_visible_in_tree()) { - continue; - } - - Vector<Color> colors; - if (from_node->has_meta("_edit_ik_")) { - colors.push_back(bone_ik_color); - colors.push_back(bone_ik_color); - colors.push_back(bone_ik_color); - colors.push_back(bone_ik_color); - } else { - colors.push_back(bone_color1); - colors.push_back(bone_color2); - colors.push_back(bone_color1); - colors.push_back(bone_color2); - } - - Vector<Color> outline_colors; - - if (editor_selection->is_selected(from_node)) { - outline_colors.push_back(bone_selected_color); - outline_colors.push_back(bone_selected_color); - outline_colors.push_back(bone_selected_color); - outline_colors.push_back(bone_selected_color); - outline_colors.push_back(bone_selected_color); - outline_colors.push_back(bone_selected_color); - } else { - outline_colors.push_back(bone_outline_color); - outline_colors.push_back(bone_outline_color); - outline_colors.push_back(bone_outline_color); - outline_colors.push_back(bone_outline_color); - outline_colors.push_back(bone_outline_color); - outline_colors.push_back(bone_outline_color); - } - - RenderingServer::get_singleton()->canvas_item_add_polygon(ci, bone_shape_outline, outline_colors); - RenderingServer::get_singleton()->canvas_item_add_primitive(ci, bone_shape, colors, Vector<Vector2>(), RID()); - } - } -} - void CanvasItemEditor::_draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform, const Transform2D &p_canvas_xform) { ERR_FAIL_COND(!p_node); @@ -3907,72 +3601,6 @@ void CanvasItemEditor::_draw_locks_and_groups(Node *p_node, const Transform2D &p } } -bool CanvasItemEditor::_build_bones_list(Node *p_node) { - ERR_FAIL_COND_V(!p_node, false); - - bool has_child_bones = false; - - for (int i = 0; i < p_node->get_child_count(); i++) { - if (_build_bones_list(p_node->get_child(i))) { - has_child_bones = true; - } - } - - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(p_node); - Node *scene = editor->get_edited_scene(); - if (!canvas_item || !canvas_item->is_visible() || (canvas_item != scene && canvas_item->get_owner() != scene && canvas_item != scene->get_deepest_editable_node(canvas_item))) { - return false; - } - - Node *parent = canvas_item->get_parent(); - - if (Object::cast_to<Bone2D>(canvas_item)) { - if (Object::cast_to<Bone2D>(parent)) { - // Add as bone->parent relationship - BoneKey bk; - bk.from = parent->get_instance_id(); - bk.to = canvas_item->get_instance_id(); - if (!bone_list.has(bk)) { - BoneList b; - b.length = 0; - bone_list[bk] = b; - } - - bone_list[bk].last_pass = bone_last_frame; - } - - if (!has_child_bones) { - // Add a last bone if the Bone2D has no Bone2D child - BoneKey bk; - bk.from = canvas_item->get_instance_id(); - bk.to = ObjectID(); - if (!bone_list.has(bk)) { - BoneList b; - b.length = 0; - bone_list[bk] = b; - } - bone_list[bk].last_pass = bone_last_frame; - } - - return true; - } - - if (canvas_item->has_meta("_edit_bone_")) { - // Add a "custom bone" - BoneKey bk; - bk.from = parent->get_instance_id(); - bk.to = canvas_item->get_instance_id(); - if (!bone_list.has(bk)) { - BoneList b; - b.length = 0; - bone_list[bk] = b; - } - bone_list[bk].last_pass = bone_last_frame; - } - - return false; -} - void CanvasItemEditor::_draw_viewport() { // Update the transform transform = Transform2D(); @@ -4032,7 +3660,6 @@ void CanvasItemEditor::_draw_viewport() { force_over_plugin_list->forward_canvas_force_draw_over_viewport(viewport); } - _draw_bones(); if (show_rulers) { _draw_rulers(); } @@ -4158,8 +3785,8 @@ void CanvasItemEditor::_notification(int p_what) { } Bone2D *bone = Object::cast_to<Bone2D>(b); - if (bone && bone->get_default_length() != E->get().length) { - E->get().length = bone->get_default_length(); + if (bone && bone->get_length() != E->get().length) { + E->get().length = bone->get_length(); viewport->update(); } } @@ -4174,18 +3801,11 @@ void CanvasItemEditor::_notification(int p_what) { AnimationPlayerEditor::singleton->get_track_editor()->connect("visibility_changed", callable_mp(this, &CanvasItemEditor::_keying_changed)); _keying_changed(); - get_tree()->connect("node_added", callable_mp(this, &CanvasItemEditor::_tree_changed), varray()); - get_tree()->connect("node_removed", callable_mp(this, &CanvasItemEditor::_tree_changed), varray()); } else if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { select_sb->set_texture(get_theme_icon("EditorRect2D", "EditorIcons")); } - if (p_what == NOTIFICATION_EXIT_TREE) { - get_tree()->disconnect("node_added", callable_mp(this, &CanvasItemEditor::_tree_changed)); - get_tree()->disconnect("node_removed", callable_mp(this, &CanvasItemEditor::_tree_changed)); - } - if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { select_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); list_select_button->set_icon(get_theme_icon("ListSelect", "EditorIcons")); @@ -4219,9 +3839,6 @@ void CanvasItemEditor::_notification(int p_what) { key_auto_insert_button->add_theme_color_override("icon_pressed_color", key_auto_color.lerp(Color(1, 0, 0), 0.55)); animation_menu->set_icon(get_theme_icon("GuiTabMenuHl", "EditorIcons")); - zoom_minus->set_icon(get_theme_icon("ZoomLess", "EditorIcons")); - zoom_plus->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); - presets_menu->set_icon(get_theme_icon("ControlLayout", "EditorIcons")); PopupMenu *p = presets_menu->get_popup(); @@ -4323,46 +3940,6 @@ void CanvasItemEditor::edit(CanvasItem *p_canvas_item) { } } -void CanvasItemEditor::_queue_update_bone_list() { - if (bone_list_dirty) { - return; - } - - call_deferred("_update_bone_list"); - bone_list_dirty = true; -} - -void CanvasItemEditor::_update_bone_list() { - bone_last_frame++; - - if (editor->get_edited_scene()) { - _build_bones_list(editor->get_edited_scene()); - } - - List<Map<BoneKey, BoneList>::Element *> bone_to_erase; - for (Map<BoneKey, BoneList>::Element *E = bone_list.front(); E; E = E->next()) { - if (E->get().last_pass != bone_last_frame) { - bone_to_erase.push_back(E); - continue; - } - - Node *node = Object::cast_to<Node>(ObjectDB::get_instance(E->key().from)); - if (!node || !node->is_inside_tree() || (node != get_tree()->get_edited_scene_root() && !get_tree()->get_edited_scene_root()->is_a_parent_of(node))) { - bone_to_erase.push_back(E); - continue; - } - } - while (bone_to_erase.size()) { - bone_list.erase(bone_to_erase.front()->get()); - bone_to_erase.pop_front(); - } - bone_list_dirty = false; -} - -void CanvasItemEditor::_tree_changed(Node *) { - _queue_update_bone_list(); -} - void CanvasItemEditor::_update_scrollbars() { updating_scroll = true; @@ -4378,11 +3955,9 @@ void CanvasItemEditor::_update_scrollbars() { Size2 screen_rect = Size2(ProjectSettings::get_singleton()->get("display/window/size/width"), ProjectSettings::get_singleton()->get("display/window/size/height")); Rect2 local_rect = Rect2(Point2(), viewport->get_size() - Size2(vmin.width, hmin.height)); - _queue_update_bone_list(); - // Calculate scrollable area. Rect2 canvas_item_rect = Rect2(Point2(), screen_rect); - if (editor->get_edited_scene()) { + if (editor->is_inside_tree() && editor->get_edited_scene()) { Rect2 content_rect = _get_encompassing_rect(editor->get_edited_scene()); canvas_item_rect.expand_to(content_rect.position); canvas_item_rect.expand_to(content_rect.position + content_rect.size); @@ -4579,33 +4154,6 @@ void CanvasItemEditor::_set_anchors_preset(Control::LayoutPreset p_preset) { undo_redo->commit_action(); } -float CanvasItemEditor::_get_next_zoom_value(int p_increment_count) const { - // Base increment factor defined as the twelveth root of two. - // This allow a smooth geometric evolution of the zoom, with the advantage of - // visiting all integer power of two scale factors. - // note: this is analogous to the 'semitones' interval in the music world - // In order to avoid numerical imprecisions, we compute and edit a zoom index - // with the following relation: zoom = 2 ^ (index / 12) - - if (zoom < CMP_EPSILON || p_increment_count == 0) { - return 1.f; - } - - // Remove Editor scale from the index computation - float zoom_noscale = zoom / MAX(1, EDSCALE); - - // zoom = 2**(index/12) => log2(zoom) = index/12 - float closest_zoom_index = Math::round(Math::log(zoom_noscale) * 12.f / Math::log(2.f)); - - float new_zoom_index = closest_zoom_index + p_increment_count; - float new_zoom = Math::pow(2.f, new_zoom_index / 12.f); - - // Restore Editor scale transformation - new_zoom *= MAX(1, EDSCALE); - - return new_zoom; -} - void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) { p_zoom = CLAMP(p_zoom, MIN_ZOOM, MAX_ZOOM); @@ -4630,36 +4178,12 @@ void CanvasItemEditor::_zoom_on_position(float p_zoom, Point2 p_position) { view_offset = view_offset_int + (view_offset_frac * closest_zoom_factor).round() / closest_zoom_factor; } - _update_zoom_label(); + zoom_widget->set_zoom(zoom); update_viewport(); } -void CanvasItemEditor::_update_zoom_label() { - String zoom_text; - // The zoom level displayed is relative to the editor scale - // (like in most image editors). Its lower bound is clamped to 1 as some people - // lower the editor scale to increase the available real estate, - // even if their display doesn't have a particularly low DPI. - if (zoom >= 10) { - // Don't show a decimal when the zoom level is higher than 1000 %. - zoom_text = TS->format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100))) + " " + TS->percent_sign(); - } else { - zoom_text = TS->format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, 0.1))) + " " + TS->percent_sign(); - } - - zoom_reset->set_text(zoom_text); -} - -void CanvasItemEditor::_button_zoom_minus() { - _zoom_on_position(_get_next_zoom_value(-6), viewport_scrollable->get_size() / 2.0); -} - -void CanvasItemEditor::_button_zoom_reset() { - _zoom_on_position(1.0 * MAX(1, EDSCALE), viewport_scrollable->get_size() / 2.0); -} - -void CanvasItemEditor::_button_zoom_plus() { - _zoom_on_position(_get_next_zoom_value(6), viewport_scrollable->get_size() / 2.0); +void CanvasItemEditor::_update_zoom(float p_zoom) { + _zoom_on_position(p_zoom, viewport_scrollable->get_size() / 2.0); } void CanvasItemEditor::_button_toggle_smart_snap(bool p_status) { @@ -4718,7 +4242,7 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, AnimationPlayerEditor::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_degrees", Math::rad2deg(n2d->get_rotation()), p_on_existing); + AnimationPlayerEditor::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); @@ -4750,7 +4274,7 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "position", F->get()->get_position(), p_on_existing); } if (key_rot) { - AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation_degrees", Math::rad2deg(F->get()->get_rotation()), p_on_existing); + AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "rotation", F->get()->get_rotation(), p_on_existing); } if (key_scale) { AnimationPlayerEditor::singleton->get_track_editor()->insert_node_value_key(F->get(), "scale", F->get()->get_scale(), p_on_existing); @@ -4766,7 +4290,7 @@ void CanvasItemEditor::_insert_animation_keys(bool p_location, bool p_rotation, AnimationPlayerEditor::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_degrees(), p_on_existing); + AnimationPlayerEditor::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); @@ -4793,11 +4317,11 @@ void CanvasItemEditor::_button_toggle_anchor_mode(bool p_status) { void CanvasItemEditor::_update_override_camera_button(bool p_game_running) { if (p_game_running) { override_camera_button->set_disabled(false); - override_camera_button->set_tooltip(TTR("Game Camera Override\nOverrides game camera with editor viewport camera.")); + override_camera_button->set_tooltip(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera.")); } else { override_camera_button->set_disabled(true); override_camera_button->set_pressed(false); - override_camera_button->set_tooltip(TTR("Game Camera Override\nNo game instance running.")); + override_camera_button->set_tooltip(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature.")); } } @@ -4890,10 +4414,19 @@ void CanvasItemEditor::_popup_callback(int p_op) { snap_dialog->popup_centered(Size2(220, 160) * EDSCALE); } break; case SKELETON_SHOW_BONES: { - skeleton_show_bones = !skeleton_show_bones; - int idx = skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES); - skeleton_menu->get_popup()->set_item_checked(idx, skeleton_show_bones); - viewport->update(); + List<Node *> selection = editor_selection->get_selected_node_list(); + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + // Add children nodes so they are processed + for (int child = 0; child < E->get()->get_child_count(); child++) { + selection.push_back(E->get()->get_child(child)); + } + + Bone2D *bone_2d = Object::cast_to<Bone2D>(E->get()); + if (!bone_2d || !bone_2d->is_inside_tree()) { + continue; + } + bone_2d->_editor_set_show_bone_gizmo(!bone_2d->_editor_get_show_bone_gizmo()); + } } break; case SHOW_HELPERS: { show_helpers = !show_helpers; @@ -5242,107 +4775,45 @@ void CanvasItemEditor::_popup_callback(int p_op) { } break; case SKELETON_MAKE_BONES: { Map<Node *, Object *> &selection = editor_selection->get_selection(); + Node *editor_root = EditorNode::get_singleton()->get_edited_scene()->get_tree()->get_edited_scene_root(); - undo_redo->create_action(TTR("Create Custom Bone(s) from Node(s)")); + 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()); - if (!n2d) { - continue; - } - if (!n2d->is_visible_in_tree()) { - continue; - } - if (!n2d->get_parent_item()) { - continue; - } - if (n2d->has_meta("_edit_bone_") && n2d->get_meta("_edit_bone_")) { - continue; - } - - undo_redo->add_do_method(n2d, "set_meta", "_edit_bone_", true); - undo_redo->add_undo_method(n2d, "remove_meta", "_edit_bone_"); - } - undo_redo->add_do_method(this, "_queue_update_bone_list"); - undo_redo->add_undo_method(this, "_queue_update_bone_list"); - undo_redo->add_do_method(viewport, "update"); - undo_redo->add_undo_method(viewport, "update"); - undo_redo->commit_action(); - } break; - case SKELETON_CLEAR_BONES: { - Map<Node *, Object *> &selection = editor_selection->get_selection(); + Bone2D *new_bone = memnew(Bone2D); + String new_bone_name = n2d->get_name(); + new_bone_name += "Bone2D"; + new_bone->set_name(new_bone_name); + new_bone->set_transform(n2d->get_transform()); - undo_redo->create_action(TTR("Clear Bones")); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - Node2D *n2d = Object::cast_to<Node2D>(E->key()); - if (!n2d) { - continue; - } - if (!n2d->is_visible_in_tree()) { + Node *n2d_parent = n2d->get_parent(); + if (!n2d_parent) { continue; } - if (!n2d->has_meta("_edit_bone_")) { - continue; - } - - undo_redo->add_do_method(n2d, "remove_meta", "_edit_bone_"); - undo_redo->add_undo_method(n2d, "set_meta", "_edit_bone_", n2d->get_meta("_edit_bone_")); - } - undo_redo->add_do_method(this, "_queue_update_bone_list"); - undo_redo->add_undo_method(this, "_queue_update_bone_list"); - undo_redo->add_do_method(viewport, "update"); - undo_redo->add_undo_method(viewport, "update"); - undo_redo->commit_action(); - } break; - case SKELETON_SET_IK_CHAIN: { - List<Node *> selection = editor_selection->get_selected_node_list(); + undo_redo->add_do_method(n2d_parent, "add_child", new_bone); + undo_redo->add_do_method(n2d_parent, "remove_child", n2d); + undo_redo->add_do_method(new_bone, "add_child", n2d); + undo_redo->add_do_method(n2d, "set_transform", Transform2D()); + undo_redo->add_do_method(this, "_set_owner_for_node_and_children", new_bone, editor_root); - undo_redo->create_action(TTR("Make IK Chain")); - for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *canvas_item = Object::cast_to<CanvasItem>(E->get()); - if (!canvas_item || !canvas_item->is_visible_in_tree()) { - continue; - } - if (canvas_item->get_viewport() != EditorNode::get_singleton()->get_scene_root()) { - continue; - } - if (canvas_item->has_meta("_edit_ik_") && canvas_item->get_meta("_edit_ik_")) { - continue; - } - - undo_redo->add_do_method(canvas_item, "set_meta", "_edit_ik_", true); - undo_redo->add_undo_method(canvas_item, "remove_meta", "_edit_ik_"); + undo_redo->add_undo_method(new_bone, "remove_child", n2d); + undo_redo->add_undo_method(n2d_parent, "add_child", n2d); + undo_redo->add_undo_method(n2d, "set_transform", new_bone->get_transform()); + undo_redo->add_undo_method(new_bone, "queue_free"); + undo_redo->add_undo_method(this, "_set_owner_for_node_and_children", n2d, editor_root); } - undo_redo->add_do_method(viewport, "update"); - undo_redo->add_undo_method(viewport, "update"); undo_redo->commit_action(); } break; - case SKELETON_CLEAR_IK_CHAIN: { - Map<Node *, Object *> &selection = editor_selection->get_selection(); - - undo_redo->create_action(TTR("Clear IK Chain")); - for (Map<Node *, Object *>::Element *E = selection.front(); E; E = E->next()) { - CanvasItem *n2d = Object::cast_to<CanvasItem>(E->key()); - if (!n2d) { - continue; - } - if (!n2d->is_visible_in_tree()) { - continue; - } - if (!n2d->has_meta("_edit_ik_")) { - continue; - } - - undo_redo->add_do_method(n2d, "remove_meta", "_edit_ik_"); - undo_redo->add_undo_method(n2d, "set_meta", "_edit_ik_", n2d->get_meta("_edit_ik_")); - } - undo_redo->add_do_method(viewport, "update"); - undo_redo->add_undo_method(viewport, "update"); - undo_redo->commit_action(); + } +} - } break; +void CanvasItemEditor::_set_owner_for_node_and_children(Node *p_node, Node *p_owner) { + p_node->set_owner(p_owner); + for (int i = 0; i < p_node->get_child_count(); i++) { + _set_owner_for_node_and_children(p_node->get_child(i), p_owner); } } @@ -5401,7 +4872,7 @@ void CanvasItemEditor::_focus_selection(int p_op) { zoom = scale_x < scale_y ? scale_x : scale_y; zoom *= 0.90; viewport->update(); - _update_zoom_label(); + zoom_widget->set_zoom(zoom); call_deferred("_popup_callback", VIEW_CENTER_TO_SELECTION); } } @@ -5411,11 +4882,11 @@ void CanvasItemEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_override_camera_button", "game_running"), &CanvasItemEditor::_update_override_camera_button); ClassDB::bind_method("_get_editor_data", &CanvasItemEditor::_get_editor_data); ClassDB::bind_method("_unhandled_key_input", &CanvasItemEditor::_unhandled_key_input); - ClassDB::bind_method("_queue_update_bone_list", &CanvasItemEditor::_update_bone_list); - ClassDB::bind_method("_update_bone_list", &CanvasItemEditor::_update_bone_list); - ClassDB::bind_method("_reset_create_position", &CanvasItemEditor::_reset_create_position); ClassDB::bind_method(D_METHOD("set_state"), &CanvasItemEditor::set_state); ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport); + ClassDB::bind_method(D_METHOD("_zoom_on_position"), &CanvasItemEditor::_zoom_on_position); + + ClassDB::bind_method("_set_owner_for_node_and_children", &CanvasItemEditor::_set_owner_for_node_and_children); ADD_SIGNAL(MethodInfo("item_lock_status_changed")); ADD_SIGNAL(MethodInfo("item_group_status_changed")); @@ -5446,14 +4917,13 @@ Dictionary CanvasItemEditor::get_state() const { state["show_rulers"] = show_rulers; state["show_guides"] = show_guides; state["show_helpers"] = show_helpers; - state["show_zoom_control"] = zoom_hb->is_visible(); + state["show_zoom_control"] = zoom_widget->is_visible(); state["show_edit_locks"] = show_edit_locks; state["show_transformation_gizmos"] = show_transformation_gizmos; state["snap_rotation"] = snap_rotation; state["snap_scale"] = snap_scale; state["snap_relative"] = snap_relative; state["snap_pixel"] = snap_pixel; - state["skeleton_show_bones"] = skeleton_show_bones; return state; } @@ -5464,7 +4934,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { // Compensate the editor scale, so that the editor scale can be changed // and the zoom level will still be the same (relative to the editor scale). zoom = float(p_state["zoom"]) * MAX(1, EDSCALE); - _update_zoom_label(); + zoom_widget->set_zoom(zoom); } if (state.has("ofs")) { @@ -5594,7 +5064,7 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { if (state.has("show_zoom_control")) { // This one is not user-controllable, but instrumentable - zoom_hb->set_visible(state["show_zoom_control"]); + zoom_widget->set_visible(state["show_zoom_control"]); } if (state.has("snap_rotation")) { @@ -5621,12 +5091,6 @@ void CanvasItemEditor::set_state(const Dictionary &p_state) { snap_config_menu->get_popup()->set_item_checked(idx, snap_pixel); } - if (state.has("skeleton_show_bones")) { - skeleton_show_bones = state["skeleton_show_bones"]; - int idx = skeleton_menu->get_popup()->get_item_index(SKELETON_SHOW_BONES); - skeleton_menu->get_popup()->set_item_checked(idx, skeleton_show_bones); - } - if (update_scrollbars) { _update_scrollbars(); } @@ -5709,8 +5173,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { selected_from_canvas = false; anchors_mode = false; - skeleton_show_bones = true; - drag_type = DRAG_NONE; drag_from = Vector2(); drag_to = Vector2(); @@ -5726,7 +5188,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { bone_last_frame = 0; - bone_list_dirty = false; tool = TOOL_SELECT; undo_redo = p_editor->get_undo_redo(); editor = p_editor; @@ -5770,11 +5231,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { controls_vb = memnew(VBoxContainer); controls_vb->set_begin(Point2(5, 5)); - zoom_hb = memnew(HBoxContainer); - // Bring the zoom percentage closer to the zoom buttons - zoom_hb->add_theme_constant_override("separation", Math::round(-8 * EDSCALE)); - controls_vb->add_child(zoom_hb); - viewport = memnew(CanvasItemEditorViewport(p_editor, this)); viewport_scrollable->add_child(viewport); viewport->set_mouse_filter(MOUSE_FILTER_PASS); @@ -5821,38 +5277,20 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { viewport->add_child(controls_vb); - zoom_minus = memnew(Button); - zoom_minus->set_flat(true); - zoom_hb->add_child(zoom_minus); - zoom_minus->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_minus)); - zoom_minus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_minus", TTR("Zoom Out"), KEY_MASK_CMD | KEY_MINUS)); - zoom_minus->set_shortcut_context(this); - zoom_minus->set_focus_mode(FOCUS_NONE); - - zoom_reset = memnew(Button); - zoom_reset->set_flat(true); - zoom_hb->add_child(zoom_reset); - zoom_reset->add_theme_constant_override("outline_size", 1); - zoom_reset->add_theme_color_override("font_outline_color", Color(0, 0, 0)); - zoom_reset->add_theme_color_override("font_color", Color(1, 1, 1)); - zoom_reset->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_reset)); - zoom_reset->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_reset", TTR("Zoom Reset"), KEY_MASK_CMD | KEY_0)); - zoom_reset->set_shortcut_context(this); - zoom_reset->set_focus_mode(FOCUS_NONE); - zoom_reset->set_text_align(Button::TextAlign::ALIGN_CENTER); - // Prevent the button's size from changing when the text size changes - zoom_reset->set_custom_minimum_size(Size2(75 * EDSCALE, 0)); - - zoom_plus = memnew(Button); - zoom_plus->set_flat(true); - zoom_hb->add_child(zoom_plus); - zoom_plus->connect("pressed", callable_mp(this, &CanvasItemEditor::_button_zoom_plus)); - zoom_plus->set_shortcut(ED_SHORTCUT("canvas_item_editor/zoom_plus", TTR("Zoom In"), KEY_MASK_CMD | KEY_EQUAL)); // Usually direct access key for PLUS - zoom_plus->set_shortcut_context(this); - zoom_plus->set_focus_mode(FOCUS_NONE); + zoom_widget = memnew(EditorZoomWidget); + controls_vb->add_child(zoom_widget); + zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); + zoom_widget->connect("zoom_changed", callable_mp(this, &CanvasItemEditor::_update_zoom)); updating_scroll = false; + // Add some margin to the left for better aesthetics. + // This prevents the first button's hover/pressed effect from "touching" the panel's border, + // which looks ugly. + Control *margin_left = memnew(Control); + hb->add_child(margin_left); + margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE); + select_button = memnew(Button); select_button->set_flat(true); hb->add_child(select_button); @@ -5985,24 +5423,32 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { lock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(LOCK_SELECTED)); lock_button->set_tooltip(TTR("Lock the selected object in place (can't be moved).")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + lock_button->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KEY_MASK_CMD | KEY_L)); unlock_button = memnew(Button); unlock_button->set_flat(true); hb->add_child(unlock_button); unlock_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(UNLOCK_SELECTED)); unlock_button->set_tooltip(TTR("Unlock the selected object (can be moved).")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + unlock_button->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_L)); group_button = memnew(Button); group_button->set_flat(true); hb->add_child(group_button); group_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(GROUP_SELECTED)); group_button->set_tooltip(TTR("Makes sure the object's children are not selectable.")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + group_button->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KEY_MASK_CMD | KEY_G)); ungroup_button = memnew(Button); ungroup_button->set_flat(true); hb->add_child(ungroup_button); ungroup_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(UNGROUP_SELECTED)); ungroup_button->set_tooltip(TTR("Restores the object's children's ability to be selected.")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + ungroup_button->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G)); hb->add_child(memnew(VSeparator)); @@ -6014,13 +5460,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p = skeleton_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTR("Show Bones")), SKELETON_SHOW_BONES); - p->add_separator(); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_set_ik_chain", TTR("Make IK Chain")), SKELETON_SET_IK_CHAIN); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_clear_ik_chain", TTR("Clear IK Chain")), SKELETON_CLEAR_IK_CHAIN); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_show_bones", TTR("Show Bones")), SKELETON_SHOW_BONES); p->add_separator(); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Custom Bone(s) from Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B), SKELETON_MAKE_BONES); - p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_clear_bones", TTR("Clear Custom Bones")), SKELETON_CLEAR_BONES); + p->add_shortcut(ED_SHORTCUT("canvas_item_editor/skeleton_make_bones", TTR("Make Bone2D Node(s) from Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_B), SKELETON_MAKE_BONES); p->connect("id_pressed", callable_mp(this, &CanvasItemEditor::_popup_callback)); hb->add_child(memnew(VSeparator)); @@ -6044,7 +5486,7 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p = view_menu->get_popup(); p->set_hide_on_checkable_item_selection(false); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_MASK_CTRL | KEY_G), SHOW_GRID); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Always Show Grid"), KEY_NUMBERSIGN), SHOW_GRID); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers")), SHOW_RULERS); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES); @@ -6089,7 +5531,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_loc_button = memnew(Button); key_loc_button->set_toggle_mode(true); - key_loc_button->set_flat(true); key_loc_button->set_pressed(true); key_loc_button->set_focus_mode(FOCUS_NONE); key_loc_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_POS)); @@ -6098,7 +5539,6 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_rot_button = memnew(Button); key_rot_button->set_toggle_mode(true); - key_rot_button->set_flat(true); key_rot_button->set_pressed(true); key_rot_button->set_focus_mode(FOCUS_NONE); key_rot_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_ROT)); @@ -6107,14 +5547,12 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { key_scale_button = memnew(Button); key_scale_button->set_toggle_mode(true); - key_scale_button->set_flat(true); key_scale_button->set_focus_mode(FOCUS_NONE); key_scale_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_SCALE)); key_scale_button->set_tooltip(TTR("Scale mask for inserting keys.")); animation_hb->add_child(key_scale_button); key_insert_button = memnew(Button); - key_insert_button->set_flat(true); key_insert_button->set_focus_mode(FOCUS_NONE); key_insert_button->connect("pressed", callable_mp(this, &CanvasItemEditor::_popup_callback), varray(ANIM_INSERT_KEY)); key_insert_button->set_tooltip(TTR("Insert keys (based on mask).")); @@ -6193,12 +5631,12 @@ void CanvasItemEditorPlugin::make_visible(bool p_visible) { if (p_visible) { canvas_item_editor->show(); canvas_item_editor->set_physics_process(true); - RenderingServer::get_singleton()->viewport_set_hide_canvas(editor->get_scene_root()->get_viewport_rid(), false); + RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), false); } else { canvas_item_editor->hide(); canvas_item_editor->set_physics_process(false); - RenderingServer::get_singleton()->viewport_set_hide_canvas(editor->get_scene_root()->get_viewport_rid(), true); + RenderingServer::get_singleton()->viewport_set_disable_2d(editor->get_scene_root()->get_viewport_rid(), true); } } @@ -6268,7 +5706,7 @@ void CanvasItemEditorViewport::_create_preview(const Vector<String> &files) cons label_desc->show(); } else { if (scene.is_valid()) { - Node *instance = scene->instance(); + Node *instance = scene->instantiate(); if (instance) { preview_node->add_child(instance); } @@ -6381,26 +5819,26 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons return false; } - Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instanced_scene) { // error on instancing + Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!instantiated_scene) { // error on instancing return false; } if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instanced_scene)) { - memdelete(instanced_scene); + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + memdelete(instantiated_scene); return false; } } - instanced_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); - editor_data->get_undo_redo().add_do_method(parent, "add_child", instanced_scene); - editor_data->get_undo_redo().add_do_method(instanced_scene, "set_owner", editor->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(instanced_scene); - editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene); + editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); + editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); + editor_data->get_undo_redo().add_do_reference(instantiated_scene); + editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instantiated_scene); - String new_name = parent->validate_child_name(instanced_scene); + String new_name = parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); @@ -6411,11 +5849,11 @@ bool CanvasItemEditorViewport::_create_instance(Node *parent, String &path, cons target_pos = canvas_item_editor->snap_point(target_pos); target_pos = parent_ci->get_global_transform_with_canvas().affine_inverse().xform(target_pos); // Preserve instance position of the original scene. - CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instanced_scene); + CanvasItem *instance_ci = Object::cast_to<CanvasItem>(instantiated_scene); if (instance_ci) { target_pos += instance_ci->_edit_get_position(); } - editor_data->get_undo_redo().add_do_method(instanced_scene, "set_position", target_pos); + editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_position", target_pos); } return true; @@ -6498,7 +5936,7 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian if (d.has("type")) { if (String(d["type"]) == "files") { Vector<String> files = d["files"]; - bool can_instance = false; + bool can_instantiate = false; for (int i = 0; i < files.size(); i++) { // check if dragged files contain resource or scene can be created at least once RES res = ResourceLoader::load(files[i]); if (res.is_null()) { @@ -6507,11 +5945,11 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian String type = res->get_class(); if (type == "PackedScene") { Ref<PackedScene> sdata = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); - Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instanced_scene) { + Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!instantiated_scene) { continue; } - memdelete(instanced_scene); + memdelete(instantiated_scene); } else if (type == "Texture2D" || type == "ImageTexture" || type == "ViewportTexture" || @@ -6526,10 +5964,10 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian } else { continue; } - can_instance = true; + can_instantiate = true; break; } - if (can_instance) { + if (can_instantiate) { if (!preview_node->get_parent()) { // create preview only once _create_preview(files); } @@ -6537,7 +5975,7 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian preview_node->set_position((p_point - trans.get_origin()) / trans.get_scale().x); label->set_text(vformat(TTR("Adding %s..."), default_type)); } - return can_instance; + return can_instantiate; } } label->hide(); @@ -6662,7 +6100,7 @@ CanvasItemEditorViewport::CanvasItemEditorViewport(EditorNode *p_node, CanvasIte vbc->add_child(btn_group); btn_group->set_h_size_flags(0); - button_group.instance(); + button_group.instantiate(); for (int i = 0; i < types.size(); i++) { CheckBox *check = memnew(CheckBox); btn_group->add_child(check); diff --git a/editor/plugins/canvas_item_editor_plugin.h b/editor/plugins/canvas_item_editor_plugin.h index 62a9b1e162..7b64d0cb5d 100644 --- a/editor/plugins/canvas_item_editor_plugin.h +++ b/editor/plugins/canvas_item_editor_plugin.h @@ -33,6 +33,7 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" +#include "editor/editor_zoom_widget.h" #include "scene/gui/box_container.h" #include "scene/gui/check_box.h" #include "scene/gui/label.h" @@ -186,10 +187,7 @@ private: VIEW_FRAME_TO_SELECTION, PREVIEW_CANVAS_SCALE, SKELETON_MAKE_BONES, - SKELETON_CLEAR_BONES, - SKELETON_SHOW_BONES, - SKELETON_SET_IK_CHAIN, - SKELETON_CLEAR_IK_CHAIN + SKELETON_SHOW_BONES }; enum DragType { @@ -208,6 +206,7 @@ private: DRAG_ANCHOR_BOTTOM_RIGHT, DRAG_ANCHOR_BOTTOM_LEFT, DRAG_ANCHOR_ALL, + DRAG_QUEUED, DRAG_MOVE, DRAG_MOVE_X, DRAG_MOVE_Y, @@ -222,7 +221,6 @@ private: DRAG_KEY_MOVE }; - EditorSelection *editor_selection; bool selection_menu_additive_selection; Tool tool; @@ -233,10 +231,6 @@ private: VScrollBar *v_scroll; HBoxContainer *hb; - Button *zoom_minus; - Button *zoom_reset; - Button *zoom_plus; - Map<Control *, Timer *> popup_temporarily_timers; Label *warning_child_of_container; @@ -280,7 +274,6 @@ private: bool snap_scale; bool snap_relative; bool snap_pixel; - bool skeleton_show_bones; bool key_pos; bool key_rot; bool key_scale; @@ -387,6 +380,7 @@ private: Control *top_ruler; Control *left_ruler; + Point2 drag_start_origin; DragType drag_type; Point2 drag_from; Point2 drag_to; @@ -415,7 +409,6 @@ private: bool _is_node_movable(const Node *p_node, bool p_popup_warning = false); void _find_canvas_items_at_pos(const Point2 &p_pos, Node *p_node, Vector<_SelectResult> &r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _get_canvas_items_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items, bool p_allow_locked = false); - void _get_bones_at_pos(const Point2 &p_pos, Vector<_SelectResult> &r_items); void _find_canvas_items_in_rect(const Rect2 &p_rect, Node *p_node, List<CanvasItem *> *r_items, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); bool _select_click_on_item(CanvasItem *item, Point2 p_click_pos, bool p_append); @@ -426,9 +419,7 @@ private: void _add_canvas_item(CanvasItem *p_canvas_item); - void _save_canvas_item_ik_chain(const CanvasItem *p_canvas_item, List<float> *p_bones_length, List<Dictionary> *p_bones_state); void _save_canvas_item_state(List<CanvasItem *> p_canvas_items, bool save_bones = false); - void _restore_canvas_item_ik_chain(CanvasItem *p_canvas_item, const List<Dictionary> *p_bones_state); void _restore_canvas_item_state(List<CanvasItem *> p_canvas_items, bool restore_bones = false); void _commit_canvas_item_state(List<CanvasItem *> p_canvas_items, String action_name, bool commit_bones = false); @@ -448,8 +439,6 @@ private: void _reset_create_position(); UndoRedo *undo_redo; - bool _build_bones_list(Node *p_node); - bool _get_bone_shape(Vector<Vector2> *shape, Vector<Vector2> *outline_shape, Map<BoneKey, BoneList>::Element *bone); List<CanvasItem *> _get_edited_canvas_items(bool retreive_locked = false, bool remove_canvas_item_if_parent_in_selection = true); Rect2 _get_encompassing_rect_from_list(List<CanvasItem *> p_list); @@ -479,7 +468,6 @@ private: void _draw_control_helpers(Control *control); void _draw_selection(); void _draw_axis(); - void _draw_bones(); void _draw_invisible_nodes_positions(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _draw_locks_and_groups(Node *p_node, const Transform2D &p_parent_xform = Transform2D(), const Transform2D &p_canvas_xform = Transform2D()); void _draw_hover(); @@ -506,8 +494,6 @@ private: void _focus_selection(int p_op); - void _solve_IK(Node2D *leaf_node, Point2 target_position); - SnapTarget snap_target[2]; Transform2D snap_transform; void _snap_if_closer_float( @@ -536,13 +522,9 @@ private: void _button_toggle_anchor_mode(bool p_status); VBoxContainer *controls_vb; - HBoxContainer *zoom_hb; - float _get_next_zoom_value(int p_increment_count) const; + EditorZoomWidget *zoom_widget; + void _update_zoom(float p_zoom); void _zoom_on_position(float p_zoom, Point2 p_position = Point2()); - void _update_zoom_label(); - void _button_zoom_minus(); - void _button_zoom_reset(); - void _button_zoom_plus(); void _button_toggle_smart_snap(bool p_status); void _button_toggle_grid_snap(bool p_status); void _button_override_camera(bool p_pressed); @@ -553,14 +535,11 @@ private: HSplitContainer *palette_split; VSplitContainer *bottom_split; - bool bone_list_dirty; - void _queue_update_bone_list(); - void _update_bone_list(); - void _tree_changed(Node *); - void _popup_warning_temporarily(Control *p_control, const float p_duration); void _popup_warning_depop(Control *p_control); + void _set_owner_for_node_and_children(Node *p_node, Node *p_owner); + friend class CanvasItemEditorPlugin; protected: @@ -648,6 +627,8 @@ public: bool is_anchors_mode_enabled() { return anchors_mode; }; + EditorSelection *editor_selection; + CanvasItemEditor(EditorNode *p_editor); }; diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.cpp b/editor/plugins/collision_polygon_3d_editor_plugin.cpp index b50a497ccf..6f90d278bd 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.cpp +++ b/editor/plugins/collision_polygon_3d_editor_plugin.cpp @@ -32,8 +32,8 @@ #include "canvas_item_editor_plugin.h" #include "core/input/input.h" +#include "core/io/file_access.h" #include "core/math/geometry_2d.h" -#include "core/os/file_access.h" #include "core/os/keyboard.h" #include "editor/editor_settings.h" #include "node_3d_editor_plugin.h" @@ -108,8 +108,8 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con return false; } - Transform gt = node->get_global_transform(); - Transform gi = gt.affine_inverse(); + 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); @@ -175,7 +175,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con case MODE_EDIT: { if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { if (mb->is_pressed()) { - if (mb->get_control()) { + if (mb->is_ctrl_pressed()) { if (poly.size() < 3) { undo_redo->create_action(TTR("Edit Poly")); undo_redo->add_undo_method(node, "set_polygon", poly); @@ -317,7 +317,7 @@ bool CollisionPolygon3DEditor::forward_spatial_gui_input(Camera3D *p_camera, con Vector2 cpoint(spoint.x, spoint.y); - if (snap_ignore && !Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + if (snap_ignore && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) { snap_ignore = false; } @@ -358,9 +358,9 @@ void CollisionPolygon3DEditor::_polygon_draw() { float depth = _get_depth() * 0.5; - imgeom->clear(); + imesh->clear_surfaces(); imgeom->set_material_override(line_material); - imgeom->begin(Mesh::PRIMITIVE_LINES, Ref<Texture2D>()); + imesh->surface_begin(Mesh::PRIMITIVE_LINES); Rect2 rect; @@ -382,10 +382,10 @@ void CollisionPolygon3DEditor::_polygon_draw() { Vector3 point = Vector3(p.x, p.y, depth); Vector3 next_point = Vector3(p2.x, p2.y, depth); - imgeom->set_color(Color(1, 0.3, 0.1, 0.8)); - imgeom->add_vertex(point); - imgeom->set_color(Color(1, 0.3, 0.1, 0.8)); - imgeom->add_vertex(next_point); + imesh->surface_set_color(Color(1, 0.3, 0.1, 0.8)); + imesh->surface_add_vertex(point); + imesh->surface_set_color(Color(1, 0.3, 0.1, 0.8)); + imesh->surface_add_vertex(next_point); //Color col=Color(1,0.3,0.1,0.8); //vpc->draw_line(point,next_point,col,2); @@ -402,45 +402,43 @@ void CollisionPolygon3DEditor::_polygon_draw() { r.size.y = rect.size.y; r.size.z = 0; - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(0.3, 0, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(0.0, 0.3, 0)); - - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0) - Vector3(0.3, 0, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(r.size.x, 0, 0) + Vector3(0, 0.3, 0)); - - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0) - Vector3(0, 0.3, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + Vector3(0, r.size.y, 0) + Vector3(0.3, 0, 0)); - - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + r.size); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + r.size - Vector3(0.3, 0, 0)); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + r.size); - imgeom->set_color(Color(0.8, 0.8, 0.8, 0.2)); - imgeom->add_vertex(r.position + r.size - Vector3(0.0, 0.3, 0)); - - imgeom->end(); - - m->clear_surfaces(); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(0.3, 0, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(0.0, 0.3, 0)); + + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0) - Vector3(0.3, 0, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(r.size.x, 0, 0) + Vector3(0, 0.3, 0)); + + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0) - Vector3(0, 0.3, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + Vector3(0, r.size.y, 0) + Vector3(0.3, 0, 0)); + + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + r.size); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + r.size - Vector3(0.3, 0, 0)); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + r.size); + imesh->surface_set_color(Color(0.8, 0.8, 0.8, 0.2)); + imesh->surface_add_vertex(r.position + r.size - Vector3(0.0, 0.3, 0)); + + imesh->surface_end(); if (poly.size() == 0) { return; @@ -515,8 +513,10 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { mode = MODE_EDIT; wip_active = false; - imgeom = memnew(ImmediateGeometry3D); - imgeom->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001))); + imgeom = memnew(MeshInstance3D); + imesh.instantiate(); + imgeom->set_mesh(imesh); + imgeom->set_transform(Transform3D(Basis(), Vector3(0, 0, 0.00001))); line_material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); line_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); @@ -537,9 +537,9 @@ CollisionPolygon3DEditor::CollisionPolygon3DEditor(EditorNode *p_editor) { pointsm = memnew(MeshInstance3D); imgeom->add_child(pointsm); - m.instance(); + m.instantiate(); pointsm->set_mesh(m); - pointsm->set_transform(Transform(Basis(), Vector3(0, 0, 0.00001))); + pointsm->set_transform(Transform3D(Basis(), Vector3(0, 0, 0.00001))); snap_ignore = false; } diff --git a/editor/plugins/collision_polygon_3d_editor_plugin.h b/editor/plugins/collision_polygon_3d_editor_plugin.h index c66518e3e5..5db0f7308a 100644 --- a/editor/plugins/collision_polygon_3d_editor_plugin.h +++ b/editor/plugins/collision_polygon_3d_editor_plugin.h @@ -34,8 +34,8 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "scene/3d/collision_polygon_3d.h" -#include "scene/3d/immediate_geometry_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/resources/immediate_mesh.h" class CanvasItemEditor; @@ -60,7 +60,8 @@ class CollisionPolygon3DEditor : public HBoxContainer { EditorNode *editor; Panel *panel; Node3D *node; - ImmediateGeometry3D *imgeom; + Ref<ImmediateMesh> imesh; + MeshInstance3D *imgeom; MeshInstance3D *pointsm; Ref<ArrayMesh> m; diff --git a/editor/plugins/cpu_particles_2d_editor_plugin.cpp b/editor/plugins/cpu_particles_2d_editor_plugin.cpp index 3403aeceba..6a56cd31d1 100644 --- a/editor/plugins/cpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/cpu_particles_2d_editor_plugin.cpp @@ -74,7 +74,7 @@ void CPUParticles2DEditorPlugin::_menu_callback(int p_idx) { void CPUParticles2DEditorPlugin::_generate_emission_mask() { Ref<Image> img; - img.instance(); + img.instantiate(); Error err = ImageLoader::load_image(source_emission_file, img); ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'."); diff --git a/editor/plugins/curve_editor_plugin.cpp b/editor/plugins/curve_editor_plugin.cpp index db999f50ab..706243fe25 100644 --- a/editor/plugins/curve_editor_plugin.cpp +++ b/editor/plugins/curve_editor_plugin.cpp @@ -127,6 +127,8 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) { case MOUSE_BUTTON_LEFT: _dragging = true; break; + default: + break; } } @@ -168,8 +170,8 @@ void CurveEditor::on_gui_input(const Ref<InputEvent> &p_event) { // Snap to "round" coordinates when holding Ctrl. // Be more precise when holding Shift as well. float snap_threshold; - if (mm.get_control()) { - snap_threshold = mm.get_shift() ? 0.025 : 0.1; + if (mm.is_ctrl_pressed()) { + snap_threshold = mm.is_shift_pressed() ? 0.025 : 0.1; } else { snap_threshold = 0.0; } @@ -776,7 +778,7 @@ void EditorInspectorPluginCurve::parse_begin(Object *p_object) { CurveEditorPlugin::CurveEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginCurve> curve_plugin; - curve_plugin.instance(); + curve_plugin.instantiate(); EditorInspector::add_inspector_plugin(curve_plugin); get_editor_interface()->get_resource_previewer()->add_preview_generator(memnew(CurvePreviewGenerator)); @@ -798,7 +800,7 @@ Ref<Texture2D> CurvePreviewGenerator::generate(const Ref<Resource> &p_from, cons int thumbnail_size = EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size"); thumbnail_size *= EDSCALE; Ref<Image> img_ref; - img_ref.instance(); + img_ref.instantiate(); Image &im = **img_ref; im.create(thumbnail_size, thumbnail_size / 2, false, Image::FORMAT_RGBA8); diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 87eeb82101..a233d66d82 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -174,7 +174,7 @@ Ref<Texture2D> EditorImagePreviewPlugin::generate(const RES &p_from, const Size2 post_process_preview(img); Ref<ImageTexture> ptex; - ptex.instance(); + ptex.instantiate(); ptex->create_from_image(img); return ptex; @@ -219,7 +219,7 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const RES &p_from, const Size } Ref<Image> img; - img.instance(); + img.instantiate(); img->create(bm->get_size().width, bm->get_size().height, false, Image::FORMAT_L8, data); if (img->is_compressed()) { @@ -265,7 +265,7 @@ Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const RES &p_from, const } Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const { - String temp_path = EditorSettings::get_singleton()->get_cache_dir(); + String temp_path = EditorPaths::get_singleton()->get_cache_dir(); String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text(); cache_base = temp_path.plus_file("resthumb-" + cache_base); @@ -278,7 +278,7 @@ Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String & } Ref<Image> img; - img.instance(); + img.instantiate(); Error err = img->load(path); if (err == OK) { Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); @@ -359,12 +359,12 @@ EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() { camera = RS::get_singleton()->camera_create(); RS::get_singleton()->viewport_attach_camera(viewport, camera); - RS::get_singleton()->camera_set_transform(camera, Transform(Basis(), Vector3(0, 0, 3))); + RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3))); RS::get_singleton()->camera_set_perspective(camera, 45, 0.1, 10); light = RS::get_singleton()->directional_light_create(); light_instance = RS::get_singleton()->instance_create2(light, scenario); - RS::get_singleton()->instance_set_transform(light_instance, Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); + RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); light2 = RS::get_singleton()->directional_light_create(); RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7)); @@ -372,7 +372,7 @@ EditorMaterialPreviewPlugin::EditorMaterialPreviewPlugin() { light_instance2 = RS::get_singleton()->instance_create2(light2, scenario); - RS::get_singleton()->instance_set_transform(light_instance2, Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); + RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); sphere = RS::get_singleton()->mesh_create(); sphere_instance = RS::get_singleton()->instance_create2(sphere, scenario); @@ -487,23 +487,30 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size List<String> kwors; scr->get_language()->get_reserved_words(&kwors); + Set<String> control_flow_keywords; Set<String> keywords; for (List<String>::Element *E = kwors.front(); E; E = E->next()) { - keywords.insert(E->get()); + if (scr->get_language()->is_control_flow_keyword(E->get())) { + control_flow_keywords.insert(E->get()); + } else { + keywords.insert(E->get()); + } } int line = 0; int col = 0; Ref<Image> img; - img.instance(); + img.instantiate(); int thumbnail_size = MAX(p_size.x, p_size.y); img->create(thumbnail_size, thumbnail_size, false, Image::FORMAT_RGBA8); Color bg_color = EditorSettings::get_singleton()->get("text_editor/highlighting/background_color"); Color keyword_color = EditorSettings::get_singleton()->get("text_editor/highlighting/keyword_color"); + Color control_flow_keyword_color = EditorSettings::get_singleton()->get("text_editor/highlighting/control_flow_keyword_color"); Color text_color = EditorSettings::get_singleton()->get("text_editor/highlighting/text_color"); Color symbol_color = EditorSettings::get_singleton()->get("text_editor/highlighting/symbol_color"); + Color comment_color = EditorSettings::get_singleton()->get("text_editor/highlighting/comment_color"); if (bg_color.a == 0) { bg_color = Color(0, 0, 0, 0); @@ -522,36 +529,50 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size col = x0; bool prev_is_text = false; + bool in_control_flow_keyword = false; bool in_keyword = false; + bool in_comment = false; for (int i = 0; i < code.length(); i++) { char32_t c = code[i]; if (c > 32) { if (col < thumbnail_size) { Color color = text_color; - if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) { - //make symbol a little visible - color = symbol_color; - in_keyword = false; - } else if (!prev_is_text && _is_text_char(c)) { - int pos = i; + if (c == '#') { + in_comment = true; + } - while (_is_text_char(code[pos])) { - pos++; - } - String word = code.substr(i, pos - i); - if (keywords.has(word)) { - in_keyword = true; + if (in_comment) { + color = comment_color; + } else { + if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) { + //make symbol a little visible + color = symbol_color; + in_control_flow_keyword = false; + in_keyword = false; + } else if (!prev_is_text && _is_text_char(c)) { + int pos = i; + + while (_is_text_char(code[pos])) { + pos++; + } + String word = code.substr(i, pos - i); + if (control_flow_keywords.has(word)) { + in_control_flow_keyword = true; + } else if (keywords.has(word)) { + in_keyword = true; + } + + } else if (!_is_text_char(c)) { + in_keyword = false; } - } else if (!_is_text_char(c)) { - in_keyword = false; - } - - if (in_keyword) { - color = keyword_color; + if (in_control_flow_keyword) { + color = control_flow_keyword_color; + } else if (in_keyword) { + color = keyword_color; + } } - Color ul = color; ul.a *= 0.5; img->set_pixel(col, y0 + line * 2, bg_color.blend(ul)); @@ -559,11 +580,15 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size prev_is_text = _is_text_char(c); } + col++; } else { prev_is_text = false; + in_control_flow_keyword = false; in_keyword = false; if (c == '\n') { + in_comment = false; + col = x0; line++; if (line >= available_height / 2) { @@ -571,9 +596,10 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size } } else if (c == '\t') { col += 3; + } else { + col++; } } - col++; } post_process_preview(img); @@ -662,7 +688,7 @@ Ref<Texture2D> EditorAudioStreamPreviewPlugin::generate(const RES &p_from, const Ref<ImageTexture> ptex = Ref<ImageTexture>(memnew(ImageTexture)); Ref<Image> image; - image.instance(); + image.instantiate(); image->create(w, h, false, Image::FORMAT_RGB8, img); ptex->create_from_image(image); return ptex; @@ -694,7 +720,7 @@ Ref<Texture2D> EditorMeshPreviewPlugin::generate(const RES &p_from, const Size2 AABB aabb = mesh->get_aabb(); Vector3 ofs = aabb.position + aabb.size * 0.5; aabb.position -= ofs; - Transform xform; + Transform3D xform; xform.basis = Basis().rotated(Vector3(0, 1, 0), -Math_PI * 0.125); xform.basis = Basis().rotated(Vector3(1, 0, 0), Math_PI * 0.125) * xform.basis; AABB rot_aabb = xform.xform(aabb); @@ -754,20 +780,20 @@ EditorMeshPreviewPlugin::EditorMeshPreviewPlugin() { camera = RS::get_singleton()->camera_create(); RS::get_singleton()->viewport_attach_camera(viewport, camera); - RS::get_singleton()->camera_set_transform(camera, Transform(Basis(), Vector3(0, 0, 3))); + RS::get_singleton()->camera_set_transform(camera, Transform3D(Basis(), Vector3(0, 0, 3))); //RS::get_singleton()->camera_set_perspective(camera,45,0.1,10); RS::get_singleton()->camera_set_orthogonal(camera, 1.0, 0.01, 1000.0); light = RS::get_singleton()->directional_light_create(); light_instance = RS::get_singleton()->instance_create2(light, scenario); - RS::get_singleton()->instance_set_transform(light_instance, Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); + RS::get_singleton()->instance_set_transform(light_instance, Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); light2 = RS::get_singleton()->directional_light_create(); RS::get_singleton()->light_set_color(light2, Color(0.7, 0.7, 0.7)); //RS::get_singleton()->light_set_color(light2, RS::LIGHT_COLOR_SPECULAR, Color(0.0, 0.0, 0.0)); light_instance2 = RS::get_singleton()->instance_create2(light2, scenario); - RS::get_singleton()->instance_set_transform(light_instance2, Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); + RS::get_singleton()->instance_set_transform(light_instance2, Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); //sphere = RS::get_singleton()->mesh_create(); mesh_instance = RS::get_singleton()->instance_create(); @@ -806,7 +832,7 @@ struct FSample { }; static FSample _samples[] = { - { "hani", U"漢語" }, + { "hani", U"漢字" }, { "armn", U"Աբ" }, { "copt", U"Αα" }, { "cyrl", U"Аб" }, @@ -855,7 +881,7 @@ Ref<Texture2D> EditorFontPreviewPlugin::generate_from_path(const String &p_path, if (res->is_class("Font")) { sampled_font = res->duplicate(); } else if (res->is_class("FontData")) { - sampled_font.instance(); + sampled_font.instantiate(); sampled_font->add_data(res->duplicate()); } diff --git a/editor/plugins/font_editor_plugin.cpp b/editor/plugins/font_editor_plugin.cpp index fa58eb5480..e385a84087 100644 --- a/editor/plugins/font_editor_plugin.cpp +++ b/editor/plugins/font_editor_plugin.cpp @@ -56,7 +56,7 @@ struct FSample { }; static FSample _samples[] = { - { "hani", U"漢語" }, + { "hani", U"漢字" }, { "armn", U"Աբ" }, { "copt", U"Αα" }, { "cyrl", U"Аб" }, @@ -119,7 +119,7 @@ void FontDataPreview::set_data(const Ref<FontData> &p_data) { } FontDataPreview::FontDataPreview() { - line.instance(); + line.instantiate(); } /*************************************************************************/ @@ -290,7 +290,7 @@ void EditorInspectorPluginFont::parse_begin(Object *p_object) { add_custom_control(editor); } -bool EditorInspectorPluginFont::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) { +bool EditorInspectorPluginFont::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { if (p_path.begins_with("language_support_override/") && p_object->is_class("FontData")) { String lang = p_path.replace("language_support_override/", ""); @@ -326,6 +326,6 @@ bool EditorInspectorPluginFont::parse_property(Object *p_object, Variant::Type p FontEditorPlugin::FontEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginFont> fd_plugin; - fd_plugin.instance(); + fd_plugin.instantiate(); EditorInspector::add_inspector_plugin(fd_plugin); } diff --git a/editor/plugins/font_editor_plugin.h b/editor/plugins/font_editor_plugin.h index 04e6c1dac7..71464003a0 100644 --- a/editor/plugins/font_editor_plugin.h +++ b/editor/plugins/font_editor_plugin.h @@ -94,7 +94,7 @@ class EditorInspectorPluginFont : public EditorInspectorPlugin { public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; - virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; }; /*************************************************************************/ diff --git a/editor/plugins/gpu_particles_2d_editor_plugin.cpp b/editor/plugins/gpu_particles_2d_editor_plugin.cpp index b447304a3f..37f900280b 100644 --- a/editor/plugins/gpu_particles_2d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_2d_editor_plugin.cpp @@ -146,7 +146,7 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } Ref<Image> img; - img.instance(); + img.instantiate(); Error err = ImageLoader::load_image(source_emission_file, img); ERR_FAIL_COND_MSG(err != OK, "Error loading image '" + source_emission_file + "'."); @@ -270,11 +270,11 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } } - img.instance(); + img.instantiate(); img->create(w, h, false, Image::FORMAT_RGF, texdata); Ref<ImageTexture> imgt; - imgt.instance(); + imgt.instantiate(); imgt->create_from_image(img); pm->set_emission_point_texture(imgt); @@ -291,10 +291,10 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } } - img.instance(); + img.instantiate(); img->create(w, h, false, Image::FORMAT_RGBA8, colordata); - imgt.instance(); + imgt.instantiate(); imgt->create_from_image(img); pm->set_emission_color_texture(imgt); } @@ -314,10 +314,10 @@ void GPUParticles2DEditorPlugin::_generate_emission_mask() { } } - img.instance(); + img.instantiate(); img->create(w, h, false, Image::FORMAT_RGF, normdata); - imgt.instance(); + imgt.instantiate(); imgt->create_from_image(img); pm->set_emission_normal_texture(imgt); diff --git a/editor/plugins/gpu_particles_3d_editor_plugin.cpp b/editor/plugins/gpu_particles_3d_editor_plugin.cpp index 89d6aaa5f9..571bcf9c4a 100644 --- a/editor/plugins/gpu_particles_3d_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_3d_editor_plugin.cpp @@ -177,7 +177,7 @@ void GPUParticles3DEditorBase::_node_selected(const NodePath &p_path) { return; } - Transform geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform(); + Transform3D geom_xform = base_node->get_global_transform().affine_inverse() * vi->get_global_transform(); int gc = geometry.size(); Face3 *w = geometry.ptrw(); @@ -359,7 +359,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<Image> image = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img)); Ref<ImageTexture> tex; - tex.instance(); + tex.instantiate(); Ref<ParticlesMaterial> material = node->get_process_material(); ERR_FAIL_COND(material.is_null()); @@ -387,7 +387,7 @@ void GPUParticles3DEditor::_generate_emission_points() { Ref<Image> image2 = memnew(Image(w, h, false, Image::FORMAT_RGBF, point_img2)); Ref<ImageTexture> tex2; - tex2.instance(); + tex2.instantiate(); 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 8c4928b7cb..a2dee4a1dc 100644 --- a/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp +++ b/editor/plugins/gpu_particles_collision_sdf_editor_plugin.cpp @@ -137,7 +137,7 @@ void GPUParticlesCollisionSDFEditorPlugin::_sdf_save_path_and_bake(const String Ref<ConfigFile> config; - config.instance(); + config.instantiate(); if (FileAccess::exists(p_path + ".import")) { config->load(p_path + ".import"); } diff --git a/editor/plugins/gradient_editor_plugin.cpp b/editor/plugins/gradient_editor_plugin.cpp index 46fa00f730..355bdb69d8 100644 --- a/editor/plugins/gradient_editor_plugin.cpp +++ b/editor/plugins/gradient_editor_plugin.cpp @@ -92,6 +92,6 @@ void EditorInspectorPluginGradient::parse_begin(Object *p_object) { GradientEditorPlugin::GradientEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginGradient> plugin; - plugin.instance(); + plugin.instantiate(); add_inspector_plugin(plugin); } diff --git a/editor/plugins/input_event_editor_plugin.cpp b/editor/plugins/input_event_editor_plugin.cpp new file mode 100644 index 0000000000..f1aa10844b --- /dev/null +++ b/editor/plugins/input_event_editor_plugin.cpp @@ -0,0 +1,122 @@ +/*************************************************************************/ +/* input_event_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "input_event_editor_plugin.h" + +void InputEventConfigContainer::_bind_methods() { +} + +void InputEventConfigContainer::_configure_pressed() { + config_dialog->popup_and_configure(input_event); +} + +void InputEventConfigContainer::_event_changed() { + input_event_text->set_text(input_event->as_text()); +} + +void InputEventConfigContainer::_config_dialog_confirmed() { + Ref<InputEvent> ie = config_dialog->get_event(); + input_event->copy_from(ie); + _event_changed(); +} + +Size2 InputEventConfigContainer::get_minimum_size() const { + // Don't bother with a minimum x size for the control - we don't want the inspector + // to jump in size if a long text is placed in the label (e.g. Joypad Axis description) + return Size2(0, HBoxContainer::get_minimum_size().y); +} + +void InputEventConfigContainer::set_event(const Ref<InputEvent> &p_event) { + Ref<InputEventKey> k = p_event; + Ref<InputEventMouseButton> m = p_event; + Ref<InputEventJoypadButton> jb = p_event; + Ref<InputEventJoypadMotion> jm = p_event; + + if (k.is_valid()) { + config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_KEY); + } else if (m.is_valid()) { + config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_MOUSE_BUTTON); + } else if (jb.is_valid()) { + config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_BUTTON); + } else if (jm.is_valid()) { + config_dialog->set_allowed_input_types(InputEventConfigurationDialog::InputType::INPUT_JOY_MOTION); + } + + input_event = p_event; + _event_changed(); + input_event->connect("changed", callable_mp(this, &InputEventConfigContainer::_event_changed)); +} + +InputEventConfigContainer::InputEventConfigContainer() { + MarginContainer *mc = memnew(MarginContainer); + mc->add_theme_constant_override("margin_left", 10); + mc->add_theme_constant_override("margin_right", 10); + mc->add_theme_constant_override("margin_top", 10); + mc->add_theme_constant_override("margin_bottom", 10); + add_child(mc); + + HBoxContainer *hb = memnew(HBoxContainer); + mc->add_child(hb); + + open_config_button = memnew(Button); + open_config_button->set_text("Configure"); + open_config_button->connect("pressed", callable_mp(this, &InputEventConfigContainer::_configure_pressed)); + hb->add_child(open_config_button); + + input_event_text = memnew(Label); + hb->add_child(input_event_text); + + config_dialog = memnew(InputEventConfigurationDialog); + config_dialog->connect("confirmed", callable_mp(this, &InputEventConfigContainer::_config_dialog_confirmed)); + add_child(config_dialog); +} + +bool EditorInspectorPluginInputEvent::can_handle(Object *p_object) { + Ref<InputEventKey> k = Ref<InputEventKey>(p_object); + Ref<InputEventMouseButton> m = Ref<InputEventMouseButton>(p_object); + Ref<InputEventJoypadButton> jb = Ref<InputEventJoypadButton>(p_object); + Ref<InputEventJoypadMotion> jm = Ref<InputEventJoypadMotion>(p_object); + + return k.is_valid() || m.is_valid() || jb.is_valid() || jm.is_valid(); +} + +void EditorInspectorPluginInputEvent::parse_begin(Object *p_object) { + Ref<InputEvent> ie = Ref<InputEvent>(p_object); + + InputEventConfigContainer *picker_controls = memnew(InputEventConfigContainer); + picker_controls->set_event(ie); + add_custom_control(picker_controls); +} + +InputEventEditorPlugin::InputEventEditorPlugin(EditorNode *p_node) { + Ref<EditorInspectorPluginInputEvent> plugin; + plugin.instantiate(); + add_inspector_plugin(plugin); +} diff --git a/editor/plugins/input_event_editor_plugin.h b/editor/plugins/input_event_editor_plugin.h new file mode 100644 index 0000000000..bc8293c9e5 --- /dev/null +++ b/editor/plugins/input_event_editor_plugin.h @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* input_event_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef INPUT_EVENT_EDITOR_PLUGIN_H +#define INPUT_EVENT_EDITOR_PLUGIN_H + +#include "editor/action_map_editor.h" +#include "editor/editor_inspector.h" +#include "editor/editor_node.h" + +class InputEventConfigContainer : public HBoxContainer { + GDCLASS(InputEventConfigContainer, HBoxContainer); + + Label *input_event_text; + Button *open_config_button; + + Ref<InputEvent> input_event; + InputEventConfigurationDialog *config_dialog; + + void _config_dialog_confirmed(); + void _configure_pressed(); + + void _event_changed(); + +protected: + static void _bind_methods(); + +public: + virtual Size2 get_minimum_size() const override; + void set_event(const Ref<InputEvent> &p_event); + + InputEventConfigContainer(); +}; + +class EditorInspectorPluginInputEvent : public EditorInspectorPlugin { + GDCLASS(EditorInspectorPluginInputEvent, EditorInspectorPlugin); + +public: + virtual bool can_handle(Object *p_object) override; + virtual void parse_begin(Object *p_object) override; +}; + +class InputEventEditorPlugin : public EditorPlugin { + GDCLASS(InputEventEditorPlugin, EditorPlugin); + +public: + virtual String get_name() const override { return "InputEvent"; } + + InputEventEditorPlugin(EditorNode *p_node); +}; + +#endif // INPUT_EVENT_EDITOR_PLUGIN_H diff --git a/editor/plugins/baked_lightmap_editor_plugin.cpp b/editor/plugins/lightmap_gi_editor_plugin.cpp index 470b61bf40..484fdabfe1 100644 --- a/editor/plugins/baked_lightmap_editor_plugin.cpp +++ b/editor/plugins/lightmap_gi_editor_plugin.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* baked_lightmap_editor_plugin.cpp */ +/* lightmap_gi_editor_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "baked_lightmap_editor_plugin.h" +#include "lightmap_gi_editor_plugin.h" -void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) { +void LightmapGIEditorPlugin::_bake_select_file(const String &p_file) { if (lightmap) { - BakedLightmap::BakeError err; + LightmapGI::BakeError err; if (get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root() == lightmap) { err = lightmap->bake(lightmap, p_file, bake_func_step); } else { @@ -42,7 +42,7 @@ void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) { bake_func_end(); switch (err) { - case BakedLightmap::BAKE_ERROR_NO_SAVE_PATH: { + case LightmapGI::BAKE_ERROR_NO_SAVE_PATH: { String scene_path = lightmap->get_filename(); if (scene_path == String()) { scene_path = lightmap->get_owner()->get_filename(); @@ -57,10 +57,10 @@ void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) { file_dialog->popup_file_dialog(); } break; - case BakedLightmap::BAKE_ERROR_NO_MESHES: + case LightmapGI::BAKE_ERROR_NO_MESHES: EditorNode::get_singleton()->show_warning(TTR("No meshes to bake. Make sure they contain an UV2 channel and that the 'Bake Light' flag is on.")); break; - case BakedLightmap::BAKE_ERROR_CANT_CREATE_IMAGE: + case LightmapGI::BAKE_ERROR_CANT_CREATE_IMAGE: EditorNode::get_singleton()->show_warning(TTR("Failed creating lightmap images, make sure path is writable.")); break; default: { @@ -69,12 +69,12 @@ void BakedLightmapEditorPlugin::_bake_select_file(const String &p_file) { } } -void BakedLightmapEditorPlugin::_bake() { +void LightmapGIEditorPlugin::_bake() { _bake_select_file(""); } -void BakedLightmapEditorPlugin::edit(Object *p_object) { - BakedLightmap *s = Object::cast_to<BakedLightmap>(p_object); +void LightmapGIEditorPlugin::edit(Object *p_object) { + LightmapGI *s = Object::cast_to<LightmapGI>(p_object); if (!s) { return; } @@ -82,11 +82,11 @@ void BakedLightmapEditorPlugin::edit(Object *p_object) { lightmap = s; } -bool BakedLightmapEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("BakedLightmap"); +bool LightmapGIEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("LightmapGI"); } -void BakedLightmapEditorPlugin::make_visible(bool p_visible) { +void LightmapGIEditorPlugin::make_visible(bool p_visible) { if (p_visible) { bake->show(); } else { @@ -94,9 +94,9 @@ void BakedLightmapEditorPlugin::make_visible(bool p_visible) { } } -EditorProgress *BakedLightmapEditorPlugin::tmp_progress = nullptr; +EditorProgress *LightmapGIEditorPlugin::tmp_progress = nullptr; -bool BakedLightmapEditorPlugin::bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh) { +bool LightmapGIEditorPlugin::bake_func_step(float p_progress, const String &p_description, void *, bool p_refresh) { if (!tmp_progress) { tmp_progress = memnew(EditorProgress("bake_lightmaps", TTR("Bake Lightmaps"), 1000, false)); ERR_FAIL_COND_V(tmp_progress == nullptr, false); @@ -104,18 +104,18 @@ bool BakedLightmapEditorPlugin::bake_func_step(float p_progress, const String &p return tmp_progress->step(p_description, p_progress * 1000, p_refresh); } -void BakedLightmapEditorPlugin::bake_func_end() { +void LightmapGIEditorPlugin::bake_func_end() { if (tmp_progress != nullptr) { memdelete(tmp_progress); tmp_progress = nullptr; } } -void BakedLightmapEditorPlugin::_bind_methods() { - ClassDB::bind_method("_bake", &BakedLightmapEditorPlugin::_bake); +void LightmapGIEditorPlugin::_bind_methods() { + ClassDB::bind_method("_bake", &LightmapGIEditorPlugin::_bake); } -BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) { +LightmapGIEditorPlugin::LightmapGIEditorPlugin(EditorNode *p_node) { editor = p_node; bake = memnew(Button); bake->set_flat(true); @@ -130,9 +130,9 @@ BakedLightmapEditorPlugin::BakedLightmapEditorPlugin(EditorNode *p_node) { file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); file_dialog->add_filter("*.lmbake ; LightMap Bake"); file_dialog->set_title(TTR("Select lightmap bake file:")); - file_dialog->connect("file_selected", callable_mp(this, &BakedLightmapEditorPlugin::_bake_select_file)); + file_dialog->connect("file_selected", callable_mp(this, &LightmapGIEditorPlugin::_bake_select_file)); bake->add_child(file_dialog); } -BakedLightmapEditorPlugin::~BakedLightmapEditorPlugin() { +LightmapGIEditorPlugin::~LightmapGIEditorPlugin() { } diff --git a/editor/plugins/baked_lightmap_editor_plugin.h b/editor/plugins/lightmap_gi_editor_plugin.h index d291c377d9..12d080d6be 100644 --- a/editor/plugins/baked_lightmap_editor_plugin.h +++ b/editor/plugins/lightmap_gi_editor_plugin.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* baked_lightmap_editor_plugin.h */ +/* lightmap_gi_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -33,13 +33,13 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "scene/3d/baked_lightmap.h" +#include "scene/3d/lightmap_gi.h" #include "scene/resources/material.h" -class BakedLightmapEditorPlugin : public EditorPlugin { - GDCLASS(BakedLightmapEditorPlugin, EditorPlugin); +class LightmapGIEditorPlugin : public EditorPlugin { + GDCLASS(LightmapGIEditorPlugin, EditorPlugin); - BakedLightmap *lightmap; + LightmapGI *lightmap; Button *bake; EditorNode *editor; @@ -56,14 +56,14 @@ protected: static void _bind_methods(); public: - virtual String get_name() const override { return "BakedLightmap"; } + virtual String get_name() const override { return "LightmapGI"; } bool has_main_screen() const override { return false; } virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - BakedLightmapEditorPlugin(EditorNode *p_node); - ~BakedLightmapEditorPlugin(); + LightmapGIEditorPlugin(EditorNode *p_node); + ~LightmapGIEditorPlugin(); }; #endif diff --git a/editor/plugins/material_editor_plugin.cpp b/editor/plugins/material_editor_plugin.cpp index ad99ad7808..17a634ee14 100644 --- a/editor/plugins/material_editor_plugin.cpp +++ b/editor/plugins/material_editor_plugin.cpp @@ -111,7 +111,7 @@ MaterialEditor::MaterialEditor() { vc->set_anchors_and_offsets_preset(PRESET_WIDE); viewport = memnew(SubViewport); Ref<World3D> world_3d; - world_3d.instance(); + world_3d.instantiate(); viewport->set_world_3d(world_3d); //use own world vc->add_child(viewport); viewport->set_disable_input(true); @@ -119,17 +119,17 @@ MaterialEditor::MaterialEditor() { viewport->set_msaa(Viewport::MSAA_4X); camera = memnew(Camera3D); - camera->set_transform(Transform(Basis(), Vector3(0, 0, 3))); + camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 3))); camera->set_perspective(45, 0.1, 10); camera->make_current(); viewport->add_child(camera); light1 = memnew(DirectionalLight3D); - light1->set_transform(Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); + light1->set_transform(Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); viewport->add_child(light1); light2 = memnew(DirectionalLight3D); - light2->set_transform(Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); + light2->set_transform(Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); light2->set_color(Color(0.7, 0.7, 0.7)); viewport->add_child(light2); @@ -139,16 +139,16 @@ MaterialEditor::MaterialEditor() { box_instance = memnew(MeshInstance3D); viewport->add_child(box_instance); - Transform box_xform; + Transform3D box_xform; box_xform.basis.rotate(Vector3(1, 0, 0), Math::deg2rad(25.0)); box_xform.basis = box_xform.basis * Basis().rotated(Vector3(0, 1, 0), Math::deg2rad(-25.0)); box_xform.basis.scale(Vector3(0.8, 0.8, 0.8)); box_xform.origin.y = 0.2; box_instance->set_transform(box_xform); - sphere_mesh.instance(); + sphere_mesh.instantiate(); sphere_instance->set_mesh(sphere_mesh); - box_mesh.instance(); + box_mesh.instantiate(); box_instance->set_mesh(box_mesh); set_custom_minimum_size(Size2(1, 150) * EDSCALE); @@ -223,7 +223,7 @@ void EditorInspectorPluginMaterial::parse_begin(Object *p_object) { } EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() { - env.instance(); + env.instantiate(); Ref<Sky> sky = memnew(Sky()); env->set_sky(sky); env->set_background(Environment::BG_COLOR); @@ -233,7 +233,7 @@ EditorInspectorPluginMaterial::EditorInspectorPluginMaterial() { MaterialEditorPlugin::MaterialEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginMaterial> plugin; - plugin.instance(); + plugin.instantiate(); add_inspector_plugin(plugin); } @@ -251,10 +251,10 @@ Ref<Resource> StandardMaterial3DConversionPlugin::convert(const Ref<Resource> &p ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); Ref<ShaderMaterial> smat; - smat.instance(); + smat.instantiate(); Ref<Shader> shader; - shader.instance(); + shader.instantiate(); String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); @@ -295,10 +295,10 @@ Ref<Resource> ParticlesMaterialConversionPlugin::convert(const Ref<Resource> &p_ ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); Ref<ShaderMaterial> smat; - smat.instance(); + smat.instantiate(); Ref<Shader> shader; - shader.instance(); + shader.instantiate(); String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); @@ -332,10 +332,10 @@ Ref<Resource> CanvasItemMaterialConversionPlugin::convert(const Ref<Resource> &p ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); Ref<ShaderMaterial> smat; - smat.instance(); + smat.instantiate(); Ref<Shader> shader; - shader.instance(); + shader.instantiate(); String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); @@ -369,10 +369,10 @@ Ref<Resource> ProceduralSkyMaterialConversionPlugin::convert(const Ref<Resource> ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); Ref<ShaderMaterial> smat; - smat.instance(); + smat.instantiate(); Ref<Shader> shader; - shader.instance(); + shader.instantiate(); String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); @@ -406,10 +406,10 @@ Ref<Resource> PanoramaSkyMaterialConversionPlugin::convert(const Ref<Resource> & ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); Ref<ShaderMaterial> smat; - smat.instance(); + smat.instantiate(); Ref<Shader> shader; - shader.instance(); + shader.instantiate(); String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); @@ -443,10 +443,10 @@ Ref<Resource> PhysicalSkyMaterialConversionPlugin::convert(const Ref<Resource> & ERR_FAIL_COND_V(!mat.is_valid(), Ref<Resource>()); Ref<ShaderMaterial> smat; - smat.instance(); + smat.instantiate(); Ref<Shader> shader; - shader.instance(); + shader.instantiate(); String code = RS::get_singleton()->shader_get_code(mat->get_shader_rid()); diff --git a/editor/plugins/mesh_editor_plugin.cpp b/editor/plugins/mesh_editor_plugin.cpp index 9d29c31522..fcc6b84efb 100644 --- a/editor/plugins/mesh_editor_plugin.cpp +++ b/editor/plugins/mesh_editor_plugin.cpp @@ -65,7 +65,7 @@ void MeshEditor::_notification(int p_what) { } void MeshEditor::_update_rotation() { - Transform t; + Transform3D t; t.basis.rotate(Vector3(0, 1, 0), -rot_y); t.basis.rotate(Vector3(1, 0, 0), -rot_x); rotation->set_transform(t); @@ -85,7 +85,7 @@ void MeshEditor::edit(Ref<Mesh> p_mesh) { if (m != 0) { m = 1.0 / m; m *= 0.5; - Transform xform; + Transform3D xform; xform.basis.scale(Vector3(m, m, m)); xform.origin = -xform.basis.xform(ofs); //-ofs*m; //xform.origin.z -= aabb.get_longest_axis_size() * 2; @@ -110,23 +110,23 @@ void MeshEditor::_bind_methods() { MeshEditor::MeshEditor() { viewport = memnew(SubViewport); Ref<World3D> world_3d; - world_3d.instance(); + world_3d.instantiate(); viewport->set_world_3d(world_3d); //use own world add_child(viewport); viewport->set_disable_input(true); viewport->set_msaa(Viewport::MSAA_2X); set_stretch(true); camera = memnew(Camera3D); - camera->set_transform(Transform(Basis(), Vector3(0, 0, 1.1))); + camera->set_transform(Transform3D(Basis(), Vector3(0, 0, 1.1))); camera->set_perspective(45, 0.1, 10); viewport->add_child(camera); light1 = memnew(DirectionalLight3D); - light1->set_transform(Transform().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); + light1->set_transform(Transform3D().looking_at(Vector3(-1, -1, -1), Vector3(0, 1, 0))); viewport->add_child(light1); light2 = memnew(DirectionalLight3D); - light2->set_transform(Transform().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); + light2->set_transform(Transform3D().looking_at(Vector3(0, 1, 0), Vector3(0, 0, 1))); light2->set_color(Color(0.7, 0.7, 0.7)); viewport->add_child(light2); @@ -182,6 +182,6 @@ void EditorInspectorPluginMesh::parse_begin(Object *p_object) { MeshEditorPlugin::MeshEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginMesh> plugin; - plugin.instance(); + plugin.instantiate(); add_inspector_plugin(plugin); } diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 0d2b2ea2f5..7434accc1a 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -153,14 +153,18 @@ void MeshInstance3DEditor::_menu_option(int p_option) { ur->add_undo_method(node->get_parent(), "remove_child", cshape); ur->commit_action(); } break; - case MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE: { + + case MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE: + case MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE: { if (node == get_tree()->get_edited_scene_root()) { err_dialog->set_text(TTR("Can't create a single convex collision shape for the scene root.")); err_dialog->popup_centered(); return; } - Ref<Shape3D> shape = mesh->create_convex_shape(); + bool simplify = (p_option == MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE); + + Ref<Shape3D> shape = mesh->create_convex_shape(true, simplify); if (shape.is_null()) { err_dialog->set_text(TTR("Couldn't create a single convex collision shape.")); @@ -169,7 +173,11 @@ void MeshInstance3DEditor::_menu_option(int p_option) { } UndoRedo *ur = EditorNode::get_singleton()->get_undo_redo(); - ur->create_action(TTR("Create Single Convex Shape")); + if (simplify) { + ur->create_action(TTR("Create Simplified Convex Shape")); + } else { + ur->create_action(TTR("Create Single Convex Shape")); + } CollisionShape3D *cshape = memnew(CollisionShape3D); cshape->set_shape(shape); @@ -186,6 +194,7 @@ void MeshInstance3DEditor::_menu_option(int p_option) { ur->commit_action(); } break; + case MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES: { if (node == get_tree()->get_edited_scene_root()) { err_dialog->set_text(TTR("Can't create multiple convex collision shapes for the scene root.")); @@ -441,8 +450,10 @@ MeshInstance3DEditor::MeshInstance3DEditor() { options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is the most accurate (but slowest) option for collision detection.")); options->get_popup()->add_item(TTR("Create Single Convex Collision Sibling"), MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE); options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a single convex collision shape.\nThis is the fastest (but least accurate) option for collision detection.")); + options->get_popup()->add_item(TTR("Create Simplified Convex Collision Sibling"), MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE); + options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a simplified convex collision shape.\nThis is similar to single collision shape, but can result in a simpler geometry in some cases, at the cost of accuracy.")); options->get_popup()->add_item(TTR("Create Multiple Convex Collision Siblings"), MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES); - options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between the two above options.")); + options->get_popup()->set_item_tooltip(options->get_popup()->get_item_count() - 1, TTR("Creates a polygon-based collision shape.\nThis is a performance middle-ground between a single convex collision and a polygon-based collision.")); options->get_popup()->add_separator(); options->get_popup()->add_item(TTR("Create Navigation Mesh"), MENU_OPTION_CREATE_NAVMESH); options->get_popup()->add_separator(); diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h index 69f494de7f..98b667c978 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.h +++ b/editor/plugins/mesh_instance_3d_editor_plugin.h @@ -43,6 +43,7 @@ class MeshInstance3DEditor : public Control { MENU_OPTION_CREATE_STATIC_TRIMESH_BODY, MENU_OPTION_CREATE_TRIMESH_COLLISION_SHAPE, MENU_OPTION_CREATE_SINGLE_CONVEX_COLLISION_SHAPE, + MENU_OPTION_CREATE_SIMPLIFIED_CONVEX_COLLISION_SHAPE, MENU_OPTION_CREATE_MULTIPLE_CONVEX_COLLISION_SHAPES, MENU_OPTION_CREATE_NAVMESH, MENU_OPTION_CREATE_OUTLINE_MESH, diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index 6f1f243444..5d1d29cbc8 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -127,7 +127,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, continue; } - //Transform shape_transform = sb->shape_owner_get_transform(E->get()); + //Transform3D shape_transform = sb->shape_owner_get_transform(E->get()); //shape_transform.set_origin(shape_transform.get_origin() - phys_offset); @@ -147,7 +147,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, p_library->set_item_shapes(id, collisions); Ref<NavigationMesh> navmesh; - Transform navmesh_transform; + Transform3D navmesh_transform; for (int j = 0; j < mi->get_child_count(); j++) { Node *child2 = mi->get_child(j); if (!Object::cast_to<NavigationRegion3D>(child2)) { @@ -170,7 +170,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, if (true) { Vector<Ref<Mesh>> meshes; - Vector<Transform> transforms; + Vector<Transform3D> transforms; Vector<int> ids = p_library->get_item_list(); for (int i = 0; i < ids.size(); i++) { if (mesh_instances.find(ids[i])) { @@ -193,7 +193,7 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { Ref<PackedScene> ps = ResourceLoader::load(p_str, "PackedScene"); ERR_FAIL_COND(ps.is_null()); - Node *scene = ps->instance(); + Node *scene = ps->instantiate(); ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'."); diff --git a/editor/plugins/multimesh_editor_plugin.cpp b/editor/plugins/multimesh_editor_plugin.cpp index 19c6dcf402..48b885930f 100644 --- a/editor/plugins/multimesh_editor_plugin.cpp +++ b/editor/plugins/multimesh_editor_plugin.cpp @@ -111,7 +111,7 @@ void MultiMeshEditor::_populate() { return; } - Transform geom_xform = node->get_global_transform().affine_inverse() * ss_instance->get_global_transform(); + Transform3D geom_xform = node->get_global_transform().affine_inverse() * ss_instance->get_global_transform(); Vector<Face3> geometry = ss_instance->get_faces(VisualInstance3D::FACES_SOLID); @@ -167,7 +167,7 @@ void MultiMeshEditor::_populate() { float _scale = populate_scale->get_value(); int axis = populate_axis->get_selected(); - Transform axis_xform; + Transform3D axis_xform; if (axis == Vector3::AXIS_Z) { axis_xform.rotate(Vector3(1, 0, 0), -Math_PI * 0.5); } @@ -191,7 +191,7 @@ void MultiMeshEditor::_populate() { Vector3 normal = face.get_plane().normal; Vector3 op_axis = (face.vertex[0] - face.vertex[1]).normalized(); - Transform xform; + Transform3D xform; xform.set_look_at(pos, pos + op_axis, normal); xform = xform * axis_xform; diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 023d91be30..72a63539b3 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/input/input.h" #include "core/math/camera_matrix.h" +#include "core/math/math_funcs.h" #include "core/os/keyboard.h" #include "core/string/print_string.h" #include "core/templates/sort_array.h" @@ -85,10 +86,10 @@ void ViewportRotationControl::_notification(int p_what) { axis_menu_options.clear(); axis_menu_options.push_back(Node3DEditorViewport::VIEW_RIGHT); axis_menu_options.push_back(Node3DEditorViewport::VIEW_TOP); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); axis_menu_options.push_back(Node3DEditorViewport::VIEW_LEFT); axis_menu_options.push_back(Node3DEditorViewport::VIEW_BOTTOM); - axis_menu_options.push_back(Node3DEditorViewport::VIEW_REAR); + axis_menu_options.push_back(Node3DEditorViewport::VIEW_FRONT); axis_colors.clear(); axis_colors.push_back(get_theme_color("axis_x_color", "Editor")); @@ -309,7 +310,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) { bool manipulated = Input::get_singleton()->get_mouse_button_mask() & (2 | 4); manipulated |= Input::get_singleton()->is_key_pressed(KEY_SHIFT); manipulated |= Input::get_singleton()->is_key_pressed(KEY_ALT); - manipulated |= Input::get_singleton()->is_key_pressed(KEY_CONTROL); + manipulated |= Input::get_singleton()->is_key_pressed(KEY_CTRL); float orbit_inertia = MAX(0.00001, manipulated ? manip_orbit_inertia : free_orbit_inertia); float translation_inertia = MAX(0.0001, manipulated ? manip_translation_inertia : free_translation_inertia); @@ -344,7 +345,7 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) { equal = false; } - if (!equal || p_interp_delta == 0 || is_freelook_active() || is_orthogonal != orthogonal) { + if (!equal || p_interp_delta == 0 || is_orthogonal != orthogonal) { camera->set_global_transform(to_camera_transform(camera_cursor)); if (orthogonal) { @@ -361,8 +362,8 @@ void Node3DEditorViewport::_update_camera(float p_interp_delta) { } } -Transform Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const { - Transform camera_transform; +Transform3D Node3DEditorViewport::to_camera_transform(const Cursor &p_cursor) const { + Transform3D camera_transform; camera_transform.translate(p_cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -p_cursor.x_rot); camera_transform.basis.rotate(Vector3(0, 1, 0), -p_cursor.y_rot); @@ -410,7 +411,7 @@ float Node3DEditorViewport::get_fov() const { return CLAMP(spatial_editor->get_fov(), MIN_FOV, MAX_FOV); } -Transform Node3DEditorViewport::_get_camera_transform() const { +Transform3D Node3DEditorViewport::_get_camera_transform() const { return camera->get_global_transform(); } @@ -466,22 +467,31 @@ void Node3DEditorViewport::_select_clicked(bool p_append, bool p_single, bool p_ } void Node3DEditorViewport::_select(Node *p_node, bool p_append, bool p_single) { - if (!p_append) { - editor_selection->clear(); - } - - if (editor_selection->is_selected(p_node)) { - //erase - editor_selection->remove_node(p_node); + // Add or remove a single node from the selection + if (p_append && p_single) { + if (editor_selection->is_selected(p_node)) { + // Already in the selection, remove it from the selected nodes + editor_selection->remove_node(p_node); + } else { + // Add the item to the selection + editor_selection->add_node(p_node); + } + } else if (p_append && !p_single) { + // Add the item to the selection + editor_selection->add_node(p_node); } else { + // No append; single select + editor_selection->clear(); editor_selection->add_node(p_node); - } - - if (p_single) { + // Reselect if (Engine::get_singleton()->is_editor_hint()) { editor->call("edit_node", p_node); } } + + if (editor_selection->get_selected_node_list().size() == 1) { + editor->push_item(editor_selection->get_selected_node_list()[0]); + } } ObjectID Node3DEditorViewport::_select_ray(const Point2 &p_pos, bool p_append, bool &r_includes_current, int *r_gizmo_handle, bool p_alt_select) { @@ -631,7 +641,7 @@ Vector3 Node3DEditorViewport::_get_screen_to_space(const Vector3 &p_vector3) { } Vector2 screen_he = cm.get_viewport_half_extents(); - Transform camera_transform; + Transform3D camera_transform; camera_transform.translate(cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); camera_transform.basis.rotate(Vector3(0, 1, 0), -cursor.y_rot); @@ -794,22 +804,22 @@ static int _get_key_modifier_setting(const String &p_property) { case 3: return KEY_META; case 4: - return KEY_CONTROL; + return KEY_CTRL; } return 0; } static int _get_key_modifier(Ref<InputEventWithModifiers> e) { - if (e->get_shift()) { + if (e->is_shift_pressed()) { return KEY_SHIFT; } - if (e->get_alt()) { + if (e->is_alt_pressed()) { return KEY_ALT; } - if (e->get_control()) { - return KEY_CONTROL; + if (e->is_ctrl_pressed()) { + return KEY_CTRL; } - if (e->get_metakey()) { + if (e->is_meta_pressed()) { return KEY_META; } return 0; @@ -829,7 +839,7 @@ bool Node3DEditorViewport::_gizmo_select(const Vector2 &p_screenpos, bool p_high Vector3 ray_pos = _get_ray_pos(Vector2(p_screenpos.x, p_screenpos.y)); Vector3 ray = _get_ray(Vector2(p_screenpos.x, p_screenpos.y)); - Transform gt = spatial_editor->get_gizmo_transform(); + Transform3D gt = spatial_editor->get_gizmo_transform(); float gs = gizmo_scale; if (spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_MOVE) { @@ -1024,7 +1034,7 @@ bool Node3DEditorViewport ::_is_node_locked(const Node *p_node) { } void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { - _find_items_at_pos(b->get_position(), clicked_includes_current, selection_results, b->get_shift()); + _find_items_at_pos(b->get_position(), clicked_includes_current, selection_results, b->is_shift_pressed()); Node *scene = editor->get_edited_scene(); @@ -1037,7 +1047,7 @@ void Node3DEditorViewport::_list_select(Ref<InputEventMouseButton> b) { } } - clicked_wants_append = b->get_shift(); + clicked_wants_append = b->is_shift_pressed(); if (selection_results.size() == 1) { clicked = selection_results[0].item->get_instance_id(); @@ -1149,7 +1159,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } if (_edit.mode == TRANSFORM_NONE && b->is_pressed()) { - if (b->get_alt()) { + if (b->is_alt_pressed()) { if (nav_scheme == NAVIGATION_MAYA) { break; } @@ -1234,7 +1244,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { case MOUSE_BUTTON_LEFT: { if (b->is_pressed()) { NavigationScheme nav_scheme = (NavigationScheme)EditorSettings::get_singleton()->get("editors/3d/navigation/navigation_scheme").operator int(); - if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->get_alt()) { + if ((nav_scheme == NAVIGATION_MAYA || nav_scheme == NAVIGATION_MODO) && b->is_alt_pressed()) { break; } @@ -1244,6 +1254,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } _edit.mouse_pos = b->get_position(); + _edit.original_mouse_pos = b->get_position(); _edit.snap = spatial_editor->is_snap_enabled(); _edit.mode = TRANSFORM_NONE; @@ -1262,7 +1273,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { int handle = -1; Vector3 point; Vector3 normal; - bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, b->get_shift()); + bool inters = seg->intersect_ray(camera, _edit.mouse_pos, point, normal, &handle, b->is_shift_pressed()); if (inters && handle != -1) { _edit.gizmo = seg; _edit.gizmo_handle = handle; @@ -1279,7 +1290,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { clicked = ObjectID(); clicked_includes_current = false; - if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->get_command()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { + if ((spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_SELECT && b->is_command_pressed()) || spatial_editor->get_tool_mode() == Node3DEditor::TOOL_MODE_ROTATE) { /* HANDLE ROTATION */ if (get_selected_count() == 0) { break; //bye @@ -1314,11 +1325,11 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { int gizmo_handle = -1; - clicked = _select_ray(b->get_position(), b->get_shift(), clicked_includes_current, &gizmo_handle, b->get_shift()); + clicked = _select_ray(b->get_position(), b->is_shift_pressed(), clicked_includes_current, &gizmo_handle, b->is_shift_pressed()); //clicking is always deferred to either move or release - clicked_wants_append = b->get_shift(); + clicked_wants_append = b->is_shift_pressed(); if (clicked.is_null()) { if (!clicked_wants_append) { @@ -1396,6 +1407,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } break; + default: + break; } } @@ -1441,16 +1454,17 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { set_message(n + ": " + String(v)); } else if (m->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { - if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) { + if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) { nav_mode = NAVIGATION_ORBIT; - } else if (nav_scheme == NAVIGATION_MODO && m->get_alt() && m->get_shift()) { + } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_shift_pressed()) { nav_mode = NAVIGATION_PAN; - } else if (nav_scheme == NAVIGATION_MODO && m->get_alt() && m->get_control()) { + } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed() && m->is_ctrl_pressed()) { nav_mode = NAVIGATION_ZOOM; - } else if (nav_scheme == NAVIGATION_MODO && m->get_alt()) { + } else if (nav_scheme == NAVIGATION_MODO && m->is_alt_pressed()) { nav_mode = NAVIGATION_ORBIT; } else { - if (clicked.is_valid()) { + bool movement_threshold_passed = _edit.original_mouse_pos.distance_to(_edit.mouse_pos) > 10 * EDSCALE; + if (clicked.is_valid() && movement_threshold_passed) { if (!clicked_includes_current) { _select_clicked(clicked_wants_append, true); // Processing was deferred. @@ -1579,10 +1593,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { continue; } - Transform original = se->original; - Transform original_local = se->original_local; - Transform base = Transform(Basis(), _edit.center); - Transform t; + Transform3D original = se->original; + Transform3D original_local = se->original_local; + Transform3D base = Transform3D(Basis(), _edit.center); + Transform3D t; Vector3 local_scale; if (local_coords) { @@ -1608,7 +1622,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { motion.snap(Vector3(snap, snap, snap)); } - Transform r; + Transform3D r; r.basis.scale(motion + Vector3(1, 1, 1)); t = base * (r * (base.inverse() * original)); @@ -1701,8 +1715,8 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { continue; } - Transform original = se->original; - Transform t; + Transform3D original = se->original; + Transform3D t; if (local_coords) { if (_edit.snap || spatial_editor->is_snap_enabled()) { @@ -1797,10 +1811,10 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { continue; } - Transform t; + Transform3D t; if (local_coords) { - Transform original_local = se->original_local; + Transform3D original_local = se->original_local; Basis rot = Basis(axis, angle); t.basis = original_local.get_basis().orthonormalized() * rot; @@ -1811,9 +1825,9 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { sp->set_scale(original_local.basis.get_scale()); // re-apply original scale } else { - Transform original = se->original; - Transform r; - Transform base = Transform(Basis(), _edit.center); + Transform3D original = se->original; + Transform3D r; + Transform3D base = Transform3D(Basis(), _edit.center); r.basis.rotate(plane.normal, angle); t = base * r * base.inverse() * original; @@ -1831,7 +1845,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } } else if ((m->get_button_mask() & MOUSE_BUTTON_MASK_RIGHT) || freelook_active) { - if (nav_scheme == NAVIGATION_MAYA && m->get_alt()) { + if (nav_scheme == NAVIGATION_MAYA && m->is_alt_pressed()) { nav_mode = NAVIGATION_ZOOM; } else if (freelook_active) { nav_mode = NAVIGATION_LOOK; @@ -1924,7 +1938,7 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { } } else if (nav_scheme == NAVIGATION_MAYA) { - if (pan_gesture->get_alt()) { + if (pan_gesture->is_alt_pressed()) { nav_mode = NAVIGATION_PAN; } } @@ -1962,6 +1976,13 @@ void Node3DEditorViewport::_sinput(const Ref<InputEvent> &p_event) { return; } + if (EditorSettings::get_singleton()->get("editors/3d/navigation/emulate_numpad")) { + const uint32_t code = k->get_keycode(); + if (code >= KEY_0 && code <= KEY_9) { + k->set_keycode(code - KEY_0 + KEY_KP_0); + } + } + if (ED_IS_SHORTCUT("spatial_editor/snap", p_event)) { if (_edit.mode != TRANSFORM_NONE) { _edit.snap = !_edit.snap; @@ -2053,11 +2074,11 @@ void Node3DEditorViewport::_nav_pan(Ref<InputEventWithModifiers> p_event, const real_t pan_speed = 1 / 150.0; int pan_speed_modifier = 10; - if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift()) { + if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { pan_speed *= pan_speed_modifier; } - Transform camera_transform; + Transform3D camera_transform; camera_transform.translate(cursor.pos); camera_transform.basis.rotate(Vector3(1, 0, 0), -cursor.x_rot); @@ -2078,7 +2099,7 @@ void Node3DEditorViewport::_nav_zoom(Ref<InputEventWithModifiers> p_event, const real_t zoom_speed = 1 / 80.0; int zoom_speed_modifier = 10; - if (nav_scheme == NAVIGATION_MAYA && p_event->get_shift()) { + if (nav_scheme == NAVIGATION_MAYA && p_event->is_shift_pressed()) { zoom_speed *= zoom_speed_modifier; } @@ -2145,7 +2166,7 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const const bool invert_y_axis = EditorSettings::get_singleton()->get("editors/3d/navigation/invert_y_axis"); // Note: do NOT assume the camera has the "current" transform, because it is interpolated and may have "lag". - const Transform prev_camera_transform = to_camera_transform(cursor); + const Transform3D prev_camera_transform = to_camera_transform(cursor); if (invert_y_axis) { cursor.x_rot -= p_relative.y * radians_per_pixel; @@ -2158,7 +2179,7 @@ void Node3DEditorViewport::_nav_look(Ref<InputEventWithModifiers> p_event, const cursor.y_rot += p_relative.x * radians_per_pixel; // Look is like the opposite of Orbit: the focus point rotates around the camera - Transform camera_transform = to_camera_transform(cursor); + Transform3D camera_transform = to_camera_transform(cursor); Vector3 pos = camera_transform.xform(Vector3(0, 0, 0)); Vector3 prev_pos = prev_camera_transform.xform(Vector3(0, 0, 0)); Vector3 diff = prev_pos - pos; @@ -2444,7 +2465,7 @@ void Node3DEditorViewport::_notification(int p_what) { continue; } - Transform t = sp->get_global_gizmo_transform(); + Transform3D t = sp->get_global_gizmo_transform(); VisualInstance3D *vi = Object::cast_to<VisualInstance3D>(sp); AABB new_aabb = vi ? vi->get_aabb() : _calculate_spatial_bounds(sp); @@ -2499,25 +2520,21 @@ void Node3DEditorViewport::_notification(int p_what) { } if (show_info) { + const String viewport_size = vformat(String::utf8("%d × %d"), viewport->get_size().x, viewport->get_size().y); String text; - text += "X: " + rtos(current_camera->get_translation().x).pad_decimals(1) + "\n"; - text += "Y: " + rtos(current_camera->get_translation().y).pad_decimals(1) + "\n"; - text += "Z: " + rtos(current_camera->get_translation().z).pad_decimals(1) + "\n"; - text += TTR("Pitch") + ": " + itos(Math::round(current_camera->get_rotation_degrees().x)) + "\n"; - text += TTR("Yaw") + ": " + itos(Math::round(current_camera->get_rotation_degrees().y)) + "\n\n"; - - text += TTR("Size") + - vformat( - ": %dx%d (%.1fMP)\n", - viewport->get_size().x, - viewport->get_size().y, - viewport->get_size().x * viewport->get_size().y * 0.000'001); - text += TTR("Objects Drawn") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_OBJECTS_IN_FRAME)) + "\n"; - text += TTR("Material Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_MATERIAL_CHANGES_IN_FRAME)) + "\n"; - text += TTR("Shader Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_SHADER_CHANGES_IN_FRAME)) + "\n"; - text += TTR("Surface Changes") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_SURFACE_CHANGES_IN_FRAME)) + "\n"; - text += TTR("Draw Calls") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)) + "\n"; - text += TTR("Vertices") + ": " + itos(viewport->get_render_info(Viewport::RENDER_INFO_VERTICES_IN_FRAME)); + text += vformat(TTR("X: %s\n"), rtos(current_camera->get_position().x).pad_decimals(1)); + text += vformat(TTR("Y: %s\n"), rtos(current_camera->get_position().y).pad_decimals(1)); + text += vformat(TTR("Z: %s\n"), rtos(current_camera->get_position().z).pad_decimals(1)); + text += "\n"; + text += vformat( + TTR("Size: %s (%.1fMP)\n"), + viewport_size, + viewport->get_size().x * viewport->get_size().y * 0.000001); + + text += "\n"; + text += vformat(TTR("Objects: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_OBJECTS_IN_FRAME)); + text += vformat(TTR("Primitives: %d\n"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_PRIMITIVES_IN_FRAME)); + text += vformat(TTR("Draw Calls: %d"), viewport->get_render_info(Viewport::RENDER_INFO_TYPE_VISIBLE, Viewport::RENDER_INFO_DRAW_CALLS_IN_FRAME)); info_label->set_text(text); } @@ -2878,7 +2895,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { break; } - Transform camera_transform = camera->get_global_transform(); + Transform3D camera_transform = camera->get_global_transform(); List<Node *> &selection = editor_selection->get_selected_node_list(); @@ -2895,7 +2912,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { continue; } - Transform xform; + Transform3D xform; if (orthogonal) { xform = sp->get_global_transform(); xform.basis.set_euler(camera_transform.basis.get_euler()); @@ -2915,7 +2932,7 @@ void Node3DEditorViewport::_menu_option(int p_option) { break; } - Transform camera_transform = camera->get_global_transform(); + Transform3D camera_transform = camera->get_global_transform(); List<Node *> &selection = editor_selection->get_selected_node_list(); @@ -3060,9 +3077,9 @@ void Node3DEditorViewport::_menu_option(int p_option) { case VIEW_DISPLAY_NORMAL_BUFFER: case VIEW_DISPLAY_DEBUG_SHADOW_ATLAS: case VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS: - case VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO: - case VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING: - case VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION: + case VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO: + case VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING: + case VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION: case VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE: case VIEW_DISPLAY_DEBUG_SSAO: case VIEW_DISPLAY_DEBUG_PSSM_SPLITS: @@ -3086,9 +3103,9 @@ void Node3DEditorViewport::_menu_option(int p_option) { VIEW_DISPLAY_WIREFRAME, VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, - VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO, - VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING, - VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION, + VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, + VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, + VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, VIEW_DISPLAY_DEBUG_SSAO, VIEW_DISPLAY_DEBUG_GI_BUFFER, @@ -3114,9 +3131,9 @@ void Node3DEditorViewport::_menu_option(int p_option) { Viewport::DEBUG_DRAW_WIREFRAME, Viewport::DEBUG_DRAW_SHADOW_ATLAS, Viewport::DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS, - Viewport::DEBUG_DRAW_GI_PROBE_ALBEDO, - Viewport::DEBUG_DRAW_GI_PROBE_LIGHTING, - Viewport::DEBUG_DRAW_GI_PROBE_EMISSION, + Viewport::DEBUG_DRAW_VOXEL_GI_ALBEDO, + Viewport::DEBUG_DRAW_VOXEL_GI_LIGHTING, + Viewport::DEBUG_DRAW_VOXEL_GI_EMISSION, Viewport::DEBUG_DRAW_SCENE_LUMINANCE, Viewport::DEBUG_DRAW_SSAO, Viewport::DEBUG_DRAW_GI_BUFFER, @@ -3249,14 +3266,12 @@ void Node3DEditorViewport::_toggle_camera_preview(bool p_activate) { if (!preview) { preview_camera->hide(); } - view_menu->set_disabled(false); surface->update(); } else { previewing = preview; previewing->connect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), preview->get_camera()); //replace - view_menu->set_disabled(true); surface->update(); } } @@ -3315,9 +3330,9 @@ void Node3DEditorViewport::update_transform_gizmo_view() { return; } - Transform xform = spatial_editor->get_gizmo_transform(); + Transform3D xform = spatial_editor->get_gizmo_transform(); - Transform camera_xform = camera->get_transform(); + Transform3D camera_xform = camera->get_transform(); if (xform.origin.distance_squared_to(camera_xform.origin) < 0.01) { for (int i = 0; i < 3; i++) { @@ -3503,7 +3518,6 @@ void Node3DEditorViewport::set_state(const Dictionary &p_state) { previewing = Object::cast_to<Camera3D>(pv); previewing->connect("tree_exiting", callable_mp(this, &Node3DEditorViewport::_preview_exited_scene)); RS::get_singleton()->viewport_attach_camera(viewport->get_viewport_rid(), previewing->get_camera()); //replace - view_menu->set_disabled(true); surface->update(); preview_camera->set_pressed(true); preview_camera->show(); @@ -3551,8 +3565,8 @@ Dictionary Node3DEditorViewport::get_state() const { void Node3DEditorViewport::_bind_methods() { ClassDB::bind_method(D_METHOD("update_transform_gizmo_view"), &Node3DEditorViewport::update_transform_gizmo_view); // Used by call_deferred. - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &Node3DEditorViewport::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &Node3DEditorViewport::drop_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &Node3DEditorViewport::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &Node3DEditorViewport::drop_data_fw); ADD_SIGNAL(MethodInfo("toggle_maximize_view", PropertyInfo(Variant::OBJECT, "viewport"))); ADD_SIGNAL(MethodInfo("clicked", PropertyInfo(Variant::OBJECT, "viewport"))); @@ -3711,7 +3725,7 @@ void Node3DEditorViewport::_create_preview(const Vector<String> &files) const { preview_node->add_child(mesh_instance); } else { if (scene.is_valid()) { - Node *instance = scene->instance(); + Node *instance = scene->instantiate(); if (instance) { preview_node->add_child(instance); } @@ -3756,51 +3770,51 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po Ref<PackedScene> scene = Ref<PackedScene>(Object::cast_to<PackedScene>(*res)); Ref<Mesh> mesh = Ref<Mesh>(Object::cast_to<Mesh>(*res)); - Node *instanced_scene = nullptr; + Node *instantiated_scene = nullptr; if (mesh != nullptr || scene != nullptr) { if (mesh != nullptr) { MeshInstance3D *mesh_instance = memnew(MeshInstance3D); mesh_instance->set_mesh(mesh); mesh_instance->set_name(path.get_file().get_basename()); - instanced_scene = mesh_instance; + instantiated_scene = mesh_instance; } else { if (!scene.is_valid()) { // invalid scene return false; } else { - instanced_scene = scene->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); + instantiated_scene = scene->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); } } } - if (instanced_scene == nullptr) { + if (instantiated_scene == nullptr) { return false; } if (editor->get_edited_scene()->get_filename() != "") { // cyclical instancing - if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instanced_scene)) { - memdelete(instanced_scene); + if (_cyclical_dependency_exists(editor->get_edited_scene()->get_filename(), instantiated_scene)) { + memdelete(instantiated_scene); return false; } } if (scene != nullptr) { - instanced_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); + instantiated_scene->set_filename(ProjectSettings::get_singleton()->localize_path(path)); } - editor_data->get_undo_redo().add_do_method(parent, "add_child", instanced_scene); - editor_data->get_undo_redo().add_do_method(instanced_scene, "set_owner", editor->get_edited_scene()); - editor_data->get_undo_redo().add_do_reference(instanced_scene); - editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instanced_scene); + editor_data->get_undo_redo().add_do_method(parent, "add_child", instantiated_scene); + editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_owner", editor->get_edited_scene()); + editor_data->get_undo_redo().add_do_reference(instantiated_scene); + editor_data->get_undo_redo().add_undo_method(parent, "remove_child", instantiated_scene); - String new_name = parent->validate_child_name(instanced_scene); + String new_name = parent->validate_child_name(instantiated_scene); EditorDebuggerNode *ed = EditorDebuggerNode::get_singleton(); editor_data->get_undo_redo().add_do_method(ed, "live_debug_instance_node", editor->get_edited_scene()->get_path_to(parent), path, new_name); editor_data->get_undo_redo().add_undo_method(ed, "live_debug_remove_node", NodePath(String(editor->get_edited_scene()->get_path_to(parent)) + "/" + new_name)); - Node3D *node3d = Object::cast_to<Node3D>(instanced_scene); + Node3D *node3d = Object::cast_to<Node3D>(instantiated_scene); if (node3d) { - Transform global_transform; + Transform3D global_transform; Node3D *parent_node3d = Object::cast_to<Node3D>(parent); if (parent_node3d) { global_transform = parent_node3d->get_global_gizmo_transform(); @@ -3809,7 +3823,7 @@ bool Node3DEditorViewport::_create_instance(Node *parent, String &path, const Po global_transform.origin = spatial_editor->snap_point(_get_instance_position(p_point)); global_transform.basis *= node3d->get_transform().basis; - editor_data->get_undo_redo().add_do_method(instanced_scene, "set_global_transform", global_transform); + editor_data->get_undo_redo().add_do_method(instantiated_scene, "set_global_transform", global_transform); } return true; @@ -3852,7 +3866,7 @@ void Node3DEditorViewport::_perform_drop_data() { } bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { - bool can_instance = false; + bool can_instantiate = false; if (!preview_node->is_inside_tree()) { Dictionary d = p_data; @@ -3874,11 +3888,11 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant String type = res->get_class(); if (type == "PackedScene") { Ref<PackedScene> sdata = ResourceLoader::load(files[i]); - Node *instanced_scene = sdata->instance(PackedScene::GEN_EDIT_STATE_INSTANCE); - if (!instanced_scene) { + Node *instantiated_scene = sdata->instantiate(PackedScene::GEN_EDIT_STATE_INSTANCE); + if (!instantiated_scene) { continue; } - memdelete(instanced_scene); + memdelete(instantiated_scene); } else if (type == "Mesh" || type == "ArrayMesh" || type == "PrimitiveMesh") { Ref<Mesh> mesh = ResourceLoader::load(files[i]); if (!mesh.is_valid()) { @@ -3887,24 +3901,24 @@ bool Node3DEditorViewport::can_drop_data_fw(const Point2 &p_point, const Variant } else { continue; } - can_instance = true; + can_instantiate = true; break; } } - if (can_instance) { + if (can_instantiate) { _create_preview(files); } } } else { - can_instance = true; + can_instantiate = true; } - if (can_instance) { - Transform global_transform = Transform(Basis(), _get_instance_position(p_point)); + if (can_instantiate) { + Transform3D global_transform = Transform3D(Basis(), _get_instance_position(p_point)); preview_node->set_global_transform(global_transform); } - return can_instance; + return can_instantiate; } void Node3DEditorViewport::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { @@ -4036,9 +4050,9 @@ Node3DEditorViewport::Node3DEditorViewport(Node3DEditor *p_spatial_editor, Edito display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("Decal Atlas"), VIEW_DISPLAY_DEBUG_DECAL_ATLAS); display_submenu->add_separator(); - display_submenu->add_radio_check_item(TTR("GIProbe Lighting"), VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING); - display_submenu->add_radio_check_item(TTR("GIProbe Albedo"), VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO); - display_submenu->add_radio_check_item(TTR("GIProbe Emission"), VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION); + display_submenu->add_radio_check_item(TTR("VoxelGI Lighting"), VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING); + display_submenu->add_radio_check_item(TTR("VoxelGI Albedo"), VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO); + display_submenu->add_radio_check_item(TTR("VoxelGI Emission"), VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION); display_submenu->add_separator(); display_submenu->add_radio_check_item(TTR("SDFGI Cascades"), VIEW_DISPLAY_DEBUG_SDFGI); display_submenu->add_radio_check_item(TTR("SDFGI Probes"), VIEW_DISPLAY_DEBUG_SDFGI_PROBES); @@ -4574,7 +4588,7 @@ void Node3DEditor::update_transform_gizmo() { continue; } - Transform xf = se->sp->get_global_gizmo_transform(); + Transform3D xf = se->sp->get_global_gizmo_transform(); if (first) { center.position = xf.origin; @@ -4955,7 +4969,7 @@ void Node3DEditor::_snap_update() { } void Node3DEditor::_xform_dialog_action() { - Transform t; + Transform3D t; //translation Vector3 scale; Vector3 rotate; @@ -4988,7 +5002,7 @@ void Node3DEditor::_xform_dialog_action() { bool post = xform_type->get_selected() > 0; - Transform tr = sp->get_global_gizmo_transform(); + Transform3D tr = sp->get_global_gizmo_transform(); if (post) { tr = tr * t; } else { @@ -5056,11 +5070,11 @@ void Node3DEditor::_update_camera_override_button(bool p_game_running) { if (p_game_running) { button->set_disabled(false); - button->set_tooltip(TTR("Game Camera Override\nNo game instance running.")); + button->set_tooltip(TTR("Project Camera Override\nOverrides the running project's camera with the editor viewport camera.")); } else { button->set_disabled(true); button->set_pressed(false); - button->set_tooltip(TTR("Game Camera Override\nOverrides game camera with editor viewport camera.")); + button->set_tooltip(TTR("Project Camera Override\nNo project instance running. Run the project from the editor to use this feature.")); } } @@ -5314,7 +5328,7 @@ void Node3DEditor::_init_indicators() { origin_enabled = true; grid_enabled = true; - indicator_mat.instance(); + indicator_mat.instantiate(); indicator_mat->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); indicator_mat->set_flag(StandardMaterial3D::FLAG_ALBEDO_FROM_VERTEX_COLOR, true); indicator_mat->set_flag(StandardMaterial3D::FLAG_SRGB_VERTEX_COLOR, true); @@ -5394,7 +5408,7 @@ void Node3DEditor::_init_indicators() { "}"); for (int i = 0; i < 3; i++) { - grid_mat[i].instance(); + grid_mat[i].instantiate(); grid_mat[i]->set_shader(grid_shader); } @@ -5834,7 +5848,7 @@ void Node3DEditor::_init_grid() { return; } Camera3D *camera = get_editor_viewport(0)->camera; - Vector3 camera_position = camera->get_translation(); + Vector3 camera_position = camera->get_position(); if (camera_position == Vector3()) { return; // Camera3D is invalid, don't draw the grid. } @@ -6180,7 +6194,7 @@ void Node3DEditor::snap_selected_nodes_to_floor() { if (ss->intersect_ray(from, to, result, excluded)) { Vector3 position_offset = d["position_offset"]; - Transform new_transform = sp->get_global_transform(); + Transform3D new_transform = sp->get_global_transform(); new_transform.origin.y = result.position.y; new_transform.origin = new_transform.origin - position_offset; @@ -6204,7 +6218,7 @@ void Node3DEditor::_unhandled_key_input(Ref<InputEvent> p_event) { return; } - snap_key_enabled = Input::get_singleton()->is_key_pressed(KEY_CONTROL); + snap_key_enabled = Input::get_singleton()->is_key_pressed(KEY_CTRL); } void Node3DEditor::_sun_environ_settings_pressed() { @@ -6213,31 +6227,47 @@ void Node3DEditor::_sun_environ_settings_pressed() { sun_environ_popup->popup(); } -void Node3DEditor::_add_sun_to_scene() { +void Node3DEditor::_add_sun_to_scene(bool p_already_added_environment) { sun_environ_popup->hide(); + if (!p_already_added_environment && world_env_count == 0 && Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + // Prevent infinite feedback loop between the sun and environment methods. + _add_environment_to_scene(true); + } + Node *base = get_tree()->get_edited_scene_root(); if (!base) { - EditorNode::get_singleton()->show_warning(TTR("A root node is needed for this operation")); - return; + // Create a root node so we can add child nodes to it. + EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D)); + base = get_tree()->get_edited_scene_root(); } ERR_FAIL_COND(!base); Node *new_sun = preview_sun->duplicate(); undo_redo->create_action("Add Preview Sun to Scene"); undo_redo->add_do_method(base, "add_child", new_sun); + // Move to the beginning of the scene tree since more "global" nodes + // generally look better when placed at the top. + undo_redo->add_do_method(base, "move_child", new_sun, 0); undo_redo->add_do_method(new_sun, "set_owner", base); undo_redo->add_undo_method(base, "remove_child", new_sun); undo_redo->add_do_reference(new_sun); undo_redo->commit_action(); } -void Node3DEditor::_add_environment_to_scene() { + +void Node3DEditor::_add_environment_to_scene(bool p_already_added_sun) { sun_environ_popup->hide(); + if (!p_already_added_sun && directional_light_count == 0 && Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + // Prevent infinite feedback loop between the sun and environment methods. + _add_sun_to_scene(true); + } + Node *base = get_tree()->get_edited_scene_root(); if (!base) { - EditorNode::get_singleton()->show_warning(TTR("A root node is needed for this operation")); - return; + // Create a root node so we can add child nodes to it. + EditorNode::get_singleton()->get_scene_tree_dock()->add_root_node(memnew(Node3D)); + base = get_tree()->get_edited_scene_root(); } ERR_FAIL_COND(!base); @@ -6246,6 +6276,9 @@ void Node3DEditor::_add_environment_to_scene() { undo_redo->create_action("Add Preview Environment to Scene"); undo_redo->add_do_method(base, "add_child", new_env); + // Move to the beginning of the scene tree since more "global" nodes + // generally look better when placed at the top. + undo_redo->add_do_method(base, "move_child", new_env, 0); undo_redo->add_do_method(new_env, "set_owner", base); undo_redo->add_undo_method(base, "remove_child", new_env); undo_redo->add_do_reference(new_env); @@ -6368,7 +6401,7 @@ void Node3DEditor::_request_gizmo(Object *p_obj) { if (!sp) { return; } - if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_a_parent_of(sp)))) { + if (editor->get_edited_scene() && (sp == editor->get_edited_scene() || (sp->get_owner() && editor->get_edited_scene()->is_ancestor_of(sp)))) { Ref<EditorNode3DGizmo> seg; for (int i = 0; i < gizmo_plugins_by_priority.size(); ++i) { @@ -6442,7 +6475,7 @@ void Node3DEditor::_toggle_maximize_view(Object *p_viewport) { } void Node3DEditor::_node_added(Node *p_node) { - if (EditorNode::get_singleton()->get_scene_root()->is_a_parent_of(p_node)) { + if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) { if (Object::cast_to<WorldEnvironment>(p_node)) { world_env_count++; if (world_env_count == 1) { @@ -6458,7 +6491,7 @@ void Node3DEditor::_node_added(Node *p_node) { } void Node3DEditor::_node_removed(Node *p_node) { - if (EditorNode::get_singleton()->get_scene_root()->is_a_parent_of(p_node)) { + if (EditorNode::get_singleton()->get_scene_root()->is_ancestor_of(p_node)) { if (Object::cast_to<WorldEnvironment>(p_node)) { world_env_count--; if (world_env_count == 0) { @@ -6490,14 +6523,14 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<RayCast3DGizmoPlugin>(memnew(RayCast3DGizmoPlugin))); add_gizmo_plugin(Ref<SpringArm3DGizmoPlugin>(memnew(SpringArm3DGizmoPlugin))); add_gizmo_plugin(Ref<VehicleWheel3DGizmoPlugin>(memnew(VehicleWheel3DGizmoPlugin))); - add_gizmo_plugin(Ref<VisibilityNotifier3DGizmoPlugin>(memnew(VisibilityNotifier3DGizmoPlugin))); + add_gizmo_plugin(Ref<VisibleOnScreenNotifier3DGizmoPlugin>(memnew(VisibleOnScreenNotifier3DGizmoPlugin))); add_gizmo_plugin(Ref<GPUParticles3DGizmoPlugin>(memnew(GPUParticles3DGizmoPlugin))); add_gizmo_plugin(Ref<GPUParticlesCollision3DGizmoPlugin>(memnew(GPUParticlesCollision3DGizmoPlugin))); add_gizmo_plugin(Ref<CPUParticles3DGizmoPlugin>(memnew(CPUParticles3DGizmoPlugin))); add_gizmo_plugin(Ref<ReflectionProbeGizmoPlugin>(memnew(ReflectionProbeGizmoPlugin))); add_gizmo_plugin(Ref<DecalGizmoPlugin>(memnew(DecalGizmoPlugin))); - add_gizmo_plugin(Ref<GIProbeGizmoPlugin>(memnew(GIProbeGizmoPlugin))); - add_gizmo_plugin(Ref<BakedLightmapGizmoPlugin>(memnew(BakedLightmapGizmoPlugin))); + add_gizmo_plugin(Ref<VoxelGIGizmoPlugin>(memnew(VoxelGIGizmoPlugin))); + add_gizmo_plugin(Ref<LightmapGIGizmoPlugin>(memnew(LightmapGIGizmoPlugin))); add_gizmo_plugin(Ref<LightmapProbeGizmoPlugin>(memnew(LightmapProbeGizmoPlugin))); add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin))); add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin))); @@ -6545,9 +6578,11 @@ void Node3DEditor::clear() { void Node3DEditor::_sun_direction_draw() { sun_direction->draw_rect(Rect2(Vector2(), sun_direction->get_size()), Color(1, 1, 1, 1)); - sun_direction_material->set_shader_param("sun_direction", -preview_sun->get_transform().basis.get_axis(Vector3::AXIS_Z)); - float nrg = sun_energy->get_value(); - sun_direction_material->set_shader_param("sun_color", Vector3(sun_color->get_pick_color().r * nrg, sun_color->get_pick_color().g * nrg, sun_color->get_pick_color().b * nrg)); + Vector3 z_axis = preview_sun->get_transform().basis.get_axis(Vector3::AXIS_Z); + z_axis = get_editor_viewport(0)->camera->get_camera_transform().basis.xform_inv(z_axis); + sun_direction_material->set_shader_param("sun_direction", Vector3(z_axis.x, -z_axis.y, z_axis.z)); + Color color = sun_color->get_pick_color() * sun_energy->get_value(); + sun_direction_material->set_shader_param("sun_color", Vector3(color.r, color.g, color.b)); } void Node3DEditor::_preview_settings_changed() { @@ -6556,8 +6591,8 @@ void Node3DEditor::_preview_settings_changed() { } { // preview sun - Transform t; - t.basis = sun_rotation; + Transform3D t; + t.basis = Basis(Vector3(sun_rotation.x, sun_rotation.y, 0)); preview_sun->set_transform(t); sun_direction->update(); preview_sun->set_param(Light3D::PARAM_ENERGY, sun_energy->get_value()); @@ -6579,11 +6614,20 @@ void Node3DEditor::_preview_settings_changed() { environment->set_tonemapper(environ_tonemap_button->is_pressed() ? Environment::TONE_MAPPER_FILMIC : Environment::TONE_MAPPER_LINEAR); } } + void Node3DEditor::_load_default_preview_settings() { sun_environ_updating = true; - sun_rotation = Basis(Vector3(0, 1, 0), Math_PI * 3.0 / 4) * Basis(Vector3(1, 0, 0), -Math_PI / 4); + // These default rotations place the preview sun at an angular altitude + // of 60 degrees (must be negative) and an azimuth of 30 degrees clockwise + // from north (or 150 CCW from south), from north east, facing south west. + // On any not-tidally-locked planet, a sun would have an angular altitude + // of 60 degrees as the average of all points on the sphere at noon. + // The azimuth choice is arbitrary, but ideally shouldn't be on an axis. + sun_rotation = Vector2(-Math::deg2rad(60.0), Math::deg2rad(150.0)); + sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x)); + sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y)); sun_direction->update(); environ_sky_color->set_pick_color(Color::hex(0x91b2ceff)); environ_ground_color->set_pick_color(Color::hex(0x1f1f21ff)); @@ -6626,6 +6670,9 @@ void Node3DEditor::_update_preview_environment() { } } + sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x)); + sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y)); + bool disable_env = world_env_count > 0 || environ_button->is_pressed(); environ_button->set_disabled(world_env_count > 0); @@ -6654,17 +6701,21 @@ void Node3DEditor::_update_preview_environment() { void Node3DEditor::_sun_direction_input(const Ref<InputEvent> &p_event) { Ref<InputEventMouseMotion> mm = p_event; if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { - float x = -mm->get_relative().y * 0.02 * EDSCALE; - float y = mm->get_relative().x * 0.02 * EDSCALE; - - Basis rot = Basis(Vector3(0, 1, 0), y) * Basis(Vector3(1, 0, 0), x); - - sun_rotation = rot * sun_rotation; - sun_rotation.orthonormalize(); + sun_rotation.x += mm->get_relative().y * (0.02 * EDSCALE); + sun_rotation.y -= mm->get_relative().x * (0.02 * EDSCALE); + sun_rotation.x = CLAMP(sun_rotation.x, -Math_TAU / 4, Math_TAU / 4); + sun_angle_altitude->set_value(-Math::rad2deg(sun_rotation.x)); + sun_angle_azimuth->set_value(180.0 - Math::rad2deg(sun_rotation.y)); _preview_settings_changed(); } } +void Node3DEditor::_sun_direction_angle_set() { + sun_rotation.x = Math::deg2rad(-sun_angle_altitude->get_value()); + sun_rotation.y = Math::deg2rad(180.0 - sun_angle_azimuth->get_value()); + _preview_settings_changed(); +} + Node3DEditor::Node3DEditor(EditorNode *p_editor) { gizmo.visible = true; gizmo.scale = 1.0; @@ -6692,6 +6743,13 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { button_binds.resize(1); String sct; + // Add some margin to the left for better aesthetics. + // This prevents the first button's hover/pressed effect from "touching" the panel's border, + // which looks ugly. + Control *margin_left = memnew(Control); + hbc_menu->add_child(margin_left); + margin_left->set_custom_minimum_size(Size2(2, 0) * EDSCALE); + tool_button[TOOL_MODE_SELECT] = memnew(Button); hbc_menu->add_child(tool_button[TOOL_MODE_SELECT]); tool_button[TOOL_MODE_SELECT]->set_toggle_mode(true); @@ -6748,6 +6806,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { button_binds.write[0] = MENU_LOCK_SELECTED; tool_button[TOOL_LOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); tool_button[TOOL_LOCK_SELECTED]->set_tooltip(TTR("Lock the selected object in place (can't be moved).")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + tool_button[TOOL_LOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KEY_MASK_CMD | KEY_L)); tool_button[TOOL_UNLOCK_SELECTED] = memnew(Button); hbc_menu->add_child(tool_button[TOOL_UNLOCK_SELECTED]); @@ -6755,6 +6815,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { button_binds.write[0] = MENU_UNLOCK_SELECTED; tool_button[TOOL_UNLOCK_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); tool_button[TOOL_UNLOCK_SELECTED]->set_tooltip(TTR("Unlock the selected object (can be moved).")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + tool_button[TOOL_UNLOCK_SELECTED]->set_shortcut(ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_L)); tool_button[TOOL_GROUP_SELECTED] = memnew(Button); hbc_menu->add_child(tool_button[TOOL_GROUP_SELECTED]); @@ -6762,6 +6824,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { button_binds.write[0] = MENU_GROUP_SELECTED; tool_button[TOOL_GROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); tool_button[TOOL_GROUP_SELECTED]->set_tooltip(TTR("Makes sure the object's children are not selectable.")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + tool_button[TOOL_GROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/group_selected_nodes", TTR("Group Selected Node(s)"), KEY_MASK_CMD | KEY_G)); tool_button[TOOL_UNGROUP_SELECTED] = memnew(Button); hbc_menu->add_child(tool_button[TOOL_UNGROUP_SELECTED]); @@ -6769,6 +6833,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { button_binds.write[0] = MENU_UNGROUP_SELECTED; tool_button[TOOL_UNGROUP_SELECTED]->connect("pressed", callable_mp(this, &Node3DEditor::_menu_item_pressed), button_binds); tool_button[TOOL_UNGROUP_SELECTED]->set_tooltip(TTR("Restores the object's children's ability to be selected.")); + // Define the shortcut globally (without a context) so that it works if the Scene tree dock is currently focused. + tool_button[TOOL_UNGROUP_SELECTED]->set_shortcut(ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_G)); hbc_menu->add_child(memnew(VSeparator)); @@ -6887,7 +6953,7 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { p->add_separator(); p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_origin", TTR("View Origin")), MENU_VIEW_ORIGIN); - p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid")), MENU_VIEW_GRID); + p->add_check_shortcut(ED_SHORTCUT("spatial_editor/view_grid", TTR("View Grid"), KEY_NUMBERSIGN), MENU_VIEW_GRID); p->add_separator(); p->add_shortcut(ED_SHORTCUT("spatial_editor/settings", TTR("Settings...")), MENU_VIEW_CAMERA_SETTINGS); @@ -7043,8 +7109,6 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { xform_dialog->connect("confirmed", callable_mp(this, &Node3DEditor::_xform_dialog_action)); - scenario_debug = RenderingServer::SCENARIO_DEBUG_DISABLED; - selected = nullptr; set_process_unhandled_key_input(true); @@ -7087,14 +7151,43 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { sun_direction->set_default_cursor_shape(CURSOR_MOVE); String sun_dir_shader_code = "shader_type canvas_item; uniform vec3 sun_direction; uniform vec3 sun_color; void fragment() { vec3 n; n.xy = UV * 2.0 - 1.0; n.z = sqrt(max(0.0, 1.0 - dot(n.xy, n.xy))); COLOR.rgb = dot(n,sun_direction) * sun_color; COLOR.a = 1.0 - smoothstep(0.99,1.0,length(n.xy)); }"; - sun_direction_shader.instance(); + sun_direction_shader.instantiate(); sun_direction_shader->set_code(sun_dir_shader_code); - sun_direction_material.instance(); + sun_direction_material.instantiate(); sun_direction_material->set_shader(sun_direction_shader); sun_direction_material->set_shader_param("sun_direction", Vector3(0, 0, 1)); sun_direction_material->set_shader_param("sun_color", Vector3(1, 1, 1)); sun_direction->set_material(sun_direction_material); + HBoxContainer *sun_angle_hbox = memnew(HBoxContainer); + VBoxContainer *sun_angle_altitude_vbox = memnew(VBoxContainer); + Label *sun_angle_altitude_label = memnew(Label); + sun_angle_altitude_label->set_text(TTR("Angular Altitude")); + sun_angle_altitude_vbox->add_child(sun_angle_altitude_label); + sun_angle_altitude = memnew(EditorSpinSlider); + sun_angle_altitude->set_max(90); + sun_angle_altitude->set_min(-90); + sun_angle_altitude->set_step(0.1); + sun_angle_altitude->connect("value_changed", callable_mp(this, &Node3DEditor::_sun_direction_angle_set).unbind(1)); + sun_angle_altitude_vbox->add_child(sun_angle_altitude); + sun_angle_hbox->add_child(sun_angle_altitude_vbox); + VBoxContainer *sun_angle_azimuth_vbox = memnew(VBoxContainer); + sun_angle_azimuth_vbox->set_custom_minimum_size(Vector2(100, 0)); + Label *sun_angle_azimuth_label = memnew(Label); + sun_angle_azimuth_label->set_text(TTR("Azimuth")); + sun_angle_azimuth_vbox->add_child(sun_angle_azimuth_label); + sun_angle_azimuth = memnew(EditorSpinSlider); + sun_angle_azimuth->set_max(180); + sun_angle_azimuth->set_min(-180); + sun_angle_azimuth->set_step(0.1); + sun_angle_azimuth->set_allow_greater(true); + sun_angle_azimuth->set_allow_lesser(true); + sun_angle_azimuth->connect("value_changed", callable_mp(this, &Node3DEditor::_sun_direction_angle_set).unbind(1)); + sun_angle_azimuth_vbox->add_child(sun_angle_azimuth); + sun_angle_hbox->add_child(sun_angle_azimuth_vbox); + sun_angle_hbox->add_theme_constant_override("separation", 10); + sun_vb->add_child(sun_angle_hbox); + sun_color = memnew(ColorPickerButton); sun_color->set_edit_alpha(false); sun_vb->add_margin_child(TTR("Sun Color"), sun_color); @@ -7113,7 +7206,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { sun_add_to_scene = memnew(Button); sun_add_to_scene->set_text(TTR("Add Sun to Scene")); - sun_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_sun_to_scene)); + sun_add_to_scene->set_tooltip(TTR("Adds a DirectionalLight3D node matching the preview sun settings to the current scene.\nHold Shift while clicking to also add the preview environment to the current scene.")); + sun_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_sun_to_scene), varray(false)); sun_vb->add_spacer(); sun_vb->add_child(sun_add_to_scene); @@ -7177,7 +7271,8 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { environ_add_to_scene = memnew(Button); environ_add_to_scene->set_text(TTR("Add Environment to Scene")); - environ_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_environment_to_scene)); + environ_add_to_scene->set_tooltip(TTR("Adds a WorldEnvironment node matching the preview environment settings to the current scene.\nHold Shift while clicking to also add the preview sun to the current scene.")); + environ_add_to_scene->connect("pressed", callable_mp(this, &Node3DEditor::_add_environment_to_scene), varray(false)); environ_vb->add_spacer(); environ_vb->add_child(environ_add_to_scene); @@ -7191,11 +7286,11 @@ Node3DEditor::Node3DEditor(EditorNode *p_editor) { preview_sun->set_shadow(true); preview_sun->set_shadow_mode(DirectionalLight3D::SHADOW_PARALLEL_4_SPLITS); preview_environment = memnew(WorldEnvironment); - environment.instance(); + environment.instantiate(); preview_environment->set_environment(environment); Ref<Sky> sky; - sky.instance(); - sky_material.instance(); + sky.instantiate(); + sky_material.instantiate(); sky->set_material(sky_material); environment->set_sky(sky); environment->set_background(Environment::BG_SKY); @@ -7236,10 +7331,6 @@ void Node3DEditorPlugin::set_state(const Dictionary &p_state) { spatial_editor->set_state(p_state); } -void Node3DEditor::snap_cursor_to_plane(const Plane &p_plane) { - //cursor.pos=p_plane.project(cursor.pos); -} - Vector3 Node3DEditor::snap_point(Vector3 p_target, Vector3 p_start) const { if (is_snap_enabled()) { p_target.x = Math::snap_scalar(0.0, get_translate_snap(), p_target.x); @@ -7282,14 +7373,6 @@ float Node3DEditor::get_scale_snap() const { return snap_value; } -void Node3DEditorPlugin::_bind_methods() { - ClassDB::bind_method("snap_cursor_to_plane", &Node3DEditorPlugin::snap_cursor_to_plane); -} - -void Node3DEditorPlugin::snap_cursor_to_plane(const Plane &p_plane) { - spatial_editor->snap_cursor_to_plane(p_plane); -} - struct _GizmoPluginPriorityComparator { bool operator()(const Ref<EditorNode3DGizmoPlugin> &p_a, const Ref<EditorNode3DGizmoPlugin> &p_b) const { if (p_a->get_priority() == p_b->get_priority()) { @@ -7338,17 +7421,17 @@ Node3DEditorPlugin::~Node3DEditorPlugin() { } void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color &p_color, bool p_billboard, bool p_on_top, bool p_use_vertex_color) { - Color instanced_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instanced", Color(0.7, 0.7, 0.7, 0.6)); + Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6)); Vector<Ref<StandardMaterial3D>> mats; for (int i = 0; i < 4; i++) { bool selected = i % 2 == 1; - bool instanced = i < 2; + bool instantiated = i < 2; Ref<StandardMaterial3D> material = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); - Color color = instanced ? instanced_color : p_color; + Color color = instantiated ? instantiated_color : p_color; if (!selected) { color.a *= 0.3; @@ -7380,17 +7463,17 @@ void EditorNode3DGizmoPlugin::create_material(const String &p_name, const Color } void EditorNode3DGizmoPlugin::create_icon_material(const String &p_name, const Ref<Texture2D> &p_texture, bool p_on_top, const Color &p_albedo) { - Color instanced_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instanced", Color(0.7, 0.7, 0.7, 0.6)); + Color instantiated_color = EDITOR_DEF("editors/3d_gizmos/gizmo_colors/instantiated", Color(0.7, 0.7, 0.7, 0.6)); Vector<Ref<StandardMaterial3D>> icons; for (int i = 0; i < 4; i++) { bool selected = i % 2 == 1; - bool instanced = i < 2; + bool instantiated = i < 2; Ref<StandardMaterial3D> icon = Ref<StandardMaterial3D>(memnew(StandardMaterial3D)); - Color color = instanced ? instanced_color : p_albedo; + Color color = instantiated ? instantiated_color : p_albedo; if (!selected) { color.a *= 0.85; @@ -7468,15 +7551,15 @@ Ref<StandardMaterial3D> EditorNode3DGizmoPlugin::get_material(const String &p_na } String EditorNode3DGizmoPlugin::get_gizmo_name() const { - if (get_script_instance() && get_script_instance()->has_method("get_gizmo_name")) { - return get_script_instance()->call("get_gizmo_name"); + if (get_script_instance() && get_script_instance()->has_method("_get_gizmo_name")) { + return get_script_instance()->call("_get_gizmo_name"); } return TTR("Nameless gizmo"); } int EditorNode3DGizmoPlugin::get_priority() const { - if (get_script_instance() && get_script_instance()->has_method("get_priority")) { - return get_script_instance()->call("get_priority"); + if (get_script_instance() && get_script_instance()->has_method("_get_priority")) { + return get_script_instance()->call("_get_priority"); } return 0; } @@ -7503,8 +7586,8 @@ Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::get_gizmo(Node3D *p_spatial) { void EditorNode3DGizmoPlugin::_bind_methods() { #define GIZMO_REF PropertyInfo(Variant::OBJECT, "gizmo", PROPERTY_HINT_RESOURCE_TYPE, "EditorNode3DGizmo") - BIND_VMETHOD(MethodInfo(Variant::BOOL, "has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"))); - BIND_VMETHOD(MethodInfo(GIZMO_REF, "create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"))); + BIND_VMETHOD(MethodInfo(Variant::BOOL, "_has_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"))); + BIND_VMETHOD(MethodInfo(GIZMO_REF, "_create_gizmo", PropertyInfo(Variant::OBJECT, "spatial", PROPERTY_HINT_RESOURCE_TYPE, "Node3D"))); ClassDB::bind_method(D_METHOD("create_material", "name", "color", "billboard", "on_top", "use_vertex_color"), &EditorNode3DGizmoPlugin::create_material, DEFVAL(false), DEFVAL(false), DEFVAL(false)); ClassDB::bind_method(D_METHOD("create_icon_material", "name", "texture", "on_top", "color"), &EditorNode3DGizmoPlugin::create_icon_material, DEFVAL(false), DEFVAL(Color(1, 1, 1, 1))); @@ -7513,97 +7596,97 @@ void EditorNode3DGizmoPlugin::_bind_methods() { ClassDB::bind_method(D_METHOD("get_material", "name", "gizmo"), &EditorNode3DGizmoPlugin::get_material, DEFVAL(Ref<EditorNode3DGizmo>())); - BIND_VMETHOD(MethodInfo(Variant::STRING, "get_gizmo_name")); - BIND_VMETHOD(MethodInfo(Variant::INT, "get_priority")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "can_be_hidden")); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "is_selectable_when_hidden")); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_gizmo_name")); + BIND_VMETHOD(MethodInfo(Variant::INT, "_get_priority")); + BIND_VMETHOD(MethodInfo(Variant::BOOL, "_can_be_hidden")); + BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_selectable_when_hidden")); - BIND_VMETHOD(MethodInfo("redraw", GIZMO_REF)); - BIND_VMETHOD(MethodInfo(Variant::STRING, "get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "index"))); + BIND_VMETHOD(MethodInfo("_redraw", GIZMO_REF)); + BIND_VMETHOD(MethodInfo(Variant::STRING, "_get_handle_name", GIZMO_REF, PropertyInfo(Variant::INT, "index"))); - MethodInfo hvget(Variant::NIL, "get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "index")); + MethodInfo hvget(Variant::NIL, "_get_handle_value", GIZMO_REF, PropertyInfo(Variant::INT, "index")); hvget.return_val.usage |= PROPERTY_USAGE_NIL_IS_VARIANT; BIND_VMETHOD(hvget); - BIND_VMETHOD(MethodInfo("set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point"))); - MethodInfo cm = MethodInfo("commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel")); + BIND_VMETHOD(MethodInfo("_set_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::OBJECT, "camera", PROPERTY_HINT_RESOURCE_TYPE, "Camera3D"), PropertyInfo(Variant::VECTOR2, "point"))); + MethodInfo cm = MethodInfo("_commit_handle", GIZMO_REF, PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::NIL, "restore"), PropertyInfo(Variant::BOOL, "cancel")); cm.default_arguments.push_back(false); BIND_VMETHOD(cm); - BIND_VMETHOD(MethodInfo(Variant::BOOL, "is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "index"))); + BIND_VMETHOD(MethodInfo(Variant::BOOL, "_is_handle_highlighted", GIZMO_REF, PropertyInfo(Variant::INT, "index"))); #undef GIZMO_REF } bool EditorNode3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { - if (get_script_instance() && get_script_instance()->has_method("has_gizmo")) { - return get_script_instance()->call("has_gizmo", p_spatial); + if (get_script_instance() && get_script_instance()->has_method("_has_gizmo")) { + return get_script_instance()->call("_has_gizmo", p_spatial); } return false; } Ref<EditorNode3DGizmo> EditorNode3DGizmoPlugin::create_gizmo(Node3D *p_spatial) { - if (get_script_instance() && get_script_instance()->has_method("create_gizmo")) { - return get_script_instance()->call("create_gizmo", p_spatial); + if (get_script_instance() && get_script_instance()->has_method("_create_gizmo")) { + return get_script_instance()->call("_create_gizmo", p_spatial); } Ref<EditorNode3DGizmo> ref; if (has_gizmo(p_spatial)) { - ref.instance(); + ref.instantiate(); } return ref; } bool EditorNode3DGizmoPlugin::can_be_hidden() const { - if (get_script_instance() && get_script_instance()->has_method("can_be_hidden")) { - return get_script_instance()->call("can_be_hidden"); + if (get_script_instance() && get_script_instance()->has_method("_can_be_hidden")) { + return get_script_instance()->call("_can_be_hidden"); } return true; } bool EditorNode3DGizmoPlugin::is_selectable_when_hidden() const { - if (get_script_instance() && get_script_instance()->has_method("is_selectable_when_hidden")) { - return get_script_instance()->call("is_selectable_when_hidden"); + if (get_script_instance() && get_script_instance()->has_method("_is_selectable_when_hidden")) { + return get_script_instance()->call("_is_selectable_when_hidden"); } return false; } void EditorNode3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { - if (get_script_instance() && get_script_instance()->has_method("redraw")) { + if (get_script_instance() && get_script_instance()->has_method("_redraw")) { Ref<EditorNode3DGizmo> ref(p_gizmo); - get_script_instance()->call("redraw", ref); + get_script_instance()->call("_redraw", ref); } } String EditorNode3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_idx) const { - if (get_script_instance() && get_script_instance()->has_method("get_handle_name")) { - return get_script_instance()->call("get_handle_name", p_gizmo, p_idx); + if (get_script_instance() && get_script_instance()->has_method("_get_handle_name")) { + return get_script_instance()->call("_get_handle_name", p_gizmo, p_idx); } return ""; } Variant EditorNode3DGizmoPlugin::get_handle_value(EditorNode3DGizmo *p_gizmo, int p_idx) const { - if (get_script_instance() && get_script_instance()->has_method("get_handle_value")) { - return get_script_instance()->call("get_handle_value", p_gizmo, p_idx); + if (get_script_instance() && get_script_instance()->has_method("_get_handle_value")) { + return get_script_instance()->call("_get_handle_value", p_gizmo, p_idx); } return Variant(); } void EditorNode3DGizmoPlugin::set_handle(EditorNode3DGizmo *p_gizmo, int p_idx, Camera3D *p_camera, const Point2 &p_point) { - if (get_script_instance() && get_script_instance()->has_method("set_handle")) { - get_script_instance()->call("set_handle", p_gizmo, p_idx, p_camera, p_point); + if (get_script_instance() && get_script_instance()->has_method("_set_handle")) { + get_script_instance()->call("_set_handle", p_gizmo, p_idx, p_camera, p_point); } } void EditorNode3DGizmoPlugin::commit_handle(EditorNode3DGizmo *p_gizmo, int p_idx, const Variant &p_restore, bool p_cancel) { - if (get_script_instance() && get_script_instance()->has_method("commit_handle")) { - get_script_instance()->call("commit_handle", p_gizmo, p_idx, p_restore, p_cancel); + if (get_script_instance() && get_script_instance()->has_method("_commit_handle")) { + get_script_instance()->call("_commit_handle", p_gizmo, p_idx, p_restore, p_cancel); } } bool EditorNode3DGizmoPlugin::is_handle_highlighted(const EditorNode3DGizmo *p_gizmo, int p_idx) const { - if (get_script_instance() && get_script_instance()->has_method("is_handle_highlighted")) { - return get_script_instance()->call("is_handle_highlighted", p_gizmo, p_idx); + if (get_script_instance() && get_script_instance()->has_method("_is_handle_highlighted")) { + return get_script_instance()->call("_is_handle_highlighted", p_gizmo, p_idx); } return false; } diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 33f4c32471..a195a0eee0 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -34,7 +34,6 @@ #include "editor/editor_node.h" #include "editor/editor_plugin.h" #include "editor/editor_scale.h" -#include "scene/3d/immediate_geometry_3d.h" #include "scene/3d/light_3d.h" #include "scene/3d/visual_instance_3d.h" #include "scene/3d/world_environment.h" @@ -52,7 +51,7 @@ class EditorNode3DGizmo : public Node3DGizmo { GDCLASS(EditorNode3DGizmo, Node3DGizmo); bool selected; - bool instanced; + bool instantiated; public: void set_selected(bool p_selected) { selected = p_selected; } @@ -206,9 +205,9 @@ class Node3DEditorViewport : public Control { VIEW_DISPLAY_NORMAL_BUFFER, VIEW_DISPLAY_DEBUG_SHADOW_ATLAS, VIEW_DISPLAY_DEBUG_DIRECTIONAL_SHADOW_ATLAS, - VIEW_DISPLAY_DEBUG_GIPROBE_ALBEDO, - VIEW_DISPLAY_DEBUG_GIPROBE_LIGHTING, - VIEW_DISPLAY_DEBUG_GIPROBE_EMISSION, + VIEW_DISPLAY_DEBUG_VOXEL_GI_ALBEDO, + VIEW_DISPLAY_DEBUG_VOXEL_GI_LIGHTING, + VIEW_DISPLAY_DEBUG_VOXEL_GI_EMISSION, VIEW_DISPLAY_DEBUG_SCENE_LUMINANCE, VIEW_DISPLAY_DEBUG_SSAO, VIEW_DISPLAY_DEBUG_PSSM_SPLITS, @@ -322,7 +321,7 @@ private: Vector3 _get_ray_pos(const Vector2 &p_pos) const; Vector3 _get_ray(const Vector2 &p_pos) const; Point2 _point_to_screen(const Vector3 &p_point); - Transform _get_camera_transform() const; + Transform3D _get_camera_transform() const; int get_selected_count() const; Vector3 _get_camera_position() const; @@ -380,13 +379,14 @@ private: struct EditData { TransformMode mode; TransformPlane plane; - Transform original; + Transform3D original; Vector3 click_ray; Vector3 click_ray_pos; Vector3 center; Vector3 orig_gizmo_pos; int edited_gizmo = 0; Point2 mouse_pos; + Point2 original_mouse_pos; bool snap = false; Ref<EditorNode3DGizmo> gizmo; int gizmo_handle = 0; @@ -432,7 +432,7 @@ private: // void _update_camera(float p_interp_delta); - Transform to_camera_transform(const Cursor &p_cursor) const; + Transform3D to_camera_transform(const Cursor &p_cursor) const; void _draw(); void _surface_mouse_enter(); @@ -505,9 +505,9 @@ class Node3DEditorSelectedItem : public Object { public: AABB aabb; - Transform original; // original location when moving - Transform original_local; - Transform last_xform; // last transform + Transform3D original; // original location when moving + Transform3D original_local; + Transform3D last_xform; // last transform bool last_xform_dirty; Node3D *sp; RID sbox_instance; @@ -600,8 +600,6 @@ private: ToolMode tool_mode; - RenderingServer::ScenarioDebugMode scenario_debug; - RID origin; RID origin_instance; bool origin_enabled; @@ -641,7 +639,7 @@ private: struct Gizmo { bool visible = false; float scale = 0; - Transform transform; + Transform3D transform; } gizmo; enum MenuOption { @@ -763,6 +761,8 @@ private: VBoxContainer *sun_vb; Popup *sun_environ_popup; Control *sun_direction; + EditorSpinSlider *sun_angle_altitude; + EditorSpinSlider *sun_angle_azimuth; ColorPickerButton *sun_color; EditorSpinSlider *sun_energy; EditorSpinSlider *sun_max_distance; @@ -770,8 +770,9 @@ private: void _sun_direction_draw(); void _sun_direction_input(const Ref<InputEvent> &p_event); + void _sun_direction_angle_set(); - Basis sun_rotation; + Vector2 sun_rotation; Ref<Shader> sun_direction_shader; Ref<ShaderMaterial> sun_direction_material; @@ -804,8 +805,8 @@ private: void _preview_settings_changed(); void _sun_environ_settings_pressed(); - void _add_sun_to_scene(); - void _add_environment_to_scene(); + void _add_sun_to_scene(bool p_already_added_environment = false); + void _add_environment_to_scene(bool p_already_added_sun = false); protected: void _notification(int p_what); @@ -816,7 +817,6 @@ protected: public: static Node3DEditor *get_singleton() { return singleton; } - void snap_cursor_to_plane(const Plane &p_plane); Vector3 snap_point(Vector3 p_target, Vector3 p_start = Vector3(0, 0, 0)) const; @@ -824,7 +824,7 @@ public: float get_zfar() const { return settings_zfar->get_value(); } float get_fov() const { return settings_fov->get_value(); } - Transform get_gizmo_transform() const { return gizmo.transform; } + Transform3D get_gizmo_transform() const { return gizmo.transform; } bool is_gizmo_visible() const { return gizmo.visible; } ToolMode get_tool_mode() const { return tool_mode; } @@ -889,12 +889,7 @@ class Node3DEditorPlugin : public EditorPlugin { Node3DEditor *spatial_editor; EditorNode *editor; -protected: - static void _bind_methods(); - public: - void snap_cursor_to_plane(const Plane &p_plane); - Node3DEditor *get_spatial_editor() { return spatial_editor; } virtual String get_name() const override { return "3D"; } bool has_main_screen() const override { return true; } diff --git a/editor/plugins/occluder_instance_3d_editor_plugin.cpp b/editor/plugins/occluder_instance_3d_editor_plugin.cpp index 0821f140b3..b0cafd83be 100644 --- a/editor/plugins/occluder_instance_3d_editor_plugin.cpp +++ b/editor/plugins/occluder_instance_3d_editor_plugin.cpp @@ -56,7 +56,7 @@ void OccluderInstance3DEditorPlugin::_bake_select_file(const String &p_file) { } break; case OccluderInstance3D::BAKE_ERROR_NO_MESHES: { - EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.")); + EditorNode::get_singleton()->show_warning(TTR("No meshes to bake.\nMake sure there is at least one MeshInstance3D node in the scene whose visual layers are part of the OccluderInstance3D's Bake Mask property.")); break; } default: { diff --git a/editor/plugins/ot_features_plugin.cpp b/editor/plugins/ot_features_plugin.cpp index ebfdf2c7cd..2ac90762e3 100644 --- a/editor/plugins/ot_features_plugin.cpp +++ b/editor/plugins/ot_features_plugin.cpp @@ -191,7 +191,7 @@ void EditorInspectorPluginOpenTypeFeatures::parse_begin(Object *p_object) { void EditorInspectorPluginOpenTypeFeatures::parse_category(Object *p_object, const String &p_parse_category) { } -bool EditorInspectorPluginOpenTypeFeatures::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) { +bool EditorInspectorPluginOpenTypeFeatures::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { if (p_path == "opentype_features/_new") { OpenTypeFeaturesAdd *editor = memnew(OpenTypeFeaturesAdd); add_property_editor(p_path, editor); @@ -208,6 +208,6 @@ bool EditorInspectorPluginOpenTypeFeatures::parse_property(Object *p_object, Var OpenTypeFeaturesEditorPlugin::OpenTypeFeaturesEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginOpenTypeFeatures> ftr_plugin; - ftr_plugin.instance(); + ftr_plugin.instantiate(); EditorInspector::add_inspector_plugin(ftr_plugin); } diff --git a/editor/plugins/ot_features_plugin.h b/editor/plugins/ot_features_plugin.h index 9559a6c0c3..dbafa3bbf6 100644 --- a/editor/plugins/ot_features_plugin.h +++ b/editor/plugins/ot_features_plugin.h @@ -88,7 +88,7 @@ public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; virtual void parse_category(Object *p_object, const String &p_parse_category) override; - virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; }; /*************************************************************************/ diff --git a/editor/plugins/path_2d_editor_plugin.cpp b/editor/plugins/path_2d_editor_plugin.cpp index 84b4516452..838e67b5e7 100644 --- a/editor/plugins/path_2d_editor_plugin.cpp +++ b/editor/plugins/path_2d_editor_plugin.cpp @@ -31,7 +31,7 @@ #include "path_2d_editor_plugin.h" #include "canvas_item_editor_plugin.h" -#include "core/os/file_access.h" +#include "core/io/file_access.h" #include "core/os/keyboard.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -89,7 +89,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { // Check for point movement start (for point + in/out controls). if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (mode == MODE_EDIT && !mb->get_shift() && dist_to_p < grab_threshold) { + if (mode == MODE_EDIT && !mb->is_shift_pressed() && dist_to_p < grab_threshold) { // Points can only be moved in edit mode. action = ACTION_MOVING_POINT; @@ -149,7 +149,7 @@ bool Path2DEditor::forward_gui_input(const Ref<InputEvent> &p_event) { } // Check for point creation. - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && ((mb->get_command() && mode == MODE_EDIT) || mode == MODE_CREATE)) { + if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && ((mb->is_command_pressed() && mode == MODE_EDIT) || mode == MODE_CREATE)) { Ref<Curve2D> curve = node->get_curve(); undo_redo->create_action(TTR("Add Point to Curve")); diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 47bd1114d2..82b51f8a06 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -94,8 +94,8 @@ void Path3DGizmo::set_handle(int p_idx, Camera3D *p_camera, const Point2 &p_poin return; } - Transform gt = path->get_global_transform(); - Transform gi = gt.affine_inverse(); + Transform3D gt = path->get_global_transform(); + Transform3D gi = gt.affine_inverse(); Vector3 ray_from = p_camera->project_ray_origin(p_point); Vector3 ray_dir = p_camera->project_ray_normal(p_point); @@ -302,8 +302,8 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref if (c.is_null()) { return false; } - Transform gt = path->get_global_transform(); - Transform it = gt.affine_inverse(); + Transform3D gt = path->get_global_transform(); + Transform3D it = gt.affine_inverse(); static const int click_dist = 10; //should make global @@ -316,7 +316,7 @@ bool Path3DEditorPlugin::forward_spatial_gui_input(Camera3D *p_camera, const Ref set_handle_clicked(false); } - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->get_control()))) { + if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && (curve_create->is_pressed() || (curve_edit->is_pressed() && mb->is_ctrl_pressed()))) { //click into curve, break it down Vector<Vector3> v3a = c->tessellate(); int idx = 0; @@ -554,7 +554,7 @@ Path3DEditorPlugin::Path3DEditorPlugin(EditorNode *p_node) { mirror_handle_length = true; Ref<Path3DGizmoPlugin> gizmo_plugin; - gizmo_plugin.instance(); + gizmo_plugin.instantiate(); Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); sep = memnew(VSeparator); diff --git a/editor/plugins/polygon_2d_editor_plugin.cpp b/editor/plugins/polygon_2d_editor_plugin.cpp index 470d897dcc..1a13a028c8 100644 --- a/editor/plugins/polygon_2d_editor_plugin.cpp +++ b/editor/plugins/polygon_2d_editor_plugin.cpp @@ -32,8 +32,8 @@ #include "canvas_item_editor_plugin.h" #include "core/input/input.h" +#include "core/io/file_access.h" #include "core/math/geometry_2d.h" -#include "core/os/file_access.h" #include "core/os/keyboard.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" @@ -162,7 +162,7 @@ void Polygon2DEditor::_update_bone_list() { } Ref<ButtonGroup> bg; - bg.instance(); + bg.instantiate(); for (int i = 0; i < node->get_bone_count(); i++) { CheckBox *cb = memnew(CheckBox); NodePath np = node->get_bone_path(i); @@ -613,11 +613,11 @@ void Polygon2DEditor::_uv_input(const Ref<InputEvent> &p_input) { } if (uv_move_current == UV_MODE_EDIT_POINT) { - if (mb->get_shift() && mb->get_command()) { + if (mb->is_shift_pressed() && mb->is_command_pressed()) { uv_move_current = UV_MODE_SCALE; - } else if (mb->get_shift()) { + } else if (mb->is_shift_pressed()) { uv_move_current = UV_MODE_MOVE; - } else if (mb->get_command()) { + } else if (mb->is_command_pressed()) { uv_move_current = UV_MODE_ROTATE; } } @@ -1144,7 +1144,7 @@ void Polygon2DEditor::_uv_draw() { if (!found_child) { //draw normally Transform2D bone_xform = node->get_global_transform().affine_inverse() * (skeleton->get_global_transform() * bone->get_skeleton_rest()); - Transform2D endpoint_xform = bone_xform * Transform2D(0, Vector2(bone->get_default_length(), 0)); + Transform2D endpoint_xform = bone_xform * Transform2D(0, Vector2(bone->get_length(), 0)); Color color = current ? Color(1, 1, 1) : Color(0.5, 0.5, 0.5); uv_edit_draw->draw_line(mtx.xform(bone_xform.get_origin()), mtx.xform(endpoint_xform.get_origin()), Color(0, 0, 0), Math::round((current ? 5 : 4) * EDSCALE)); @@ -1231,7 +1231,7 @@ Polygon2DEditor::Polygon2DEditor(EditorNode *p_editor) : uv_edit->add_child(uv_main_vb); HBoxContainer *uv_mode_hb = memnew(HBoxContainer); - uv_edit_group.instance(); + uv_edit_group.instantiate(); uv_edit_mode[0] = memnew(Button); uv_mode_hb->add_child(uv_edit_mode[0]); diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp index b4b8e82124..488aa8c861 100644 --- a/editor/plugins/resource_preloader_editor_plugin.cpp +++ b/editor/plugins/resource_preloader_editor_plugin.cpp @@ -339,9 +339,9 @@ void ResourcePreloaderEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_library"), &ResourcePreloaderEditor::_update_library); ClassDB::bind_method(D_METHOD("_remove_resource", "to_remove"), &ResourcePreloaderEditor::_remove_resource); - ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &ResourcePreloaderEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &ResourcePreloaderEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &ResourcePreloaderEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &ResourcePreloaderEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &ResourcePreloaderEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &ResourcePreloaderEditor::drop_data_fw); } ResourcePreloaderEditor::ResourcePreloaderEditor() { @@ -367,8 +367,10 @@ ResourcePreloaderEditor::ResourcePreloaderEditor() { tree = memnew(Tree); tree->connect("button_pressed", callable_mp(this, &ResourcePreloaderEditor::_cell_button_pressed)); tree->set_columns(2); - tree->set_column_min_width(0, 2); - tree->set_column_min_width(1, 3); + tree->set_column_expand_ratio(0, 2); + tree->set_column_clip_content(0, true); + tree->set_column_expand_ratio(1, 3); + tree->set_column_clip_content(1, true); tree->set_column_expand(0, true); tree->set_column_expand(1, true); tree->set_v_size_flags(SIZE_EXPAND_FILL); diff --git a/editor/plugins/root_motion_editor_plugin.cpp b/editor/plugins/root_motion_editor_plugin.cpp index 50f4d8493f..120b0bc0bb 100644 --- a/editor/plugins/root_motion_editor_plugin.cpp +++ b/editor/plugins/root_motion_editor_plugin.cpp @@ -205,7 +205,6 @@ void EditorPropertyRootMotion::update_property() { assign->set_flat(false); return; } - assign->set_flat(true); Node *base_node = nullptr; if (base_hint != NodePath()) { @@ -247,14 +246,12 @@ EditorPropertyRootMotion::EditorPropertyRootMotion() { HBoxContainer *hbc = memnew(HBoxContainer); add_child(hbc); assign = memnew(Button); - assign->set_flat(true); assign->set_h_size_flags(SIZE_EXPAND_FILL); assign->set_clip_text(true); assign->connect("pressed", callable_mp(this, &EditorPropertyRootMotion::_node_assign)); hbc->add_child(assign); clear = memnew(Button); - clear->set_flat(true); clear->connect("pressed", callable_mp(this, &EditorPropertyRootMotion::_node_clear)); hbc->add_child(clear); @@ -281,7 +278,7 @@ void EditorInspectorRootMotionPlugin::parse_begin(Object *p_object) { //do none } -bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) { +bool EditorInspectorRootMotionPlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { if (p_path == "root_motion_track" && p_object->is_class("AnimationTree") && p_type == Variant::NODE_PATH) { EditorPropertyRootMotion *editor = memnew(EditorPropertyRootMotion); if (p_hint == PROPERTY_HINT_NODE_PATH_TO_EDITED_NODE && p_hint_text != String()) { diff --git a/editor/plugins/root_motion_editor_plugin.h b/editor/plugins/root_motion_editor_plugin.h index c70fff7db7..1484af62e8 100644 --- a/editor/plugins/root_motion_editor_plugin.h +++ b/editor/plugins/root_motion_editor_plugin.h @@ -65,7 +65,7 @@ class EditorInspectorRootMotionPlugin : public EditorInspectorPlugin { public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; - virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; virtual void parse_end() override; }; diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index 58e6717a3d..498d5b0711 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -32,8 +32,8 @@ #include "core/config/project_settings.h" #include "core/input/input.h" +#include "core/io/file_access.h" #include "core/io/resource_loader.h" -#include "core/os/file_access.h" #include "core/os/keyboard.h" #include "core/os/os.h" #include "editor/debugger/editor_debugger_node.h" @@ -71,7 +71,7 @@ Array EditorSyntaxHighlighter::_get_supported_languages() const { Ref<EditorSyntaxHighlighter> EditorSyntaxHighlighter::_create() const { Ref<EditorSyntaxHighlighter> syntax_highlighter; - syntax_highlighter.instance(); + syntax_highlighter.instantiate(); if (get_script_instance()) { syntax_highlighter->set_script(get_script_instance()->get_script()); } @@ -140,10 +140,15 @@ void EditorStandardSyntaxHighlighter::_update_cache() { /* Reserved words. */ const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color"); List<String> keywords; script->get_language()->get_reserved_words(&keywords); for (List<String>::Element *E = keywords.front(); E; E = E->next()) { - highlighter->add_keyword_color(E->get(), keyword_color); + if (script->get_language()->is_control_flow_keyword(E->get())) { + highlighter->add_keyword_color(E->get(), control_flow_keyword_color); + } else { + highlighter->add_keyword_color(E->get(), keyword_color); + } } /* Member types. */ @@ -196,7 +201,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() { Ref<EditorSyntaxHighlighter> EditorStandardSyntaxHighlighter::_create() const { Ref<EditorStandardSyntaxHighlighter> syntax_highlighter; - syntax_highlighter.instance(); + syntax_highlighter.instantiate(); return syntax_highlighter; } @@ -204,7 +209,7 @@ Ref<EditorSyntaxHighlighter> EditorStandardSyntaxHighlighter::_create() const { Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { Ref<EditorPlainTextSyntaxHighlighter> syntax_highlighter; - syntax_highlighter.instance(); + syntax_highlighter.instantiate(); return syntax_highlighter; } @@ -213,6 +218,8 @@ Ref<EditorSyntaxHighlighter> EditorPlainTextSyntaxHighlighter::_create() const { /*** SCRIPT EDITOR ****/ void ScriptEditorBase::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_base_editor"), &ScriptEditorBase::get_base_editor); + ADD_SIGNAL(MethodInfo("name_changed")); ADD_SIGNAL(MethodInfo("edited_script_changed")); ADD_SIGNAL(MethodInfo("request_help", PropertyInfo(Variant::STRING, "topic"))); @@ -223,7 +230,7 @@ void ScriptEditorBase::_bind_methods() { ADD_SIGNAL(MethodInfo("search_in_files_requested", PropertyInfo(Variant::STRING, "text"))); ADD_SIGNAL(MethodInfo("replace_in_files_requested", PropertyInfo(Variant::STRING, "text"))); - BIND_VMETHOD(MethodInfo("add_syntax_highlighter", PropertyInfo(Variant::OBJECT, "highlighter"))); + BIND_VMETHOD(MethodInfo("_add_syntax_highlighter", PropertyInfo(Variant::OBJECT, "highlighter"))); } static bool _is_built_in_script(Script *p_script) { @@ -332,13 +339,13 @@ void ScriptEditorQuickOpen::_update_search() { if ((search_box->get_text() == "" || file.findn(search_box->get_text()) != -1)) { TreeItem *ti = search_options->create_item(root); ti->set_text(0, file); - if (root->get_children() == ti) { + if (root->get_first_child() == ti) { ti->select(0); } } } - get_ok_button()->set_disabled(root->get_children() == nullptr); + get_ok_button()->set_disabled(root->get_first_child() == nullptr); } void ScriptEditorQuickOpen::_confirmed() { @@ -698,7 +705,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { // 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) { - _menu_option(FILE_SAVE); + save_current_script(); } } if (script.is_valid()) { @@ -753,6 +760,7 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) { _update_members_overview_visibility(); _update_help_overview_visibility(); _save_layout(); + _update_find_replace_bar(); } void ScriptEditor::_close_current_tab(bool p_save) { @@ -822,6 +830,7 @@ void ScriptEditor::_close_all_tabs() { _close_current_tab(false); } + _update_find_replace_bar(); } void ScriptEditor::_ask_close_current_unsaved_tab(ScriptEditorBase *current) { @@ -1224,55 +1233,7 @@ void ScriptEditor::_menu_option(int p_option) { if (current) { switch (p_option) { case FILE_SAVE: { - if (_test_script_times_on_disk()) { - return; - } - - if (trim_trailing_whitespace_on_save) { - current->trim_trailing_whitespace(); - } - - current->insert_final_newline(); - - if (convert_indent_on_save) { - if (use_space_indentation) { - current->convert_indent_to_spaces(); - } else { - current->convert_indent_to_tabs(); - } - } - - RES resource = current->get_edited_resource(); - Ref<TextFile> text_file = resource; - Ref<Script> script = resource; - - if (text_file != nullptr) { - current->apply_code(); - _save_text_file(text_file, text_file->get_path()); - break; - } - - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - if (EditorHelp::get_doc_data()->has_doc(doc.name)) { - EditorHelp::get_doc_data()->remove_doc(doc.name); - } - } - } - - editor->save_resource(resource); - - if (script != nullptr) { - const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); - for (int j = 0; j < documentations.size(); j++) { - const DocData::ClassDoc &doc = documentations.get(j); - EditorHelp::get_doc_data()->add_doc(doc); - update_doc(doc.name); - } - } - + save_current_script(); } break; case FILE_SAVE_AS: { if (trim_trailing_whitespace_on_save) { @@ -1544,6 +1505,11 @@ void ScriptEditor::_notification(int p_what) { filename->add_theme_style_override("normal", editor->get_gui_base()->get_theme_stylebox("normal", "LineEdit")); recent_scripts->set_as_minsize(); + + if (is_inside_tree()) { + _update_script_colors(); + _update_script_names(); + } } break; case NOTIFICATION_READY: { @@ -1665,7 +1631,7 @@ void ScriptEditor::_help_overview_selected(int p_idx) { } void ScriptEditor::_script_selected(int p_idx) { - grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(1); //amazing hack, simply amazing + grab_focus_block = !Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT); //amazing hack, simply amazing _go_to_tab(script_list->get_item_metadata(p_idx)); grab_focus_block = false; @@ -1682,6 +1648,7 @@ void ScriptEditor::ensure_select_current() { } } } + _update_find_replace_bar(); _update_selected_editor_menu(); } @@ -1718,7 +1685,7 @@ struct _ScriptEditorItemData { if (sort_key == id.sort_key) { return index < id.index; } else { - return sort_key < id.sort_key; + return sort_key.naturalnocasecmp_to(id.sort_key) < 0; } } else { return category < id.category; @@ -1827,7 +1794,6 @@ void ScriptEditor::_update_help_overview() { void ScriptEditor::_update_script_colors() { bool script_temperature_enabled = EditorSettings::get_singleton()->get("text_editor/script_list/script_temperature_enabled"); - bool highlight_current = EditorSettings::get_singleton()->get("text_editor/script_list/highlight_current_script"); int hist_size = EditorSettings::get_singleton()->get("text_editor/script_list/script_temperature_history_size"); Color hot_color = get_theme_color("accent_color", "Editor"); @@ -1842,11 +1808,7 @@ void ScriptEditor::_update_script_colors() { script_list->set_item_custom_bg_color(i, Color(0, 0, 0, 0)); - bool current = tab_container->get_current_tab() == c; - if (current && highlight_current) { - script_list->set_item_custom_bg_color(i, EditorSettings::get_singleton()->get("text_editor/script_list/current_script_background_color")); - - } else if (script_temperature_enabled) { + if (script_temperature_enabled) { if (!n->has_meta("__editor_pass")) { continue; } @@ -2325,6 +2287,58 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra return true; } +void ScriptEditor::save_current_script() { + ScriptEditorBase *current = _get_current_editor(); + if (!current || _test_script_times_on_disk()) { + return; + } + + if (trim_trailing_whitespace_on_save) { + current->trim_trailing_whitespace(); + } + + current->insert_final_newline(); + + if (convert_indent_on_save) { + if (use_space_indentation) { + current->convert_indent_to_spaces(); + } else { + current->convert_indent_to_tabs(); + } + } + + RES resource = current->get_edited_resource(); + Ref<TextFile> text_file = resource; + Ref<Script> script = resource; + + if (text_file != nullptr) { + current->apply_code(); + _save_text_file(text_file, text_file->get_path()); + return; + } + + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + if (EditorHelp::get_doc_data()->has_doc(doc.name)) { + EditorHelp::get_doc_data()->remove_doc(doc.name); + } + } + } + + editor->save_resource(resource); + + if (script != nullptr) { + const Vector<DocData::ClassDoc> &documentations = script->get_documentation(); + for (int j = 0; j < documentations.size(); j++) { + const DocData::ClassDoc &doc = documentations.get(j); + EditorHelp::get_doc_data()->add_doc(doc); + update_doc(doc.name); + } + } +} + void ScriptEditor::save_all_scripts() { for (int i = 0; i < tab_container->get_child_count(); i++) { ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i)); @@ -2439,6 +2453,11 @@ void ScriptEditor::_add_callback(Object *p_obj, const String &p_function, const script_list->select(script_list->find_metadata(i)); + // Save the current script so the changes can be picked up by an external editor. + if (!_is_built_in_script(script.ptr())) { // But only if it's not built-in script. + save_current_script(); + } + break; } } @@ -2501,6 +2520,16 @@ void ScriptEditor::_file_removed(const String &p_removed_file) { } } +void ScriptEditor::_update_find_replace_bar() { + ScriptEditorBase *se = _get_current_editor(); + if (se) { + se->set_find_replace_bar(find_replace_bar); + } else { + find_replace_bar->set_text_edit(nullptr); + find_replace_bar->hide(); + } +} + void ScriptEditor::_autosave_scripts() { save_all_scripts(); } @@ -2752,6 +2781,8 @@ void ScriptEditor::_script_list_gui_input(const Ref<InputEvent> &ev) { case MOUSE_BUTTON_RIGHT: { _make_script_list_context_menu(); } break; + default: + break; } } } @@ -3174,7 +3205,7 @@ void ScriptEditor::_on_find_in_files_result_selected(String fpath, int line_numb if (ResourceLoader::exists(fpath)) { RES res = ResourceLoader::load(fpath); - if (fpath.get_extension() == "shader") { + if (fpath.get_extension() == "gdshader") { ShaderEditorPlugin *shader_editor = Object::cast_to<ShaderEditorPlugin>(EditorNode::get_singleton()->get_editor_data().get_editor("Shader")); shader_editor->edit(res.ptr()); shader_editor->make_visible(true); @@ -3257,9 +3288,9 @@ void ScriptEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("register_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::register_syntax_highlighter); ClassDB::bind_method(D_METHOD("unregister_syntax_highlighter", "syntax_highlighter"), &ScriptEditor::unregister_syntax_highlighter); - ClassDB::bind_method(D_METHOD("get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw", "point", "data", "from"), &ScriptEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw", "point", "from"), &ScriptEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw", "point", "data", "from"), &ScriptEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw", "point", "data", "from"), &ScriptEditor::drop_data_fw); ClassDB::bind_method(D_METHOD("goto_line", "line_number"), &ScriptEditor::_goto_script_line2); ClassDB::bind_method(D_METHOD("get_current_script"), &ScriptEditor::_get_current_script); @@ -3363,11 +3394,19 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { help_overview->set_custom_minimum_size(Size2(0, 60) * EDSCALE); //need to give a bit of limit to avoid it from disappearing help_overview->set_v_size_flags(SIZE_EXPAND_FILL); + VBoxContainer *code_editor_container = memnew(VBoxContainer); + script_split->add_child(code_editor_container); + tab_container = memnew(TabContainer); tab_container->set_tabs_visible(false); tab_container->set_custom_minimum_size(Size2(200, 0) * EDSCALE); - script_split->add_child(tab_container); + code_editor_container->add_child(tab_container); tab_container->set_h_size_flags(SIZE_EXPAND_FILL); + tab_container->set_v_size_flags(SIZE_EXPAND_FILL); + + find_replace_bar = memnew(FindReplaceBar); + code_editor_container->add_child(find_replace_bar); + find_replace_bar->hide(); ED_SHORTCUT("script_editor/window_sort", TTR("Sort")); ED_SHORTCUT("script_editor/window_move_up", TTR("Move Up"), KEY_MASK_SHIFT | KEY_MASK_ALT | KEY_UP); @@ -3672,9 +3711,7 @@ ScriptEditorPlugin::ScriptEditorPlugin(EditorNode *p_node) { EDITOR_DEF("text_editor/external/use_external_editor", false); EDITOR_DEF("text_editor/external/exec_path", ""); EDITOR_DEF("text_editor/script_list/script_temperature_enabled", true); - EDITOR_DEF("text_editor/script_list/highlight_current_script", true); EDITOR_DEF("text_editor/script_list/script_temperature_history_size", 15); - EDITOR_DEF("text_editor/script_list/current_script_background_color", Color(1, 1, 1, 0.3)); EDITOR_DEF("text_editor/script_list/group_help_pages", true); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "text_editor/script_list/sort_scripts_by", PROPERTY_HINT_ENUM, "Name,Path,None")); EDITOR_DEF("text_editor/script_list/sort_scripts_by", 0); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index b2172e7f10..72a649ffbf 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -80,7 +80,7 @@ public: virtual Ref<EditorSyntaxHighlighter> _create() const override; - EditorStandardSyntaxHighlighter() { highlighter.instance(); } + EditorStandardSyntaxHighlighter() { highlighter.instantiate(); } }; class EditorPlainTextSyntaxHighlighter : public EditorSyntaxHighlighter { @@ -162,6 +162,9 @@ public: virtual void set_tooltip_request_func(String p_method, Object *p_obj) = 0; virtual Control *get_edit_menu() = 0; virtual void clear_edit_menu() = 0; + virtual void set_find_replace_bar(FindReplaceBar *p_bar) = 0; + + virtual Control *get_base_editor() const = 0; virtual void validate() = 0; @@ -268,6 +271,7 @@ class ScriptEditor : public PanelContainer { ConfirmationDialog *erase_tab_confirm; ScriptCreateDialog *script_create_dialog; Button *scripts_visible; + FindReplaceBar *find_replace_bar; String current_theme; @@ -324,6 +328,7 @@ class ScriptEditor : public PanelContainer { void _show_error_dialog(String p_path); void _close_tab(int p_idx, bool p_save = true, bool p_history_back = true); + void _update_find_replace_bar(); void _close_current_tab(bool p_save = true); void _close_discard_current_tab(const String &p_str); @@ -463,6 +468,7 @@ public: void get_breakpoints(List<String> *p_breakpoints); + void save_current_script(); void save_all_scripts(); void set_window_layout(Ref<ConfigFile> p_layout); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index c982207224..cc0fbcc634 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -109,13 +109,11 @@ ConnectionInfoDialog::ConnectionInfoDialog() { //////////////////////////////////////////////////////////////////////////////// Vector<String> ScriptTextEditor::get_functions() { - String errortxt; - int line = -1, col; CodeEdit *te = code_editor->get_text_editor(); String text = te->get_text(); List<String> fnc; - if (script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc)) { + if (script->get_language()->validate(text, script->get_path(), &fnc)) { //if valid rewrite functions to latest functions.clear(); for (List<String>::Element *E = fnc.front(); E; E = E->next()) { @@ -168,68 +166,26 @@ void ScriptTextEditor::enable_editor() { void ScriptTextEditor::_load_theme_settings() { CodeEdit *text_edit = code_editor->get_text_editor(); - text_edit->clear_keywords(); + Color updated_marked_line_color = EDITOR_GET("text_editor/highlighting/mark_color"); Color updated_safe_line_number_color = EDITOR_GET("text_editor/highlighting/safe_line_number_color"); - if (updated_safe_line_number_color != safe_line_number_color) { + + bool safe_line_number_color_updated = updated_safe_line_number_color != safe_line_number_color; + bool marked_line_color_updated = updated_marked_line_color != marked_line_color; + if (safe_line_number_color_updated || marked_line_color_updated) { safe_line_number_color = updated_safe_line_number_color; for (int i = 0; i < text_edit->get_line_count(); i++) { - if (text_edit->get_line_gutter_item_color(i, line_number_gutter) != default_line_number_color) { + if (marked_line_color_updated && text_edit->get_line_background_color(i) == marked_line_color) { + text_edit->set_line_background_color(i, updated_marked_line_color); + } + + if (safe_line_number_color_updated && text_edit->get_line_gutter_item_color(i, line_number_gutter) != default_line_number_color) { text_edit->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); } } + marked_line_color = updated_marked_line_color; } - Color background_color = EDITOR_GET("text_editor/highlighting/background_color"); - Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color"); - Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color"); - Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color"); - Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color"); - Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color"); - Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); - Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color"); - Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color"); - Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color"); - Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color"); - Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color"); - Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color"); - Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color"); - Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color"); - Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color"); - Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color"); - Color bookmark_color = EDITOR_GET("text_editor/highlighting/bookmark_color"); - Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color"); - Color executing_line_color = EDITOR_GET("text_editor/highlighting/executing_line_color"); - Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color"); - Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color"); - Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color"); - - text_edit->add_theme_color_override("background_color", background_color); - text_edit->add_theme_color_override("completion_background_color", completion_background_color); - text_edit->add_theme_color_override("completion_selected_color", completion_selected_color); - text_edit->add_theme_color_override("completion_existing_color", completion_existing_color); - text_edit->add_theme_color_override("completion_scroll_color", completion_scroll_color); - text_edit->add_theme_color_override("completion_font_color", completion_font_color); - text_edit->add_theme_color_override("font_color", text_color); - text_edit->add_theme_color_override("line_number_color", line_number_color); - text_edit->add_theme_color_override("caret_color", caret_color); - text_edit->add_theme_color_override("caret_background_color", caret_background_color); - text_edit->add_theme_color_override("font_selected_color", text_selected_color); - text_edit->add_theme_color_override("selection_color", selection_color); - text_edit->add_theme_color_override("brace_mismatch_color", brace_mismatch_color); - text_edit->add_theme_color_override("current_line_color", current_line_color); - text_edit->add_theme_color_override("line_length_guideline_color", line_length_guideline_color); - text_edit->add_theme_color_override("word_highlighted_color", word_highlighted_color); - text_edit->add_theme_color_override("bookmark_color", bookmark_color); - text_edit->add_theme_color_override("breakpoint_color", breakpoint_color); - text_edit->add_theme_color_override("executing_line_color", executing_line_color); - text_edit->add_theme_color_override("mark_color", mark_color); - text_edit->add_theme_color_override("code_folding_color", code_folding_color); - text_edit->add_theme_color_override("search_result_color", search_result_color); - text_edit->add_theme_color_override("search_result_border_color", search_result_border_color); - - text_edit->add_theme_constant_override("line_spacing", EDITOR_DEF("text_editor/theme/line_spacing", 6)); - theme_loaded = true; if (!script.is_null()) { _set_theme_for_script(); @@ -244,46 +200,29 @@ void ScriptTextEditor::_set_theme_for_script() { CodeEdit *text_edit = code_editor->get_text_editor(); text_edit->get_syntax_highlighter()->update_cache(); - /* add keywords for auto completion */ - // singleton autoloads (as types, just as engine singletons are) - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->value(); - if (info.is_singleton) { - text_edit->add_keyword(info.name); - } - } - - // engine types - List<StringName> types; - ClassDB::get_class_list(&types); - for (List<StringName>::Element *E = types.front(); E; E = E->next()) { - String n = E->get(); - if (n.begins_with("_")) { - n = n.substr(1, n.length()); - } - text_edit->add_keyword(n); - } - - // user types - List<StringName> global_classes; - ScriptServer::get_global_class_list(&global_classes); - for (List<StringName>::Element *E = global_classes.front(); E; E = E->next()) { - text_edit->add_keyword(E->get()); + List<String> strings; + script->get_language()->get_string_delimiters(&strings); + text_edit->clear_string_delimiters(); + for (List<String>::Element *E = strings.front(); E; E = E->next()) { + String string = E->get(); + String beg = string.get_slice(" ", 0); + String end = string.get_slice_count(" ") > 1 ? string.get_slice(" ", 1) : String(); + text_edit->add_string_delimiter(beg, end, end == ""); } - List<String> keywords; - script->get_language()->get_reserved_words(&keywords); - for (List<String>::Element *E = keywords.front(); E; E = E->next()) { - text_edit->add_keyword(E->get()); + List<String> comments; + script->get_language()->get_comment_delimiters(&comments); + text_edit->clear_comment_delimiters(); + for (List<String>::Element *E = comments.front(); E; E = E->next()) { + String comment = E->get(); + String beg = comment.get_slice(" ", 0); + String end = comment.get_slice_count(" ") > 1 ? comment.get_slice(" ", 1) : String(); + text_edit->add_comment_delimiter(beg, end, end == ""); } +} - // core types - List<String> core_types; - script->get_language()->get_core_type_words(&core_types); - for (List<String>::Element *E = core_types.front(); E; E = E->next()) { - text_edit->add_keyword(E->get()); - } +void ScriptTextEditor::_show_errors_panel(bool p_show) { + errors_panel->set_visible(p_show); } void ScriptTextEditor::_show_warnings_panel(bool p_show) { @@ -292,7 +231,7 @@ void ScriptTextEditor::_show_warnings_panel(bool p_show) { void ScriptTextEditor::_warning_clicked(Variant p_line) { if (p_line.get_type() == Variant::INT) { - code_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t()); + goto_line_centered(p_line.operator int64_t()); } else if (p_line.get_type() == Variant::DICTIONARY) { Dictionary meta = p_line.operator Dictionary(); code_editor->get_text_editor()->insert_at("# warning-ignore:" + meta["code"].operator String(), meta["line"].operator int64_t() - 1); @@ -300,6 +239,12 @@ void ScriptTextEditor::_warning_clicked(Variant p_line) { } } +void ScriptTextEditor::_error_clicked(Variant p_line) { + if (p_line.get_type() == Variant::INT) { + code_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t()); + } +} + void ScriptTextEditor::reload_text() { ERR_FAIL_COND(script.is_null()); @@ -450,23 +395,21 @@ Ref<Texture2D> ScriptTextEditor::get_theme_icon() { } void ScriptTextEditor::_validate_script() { - String errortxt; - int line = -1, col; CodeEdit *te = code_editor->get_text_editor(); String text = te->get_text(); List<String> fnc; Set<int> safe_lines; List<ScriptLanguage::Warning> warnings; + List<ScriptLanguage::ScriptError> errors; - if (!script->get_language()->validate(text, line, col, errortxt, script->get_path(), &fnc, &warnings, &safe_lines)) { - String error_text = "error(" + itos(line) + "," + itos(col) + "): " + errortxt; + if (!script->get_language()->validate(text, script->get_path(), &fnc, &errors, &warnings, &safe_lines)) { + String error_text = TTR("Error at ") + "(" + itos(errors[0].line) + "," + itos(errors[0].column) + "): " + errors[0].message; code_editor->set_error(error_text); - code_editor->set_error_pos(line - 1, col - 1); + code_editor->set_error_pos(errors[0].line - 1, errors[0].column - 1); script_is_valid = false; } else { code_editor->set_error(""); - line = -1; if (!script->is_tool()) { script->set_source_code(text); script->update_exports(); @@ -508,7 +451,8 @@ void ScriptTextEditor::_validate_script() { } } - code_editor->set_warning_nb(warning_nb); + code_editor->set_error_count(errors.size()); + code_editor->set_warning_count(warning_nb); // Add script warnings. warnings_panel->push_table(3); @@ -542,23 +486,52 @@ void ScriptTextEditor::_validate_script() { } warnings_panel->pop(); // Table. - line--; + errors_panel->clear(); + errors_panel->push_table(2); + for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) { + ScriptLanguage::ScriptError err = E->get(); + + errors_panel->push_cell(); + errors_panel->push_meta(err.line - 1); + errors_panel->push_color(warnings_panel->get_theme_color("error_color", "Editor")); + errors_panel->add_text(TTR("Line") + " " + itos(err.line) + ":"); + errors_panel->pop(); // Color. + errors_panel->pop(); // Meta goto. + errors_panel->pop(); // Cell. + + errors_panel->push_cell(); + errors_panel->add_text(err.message); + errors_panel->pop(); // Cell. + } + errors_panel->pop(); // Table + bool highlight_safe = EDITOR_DEF("text_editor/highlighting/highlight_type_safe_lines", true); bool last_is_safe = false; for (int i = 0; i < te->get_line_count(); i++) { - te->set_line_as_marked(i, line == i); + if (errors.is_empty()) { + te->set_line_background_color(i, Color(0, 0, 0, 0)); + } else { + for (List<ScriptLanguage::ScriptError>::Element *E = errors.front(); E; E = E->next()) { + bool error_line = i == E->get().line - 1; + te->set_line_background_color(i, error_line ? marked_line_color : Color(0, 0, 0, 0)); + if (error_line) { + break; + } + } + } + if (highlight_safe) { if (safe_lines.has(i + 1)) { te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); last_is_safe = true; - } else if (last_is_safe && (te->is_line_comment(i) || te->get_line(i).strip_edges().is_empty())) { + } else if (last_is_safe && (te->is_in_comment(i) != -1 || te->get_line(i).strip_edges().is_empty())) { te->set_line_gutter_item_color(i, line_number_gutter, safe_line_number_color); } else { te->set_line_gutter_item_color(i, line_number_gutter, default_line_number_color); last_is_safe = false; } } else { - te->set_line_gutter_item_color(line, 1, default_line_number_color); + te->set_line_gutter_item_color(i, 1, default_line_number_color); } } @@ -1065,7 +1038,7 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } - tx->indent_selected_lines_left(); + tx->unindent_lines(); } break; case EDIT_INDENT_RIGHT: { Ref<Script> scr = script; @@ -1073,16 +1046,16 @@ void ScriptTextEditor::_edit_option(int p_op) { return; } - tx->indent_selected_lines_right(); + tx->indent_lines(); } break; case EDIT_DELETE_LINE: { code_editor->delete_lines(); } break; - case EDIT_CLONE_DOWN: { - code_editor->clone_lines_down(); + case EDIT_DUPLICATE_SELECTION: { + code_editor->duplicate_selection(); } break; case EDIT_TOGGLE_FOLD_LINE: { - tx->toggle_fold_line(tx->cursor_get_line()); + tx->toggle_foldable_line(tx->cursor_get_line()); tx->update(); } break; case EDIT_FOLD_ALL_LINES: { @@ -1097,7 +1070,7 @@ void ScriptTextEditor::_edit_option(int p_op) { _edit_option_toggle_inline_comment(); } break; case EDIT_COMPLETE: { - tx->query_code_comple(); + tx->request_code_completion(true); } break; case EDIT_AUTO_INDENT: { String text = tx->get_text(); @@ -1364,9 +1337,9 @@ void ScriptTextEditor::_notification(int p_what) { void ScriptTextEditor::_bind_methods() { ClassDB::bind_method("_update_connected_methods", &ScriptTextEditor::_update_connected_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("_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); } @@ -1379,6 +1352,10 @@ void ScriptTextEditor::clear_edit_menu() { memdelete(edit_hb); } +void ScriptTextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { + code_editor->set_find_replace_bar(p_bar); +} + void ScriptTextEditor::reload(bool p_soft) { CodeEdit *te = code_editor->get_text_editor(); Ref<Script> scr = script; @@ -1402,6 +1379,10 @@ void ScriptTextEditor::set_tooltip_request_func(String p_method, Object *p_obj) void ScriptTextEditor::set_debugger_active(bool p_active) { } +Control *ScriptTextEditor::get_base_editor() const { + return code_editor->get_text_editor(); +} + Variant ScriptTextEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { return Variant(); } @@ -1463,14 +1444,21 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data } if (d.has("type") && (String(d["type"]) == "files" || String(d["type"]) == "files_and_dirs")) { + const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; Array files = d["files"]; String text_to_drop; + bool preload = Input::get_singleton()->is_key_pressed(KEY_CTRL); for (int i = 0; i < files.size(); i++) { if (i > 0) { - text_to_drop += ","; + text_to_drop += ", "; + } + + if (preload) { + text_to_drop += "preload(" + String(files[i]).c_escape().quote(quote_style) + ")"; + } else { + text_to_drop += String(files[i]).c_escape().quote(quote_style); } - text_to_drop += "\"" + String(files[i]).c_escape() + "\""; } te->cursor_set_line(row); @@ -1556,7 +1544,7 @@ void ScriptTextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { } bool has_color = (word_at_pos == "Color"); - bool foldable = tx->can_fold(row) || tx->is_folded(row); + bool foldable = tx->can_fold_line(row) || tx->is_line_folded(row); bool open_docs = false; bool goto_definition = false; @@ -1682,6 +1670,7 @@ void ScriptTextEditor::_enable_code_editor() { editor_box->set_v_size_flags(SIZE_EXPAND_FILL); editor_box->add_child(code_editor); + code_editor->connect("show_errors_panel", callable_mp(this, &ScriptTextEditor::_show_errors_panel)); 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)); @@ -1702,6 +1691,13 @@ void ScriptTextEditor::_enable_code_editor() { "normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts")); warnings_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_warning_clicked)); + editor_box->add_child(errors_panel); + errors_panel->add_theme_font_override( + "normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts")); + errors_panel->add_theme_font_size_override( + "normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts")); + errors_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_error_clicked)); + add_child(context_menu); context_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option)); @@ -1764,7 +1760,7 @@ void ScriptTextEditor::_enable_code_editor() { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/evaluate_selection"), EDIT_EVALUATE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE); @@ -1833,11 +1829,17 @@ ScriptTextEditor::ScriptTextEditor() { warnings_panel->set_focus_mode(FOCUS_CLICK); warnings_panel->hide(); + errors_panel = memnew(RichTextLabel); + errors_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); + errors_panel->set_h_size_flags(SIZE_EXPAND_FILL); + errors_panel->set_meta_underline(true); + errors_panel->set_selection_enabled(true); + errors_panel->set_focus_mode(FOCUS_CLICK); + errors_panel->hide(); + update_settings(); - code_editor->get_text_editor()->set_callhint_settings( - EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"), - EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset")); + code_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); code_editor->get_text_editor()->set_select_identifiers_on_hover(true); code_editor->get_text_editor()->set_context_menu_enabled(false); @@ -1860,11 +1862,11 @@ ScriptTextEditor::ScriptTextEditor() { highlighter_menu->set_name("highlighter_menu"); Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter; - plain_highlighter.instance(); + plain_highlighter.instantiate(); add_syntax_highlighter(plain_highlighter); Ref<EditorStandardSyntaxHighlighter> highlighter; - highlighter.instance(); + highlighter.instantiate(); add_syntax_highlighter(highlighter); set_syntax_highlighter(highlighter); @@ -1895,6 +1897,7 @@ ScriptTextEditor::~ScriptTextEditor() { if (!editor_enabled) { memdelete(code_editor); memdelete(warnings_panel); + memdelete(errors_panel); memdelete(context_menu); memdelete(color_panel); memdelete(edit_hb); @@ -1931,9 +1934,9 @@ void ScriptTextEditor::register_editor() { ED_SHORTCUT("script_text_editor/fold_all_lines", TTR("Fold All Lines"), 0); ED_SHORTCUT("script_text_editor/unfold_all_lines", TTR("Unfold All Lines"), 0); #ifdef OSX_ENABLED - ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_C); + ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_C); #else - ED_SHORTCUT("script_text_editor/clone_down", TTR("Clone Down"), KEY_MASK_CMD | KEY_D); + ED_SHORTCUT("script_text_editor/duplicate_selection", TTR("Duplicate Selection"), KEY_MASK_SHIFT | KEY_MASK_CMD | KEY_D); #endif ED_SHORTCUT("script_text_editor/evaluate_selection", TTR("Evaluate Selection"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_E); ED_SHORTCUT("script_text_editor/trim_trailing_whitespace", TTR("Trim Trailing Whitespace"), KEY_MASK_CMD | KEY_MASK_ALT | KEY_T); diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h index 17abfcf4cc..e4a13951e4 100644 --- a/editor/plugins/script_text_editor.h +++ b/editor/plugins/script_text_editor.h @@ -55,6 +55,7 @@ class ScriptTextEditor : public ScriptEditorBase { CodeTextEditor *code_editor = nullptr; RichTextLabel *warnings_panel = nullptr; + RichTextLabel *errors_panel = nullptr; Ref<Script> script; bool script_is_valid = false; @@ -89,6 +90,8 @@ class ScriptTextEditor : public ScriptEditorBase { Color default_line_number_color = Color(1, 1, 1); Color safe_line_number_color = Color(1, 1, 1); + Color marked_line_color = Color(1, 1, 1); + PopupPanel *color_panel = nullptr; ColorPicker *color_picker = nullptr; Vector2 color_position; @@ -114,7 +117,7 @@ class ScriptTextEditor : public ScriptEditorBase { EDIT_INDENT_RIGHT, EDIT_INDENT_LEFT, EDIT_DELETE_LINE, - EDIT_CLONE_DOWN, + EDIT_DUPLICATE_SELECTION, EDIT_PICK_COLOR, EDIT_TO_UPPERCASE, EDIT_TO_LOWERCASE, @@ -159,7 +162,9 @@ protected: void _load_theme_settings(); void _set_theme_for_script(); + void _show_errors_panel(bool p_show); void _show_warnings_panel(bool p_show); + void _error_clicked(Variant p_line); void _warning_clicked(Variant p_line); void _notification(int p_what); @@ -231,8 +236,12 @@ public: Control *get_edit_menu() override; virtual void clear_edit_menu() override; + virtual void set_find_replace_bar(FindReplaceBar *p_bar) override; + static void register_editor(); + virtual Control *get_base_editor() const override; + virtual void validate() override; ScriptTextEditor(); diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp index ed3b746678..c1216a9732 100644 --- a/editor/plugins/shader_editor_plugin.cpp +++ b/editor/plugins/shader_editor_plugin.cpp @@ -37,12 +37,18 @@ #include "editor/editor_node.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" +#include "editor/project_settings_editor.h" #include "editor/property_editor.h" #include "servers/display_server.h" #include "servers/rendering/shader_types.h" /*** SHADER SCRIPT EDITOR ****/ +static bool saved_warnings_enabled = false; +static bool saved_treat_warning_as_errors = false; +static Map<ShaderWarning::Code, bool> saved_warnings; +static uint32_t saved_warning_flags = 0U; + Ref<Shader> ShaderTextEditor::get_edited_shader() const { return shader; } @@ -57,6 +63,8 @@ void ShaderTextEditor::set_edited_shader(const Ref<Shader> &p_shader) { get_text_editor()->set_text(p_shader->get_code()); get_text_editor()->clear_undo_history(); + get_text_editor()->call_deferred("set_h_scroll", 0); + get_text_editor()->call_deferred("set_v_scroll", 0); _validate_script(); _line_col_changed(); @@ -82,54 +90,21 @@ void ShaderTextEditor::reload_text() { update_line_and_column(); } +void ShaderTextEditor::set_warnings_panel(RichTextLabel *p_warnings_panel) { + warnings_panel = p_warnings_panel; +} + void ShaderTextEditor::_load_theme_settings() { - Color background_color = EDITOR_GET("text_editor/highlighting/background_color"); - Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color"); - Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color"); - Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color"); - Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color"); - Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color"); - Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); - Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color"); - Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color"); - Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color"); - Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color"); - Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color"); - Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color"); - Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color"); - Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color"); - Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color"); - Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color"); - Color bookmark_color = EDITOR_GET("text_editor/highlighting/bookmark_color"); - Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color"); - Color executing_line_color = EDITOR_GET("text_editor/highlighting/executing_line_color"); - Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color"); - Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color"); - Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color"); - - get_text_editor()->add_theme_color_override("background_color", background_color); - get_text_editor()->add_theme_color_override("completion_background_color", completion_background_color); - get_text_editor()->add_theme_color_override("completion_selected_color", completion_selected_color); - get_text_editor()->add_theme_color_override("completion_existing_color", completion_existing_color); - get_text_editor()->add_theme_color_override("completion_scroll_color", completion_scroll_color); - get_text_editor()->add_theme_color_override("completion_font_color", completion_font_color); - get_text_editor()->add_theme_color_override("font_color", text_color); - get_text_editor()->add_theme_color_override("line_number_color", line_number_color); - get_text_editor()->add_theme_color_override("caret_color", caret_color); - get_text_editor()->add_theme_color_override("caret_background_color", caret_background_color); - get_text_editor()->add_theme_color_override("font_selected_color", text_selected_color); - get_text_editor()->add_theme_color_override("selection_color", selection_color); - get_text_editor()->add_theme_color_override("brace_mismatch_color", brace_mismatch_color); - get_text_editor()->add_theme_color_override("current_line_color", current_line_color); - get_text_editor()->add_theme_color_override("line_length_guideline_color", line_length_guideline_color); - get_text_editor()->add_theme_color_override("word_highlighted_color", word_highlighted_color); - get_text_editor()->add_theme_color_override("mark_color", mark_color); - get_text_editor()->add_theme_color_override("bookmark_color", bookmark_color); - get_text_editor()->add_theme_color_override("breakpoint_color", breakpoint_color); - get_text_editor()->add_theme_color_override("executing_line_color", executing_line_color); - get_text_editor()->add_theme_color_override("code_folding_color", code_folding_color); - get_text_editor()->add_theme_color_override("search_result_color", search_result_color); - get_text_editor()->add_theme_color_override("search_result_border_color", search_result_border_color); + CodeEdit *text_editor = get_text_editor(); + Color updated_marked_line_color = EDITOR_GET("text_editor/highlighting/mark_color"); + if (updated_marked_line_color != marked_line_color) { + for (int i = 0; i < text_editor->get_line_count(); i++) { + if (text_editor->get_line_background_color(i) == marked_line_color) { + text_editor->set_line_background_color(i, updated_marked_line_color); + } + } + marked_line_color = updated_marked_line_color; + } syntax_highlighter->set_number_color(EDITOR_GET("text_editor/highlighting/number_color")); syntax_highlighter->set_symbol_color(EDITOR_GET("text_editor/highlighting/symbol_color")); @@ -141,9 +116,14 @@ void ShaderTextEditor::_load_theme_settings() { List<String> keywords; ShaderLanguage::get_keyword_list(&keywords); const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + const Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color"); for (List<String>::Element *E = keywords.front(); E; E = E->next()) { - syntax_highlighter->add_keyword_color(E->get(), keyword_color); + if (ShaderLanguage::is_control_flow_keyword(E->get())) { + syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color); + } else { + syntax_highlighter->add_keyword_color(E->get(), keyword_color); + } } // Colorize built-ins like `COLOR` differently to make them easier @@ -173,6 +153,16 @@ void ShaderTextEditor::_load_theme_settings() { syntax_highlighter->clear_color_regions(); syntax_highlighter->add_color_region("/*", "*/", comment_color, false); syntax_highlighter->add_color_region("//", "", comment_color, true); + + text_editor->clear_comment_delimiters(); + text_editor->add_comment_delimiter("/*", "*/", false); + text_editor->add_comment_delimiter("//", "", true); + + if (warnings_panel) { + // Warnings panel + warnings_panel->add_theme_font_override("normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts")); + warnings_panel->add_theme_font_size_override("normal_font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts")); + } } void ShaderTextEditor::_check_shader_mode() { @@ -219,6 +209,9 @@ void ShaderTextEditor::_validate_script() { ShaderLanguage sl; + sl.enable_warning_checking(saved_warnings_enabled); + sl.set_warning_flags(saved_warning_flags); + Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type); if (err != OK) { @@ -226,25 +219,75 @@ void ShaderTextEditor::_validate_script() { set_error(error_text); set_error_pos(sl.get_error_line() - 1, 0); for (int i = 0; i < get_text_editor()->get_line_count(); i++) { - get_text_editor()->set_line_as_marked(i, false); + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); } - get_text_editor()->set_line_as_marked(sl.get_error_line() - 1, true); - + get_text_editor()->set_line_background_color(sl.get_error_line() - 1, marked_line_color); } else { for (int i = 0; i < get_text_editor()->get_line_count(); i++) { - get_text_editor()->set_line_as_marked(i, false); + get_text_editor()->set_line_background_color(i, Color(0, 0, 0, 0)); } set_error(""); } + if (warnings.size() > 0 || err != OK) { + warnings_panel->clear(); + } + warnings.clear(); + for (List<ShaderWarning>::Element *E = sl.get_warnings_ptr(); E; E = E->next()) { + warnings.push_back(E->get()); + } + if (warnings.size() > 0 && err == OK) { + warnings.sort_custom<WarningsComparator>(); + _update_warning_panel(); + } else { + set_warning_count(0); + } emit_signal("script_changed"); } +void ShaderTextEditor::_update_warning_panel() { + int warning_count = 0; + + warnings_panel->push_table(2); + for (int i = 0; i < warnings.size(); i++) { + ShaderWarning &w = warnings[i]; + + if (warning_count == 0) { + if (saved_treat_warning_as_errors) { + String error_text = "error(" + itos(w.get_line()) + "): " + w.get_message() + " " + TTR("Warnings should be fixed to prevent errors."); + set_error_pos(w.get_line() - 1, 0); + set_error(error_text); + get_text_editor()->set_line_background_color(w.get_line() - 1, marked_line_color); + } + } + + warning_count++; + + // First cell. + warnings_panel->push_cell(); + warnings_panel->push_meta(w.get_line() - 1); + warnings_panel->push_color(warnings_panel->get_theme_color("warning_color", "Editor")); + warnings_panel->add_text(TTR("Line") + " " + itos(w.get_line())); + warnings_panel->add_text(" (" + w.get_name() + "):"); + warnings_panel->pop(); // Color. + warnings_panel->pop(); // Meta goto. + warnings_panel->pop(); // Cell. + + // Second cell. + warnings_panel->push_cell(); + warnings_panel->add_text(w.get_message()); + warnings_panel->pop(); // Cell. + } + warnings_panel->pop(); // Table. + + set_warning_count(warning_count); +} + void ShaderTextEditor::_bind_methods() { } ShaderTextEditor::ShaderTextEditor() { - syntax_highlighter.instance(); + syntax_highlighter.instantiate(); get_text_editor()->set_syntax_highlighter(syntax_highlighter); } @@ -280,25 +323,19 @@ void ShaderEditor::_menu_option(int p_option) { if (shader.is_null()) { return; } - - CodeEdit *tx = shader_editor->get_text_editor(); - tx->indent_selected_lines_left(); - + shader_editor->get_text_editor()->unindent_lines(); } break; case EDIT_INDENT_RIGHT: { if (shader.is_null()) { return; } - - CodeEdit *tx = shader_editor->get_text_editor(); - tx->indent_selected_lines_right(); - + shader_editor->get_text_editor()->indent_lines(); } break; case EDIT_DELETE_LINE: { shader_editor->delete_lines(); } break; - case EDIT_CLONE_DOWN: { - shader_editor->clone_lines_down(); + case EDIT_DUPLICATE_SELECTION: { + shader_editor->duplicate_selection(); } break; case EDIT_TOGGLE_COMMENT: { if (shader.is_null()) { @@ -309,7 +346,7 @@ void ShaderEditor::_menu_option(int p_option) { } break; case EDIT_COMPLETE: { - shader_editor->get_text_editor()->query_code_comple(); + shader_editor->get_text_editor()->request_code_completion(); } break; case SEARCH_FIND: { shader_editor->get_find_replace_bar()->popup_search(); @@ -353,10 +390,6 @@ void ShaderEditor::_notification(int p_what) { } } -void ShaderEditor::_params_changed() { - shader_editor->_validate_script(); -} - void ShaderEditor::_editor_settings_changed() { shader_editor->update_editor_settings(); @@ -365,8 +398,19 @@ void ShaderEditor::_editor_settings_changed() { shader_editor->get_text_editor()->set_draw_executing_lines_gutter(false); } +void ShaderEditor::_show_warnings_panel(bool p_show) { + warnings_panel->set_visible(p_show); +} + +void ShaderEditor::_warning_clicked(Variant p_line) { + if (p_line.get_type() == Variant::INT) { + shader_editor->get_text_editor()->cursor_set_line(p_line.operator int64_t()); + } +} + void ShaderEditor::_bind_methods() { - ClassDB::bind_method("_params_changed", &ShaderEditor::_params_changed); + ClassDB::bind_method("_show_warnings_panel", &ShaderEditor::_show_warnings_panel); + ClassDB::bind_method("_warning_clicked", &ShaderEditor::_warning_clicked); } void ShaderEditor::ensure_select_current() { @@ -384,6 +428,47 @@ void ShaderEditor::goto_line_selection(int p_line, int p_begin, int p_end) { shader_editor->goto_line_selection(p_line, p_begin, p_end); } +void ShaderEditor::_project_settings_changed() { + _update_warnings(true); +} + +void ShaderEditor::_update_warnings(bool p_validate) { + bool changed = false; + + bool warnings_enabled = GLOBAL_GET("debug/shader_language/warnings/enable").booleanize(); + if (warnings_enabled != saved_warnings_enabled) { + saved_warnings_enabled = warnings_enabled; + changed = true; + } + + bool treat_warning_as_errors = GLOBAL_GET("debug/shader_language/warnings/treat_warnings_as_errors").booleanize(); + if (treat_warning_as_errors != saved_treat_warning_as_errors) { + saved_treat_warning_as_errors = treat_warning_as_errors; + changed = true; + } + + bool update_flags = false; + + for (int i = 0; i < ShaderWarning::WARNING_MAX; i++) { + ShaderWarning::Code code = (ShaderWarning::Code)i; + bool value = GLOBAL_GET("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code(code).to_lower()); + + if (saved_warnings[code] != value) { + saved_warnings[code] = value; + update_flags = true; + changed = true; + } + } + + if (update_flags) { + saved_warning_flags = (uint32_t)ShaderWarning::get_flags_from_codemap(saved_warnings); + } + + if (p_validate && changed && shader_editor && shader_editor->get_edited_shader().is_valid()) { + shader_editor->validate_script(); + } +} + void ShaderEditor::_check_for_external_edit() { if (shader.is_null() || !shader.is_valid()) { return; @@ -555,17 +640,24 @@ void ShaderEditor::_make_context_menu(bool p_selection, Vector2 p_position) { } ShaderEditor::ShaderEditor(EditorNode *p_node) { + GLOBAL_DEF("debug/shader_language/warnings/enable", true); + GLOBAL_DEF("debug/shader_language/warnings/treat_warnings_as_errors", false); + for (int i = 0; i < (int)ShaderWarning::WARNING_MAX; i++) { + GLOBAL_DEF("debug/shader_language/warnings/" + ShaderWarning::get_name_from_code((ShaderWarning::Code)i).to_lower(), true); + } + _update_warnings(false); + shader_editor = memnew(ShaderTextEditor); shader_editor->set_v_size_flags(SIZE_EXPAND_FILL); shader_editor->add_theme_constant_override("separation", 0); shader_editor->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + shader_editor->connect("show_warnings_panel", callable_mp(this, &ShaderEditor::_show_warnings_panel)); shader_editor->connect("script_changed", callable_mp(this, &ShaderEditor::apply_shaders)); EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ShaderEditor::_editor_settings_changed)); + ProjectSettingsEditor::get_singleton()->connect("confirmed", callable_mp(this, &ShaderEditor::_project_settings_changed)); - shader_editor->get_text_editor()->set_callhint_settings( - EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"), - EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset")); + shader_editor->get_text_editor()->set_code_hint_draw_below(EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line")); shader_editor->get_text_editor()->set_select_identifiers_on_hover(true); shader_editor->get_text_editor()->set_context_menu_enabled(false); @@ -600,7 +692,7 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/indent_right"), EDIT_INDENT_RIGHT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/delete_line"), EDIT_DELETE_LINE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/toggle_comment"), EDIT_TOGGLE_COMMENT); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_text_completion_query"), EDIT_COMPLETE); edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ShaderEditor::_menu_option)); @@ -646,7 +738,28 @@ ShaderEditor::ShaderEditor(EditorNode *p_node) { hbc->add_child(goto_menu); hbc->add_child(help_menu); hbc->add_theme_style_override("panel", p_node->get_gui_base()->get_theme_stylebox("ScriptEditorPanel", "EditorStyles")); - main_container->add_child(shader_editor); + + VSplitContainer *editor_box = memnew(VSplitContainer); + main_container->add_child(editor_box); + editor_box->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + editor_box->set_v_size_flags(SIZE_EXPAND_FILL); + editor_box->add_child(shader_editor); + + FindReplaceBar *bar = memnew(FindReplaceBar); + main_container->add_child(bar); + bar->hide(); + shader_editor->set_find_replace_bar(bar); + + warnings_panel = memnew(RichTextLabel); + warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE)); + warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL); + warnings_panel->set_meta_underline(true); + warnings_panel->set_selection_enabled(true); + warnings_panel->set_focus_mode(FOCUS_CLICK); + warnings_panel->hide(); + warnings_panel->connect("meta_clicked", callable_mp(this, &ShaderEditor::_warning_clicked)); + editor_box->add_child(warnings_panel); + shader_editor->set_warnings_panel(warnings_panel); goto_line_dialog = memnew(GotoLineDialog); add_child(goto_line_dialog); diff --git a/editor/plugins/shader_editor_plugin.h b/editor/plugins/shader_editor_plugin.h index 731c0a5b7e..77579754d3 100644 --- a/editor/plugins/shader_editor_plugin.h +++ b/editor/plugins/shader_editor_plugin.h @@ -35,6 +35,7 @@ #include "editor/editor_plugin.h" #include "scene/gui/menu_button.h" #include "scene/gui/panel_container.h" +#include "scene/gui/rich_text_label.h" #include "scene/gui/tab_container.h" #include "scene/gui/text_edit.h" #include "scene/main/timer.h" @@ -44,10 +45,19 @@ class ShaderTextEditor : public CodeTextEditor { GDCLASS(ShaderTextEditor, CodeTextEditor); + Color marked_line_color = Color(1, 1, 1); + + struct WarningsComparator { + _ALWAYS_INLINE_ bool operator()(const ShaderWarning &p_a, const ShaderWarning &p_b) const { return (p_a.get_line() < p_b.get_line()); } + }; + Ref<CodeHighlighter> syntax_highlighter; + RichTextLabel *warnings_panel = nullptr; Ref<Shader> shader; + List<ShaderWarning> warnings; void _check_shader_mode(); + void _update_warning_panel(); protected: static void _bind_methods(); @@ -59,6 +69,7 @@ public: virtual void _validate_script() override; void reload_text(); + void set_warnings_panel(RichTextLabel *p_warnings_panel); Ref<Shader> get_edited_shader() const; void set_edited_shader(const Ref<Shader> &p_shader); @@ -80,7 +91,7 @@ class ShaderEditor : public PanelContainer { EDIT_INDENT_LEFT, EDIT_INDENT_RIGHT, EDIT_DELETE_LINE, - EDIT_CLONE_DOWN, + EDIT_DUPLICATE_SELECTION, EDIT_TOGGLE_COMMENT, EDIT_COMPLETE, SEARCH_FIND, @@ -100,6 +111,7 @@ class ShaderEditor : public PanelContainer { PopupMenu *bookmarks_menu; MenuButton *help_menu; PopupMenu *context_menu; + RichTextLabel *warnings_panel = nullptr; uint64_t idle; GotoLineDialog *goto_line_dialog; @@ -109,13 +121,16 @@ class ShaderEditor : public PanelContainer { ShaderTextEditor *shader_editor; void _menu_option(int p_option); - void _params_changed(); mutable Ref<Shader> shader; void _editor_settings_changed(); + void _project_settings_changed(); void _check_for_external_edit(); void _reload_shader_from_disk(); + void _show_warnings_panel(bool p_show); + void _warning_clicked(Variant p_line); + void _update_warnings(bool p_validate); protected: void _notification(int p_what); diff --git a/editor/plugins/shader_file_editor_plugin.cpp b/editor/plugins/shader_file_editor_plugin.cpp index 47d7f8204b..85ccc5b798 100644 --- a/editor/plugins/shader_file_editor_plugin.cpp +++ b/editor/plugins/shader_file_editor_plugin.cpp @@ -272,7 +272,7 @@ ShaderFileEditor::ShaderFileEditor(EditorNode *p_node) { main_vb->add_child(stage_hb); Ref<ButtonGroup> bg; - bg.instance(); + bg.instantiate(); for (int i = 0; i < RD::SHADER_STAGE_MAX; i++) { Button *button = memnew(Button(stage_str[i])); button->set_toggle_mode(true); diff --git a/editor/plugins/skeleton_3d_editor_plugin.cpp b/editor/plugins/skeleton_3d_editor_plugin.cpp index 404ef62eca..0b04c2e50e 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.cpp +++ b/editor/plugins/skeleton_3d_editor_plugin.cpp @@ -95,7 +95,7 @@ void BoneTransformEditor::create_editors() { section->get_vbox()->add_child(transform_section); // Transform/Matrix property - transform_property = memnew(EditorPropertyTransform()); + transform_property = memnew(EditorPropertyTransform3D()); transform_property->setup(-10000, 10000, 0.001f, true); transform_property->set_label("Transform"); transform_property->set_use_folding(true); @@ -171,7 +171,7 @@ void BoneTransformEditor::_value_changed(const double p_value) { return; } - Transform tform = compute_transform_from_vector3s(); + Transform3D tform = compute_transform_from_vector3s(); _change_transform(tform); } @@ -179,30 +179,30 @@ void BoneTransformEditor::_value_changed_vector3(const String p_property_name, c if (updating) { return; } - Transform tform = compute_transform_from_vector3s(); + Transform3D tform = compute_transform_from_vector3s(); _change_transform(tform); } -Transform BoneTransformEditor::compute_transform_from_vector3s() const { +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); - return Transform( + return Transform3D( Basis(prop_rotation, scale_property->get_vector()), translation_property->get_vector()); } -void BoneTransformEditor::_value_changed_transform(const String p_property_name, const Transform p_transform, const StringName p_edited_property_name, const bool p_boolean) { +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); } -void BoneTransformEditor::_change_transform(Transform p_new_transform) { +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())); @@ -235,7 +235,7 @@ void BoneTransformEditor::_update_properties() { updating = true; - Transform tform = skeleton->get(property); + Transform3D tform = skeleton->get(property); _update_transform_properties(tform); } @@ -250,11 +250,11 @@ void BoneTransformEditor::_update_custom_pose_properties() { updating = true; - Transform tform = skeleton->get_bone_custom_pose(property.to_int()); + Transform3D tform = skeleton->get_bone_custom_pose(property.to_int()); _update_transform_properties(tform); } -void BoneTransformEditor::_update_transform_properties(Transform 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)); @@ -306,7 +306,7 @@ void BoneTransformEditor::_key_button_pressed() { } // Need to normalize the basis before you key it - Transform tform = compute_transform_from_vector3s(); + Transform3D tform = compute_transform_from_vector3s(); tform.orthonormalize(); AnimationPlayerEditor::singleton->get_track_editor()->insert_transform_key(skeleton, name, tform); } @@ -380,7 +380,7 @@ void Skeleton3DEditor::create_physical_skeleton() { } PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_child_id, const Vector<BoneInfo> &bones_infos) { - const Transform child_rest = skeleton->get_bone_rest(bone_child_id); + const Transform3D child_rest = skeleton->get_bone_rest(bone_child_id); const real_t half_height(child_rest.origin.length() * 0.5); const real_t radius(half_height * 0.2); @@ -392,15 +392,15 @@ PhysicalBone3D *Skeleton3DEditor::create_physical_bone(int bone_id, int bone_chi CollisionShape3D *bone_shape = memnew(CollisionShape3D); bone_shape->set_shape(bone_shape_capsule); - Transform capsule_transform; + Transform3D capsule_transform; capsule_transform.basis = Basis(Vector3(1, 0, 0), Vector3(0, 0, 1), Vector3(0, -1, 0)); bone_shape->set_transform(capsule_transform); - Transform body_transform; + Transform3D body_transform; body_transform.set_look_at(Vector3(0, 0, 0), child_rest.origin); body_transform.origin = body_transform.basis.xform(Vector3(0, 0, -half_height)); - Transform joint_transform; + Transform3D joint_transform; joint_transform.origin = Vector3(0, 0, half_height); PhysicalBone3D *physical_bone = memnew(PhysicalBone3D); @@ -666,9 +666,9 @@ void Skeleton3DEditor::_bind_methods() { 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("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); } @@ -700,7 +700,7 @@ Skeleton3DEditorPlugin::Skeleton3DEditorPlugin(EditorNode *p_node) { editor = p_node; Ref<EditorInspectorPluginSkeleton> skeleton_plugin; - skeleton_plugin.instance(); + skeleton_plugin.instantiate(); skeleton_plugin->editor = editor; EditorInspector::add_inspector_plugin(skeleton_plugin); diff --git a/editor/plugins/skeleton_3d_editor_plugin.h b/editor/plugins/skeleton_3d_editor_plugin.h index 14c213f7b2..9de52c6fa8 100644 --- a/editor/plugins/skeleton_3d_editor_plugin.h +++ b/editor/plugins/skeleton_3d_editor_plugin.h @@ -41,7 +41,7 @@ class PhysicalBone3D; class Skeleton3DEditorPlugin; class Button; class CheckBox; -class EditorPropertyTransform; +class EditorPropertyTransform3D; class EditorPropertyVector3; class BoneTransformEditor : public VBoxContainer { @@ -53,7 +53,7 @@ class BoneTransformEditor : public VBoxContainer { EditorPropertyVector3 *rotation_property = nullptr; EditorPropertyVector3 *scale_property = nullptr; EditorInspectorSection *transform_section = nullptr; - EditorPropertyTransform *transform_property = nullptr; + EditorPropertyTransform3D *transform_property = nullptr; Rect2 background_rects[5]; @@ -78,11 +78,11 @@ class BoneTransformEditor : public VBoxContainer { // 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 Transform p_transform, const StringName p_edited_property_name, const bool p_boolean); + 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(Transform p_new_transform); + void _change_transform(Transform3D p_new_transform); // Creates a Transform using the EditorPropertyVector3 properties. - Transform compute_transform_from_vector3s() const; + Transform3D compute_transform_from_vector3s() const; void update_enabled_checkbox(); @@ -98,7 +98,7 @@ public: void _update_properties(); void _update_custom_pose_properties(); - void _update_transform_properties(Transform p_transform); + 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); @@ -127,7 +127,7 @@ class Skeleton3DEditor : public VBoxContainer { struct BoneInfo { PhysicalBone3D *physical_bone = nullptr; - Transform relative_rest; // Relative to skeleton node + Transform3D relative_rest; // Relative to skeleton node }; EditorNode *editor; diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 4a7f6c0f7e..ef328bcfe2 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -186,7 +186,7 @@ void Sprite2DEditor::_update_mesh_data() { } Ref<BitMap> bm; - bm.instance(); + bm.instantiate(); bm->create_from_image_alpha(image); int shrink = shrink_pixels->get_value(); @@ -322,7 +322,7 @@ void Sprite2DEditor::_convert_to_mesh_2d_node() { } Ref<ArrayMesh> mesh; - mesh.instance(); + mesh.instantiate(); Array a; a.resize(Mesh::ARRAY_MAX); @@ -435,7 +435,7 @@ void Sprite2DEditor::_create_light_occluder_2d_node() { Vector<Vector2> outline = computed_outline_lines[i]; Ref<OccluderPolygon2D> polygon; - polygon.instance(); + polygon.instantiate(); PackedVector2Array a; a.resize(outline.size()); diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp index bd6dac7490..70c7b3072b 100644 --- a/editor/plugins/sprite_frames_editor_plugin.cpp +++ b/editor/plugins/sprite_frames_editor_plugin.cpp @@ -103,19 +103,18 @@ void SpriteFramesEditor::_sheet_preview_draw() { } void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { - Ref<InputEventMouseButton> mb = p_event; - + const Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - Size2i size = split_sheet_preview->get_size(); - int h = split_sheet_h->get_value(); - int v = split_sheet_v->get_value(); + const Size2i size = split_sheet_preview->get_size(); + const int h = split_sheet_h->get_value(); + const int v = split_sheet_v->get_value(); - int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1); - int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1); + const int x = CLAMP(int(mb->get_position().x) * h / size.width, 0, h - 1); + const int y = CLAMP(int(mb->get_position().y) * v / size.height, 0, v - 1); - int idx = h * y + x; + const int idx = h * y + x; - if (mb->get_shift() && last_frame_selected >= 0) { + if (mb->is_shift_pressed() && last_frame_selected >= 0) { //select multiple int from = idx; int to = last_frame_selected; @@ -124,13 +123,19 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { } for (int i = from; i <= to; i++) { - if (mb->get_control()) { + // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held. + frames_toggled_by_mouse_hover.insert(idx); + + if (mb->is_ctrl_pressed()) { frames_selected.erase(i); } else { frames_selected.insert(i); } } } else { + // Prevent double-toggling the same frame when moving the mouse when the mouse button is still held. + frames_toggled_by_mouse_hover.insert(idx); + if (frames_selected.has(idx)) { frames_selected.erase(idx); } else { @@ -141,6 +146,39 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) { last_frame_selected = idx; split_sheet_preview->update(); } + + if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + frames_toggled_by_mouse_hover.clear(); + } + + const Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid() && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) { + // Select by holding down the mouse button on frames. + const Size2i size = split_sheet_preview->get_size(); + const int h = split_sheet_h->get_value(); + const int v = split_sheet_v->get_value(); + + const int x = CLAMP(int(mm->get_position().x) * h / size.width, 0, h - 1); + const int y = CLAMP(int(mm->get_position().y) * v / size.height, 0, v - 1); + + const int idx = h * y + x; + + if (!frames_toggled_by_mouse_hover.has(idx)) { + // Only allow toggling each tile once per mouse hold. + // Otherwise, the selection would constantly "flicker" in and out when moving the mouse cursor. + // The mouse button must be released before it can be toggled again. + frames_toggled_by_mouse_hover.insert(idx); + + if (frames_selected.has(idx)) { + frames_selected.erase(idx); + } else { + frames_selected.insert(idx); + } + + last_frame_selected = idx; + split_sheet_preview->update(); + } + } } void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) { @@ -150,11 +188,11 @@ void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) { // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer // to allow performing this action anywhere, even if the cursor isn't // hovering the texture in the workspace. - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { _sheet_zoom_in(); // Don't scroll up after zooming in. accept_event(); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) { _sheet_zoom_out(); // Don't scroll down after zooming out. accept_event(); @@ -189,7 +227,7 @@ void SpriteFramesEditor::_sheet_add_frames() { int y = (yp * height) + region_rect.position.y; Ref<AtlasTexture> at; - at.instance(); + at.instantiate(); at->set_atlas(split_sheet_preview->get_texture()); at->set_region(Rect2(x, y, width, height)); @@ -220,7 +258,7 @@ void SpriteFramesEditor::_sheet_zoom_out() { void SpriteFramesEditor::_sheet_zoom_reset() { // Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad. - sheet_zoom = MAX(1.0, EDSCALE); + sheet_zoom = MAX(1.0f, EDSCALE); Size2 texture_size = split_sheet_preview->get_texture()->get_size(); split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom); } @@ -252,10 +290,10 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) { EditorNode::get_singleton()->show_warning(TTR("Unable to load images")); ERR_FAIL_COND(!texture.is_valid()); } - bool new_texture = texture != split_sheet_preview->get_texture(); frames_selected.clear(); last_frame_selected = -1; + bool new_texture = texture != split_sheet_preview->get_texture(); split_sheet_preview->set_texture(texture); if (new_texture) { //different texture, reset to 4x4 @@ -280,17 +318,17 @@ void SpriteFramesEditor::_notification(int p_what) { move_down->set_icon(get_theme_icon("MoveRight", "EditorIcons")); _delete->set_icon(get_theme_icon("Remove", "EditorIcons")); zoom_out->set_icon(get_theme_icon("ZoomLess", "EditorIcons")); - zoom_1->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); + zoom_reset->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); zoom_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); new_anim->set_icon(get_theme_icon("New", "EditorIcons")); remove_anim->set_icon(get_theme_icon("Remove", "EditorIcons")); split_sheet_zoom_out->set_icon(get_theme_icon("ZoomLess", "EditorIcons")); - split_sheet_zoom_1->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); + split_sheet_zoom_reset->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); split_sheet_zoom_in->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); [[fallthrough]]; } case NOTIFICATION_THEME_CHANGED: { - splite_sheet_scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree")); + split_sheet_scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree")); } break; case NOTIFICATION_READY: { add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up. @@ -513,7 +551,7 @@ void SpriteFramesEditor::_animation_select() { if (frames->has_animation(edited_anim)) { double value = anim_speed->get_line_edit()->get_text().to_float(); - if (!Math::is_equal_approx(value, frames->get_animation_speed(edited_anim))) { + if (!Math::is_equal_approx(value, (double)frames->get_animation_speed(edited_anim))) { _animation_fps_changed(value); } } @@ -694,11 +732,11 @@ void SpriteFramesEditor::_tree_input(const Ref<InputEvent> &p_event) { const Ref<InputEventMouseButton> mb = p_event; if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->is_ctrl_pressed()) { _zoom_in(); // Don't scroll up after zooming in. accept_event(); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { + } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->is_ctrl_pressed()) { _zoom_out(); // Don't scroll down after zooming out. accept_event(); @@ -733,7 +771,7 @@ void SpriteFramesEditor::_zoom_out() { } void SpriteFramesEditor::_zoom_reset() { - thumbnail_zoom = MAX(1.0, EDSCALE); + thumbnail_zoom = MAX(1.0f, EDSCALE); tree->set_fixed_column_width(thumbnail_default_size * 3 / 2); tree->set_fixed_icon_size(Size2(thumbnail_default_size, thumbnail_default_size)); } @@ -954,7 +992,7 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da if (String(d["type"]) == "files") { Vector<String> files = d["files"]; - if (Input::get_singleton()->is_key_pressed(KEY_CONTROL)) { + if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) { _prepare_sprite_sheet(files[0]); } else { _file_load_request(files, at_pos); @@ -964,9 +1002,9 @@ void SpriteFramesEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da void SpriteFramesEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_update_library", "skipsel"), &SpriteFramesEditor::_update_library, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &SpriteFramesEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &SpriteFramesEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &SpriteFramesEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &SpriteFramesEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &SpriteFramesEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &SpriteFramesEditor::drop_data_fw); } SpriteFramesEditor::SpriteFramesEditor() { @@ -1086,11 +1124,13 @@ SpriteFramesEditor::SpriteFramesEditor() { zoom_out->set_flat(true); zoom_out->set_tooltip(TTR("Zoom Out")); hbc->add_child(zoom_out); - zoom_1 = memnew(Button); - zoom_1->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_reset)); - zoom_1->set_flat(true); - zoom_1->set_tooltip(TTR("Zoom Reset")); - hbc->add_child(zoom_1); + + zoom_reset = memnew(Button); + zoom_reset->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_reset)); + zoom_reset->set_flat(true); + zoom_reset->set_tooltip(TTR("Zoom Reset")); + hbc->add_child(zoom_reset); + zoom_in = memnew(Button); zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_zoom_in)); zoom_in->set_flat(true); @@ -1183,16 +1223,16 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_preview->connect("draw", callable_mp(this, &SpriteFramesEditor::_sheet_preview_draw)); split_sheet_preview->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_preview_input)); - splite_sheet_scroll = memnew(ScrollContainer); - splite_sheet_scroll->set_enable_h_scroll(true); - splite_sheet_scroll->set_enable_v_scroll(true); - splite_sheet_scroll->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input)); - split_sheet_panel->add_child(splite_sheet_scroll); + split_sheet_scroll = memnew(ScrollContainer); + split_sheet_scroll->set_enable_h_scroll(true); + split_sheet_scroll->set_enable_v_scroll(true); + split_sheet_scroll->connect("gui_input", callable_mp(this, &SpriteFramesEditor::_sheet_scroll_input)); + split_sheet_panel->add_child(split_sheet_scroll); CenterContainer *cc = memnew(CenterContainer); cc->add_child(split_sheet_preview); cc->set_h_size_flags(SIZE_EXPAND_FILL); cc->set_v_size_flags(SIZE_EXPAND_FILL); - splite_sheet_scroll->add_child(cc); + split_sheet_scroll->add_child(cc); MarginContainer *split_sheet_zoom_margin = memnew(MarginContainer); split_sheet_panel->add_child(split_sheet_zoom_margin); @@ -1209,12 +1249,14 @@ SpriteFramesEditor::SpriteFramesEditor() { split_sheet_zoom_out->set_tooltip(TTR("Zoom Out")); split_sheet_zoom_out->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_out)); split_sheet_zoom_hb->add_child(split_sheet_zoom_out); - split_sheet_zoom_1 = memnew(Button); - split_sheet_zoom_1->set_flat(true); - split_sheet_zoom_1->set_focus_mode(FOCUS_NONE); - split_sheet_zoom_1->set_tooltip(TTR("Zoom Reset")); - split_sheet_zoom_1->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset)); - split_sheet_zoom_hb->add_child(split_sheet_zoom_1); + + split_sheet_zoom_reset = memnew(Button); + split_sheet_zoom_reset->set_flat(true); + split_sheet_zoom_reset->set_focus_mode(FOCUS_NONE); + split_sheet_zoom_reset->set_tooltip(TTR("Zoom Reset")); + split_sheet_zoom_reset->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_reset)); + split_sheet_zoom_hb->add_child(split_sheet_zoom_reset); + split_sheet_zoom_in = memnew(Button); split_sheet_zoom_in->set_flat(true); split_sheet_zoom_in->set_focus_mode(FOCUS_NONE); @@ -1230,14 +1272,14 @@ SpriteFramesEditor::SpriteFramesEditor() { // Config scale. scale_ratio = 1.2f; - thumbnail_default_size = 96 * MAX(1.0, EDSCALE); - thumbnail_zoom = MAX(1.0, EDSCALE); - max_thumbnail_zoom = 8.0f * MAX(1.0, EDSCALE); - min_thumbnail_zoom = 0.1f * MAX(1.0, EDSCALE); + thumbnail_default_size = 96 * MAX(1, EDSCALE); + thumbnail_zoom = MAX(1.0f, EDSCALE); + max_thumbnail_zoom = 8.0f * MAX(1.0f, EDSCALE); + min_thumbnail_zoom = 0.1f * MAX(1.0f, EDSCALE); // Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad. - sheet_zoom = MAX(1.0, EDSCALE); - max_sheet_zoom = 16.0f * MAX(1.0, EDSCALE); - min_sheet_zoom = 0.01f * MAX(1.0, EDSCALE); + sheet_zoom = MAX(1.0f, EDSCALE); + max_sheet_zoom = 16.0f * MAX(1.0f, EDSCALE); + min_sheet_zoom = 0.01f * MAX(1.0f, EDSCALE); _zoom_reset(); } diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h index bbc26ca726..e6c59e3533 100644 --- a/editor/plugins/sprite_frames_editor_plugin.h +++ b/editor/plugins/sprite_frames_editor_plugin.h @@ -54,13 +54,12 @@ class SpriteFramesEditor : public HSplitContainer { Button *move_up; Button *move_down; Button *zoom_out; - Button *zoom_1; + Button *zoom_reset; Button *zoom_in; ItemList *tree; bool loading_scene; int sel; - HSplitContainer *split; Button *new_anim; Button *remove_anim; @@ -79,15 +78,16 @@ class SpriteFramesEditor : public HSplitContainer { ConfirmationDialog *delete_dialog; ConfirmationDialog *split_sheet_dialog; - ScrollContainer *splite_sheet_scroll; + ScrollContainer *split_sheet_scroll; TextureRect *split_sheet_preview; SpinBox *split_sheet_h; SpinBox *split_sheet_v; Button *split_sheet_zoom_out; - Button *split_sheet_zoom_1; + Button *split_sheet_zoom_reset; Button *split_sheet_zoom_in; EditorFileDialog *file_split_sheet; Set<int> frames_selected; + Set<int> frames_toggled_by_mouse_hover; int last_frame_selected; float scale_ratio; diff --git a/editor/plugins/style_box_editor_plugin.cpp b/editor/plugins/style_box_editor_plugin.cpp index 64df982d5d..91c5e96f08 100644 --- a/editor/plugins/style_box_editor_plugin.cpp +++ b/editor/plugins/style_box_editor_plugin.cpp @@ -44,7 +44,7 @@ void EditorInspectorPluginStyleBox::parse_begin(Object *p_object) { add_custom_control(preview); } -bool EditorInspectorPluginStyleBox::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) { +bool EditorInspectorPluginStyleBox::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, bool p_wide) { return false; //do not want } @@ -93,6 +93,6 @@ StyleBoxPreview::StyleBoxPreview() { StyleBoxEditorPlugin::StyleBoxEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginStyleBox> inspector_plugin; - inspector_plugin.instance(); + inspector_plugin.instantiate(); add_inspector_plugin(inspector_plugin); } diff --git a/editor/plugins/style_box_editor_plugin.h b/editor/plugins/style_box_editor_plugin.h index d4a235cd10..8ca348bd80 100644 --- a/editor/plugins/style_box_editor_plugin.h +++ b/editor/plugins/style_box_editor_plugin.h @@ -61,7 +61,7 @@ class EditorInspectorPluginStyleBox : public EditorInspectorPlugin { public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; - virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; virtual void parse_end() override; }; diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 2b8bfe067d..5766646f7d 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -59,58 +59,7 @@ void TextEditor::_change_syntax_highlighter(int p_idx) { } void TextEditor::_load_theme_settings() { - CodeEdit *text_edit = code_editor->get_text_editor(); - text_edit->get_syntax_highlighter()->update_cache(); - - Color background_color = EDITOR_GET("text_editor/highlighting/background_color"); - Color completion_background_color = EDITOR_GET("text_editor/highlighting/completion_background_color"); - Color completion_selected_color = EDITOR_GET("text_editor/highlighting/completion_selected_color"); - Color completion_existing_color = EDITOR_GET("text_editor/highlighting/completion_existing_color"); - Color completion_scroll_color = EDITOR_GET("text_editor/highlighting/completion_scroll_color"); - Color completion_font_color = EDITOR_GET("text_editor/highlighting/completion_font_color"); - Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); - Color line_number_color = EDITOR_GET("text_editor/highlighting/line_number_color"); - Color caret_color = EDITOR_GET("text_editor/highlighting/caret_color"); - Color caret_background_color = EDITOR_GET("text_editor/highlighting/caret_background_color"); - Color text_selected_color = EDITOR_GET("text_editor/highlighting/text_selected_color"); - Color selection_color = EDITOR_GET("text_editor/highlighting/selection_color"); - Color brace_mismatch_color = EDITOR_GET("text_editor/highlighting/brace_mismatch_color"); - Color current_line_color = EDITOR_GET("text_editor/highlighting/current_line_color"); - Color line_length_guideline_color = EDITOR_GET("text_editor/highlighting/line_length_guideline_color"); - Color word_highlighted_color = EDITOR_GET("text_editor/highlighting/word_highlighted_color"); - Color mark_color = EDITOR_GET("text_editor/highlighting/mark_color"); - Color bookmark_color = EDITOR_GET("text_editor/highlighting/bookmark_color"); - Color breakpoint_color = EDITOR_GET("text_editor/highlighting/breakpoint_color"); - Color executing_line_color = EDITOR_GET("text_editor/highlighting/executing_line_color"); - Color code_folding_color = EDITOR_GET("text_editor/highlighting/code_folding_color"); - Color search_result_color = EDITOR_GET("text_editor/highlighting/search_result_color"); - Color search_result_border_color = EDITOR_GET("text_editor/highlighting/search_result_border_color"); - - text_edit->add_theme_color_override("background_color", background_color); - text_edit->add_theme_color_override("completion_background_color", completion_background_color); - text_edit->add_theme_color_override("completion_selected_color", completion_selected_color); - text_edit->add_theme_color_override("completion_existing_color", completion_existing_color); - text_edit->add_theme_color_override("completion_scroll_color", completion_scroll_color); - text_edit->add_theme_color_override("completion_font_color", completion_font_color); - text_edit->add_theme_color_override("font_color", text_color); - text_edit->add_theme_color_override("line_number_color", line_number_color); - text_edit->add_theme_color_override("caret_color", caret_color); - text_edit->add_theme_color_override("caret_background_color", caret_background_color); - text_edit->add_theme_color_override("font_selected_color", text_selected_color); - text_edit->add_theme_color_override("selection_color", selection_color); - text_edit->add_theme_color_override("brace_mismatch_color", brace_mismatch_color); - text_edit->add_theme_color_override("current_line_color", current_line_color); - text_edit->add_theme_color_override("line_length_guideline_color", line_length_guideline_color); - text_edit->add_theme_color_override("word_highlighted_color", word_highlighted_color); - text_edit->add_theme_color_override("breakpoint_color", breakpoint_color); - text_edit->add_theme_color_override("executing_line_color", executing_line_color); - text_edit->add_theme_color_override("mark_color", mark_color); - text_edit->add_theme_color_override("bookmark_color", bookmark_color); - text_edit->add_theme_color_override("code_folding_color", code_folding_color); - text_edit->add_theme_color_override("search_result_color", search_result_color); - text_edit->add_theme_color_override("search_result_border_color", search_result_border_color); - - text_edit->add_theme_constant_override("line_spacing", EDITOR_DEF("text_editor/theme/line_spacing", 6)); + code_editor->get_text_editor()->get_syntax_highlighter()->update_cache(); } String TextEditor::get_name() { @@ -171,6 +120,10 @@ void TextEditor::add_callback(const String &p_function, PackedStringArray p_args void TextEditor::set_debugger_active(bool p_active) { } +Control *TextEditor::get_base_editor() const { + return code_editor->get_text_editor(); +} + Array TextEditor::get_breakpoints() { return Array(); } @@ -328,6 +281,10 @@ void TextEditor::clear_edit_menu() { memdelete(edit_hb); } +void TextEditor::set_find_replace_bar(FindReplaceBar *p_bar) { + code_editor->set_find_replace_bar(p_bar); +} + void TextEditor::_edit_option(int p_op) { CodeEdit *tx = code_editor->get_text_editor(); @@ -363,19 +320,19 @@ void TextEditor::_edit_option(int p_op) { code_editor->move_lines_down(); } break; case EDIT_INDENT_LEFT: { - tx->indent_selected_lines_left(); + tx->unindent_lines(); } break; case EDIT_INDENT_RIGHT: { - tx->indent_selected_lines_right(); + tx->indent_lines(); } break; case EDIT_DELETE_LINE: { code_editor->delete_lines(); } break; - case EDIT_CLONE_DOWN: { - code_editor->clone_lines_down(); + case EDIT_DUPLICATE_SELECTION: { + code_editor->duplicate_selection(); } break; case EDIT_TOGGLE_FOLD_LINE: { - tx->toggle_fold_line(tx->cursor_get_line()); + tx->toggle_foldable_line(tx->cursor_get_line()); tx->update(); } break; case EDIT_FOLD_ALL_LINES: { @@ -475,8 +432,8 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { tx->_get_mouse_pos(mb->get_global_position() - tx->get_global_position(), row, col); tx->set_right_click_moves_caret(EditorSettings::get_singleton()->get("text_editor/cursor/right_click_moves_caret")); - bool can_fold = tx->can_fold(row); - bool is_folded = tx->is_folded(row); + bool can_fold = tx->can_fold_line(row); + bool is_folded = tx->is_line_folded(row); if (tx->is_right_click_moving_caret()) { if (tx->is_selection_active()) { @@ -506,7 +463,7 @@ void TextEditor::_text_edit_gui_input(const Ref<InputEvent> &ev) { if (k.is_valid() && k->is_pressed() && k->get_keycode() == KEY_MENU) { CodeEdit *tx = code_editor->get_text_editor(); int line = tx->cursor_get_line(); - _make_context_menu(tx->is_selection_active(), tx->can_fold(line), tx->is_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos())); + _make_context_menu(tx->is_selection_active(), tx->can_fold_line(line), tx->is_line_folded(line), (get_global_transform().inverse() * tx->get_global_transform()).xform(tx->_get_cursor_pixel_pos())); context_menu->grab_focus(); } } @@ -585,7 +542,7 @@ TextEditor::TextEditor() { edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &TextEditor::_edit_option)); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_undo"), EDIT_UNDO); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("un_redo"), EDIT_REDO); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_redo"), EDIT_REDO); edit_menu->get_popup()->add_separator(); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_cut"), EDIT_CUT); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("ui_copy"), EDIT_COPY); @@ -602,7 +559,7 @@ TextEditor::TextEditor() { edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/fold_all_lines"), EDIT_FOLD_ALL_LINES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/unfold_all_lines"), EDIT_UNFOLD_ALL_LINES); edit_menu->get_popup()->add_separator(); - edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/clone_down"), EDIT_CLONE_DOWN); + edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/duplicate_selection"), EDIT_DUPLICATE_SELECTION); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/trim_trailing_whitespace"), EDIT_TRIM_TRAILING_WHITESAPCE); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_spaces"), EDIT_CONVERT_INDENT_TO_SPACES); edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/convert_indent_to_tabs"), EDIT_CONVERT_INDENT_TO_TABS); @@ -624,11 +581,11 @@ TextEditor::TextEditor() { highlighter_menu->connect("id_pressed", callable_mp(this, &TextEditor::_change_syntax_highlighter)); Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter; - plain_highlighter.instance(); + plain_highlighter.instantiate(); add_syntax_highlighter(plain_highlighter); Ref<EditorStandardSyntaxHighlighter> highlighter; - highlighter.instance(); + highlighter.instantiate(); add_syntax_highlighter(highlighter); set_syntax_highlighter(plain_highlighter); diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h index c066d51b18..86a4910ac0 100644 --- a/editor/plugins/text_editor.h +++ b/editor/plugins/text_editor.h @@ -66,7 +66,7 @@ private: EDIT_INDENT_RIGHT, EDIT_INDENT_LEFT, EDIT_DELETE_LINE, - EDIT_CLONE_DOWN, + EDIT_DUPLICATE_SELECTION, EDIT_TO_UPPERCASE, EDIT_TO_LOWERCASE, EDIT_CAPITALIZE, @@ -139,9 +139,12 @@ public: virtual Control *get_edit_menu() override; virtual void clear_edit_menu() override; + virtual void set_find_replace_bar(FindReplaceBar *p_bar) override; virtual void validate() override; + virtual Control *get_base_editor() const override; + static void register_editor(); TextEditor(); diff --git a/editor/plugins/texture_3d_editor_plugin.cpp b/editor/plugins/texture_3d_editor_plugin.cpp index 36297c8a4a..696aa88e23 100644 --- a/editor/plugins/texture_3d_editor_plugin.cpp +++ b/editor/plugins/texture_3d_editor_plugin.cpp @@ -85,9 +85,9 @@ void Texture3DEditor::_make_shaders() { " COLOR = textureLod(tex,vec3(UV,layer),0.0);\n" "}"; - shader.instance(); + shader.instantiate(); shader->set_code(shader_3d); - material.instance(); + material.instantiate(); material->set_shader(shader); } @@ -207,6 +207,6 @@ void EditorInspectorPlugin3DTexture::parse_begin(Object *p_object) { Texture3DEditorPlugin::Texture3DEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPlugin3DTexture> plugin; - plugin.instance(); + plugin.instantiate(); add_inspector_plugin(plugin); } diff --git a/editor/plugins/texture_editor_plugin.cpp b/editor/plugins/texture_editor_plugin.cpp index ecf7370834..10b942d9ee 100644 --- a/editor/plugins/texture_editor_plugin.cpp +++ b/editor/plugins/texture_editor_plugin.cpp @@ -160,6 +160,6 @@ void EditorInspectorPluginTexture::parse_begin(Object *p_object) { TextureEditorPlugin::TextureEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginTexture> plugin; - plugin.instance(); + plugin.instantiate(); add_inspector_plugin(plugin); } diff --git a/editor/plugins/texture_layered_editor_plugin.cpp b/editor/plugins/texture_layered_editor_plugin.cpp index 89ed98d53e..3f46cd64a2 100644 --- a/editor/plugins/texture_layered_editor_plugin.cpp +++ b/editor/plugins/texture_layered_editor_plugin.cpp @@ -112,7 +112,7 @@ void TextureLayeredEditor::_make_shaders() { " COLOR = textureLod(tex,vec3(UV,layer),0.0);\n" "}"; - shaders[0].instance(); + shaders[0].instantiate(); shaders[0]->set_code(shader_2d_array); String shader_cube = "" @@ -125,7 +125,7 @@ void TextureLayeredEditor::_make_shaders() { " COLOR = textureLod(tex,n,0.0);\n" "}"; - shaders[1].instance(); + shaders[1].instantiate(); shaders[1]->set_code(shader_cube); String shader_cube_array = "" @@ -139,11 +139,11 @@ void TextureLayeredEditor::_make_shaders() { " COLOR = textureLod(tex,vec4(n,layer),0.0);\n" "}"; - shaders[2].instance(); + shaders[2].instantiate(); shaders[2]->set_code(shader_cube_array); for (int i = 0; i < 3; i++) { - materials[i].instance(); + materials[i].instantiate(); materials[i]->set_shader(shaders[i]); } } @@ -271,6 +271,6 @@ void EditorInspectorPluginLayeredTexture::parse_begin(Object *p_object) { TextureLayeredEditorPlugin::TextureLayeredEditorPlugin(EditorNode *p_node) { Ref<EditorInspectorPluginLayeredTexture> plugin; - plugin.instance(); + plugin.instantiate(); add_inspector_plugin(plugin); } diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp index 7b927ad98b..d0ba68138b 100644 --- a/editor/plugins/texture_region_editor_plugin.cpp +++ b/editor/plugins/texture_region_editor_plugin.cpp @@ -331,7 +331,7 @@ void TextureRegionEditor::_region_input(const Ref<InputEvent> &p_input) { for (List<Rect2>::Element *E = autoslice_cache.front(); E; E = E->next()) { if (E->get().has_point(point)) { rect = E->get(); - if (Input::get_singleton()->is_key_pressed(KEY_CONTROL) && !(Input::get_singleton()->is_key_pressed(KEY_SHIFT | KEY_ALT))) { + if (Input::get_singleton()->is_key_pressed(KEY_CTRL) && !(Input::get_singleton()->is_key_pressed(KEY_SHIFT | KEY_ALT))) { Rect2 r; if (node_sprite) { r = node_sprite->get_region_rect(); @@ -863,13 +863,13 @@ Sprite2D *TextureRegionEditor::get_sprite() { void TextureRegionEditor::edit(Object *p_obj) { if (node_sprite) { - node_sprite->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + node_sprite->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } if (node_sprite_3d) { - node_sprite_3d->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + node_sprite_3d->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } if (node_ninepatch) { - node_ninepatch->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + node_ninepatch->disconnect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } if (obj_styleBox.is_valid()) { obj_styleBox->disconnect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); @@ -881,13 +881,22 @@ void TextureRegionEditor::edit(Object *p_obj) { node_sprite = Object::cast_to<Sprite2D>(p_obj); node_sprite_3d = Object::cast_to<Sprite3D>(p_obj); node_ninepatch = Object::cast_to<NinePatchRect>(p_obj); + + bool is_resource = false; if (Object::cast_to<StyleBoxTexture>(p_obj)) { obj_styleBox = Ref<StyleBoxTexture>(Object::cast_to<StyleBoxTexture>(p_obj)); + is_resource = true; } if (Object::cast_to<AtlasTexture>(p_obj)) { atlas_tex = Ref<AtlasTexture>(Object::cast_to<AtlasTexture>(p_obj)); + is_resource = true; + } + + if (is_resource) { + p_obj->connect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); + } else { + p_obj->connect("texture_changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); } - p_obj->connect("changed", callable_mp(this, &TextureRegionEditor::_texture_changed)); _edit_region(); } else { node_sprite = nullptr; @@ -937,7 +946,6 @@ void TextureRegionEditor::_edit_region() { if (cache_map.has(texture->get_rid())) { autoslice_cache = cache_map[texture->get_rid()]; autoslice_is_dirty = false; - return; } else { if (is_visible() && snap_mode == SNAP_AUTOSLICE) { _update_autoslice(); @@ -1034,7 +1042,7 @@ TextureRegionEditor::TextureRegionEditor(EditorNode *p_editor) { hb_grid->add_child(sb_step_y); hb_grid->add_child(memnew(VSeparator)); - hb_grid->add_child(memnew(Label(TTR("Sep.:")))); + hb_grid->add_child(memnew(Label(TTR("Separation:")))); sb_sep_x = memnew(SpinBox); sb_sep_x->set_min(0); diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp index c765aa0319..be1aeb309f 100644 --- a/editor/plugins/theme_editor_plugin.cpp +++ b/editor/plugins/theme_editor_plugin.cpp @@ -30,16 +30,1200 @@ #include "theme_editor_plugin.h" -#include "core/os/file_access.h" #include "core/os/keyboard.h" -#include "core/version.h" +#include "editor/editor_resource_picker.h" #include "editor/editor_scale.h" -#include "scene/gui/progress_bar.h" +#include "editor/progress_dialog.h" + +void ThemeItemImportTree::_update_items_tree() { + import_items_tree->clear(); + TreeItem *root = import_items_tree->create_item(); + + if (base_theme.is_null()) { + return; + } + + String filter_text = import_items_filter->get_text(); + + List<StringName> types; + List<StringName> names; + List<StringName> filtered_names; + base_theme->get_type_list(&types); + types.sort_custom<StringName::AlphCompare>(); + + int color_amount = 0; + int constant_amount = 0; + int font_amount = 0; + int font_size_amount = 0; + int icon_amount = 0; + int stylebox_amount = 0; + + tree_color_items.clear(); + tree_constant_items.clear(); + tree_font_items.clear(); + tree_font_size_items.clear(); + tree_icon_items.clear(); + tree_stylebox_items.clear(); + + for (List<StringName>::Element *E = types.front(); E; E = E->next()) { + String type_name = (String)E->get(); + + TreeItem *type_node = import_items_tree->create_item(root); + type_node->set_meta("_can_be_imported", false); + type_node->set_collapsed(true); + type_node->set_text(0, type_name); + type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK); + type_node->set_checked(IMPORT_ITEM, false); + type_node->set_editable(IMPORT_ITEM, true); + type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK); + type_node->set_checked(IMPORT_ITEM_DATA, false); + type_node->set_editable(IMPORT_ITEM_DATA, true); + + bool is_matching_filter = (filter_text.is_empty() || type_name.findn(filter_text) > -1); + bool has_filtered_items = false; + bool any_checked = false; + bool any_checked_with_data = false; + + for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) { + Theme::DataType dt = (Theme::DataType)i; + + names.clear(); + filtered_names.clear(); + base_theme->get_theme_item_list(dt, E->get(), &names); + + bool data_type_has_filtered_items = false; + + for (List<StringName>::Element *F = names.front(); F; F = F->next()) { + String item_name = (String)F->get(); + bool is_item_matching_filter = (item_name.findn(filter_text) > -1); + if (!filter_text.is_empty() && !is_matching_filter && !is_item_matching_filter) { + continue; + } + + // Only mark this if actual items match the filter and not just the type group. + if (!filter_text.is_empty() && is_item_matching_filter) { + has_filtered_items = true; + data_type_has_filtered_items = true; + } + filtered_names.push_back(F->get()); + } + + if (filtered_names.size() == 0) { + continue; + } + + TreeItem *data_type_node = import_items_tree->create_item(type_node); + data_type_node->set_meta("_can_be_imported", false); + data_type_node->set_metadata(0, i); + data_type_node->set_collapsed(!data_type_has_filtered_items); + data_type_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK); + data_type_node->set_checked(IMPORT_ITEM, false); + data_type_node->set_editable(IMPORT_ITEM, true); + data_type_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK); + data_type_node->set_checked(IMPORT_ITEM_DATA, false); + data_type_node->set_editable(IMPORT_ITEM_DATA, true); + + List<TreeItem *> *item_list; + + switch (dt) { + case Theme::DATA_TYPE_COLOR: + data_type_node->set_icon(0, get_theme_icon("Color", "EditorIcons")); + data_type_node->set_text(0, TTR("Colors")); + + item_list = &tree_color_items; + color_amount += filtered_names.size(); + break; + + case Theme::DATA_TYPE_CONSTANT: + data_type_node->set_icon(0, get_theme_icon("MemberConstant", "EditorIcons")); + data_type_node->set_text(0, TTR("Constants")); + + item_list = &tree_constant_items; + constant_amount += filtered_names.size(); + break; + + case Theme::DATA_TYPE_FONT: + data_type_node->set_icon(0, get_theme_icon("Font", "EditorIcons")); + data_type_node->set_text(0, TTR("Fonts")); + + item_list = &tree_font_items; + font_amount += filtered_names.size(); + break; + + case Theme::DATA_TYPE_FONT_SIZE: + data_type_node->set_icon(0, get_theme_icon("FontSize", "EditorIcons")); + data_type_node->set_text(0, TTR("Font Sizes")); + + item_list = &tree_font_size_items; + font_size_amount += filtered_names.size(); + break; + + case Theme::DATA_TYPE_ICON: + data_type_node->set_icon(0, get_theme_icon("ImageTexture", "EditorIcons")); + data_type_node->set_text(0, TTR("Icons")); + + item_list = &tree_icon_items; + icon_amount += filtered_names.size(); + break; + + case Theme::DATA_TYPE_STYLEBOX: + data_type_node->set_icon(0, get_theme_icon("StyleBoxFlat", "EditorIcons")); + data_type_node->set_text(0, TTR("Styleboxes")); + + item_list = &tree_stylebox_items; + stylebox_amount += filtered_names.size(); + break; + + case Theme::DATA_TYPE_MAX: + break; // Can't happen, but silences warning. + } + + bool data_type_any_checked = false; + bool data_type_any_checked_with_data = false; + + filtered_names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *F = filtered_names.front(); F; F = F->next()) { + TreeItem *item_node = import_items_tree->create_item(data_type_node); + item_node->set_meta("_can_be_imported", true); + item_node->set_text(0, F->get()); + item_node->set_cell_mode(IMPORT_ITEM, TreeItem::CELL_MODE_CHECK); + item_node->set_checked(IMPORT_ITEM, false); + item_node->set_editable(IMPORT_ITEM, true); + item_node->set_cell_mode(IMPORT_ITEM_DATA, TreeItem::CELL_MODE_CHECK); + item_node->set_checked(IMPORT_ITEM_DATA, false); + item_node->set_editable(IMPORT_ITEM_DATA, true); + + _restore_selected_item(item_node); + if (item_node->is_checked(IMPORT_ITEM)) { + data_type_any_checked = true; + any_checked = true; + } + if (item_node->is_checked(IMPORT_ITEM_DATA)) { + data_type_any_checked_with_data = true; + any_checked_with_data = true; + } + + item_list->push_back(item_node); + } + + data_type_node->set_checked(IMPORT_ITEM, data_type_any_checked); + data_type_node->set_checked(IMPORT_ITEM_DATA, data_type_any_checked && data_type_any_checked_with_data); + } + + // Remove the item if it doesn't match the filter in any way. + if (!is_matching_filter && !has_filtered_items) { + root->remove_child(type_node); + memdelete(type_node); + continue; + } + + // Show one level inside of a type group if there are matches in items. + if (!filter_text.is_empty() && has_filtered_items) { + type_node->set_collapsed(false); + } + + type_node->set_checked(IMPORT_ITEM, any_checked); + type_node->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); + } + + if (color_amount > 0) { + Array arr; + arr.push_back(color_amount); + select_colors_label->set_text(TTRN("One color", "{num} colors", color_amount).format(arr, "{num}")); + select_all_colors_button->set_visible(true); + select_full_colors_button->set_visible(true); + deselect_all_colors_button->set_visible(true); + } else { + select_colors_label->set_text(TTR("No colors found.")); + select_all_colors_button->set_visible(false); + select_full_colors_button->set_visible(false); + deselect_all_colors_button->set_visible(false); + } + + if (constant_amount > 0) { + Array arr; + arr.push_back(constant_amount); + select_constants_label->set_text(TTRN("One constant", "{num} constants", constant_amount).format(arr, "{num}")); + select_all_constants_button->set_visible(true); + select_full_constants_button->set_visible(true); + deselect_all_constants_button->set_visible(true); + } else { + select_constants_label->set_text(TTR("No constants found.")); + select_all_constants_button->set_visible(false); + select_full_constants_button->set_visible(false); + deselect_all_constants_button->set_visible(false); + } + + if (font_amount > 0) { + Array arr; + arr.push_back(font_amount); + select_fonts_label->set_text(TTRN("One font", "{num} fonts", font_amount).format(arr, "{num}")); + select_all_fonts_button->set_visible(true); + select_full_fonts_button->set_visible(true); + deselect_all_fonts_button->set_visible(true); + } else { + select_fonts_label->set_text(TTR("No fonts found.")); + select_all_fonts_button->set_visible(false); + select_full_fonts_button->set_visible(false); + deselect_all_fonts_button->set_visible(false); + } + + if (font_size_amount > 0) { + Array arr; + arr.push_back(font_size_amount); + select_font_sizes_label->set_text(TTRN("One font size", "{num} font sizes", font_size_amount).format(arr, "{num}")); + select_all_font_sizes_button->set_visible(true); + select_full_font_sizes_button->set_visible(true); + deselect_all_font_sizes_button->set_visible(true); + } else { + select_font_sizes_label->set_text(TTR("No font sizes found.")); + select_all_font_sizes_button->set_visible(false); + select_full_font_sizes_button->set_visible(false); + deselect_all_font_sizes_button->set_visible(false); + } + + if (icon_amount > 0) { + Array arr; + arr.push_back(icon_amount); + select_icons_label->set_text(TTRN("One icon", "{num} icons", icon_amount).format(arr, "{num}")); + select_all_icons_button->set_visible(true); + select_full_icons_button->set_visible(true); + deselect_all_icons_button->set_visible(true); + select_icons_warning_hb->set_visible(true); + } else { + select_icons_label->set_text(TTR("No icons found.")); + select_all_icons_button->set_visible(false); + select_full_icons_button->set_visible(false); + deselect_all_icons_button->set_visible(false); + select_icons_warning_hb->set_visible(false); + } + + if (stylebox_amount > 0) { + Array arr; + arr.push_back(stylebox_amount); + select_styleboxes_label->set_text(TTRN("One stylebox", "{num} styleboxes", stylebox_amount).format(arr, "{num}")); + select_all_styleboxes_button->set_visible(true); + select_full_styleboxes_button->set_visible(true); + deselect_all_styleboxes_button->set_visible(true); + } else { + select_styleboxes_label->set_text(TTR("No styleboxes found.")); + select_all_styleboxes_button->set_visible(false); + select_full_styleboxes_button->set_visible(false); + deselect_all_styleboxes_button->set_visible(false); + } +} + +void ThemeItemImportTree::_toggle_type_items(bool p_collapse) { + TreeItem *root = import_items_tree->get_root(); + if (!root) { + return; + } + + TreeItem *type_node = root->get_first_child(); + while (type_node) { + type_node->set_collapsed(p_collapse); + type_node = type_node->get_next(); + } +} + +void ThemeItemImportTree::_filter_text_changed(const String &p_value) { + _update_items_tree(); +} + +void ThemeItemImportTree::_store_selected_item(TreeItem *p_tree_item) { + if (!p_tree_item->get_meta("_can_be_imported")) { + return; + } + + TreeItem *data_type_node = p_tree_item->get_parent(); + if (!data_type_node || data_type_node == import_items_tree->get_root()) { + return; + } + + TreeItem *type_node = data_type_node->get_parent(); + if (!type_node || type_node == import_items_tree->get_root()) { + return; + } + + ThemeItem ti; + ti.item_name = p_tree_item->get_text(0); + ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0); + ti.type_name = type_node->get_text(0); + + bool import = p_tree_item->is_checked(IMPORT_ITEM); + bool with_data = p_tree_item->is_checked(IMPORT_ITEM_DATA); + + if (import && with_data) { + selected_items[ti] = SELECT_IMPORT_FULL; + } else if (import) { + selected_items[ti] = SELECT_IMPORT_DEFINITION; + } else { + selected_items.erase(ti); + } + + _update_total_selected(ti.data_type); +} + +void ThemeItemImportTree::_restore_selected_item(TreeItem *p_tree_item) { + if (!p_tree_item->get_meta("_can_be_imported")) { + return; + } + + TreeItem *data_type_node = p_tree_item->get_parent(); + if (!data_type_node || data_type_node == import_items_tree->get_root()) { + return; + } + + TreeItem *type_node = data_type_node->get_parent(); + if (!type_node || type_node == import_items_tree->get_root()) { + return; + } + + ThemeItem ti; + ti.item_name = p_tree_item->get_text(0); + ti.data_type = (Theme::DataType)(int)data_type_node->get_metadata(0); + ti.type_name = type_node->get_text(0); + + if (!selected_items.has(ti)) { + p_tree_item->set_checked(IMPORT_ITEM, false); + p_tree_item->set_checked(IMPORT_ITEM_DATA, false); + return; + } + + if (selected_items[ti] == SELECT_IMPORT_FULL) { + p_tree_item->set_checked(IMPORT_ITEM, true); + p_tree_item->set_checked(IMPORT_ITEM_DATA, true); + } else if (selected_items[ti] == SELECT_IMPORT_DEFINITION) { + p_tree_item->set_checked(IMPORT_ITEM, true); + p_tree_item->set_checked(IMPORT_ITEM_DATA, false); + } +} + +void ThemeItemImportTree::_update_total_selected(Theme::DataType p_data_type) { + ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds."); + + Label *total_selected_items_label; + switch (p_data_type) { + case Theme::DATA_TYPE_COLOR: + total_selected_items_label = total_selected_colors_label; + break; + + case Theme::DATA_TYPE_CONSTANT: + total_selected_items_label = total_selected_constants_label; + break; + + case Theme::DATA_TYPE_FONT: + total_selected_items_label = total_selected_fonts_label; + break; + + case Theme::DATA_TYPE_FONT_SIZE: + total_selected_items_label = total_selected_font_sizes_label; + break; + + case Theme::DATA_TYPE_ICON: + total_selected_items_label = total_selected_icons_label; + break; + + case Theme::DATA_TYPE_STYLEBOX: + total_selected_items_label = total_selected_styleboxes_label; + break; + + case Theme::DATA_TYPE_MAX: + return; // Can't happen, but silences warning. + } + + if (!total_selected_items_label) { + return; + } + + int count = 0; + for (Map<ThemeItem, ItemCheckedState>::Element *E = selected_items.front(); E; E = E->next()) { + ThemeItem ti = E->key(); + if (ti.data_type == p_data_type) { + count++; + } + } + + if (count == 0) { + total_selected_items_label->hide(); + } else { + Array arr; + arr.push_back(count); + total_selected_items_label->set_text(TTRN("{num} currently selected", "{num} currently selected", count).format(arr, "{num}")); + total_selected_items_label->show(); + } +} + +void ThemeItemImportTree::_tree_item_edited() { + if (updating_tree) { + return; + } + + TreeItem *edited_item = import_items_tree->get_edited(); + if (!edited_item) { + return; + } + + updating_tree = true; + + int edited_column = import_items_tree->get_edited_column(); + bool is_checked = edited_item->is_checked(edited_column); + if (is_checked) { + if (edited_column == IMPORT_ITEM_DATA) { + edited_item->set_checked(IMPORT_ITEM, true); + } + + _select_all_subitems(edited_item, (edited_column == IMPORT_ITEM_DATA)); + } else { + if (edited_column == IMPORT_ITEM) { + edited_item->set_checked(IMPORT_ITEM_DATA, false); + } + + _deselect_all_subitems(edited_item, (edited_column == IMPORT_ITEM)); + } + + _update_parent_items(edited_item); + _store_selected_item(edited_item); + + updating_tree = false; +} + +void ThemeItemImportTree::_select_all_subitems(TreeItem *p_root_item, bool p_select_with_data) { + TreeItem *child_item = p_root_item->get_first_child(); + while (child_item) { + child_item->set_checked(IMPORT_ITEM, true); + if (p_select_with_data) { + child_item->set_checked(IMPORT_ITEM_DATA, true); + } + _store_selected_item(child_item); + + _select_all_subitems(child_item, p_select_with_data); + child_item = child_item->get_next(); + } +} + +void ThemeItemImportTree::_deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely) { + TreeItem *child_item = p_root_item->get_first_child(); + while (child_item) { + child_item->set_checked(IMPORT_ITEM_DATA, false); + if (p_deselect_completely) { + child_item->set_checked(IMPORT_ITEM, false); + } + _store_selected_item(child_item); + + _deselect_all_subitems(child_item, p_deselect_completely); + child_item = child_item->get_next(); + } +} + +void ThemeItemImportTree::_update_parent_items(TreeItem *p_root_item) { + TreeItem *parent_item = p_root_item->get_parent(); + if (!parent_item) { + return; + } + + bool any_checked = false; + bool any_checked_with_data = false; + + TreeItem *child_item = parent_item->get_first_child(); + while (child_item) { + if (child_item->is_checked(IMPORT_ITEM)) { + any_checked = true; + } + if (child_item->is_checked(IMPORT_ITEM_DATA)) { + any_checked_with_data = true; + } + + child_item = child_item->get_next(); + } + + parent_item->set_checked(IMPORT_ITEM, any_checked); + parent_item->set_checked(IMPORT_ITEM_DATA, any_checked && any_checked_with_data); + _update_parent_items(parent_item); +} + +void ThemeItemImportTree::_select_all_items_pressed() { + if (updating_tree) { + return; + } + + updating_tree = true; + + TreeItem *root = import_items_tree->get_root(); + _select_all_subitems(root, false); + + updating_tree = false; +} + +void ThemeItemImportTree::_select_full_items_pressed() { + if (updating_tree) { + return; + } + + updating_tree = true; + + TreeItem *root = import_items_tree->get_root(); + _select_all_subitems(root, true); + + updating_tree = false; +} + +void ThemeItemImportTree::_deselect_all_items_pressed() { + if (updating_tree) { + return; + } + + updating_tree = true; + + TreeItem *root = import_items_tree->get_root(); + _deselect_all_subitems(root, true); + + updating_tree = false; +} + +void ThemeItemImportTree::_select_all_data_type_pressed(int p_data_type) { + ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds."); + + if (updating_tree) { + return; + } + + Theme::DataType data_type = (Theme::DataType)p_data_type; + List<TreeItem *> *item_list; + + switch (data_type) { + case Theme::DATA_TYPE_COLOR: + item_list = &tree_color_items; + break; + + case Theme::DATA_TYPE_CONSTANT: + item_list = &tree_constant_items; + break; + + case Theme::DATA_TYPE_FONT: + item_list = &tree_font_items; + break; + + case Theme::DATA_TYPE_FONT_SIZE: + item_list = &tree_font_size_items; + break; + + case Theme::DATA_TYPE_ICON: + item_list = &tree_icon_items; + break; + + case Theme::DATA_TYPE_STYLEBOX: + item_list = &tree_stylebox_items; + break; + + case Theme::DATA_TYPE_MAX: + return; // Can't happen, but silences warning. + } + + updating_tree = true; + + for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) { + TreeItem *child_item = E->get(); + if (!child_item) { + continue; + } + + child_item->set_checked(IMPORT_ITEM, true); + _update_parent_items(child_item); + _store_selected_item(child_item); + } + + updating_tree = false; +} + +void ThemeItemImportTree::_select_full_data_type_pressed(int p_data_type) { + ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds."); + + if (updating_tree) { + return; + } + + Theme::DataType data_type = (Theme::DataType)p_data_type; + List<TreeItem *> *item_list; + + switch (data_type) { + case Theme::DATA_TYPE_COLOR: + item_list = &tree_color_items; + break; + + case Theme::DATA_TYPE_CONSTANT: + item_list = &tree_constant_items; + break; + + case Theme::DATA_TYPE_FONT: + item_list = &tree_font_items; + break; + + case Theme::DATA_TYPE_FONT_SIZE: + item_list = &tree_font_size_items; + break; + + case Theme::DATA_TYPE_ICON: + item_list = &tree_icon_items; + break; + + case Theme::DATA_TYPE_STYLEBOX: + item_list = &tree_stylebox_items; + break; + + case Theme::DATA_TYPE_MAX: + return; // Can't happen, but silences warning. + } + + updating_tree = true; + + for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) { + TreeItem *child_item = E->get(); + if (!child_item) { + continue; + } + + child_item->set_checked(IMPORT_ITEM, true); + child_item->set_checked(IMPORT_ITEM_DATA, true); + _update_parent_items(child_item); + _store_selected_item(child_item); + } + + updating_tree = false; +} + +void ThemeItemImportTree::_deselect_all_data_type_pressed(int p_data_type) { + ERR_FAIL_INDEX_MSG(p_data_type, Theme::DATA_TYPE_MAX, "Theme item data type is out of bounds."); + + if (updating_tree) { + return; + } + + Theme::DataType data_type = (Theme::DataType)p_data_type; + List<TreeItem *> *item_list; + + switch (data_type) { + case Theme::DATA_TYPE_COLOR: + item_list = &tree_color_items; + break; + + case Theme::DATA_TYPE_CONSTANT: + item_list = &tree_constant_items; + break; + + case Theme::DATA_TYPE_FONT: + item_list = &tree_font_items; + break; + + case Theme::DATA_TYPE_FONT_SIZE: + item_list = &tree_font_size_items; + break; + + case Theme::DATA_TYPE_ICON: + item_list = &tree_icon_items; + break; + + case Theme::DATA_TYPE_STYLEBOX: + item_list = &tree_stylebox_items; + break; + + case Theme::DATA_TYPE_MAX: + return; // Can't happen, but silences warning. + } + + updating_tree = true; + + for (List<TreeItem *>::Element *E = item_list->front(); E; E = E->next()) { + TreeItem *child_item = E->get(); + if (!child_item) { + continue; + } + + child_item->set_checked(IMPORT_ITEM, false); + child_item->set_checked(IMPORT_ITEM_DATA, false); + _update_parent_items(child_item); + _store_selected_item(child_item); + } + + updating_tree = false; +} + +void ThemeItemImportTree::_import_selected() { + if (selected_items.size() == 0) { + EditorNode::get_singleton()->show_accept(TTR("Nothing was selected for the import."), TTR("OK")); + return; + } + + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); + 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()) { + // 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) { + Array arr; + arr.push_back(idx + 1); + arr.push_back(selected_items.size()); + 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(); + + if (cs == SELECT_IMPORT_DEFINITION || cs == SELECT_IMPORT_FULL) { + Variant item_value = Variant(); + + if (cs == SELECT_IMPORT_FULL) { + item_value = base_theme->get_theme_item(ti.data_type, ti.item_name, ti.type_name); + } else { + switch (ti.data_type) { + case Theme::DATA_TYPE_COLOR: + item_value = Color(); + break; + + case Theme::DATA_TYPE_CONSTANT: + item_value = 0; + break; + + case Theme::DATA_TYPE_FONT: + item_value = Ref<Font>(); + break; + + case Theme::DATA_TYPE_FONT_SIZE: + item_value = -1; + break; + + case Theme::DATA_TYPE_ICON: + item_value = Ref<Texture2D>(); + break; + + case Theme::DATA_TYPE_STYLEBOX: + item_value = Ref<StyleBox>(); + break; + + case Theme::DATA_TYPE_MAX: + break; // Can't happen, but silences warning. + } + } + + edited_theme->set_theme_item(ti.data_type, ti.item_name, ti.type_name, item_value); + } + + idx++; + } + + // Allow changes to be reported now that the operation is finished. + ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Updating the editor"), idx++); + edited_theme->_unfreeze_and_propagate_changes(); + // Make sure the task is not ended before the editor freezes to update the Inspector. + ProgressDialog::get_singleton()->task_step("import_theme_items", TTR("Finalizing"), idx++); + + ProgressDialog::get_singleton()->end_task("import_theme_items"); + emit_signal("items_imported"); +} + +void ThemeItemImportTree::set_edited_theme(const Ref<Theme> &p_theme) { + edited_theme = p_theme; +} + +void ThemeItemImportTree::set_base_theme(const Ref<Theme> &p_theme) { + base_theme = p_theme; +} + +void ThemeItemImportTree::reset_item_tree() { + import_items_filter->clear(); + selected_items.clear(); + + total_selected_colors_label->hide(); + total_selected_constants_label->hide(); + total_selected_fonts_label->hide(); + total_selected_font_sizes_label->hide(); + total_selected_icons_label->hide(); + total_selected_styleboxes_label->hide(); + + _update_items_tree(); +} + +bool ThemeItemImportTree::has_selected_items() const { + return (selected_items.size() > 0); +} + +void ThemeItemImportTree::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + select_icons_warning_icon->set_texture(get_theme_icon("StatusWarning", "EditorIcons")); + select_icons_warning->add_theme_color_override("font_color", get_theme_color("disabled_font_color", "Editor")); + + // Bottom panel buttons. + import_collapse_types_button->set_icon(get_theme_icon("CollapseTree", "EditorIcons")); + import_expand_types_button->set_icon(get_theme_icon("ExpandTree", "EditorIcons")); + + import_select_all_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons")); + import_select_full_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons")); + import_deselect_all_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons")); + + // Side panel buttons. + select_colors_icon->set_texture(get_theme_icon("Color", "EditorIcons")); + deselect_all_colors_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons")); + select_all_colors_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons")); + select_full_colors_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons")); + + select_constants_icon->set_texture(get_theme_icon("MemberConstant", "EditorIcons")); + deselect_all_constants_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons")); + select_all_constants_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons")); + select_full_constants_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons")); + + select_fonts_icon->set_texture(get_theme_icon("Font", "EditorIcons")); + deselect_all_fonts_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons")); + select_all_fonts_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons")); + select_full_fonts_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons")); + + select_font_sizes_icon->set_texture(get_theme_icon("FontSize", "EditorIcons")); + deselect_all_font_sizes_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons")); + select_all_font_sizes_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons")); + select_full_font_sizes_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons")); + + select_icons_icon->set_texture(get_theme_icon("ImageTexture", "EditorIcons")); + deselect_all_icons_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons")); + select_all_icons_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons")); + select_full_icons_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons")); + + select_styleboxes_icon->set_texture(get_theme_icon("StyleBoxFlat", "EditorIcons")); + deselect_all_styleboxes_button->set_icon(get_theme_icon("ThemeDeselectAll", "EditorIcons")); + select_all_styleboxes_button->set_icon(get_theme_icon("ThemeSelectAll", "EditorIcons")); + select_full_styleboxes_button->set_icon(get_theme_icon("ThemeSelectFull", "EditorIcons")); + } break; + } +} + +void ThemeItemImportTree::_bind_methods() { + ADD_SIGNAL(MethodInfo("items_imported")); +} + +ThemeItemImportTree::ThemeItemImportTree() { + HBoxContainer *import_items_filter_hb = memnew(HBoxContainer); + add_child(import_items_filter_hb); + Label *import_items_filter_label = memnew(Label); + import_items_filter_label->set_text(TTR("Filter:")); + import_items_filter_hb->add_child(import_items_filter_label); + import_items_filter = memnew(LineEdit); + import_items_filter->set_clear_button_enabled(true); + import_items_filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); + import_items_filter_hb->add_child(import_items_filter); + import_items_filter->connect("text_changed", callable_mp(this, &ThemeItemImportTree::_filter_text_changed)); + + HBoxContainer *import_main_hb = memnew(HBoxContainer); + import_main_hb->set_v_size_flags(Control::SIZE_EXPAND_FILL); + add_child(import_main_hb); + + import_items_tree = memnew(Tree); + import_items_tree->set_hide_root(true); + import_items_tree->set_h_size_flags(Control::SIZE_EXPAND_FILL); + import_main_hb->add_child(import_items_tree); + import_items_tree->connect("item_edited", callable_mp(this, &ThemeItemImportTree::_tree_item_edited)); + + import_items_tree->set_columns(3); + import_items_tree->set_column_titles_visible(true); + import_items_tree->set_column_title(IMPORT_ITEM, TTR("Import")); + import_items_tree->set_column_title(IMPORT_ITEM_DATA, TTR("With Data")); + import_items_tree->set_column_expand(0, true); + import_items_tree->set_column_clip_content(0, true); + import_items_tree->set_column_expand(IMPORT_ITEM, false); + import_items_tree->set_column_expand(IMPORT_ITEM_DATA, false); + import_items_tree->set_column_custom_minimum_width(0, 160 * EDSCALE); + import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM, 80 * EDSCALE); + import_items_tree->set_column_custom_minimum_width(IMPORT_ITEM_DATA, 80 * EDSCALE); + import_items_tree->set_column_clip_content(1, true); + import_items_tree->set_column_clip_content(2, true); + + ScrollContainer *import_bulk_sc = memnew(ScrollContainer); + import_bulk_sc->set_custom_minimum_size(Size2(260.0, 0.0) * EDSCALE); + import_bulk_sc->set_enable_h_scroll(false); + import_main_hb->add_child(import_bulk_sc); + VBoxContainer *import_bulk_vb = memnew(VBoxContainer); + import_bulk_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + import_bulk_sc->add_child(import_bulk_vb); + + Label *import_bulk_label = memnew(Label); + import_bulk_label->set_text(TTR("Select by data type:")); + import_bulk_vb->add_child(import_bulk_label); + + select_colors_icon = memnew(TextureRect); + select_colors_label = memnew(Label); + deselect_all_colors_button = memnew(Button); + select_all_colors_button = memnew(Button); + select_full_colors_button = memnew(Button); + total_selected_colors_label = memnew(Label); + + select_constants_icon = memnew(TextureRect); + select_constants_label = memnew(Label); + deselect_all_constants_button = memnew(Button); + select_all_constants_button = memnew(Button); + select_full_constants_button = memnew(Button); + total_selected_constants_label = memnew(Label); + + select_fonts_icon = memnew(TextureRect); + select_fonts_label = memnew(Label); + deselect_all_fonts_button = memnew(Button); + select_all_fonts_button = memnew(Button); + select_full_fonts_button = memnew(Button); + total_selected_fonts_label = memnew(Label); + + select_font_sizes_icon = memnew(TextureRect); + select_font_sizes_label = memnew(Label); + deselect_all_font_sizes_button = memnew(Button); + select_all_font_sizes_button = memnew(Button); + select_full_font_sizes_button = memnew(Button); + total_selected_font_sizes_label = memnew(Label); + + select_icons_icon = memnew(TextureRect); + select_icons_label = memnew(Label); + deselect_all_icons_button = memnew(Button); + select_all_icons_button = memnew(Button); + select_full_icons_button = memnew(Button); + total_selected_icons_label = memnew(Label); + + select_styleboxes_icon = memnew(TextureRect); + select_styleboxes_label = memnew(Label); + deselect_all_styleboxes_button = memnew(Button); + select_all_styleboxes_button = memnew(Button); + select_full_styleboxes_button = memnew(Button); + total_selected_styleboxes_label = memnew(Label); + + for (int i = 0; i < Theme::DATA_TYPE_MAX; i++) { + Theme::DataType dt = (Theme::DataType)i; + + TextureRect *select_items_icon; + Label *select_items_label; + Button *deselect_all_items_button; + Button *select_all_items_button; + Button *select_full_items_button; + Label *total_selected_items_label; + + String items_title = ""; + String select_all_items_tooltip = ""; + String select_full_items_tooltip = ""; + String deselect_all_items_tooltip = ""; + + switch (dt) { + case Theme::DATA_TYPE_COLOR: + select_items_icon = select_colors_icon; + select_items_label = select_colors_label; + deselect_all_items_button = deselect_all_colors_button; + select_all_items_button = select_all_colors_button; + select_full_items_button = select_full_colors_button; + total_selected_items_label = total_selected_colors_label; + + items_title = TTR("Colors"); + select_all_items_tooltip = TTR("Select all visible color items."); + select_full_items_tooltip = TTR("Select all visible color items and their data."); + deselect_all_items_tooltip = TTR("Deselect all visible color items."); + break; + + case Theme::DATA_TYPE_CONSTANT: + select_items_icon = select_constants_icon; + select_items_label = select_constants_label; + deselect_all_items_button = deselect_all_constants_button; + select_all_items_button = select_all_constants_button; + select_full_items_button = select_full_constants_button; + total_selected_items_label = total_selected_constants_label; + + items_title = TTR("Constants"); + select_all_items_tooltip = TTR("Select all visible constant items."); + select_full_items_tooltip = TTR("Select all visible constant items and their data."); + deselect_all_items_tooltip = TTR("Deselect all visible constant items."); + break; + + case Theme::DATA_TYPE_FONT: + select_items_icon = select_fonts_icon; + select_items_label = select_fonts_label; + deselect_all_items_button = deselect_all_fonts_button; + select_all_items_button = select_all_fonts_button; + select_full_items_button = select_full_fonts_button; + total_selected_items_label = total_selected_fonts_label; + + items_title = TTR("Fonts"); + select_all_items_tooltip = TTR("Select all visible font items."); + select_full_items_tooltip = TTR("Select all visible font items and their data."); + deselect_all_items_tooltip = TTR("Deselect all visible font items."); + break; + + case Theme::DATA_TYPE_FONT_SIZE: + select_items_icon = select_font_sizes_icon; + select_items_label = select_font_sizes_label; + deselect_all_items_button = deselect_all_font_sizes_button; + select_all_items_button = select_all_font_sizes_button; + select_full_items_button = select_full_font_sizes_button; + total_selected_items_label = total_selected_font_sizes_label; + + items_title = TTR("Font sizes"); + select_all_items_tooltip = TTR("Select all visible font size items."); + select_full_items_tooltip = TTR("Select all visible font size items and their data."); + deselect_all_items_tooltip = TTR("Deselect all visible font size items."); + break; + + case Theme::DATA_TYPE_ICON: + select_items_icon = select_icons_icon; + select_items_label = select_icons_label; + deselect_all_items_button = deselect_all_icons_button; + select_all_items_button = select_all_icons_button; + select_full_items_button = select_full_icons_button; + total_selected_items_label = total_selected_icons_label; + + items_title = TTR("Icons"); + select_all_items_tooltip = TTR("Select all visible icon items."); + select_full_items_tooltip = TTR("Select all visible icon items and their data."); + deselect_all_items_tooltip = TTR("Deselect all visible icon items."); + break; + + case Theme::DATA_TYPE_STYLEBOX: + select_items_icon = select_styleboxes_icon; + select_items_label = select_styleboxes_label; + deselect_all_items_button = deselect_all_styleboxes_button; + select_all_items_button = select_all_styleboxes_button; + select_full_items_button = select_full_styleboxes_button; + total_selected_items_label = total_selected_styleboxes_label; + + items_title = TTR("Styleboxes"); + select_all_items_tooltip = TTR("Select all visible stylebox items."); + select_full_items_tooltip = TTR("Select all visible stylebox items and their data."); + deselect_all_items_tooltip = TTR("Deselect all visible stylebox items."); + break; + + case Theme::DATA_TYPE_MAX: + continue; // Can't happen, but silences warning. + } + + if (i > 0) { + import_bulk_vb->add_child(memnew(HSeparator)); + } + + HBoxContainer *all_set = memnew(HBoxContainer); + import_bulk_vb->add_child(all_set); + + HBoxContainer *label_set = memnew(HBoxContainer); + label_set->set_h_size_flags(Control::SIZE_EXPAND_FILL); + all_set->add_child(label_set); + select_items_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + label_set->add_child(select_items_icon); + select_items_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + select_items_label->set_clip_text(true); + select_items_label->set_text(items_title); + label_set->add_child(select_items_label); + + HBoxContainer *button_set = memnew(HBoxContainer); + button_set->set_alignment(BoxContainer::ALIGN_END); + all_set->add_child(button_set); + select_all_items_button->set_flat(true); + select_all_items_button->set_tooltip(select_all_items_tooltip); + button_set->add_child(select_all_items_button); + select_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_data_type_pressed), varray(i)); + select_full_items_button->set_flat(true); + select_full_items_button->set_tooltip(select_full_items_tooltip); + button_set->add_child(select_full_items_button); + select_full_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_data_type_pressed), varray(i)); + deselect_all_items_button->set_flat(true); + deselect_all_items_button->set_tooltip(deselect_all_items_tooltip); + button_set->add_child(deselect_all_items_button); + deselect_all_items_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_data_type_pressed), varray(i)); + + total_selected_items_label->set_align(Label::ALIGN_RIGHT); + total_selected_items_label->hide(); + import_bulk_vb->add_child(total_selected_items_label); + + if (dt == Theme::DATA_TYPE_ICON) { + select_icons_warning_hb = memnew(HBoxContainer); + import_bulk_vb->add_child(select_icons_warning_hb); + + select_icons_warning_icon = memnew(TextureRect); + select_icons_warning_icon->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + select_icons_warning_hb->add_child(select_icons_warning_icon); + + select_icons_warning = memnew(Label); + select_icons_warning->set_text(TTR("Caution: Adding icon data may considerably increase the size of your Theme resource.")); + select_icons_warning->set_autowrap(true); + select_icons_warning->set_h_size_flags(Control::SIZE_EXPAND_FILL); + select_icons_warning_hb->add_child(select_icons_warning); + } + } + + add_child(memnew(HSeparator)); + + HBoxContainer *import_buttons = memnew(HBoxContainer); + add_child(import_buttons); + + import_collapse_types_button = memnew(Button); + import_collapse_types_button->set_flat(true); + import_collapse_types_button->set_tooltip(TTR("Collapse types.")); + import_buttons->add_child(import_collapse_types_button); + import_collapse_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(true)); + import_expand_types_button = memnew(Button); + import_expand_types_button->set_flat(true); + import_expand_types_button->set_tooltip(TTR("Expand types.")); + import_buttons->add_child(import_expand_types_button); + import_expand_types_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_toggle_type_items), varray(false)); + + import_buttons->add_child(memnew(VSeparator)); + + import_select_all_button = memnew(Button); + import_select_all_button->set_flat(true); + import_select_all_button->set_text(TTR("Select All")); + import_select_all_button->set_tooltip(TTR("Select all Theme items.")); + import_buttons->add_child(import_select_all_button); + import_select_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_all_items_pressed)); + import_select_full_button = memnew(Button); + import_select_full_button->set_flat(true); + import_select_full_button->set_text(TTR("Select With Data")); + import_select_full_button->set_tooltip(TTR("Select all Theme items with item data.")); + import_buttons->add_child(import_select_full_button); + import_select_full_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_select_full_items_pressed)); + import_deselect_all_button = memnew(Button); + import_deselect_all_button->set_flat(true); + import_deselect_all_button->set_text(TTR("Deselect All")); + import_deselect_all_button->set_tooltip(TTR("Deselect all Theme items.")); + import_buttons->add_child(import_deselect_all_button); + import_deselect_all_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_deselect_all_items_pressed)); + + import_buttons->add_spacer(); + + Button *import_add_selected_button = memnew(Button); + import_add_selected_button->set_text(TTR("Import Selected")); + import_buttons->add_child(import_add_selected_button); + import_add_selected_button->connect("pressed", callable_mp(this, &ThemeItemImportTree::_import_selected)); +} + +void ThemeItemEditorDialog::ok_pressed() { + if (import_default_theme_items->has_selected_items() || import_editor_theme_items->has_selected_items() || import_other_theme_items->has_selected_items()) { + confirm_closing_dialog->set_text(TTR("Import Items tab has some items selected. Selection will be lost upon closing this window.\nClose anyway?")); + confirm_closing_dialog->popup_centered(Size2i(380, 120) * EDSCALE); + return; + } + + hide(); +} + +void ThemeItemEditorDialog::_close_dialog() { + hide(); +} void ThemeItemEditorDialog::_dialog_about_to_show() { - ERR_FAIL_COND(edited_theme.is_null()); + ERR_FAIL_COND_MSG(edited_theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing."); _update_edit_types(); + + import_default_theme_items->set_edited_theme(edited_theme); + import_default_theme_items->set_base_theme(Theme::get_default()); + import_default_theme_items->reset_item_tree(); + + import_editor_theme_items->set_edited_theme(edited_theme); + import_editor_theme_items->set_base_theme(EditorNode::get_singleton()->get_theme_base()->get_theme()); + import_editor_theme_items->reset_item_tree(); + + import_other_theme_items->set_edited_theme(edited_theme); + import_other_theme_items->reset_item_tree(); } void ThemeItemEditorDialog::_update_edit_types() { @@ -79,11 +1263,6 @@ void ThemeItemEditorDialog::_update_edit_types() { base_theme->get_type_list(&default_types); default_types.sort_custom<StringName::AlphCompare>(); - edit_add_class_options->clear(); - for (List<StringName>::Element *E = default_types.front(); E; E = E->next()) { - edit_add_class_options->add_item(E->get()); - } - String selected_type = ""; Vector<int> selected_ids = edit_type_list->get_selected_items(); if (selected_ids.size() > 0) { @@ -127,7 +1306,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { List<StringName> names; - { + { // Colors. names.clear(); edited_theme->get_color_list(p_item_type, &names); @@ -148,7 +1327,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } } - { + { // Constants. names.clear(); edited_theme->get_constant_list(p_item_type, &names); @@ -169,7 +1348,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } } - { + { // Fonts. names.clear(); edited_theme->get_font_list(p_item_type, &names); @@ -190,7 +1369,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } } - { + { // Font sizes. names.clear(); edited_theme->get_font_size_list(p_item_type, &names); @@ -211,7 +1390,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } } - { + { // Icons. names.clear(); edited_theme->get_icon_list(p_item_type, &names); @@ -232,7 +1411,7 @@ void ThemeItemEditorDialog::_update_edit_item_tree(String p_item_type) { } } - { + { // Styleboxes. names.clear(); edited_theme->get_stylebox_list(p_item_type, &names); @@ -280,65 +1459,17 @@ void ThemeItemEditorDialog::_item_tree_button_pressed(Object *p_item, int p_colu _update_edit_item_tree(edited_item_type); } -void ThemeItemEditorDialog::_add_class_type_items() { - int selected_idx = edit_add_class_options->get_selected(); - String type_name = edit_add_class_options->get_item_text(selected_idx); - List<StringName> names; - - { - names.clear(); - Theme::get_default()->get_icon_list(type_name, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - edited_theme->set_icon(E->get(), type_name, Ref<Texture2D>()); - } - } - { - names.clear(); - Theme::get_default()->get_stylebox_list(type_name, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - edited_theme->set_stylebox(E->get(), type_name, Ref<StyleBox>()); - } - } - { - names.clear(); - Theme::get_default()->get_font_list(type_name, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - edited_theme->set_font(E->get(), type_name, Ref<Font>()); - } - } - { - names.clear(); - Theme::get_default()->get_font_size_list(type_name, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - edited_theme->set_font_size(E->get(), type_name, Theme::get_default()->get_font_size(E->get(), type_name)); - } - } - { - names.clear(); - Theme::get_default()->get_color_list(type_name, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - edited_theme->set_color(E->get(), type_name, Theme::get_default()->get_color(E->get(), type_name)); - } - } - { - names.clear(); - Theme::get_default()->get_constant_list(type_name, &names); - for (List<StringName>::Element *E = names.front(); E; E = E->next()) { - edited_theme->set_constant(E->get(), type_name, Theme::get_default()->get_constant(E->get(), type_name)); - } - } - +void ThemeItemEditorDialog::_add_theme_type() { + edited_theme->add_icon_type(edit_add_type_value->get_text()); + edited_theme->add_stylebox_type(edit_add_type_value->get_text()); + edited_theme->add_font_type(edit_add_type_value->get_text()); + edited_theme->add_font_size_type(edit_add_type_value->get_text()); + edited_theme->add_color_type(edit_add_type_value->get_text()); + edited_theme->add_constant_type(edit_add_type_value->get_text()); _update_edit_types(); -} -void ThemeItemEditorDialog::_add_custom_type() { - edited_theme->add_icon_type(edit_add_custom_value->get_text()); - edited_theme->add_stylebox_type(edit_add_custom_value->get_text()); - edited_theme->add_font_type(edit_add_custom_value->get_text()); - edited_theme->add_font_size_type(edit_add_custom_value->get_text()); - edited_theme->add_color_type(edit_add_custom_value->get_text()); - edited_theme->add_constant_type(edit_add_custom_value->get_text()); - _update_edit_types(); + // Force emit a change so that other parts of the editor can update. + edited_theme->emit_changed(); } void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type) { @@ -369,15 +1500,24 @@ void ThemeItemEditorDialog::_add_theme_item(Theme::DataType p_data_type, String void ThemeItemEditorDialog::_remove_data_type_items(Theme::DataType p_data_type, String p_item_type) { List<StringName> names; + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); + edited_theme->get_theme_item_list(p_data_type, p_item_type, &names); for (List<StringName>::Element *E = names.front(); E; E = E->next()) { edited_theme->clear_theme_item(p_data_type, E->get(), p_item_type); } + + // Allow changes to be reported now that the operation is finished. + edited_theme->_unfreeze_and_propagate_changes(); } void ThemeItemEditorDialog::_remove_class_items() { List<StringName> names; + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); + for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { Theme::DataType data_type = (Theme::DataType)dt; @@ -390,12 +1530,18 @@ void ThemeItemEditorDialog::_remove_class_items() { } } + // Allow changes to be reported now that the operation is finished. + edited_theme->_unfreeze_and_propagate_changes(); + _update_edit_item_tree(edited_item_type); } void ThemeItemEditorDialog::_remove_custom_items() { List<StringName> names; + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); + for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { Theme::DataType data_type = (Theme::DataType)dt; @@ -408,12 +1554,18 @@ void ThemeItemEditorDialog::_remove_custom_items() { } } + // Allow changes to be reported now that the operation is finished. + edited_theme->_unfreeze_and_propagate_changes(); + _update_edit_item_tree(edited_item_type); } void ThemeItemEditorDialog::_remove_all_items() { List<StringName> names; + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); + for (int dt = 0; dt < Theme::DATA_TYPE_MAX; dt++) { Theme::DataType data_type = (Theme::DataType)dt; @@ -424,6 +1576,9 @@ void ThemeItemEditorDialog::_remove_all_items() { } } + // Allow changes to be reported now that the operation is finished. + edited_theme->_unfreeze_and_propagate_changes(); + _update_edit_item_tree(edited_item_type); } @@ -536,6 +1691,26 @@ void ThemeItemEditorDialog::_edit_theme_item_gui_input(const Ref<InputEvent> &p_ } } +void ThemeItemEditorDialog::_open_select_another_theme() { + import_another_theme_dialog->popup_file_dialog(); +} + +void ThemeItemEditorDialog::_select_another_theme_cbk(const String &p_path) { + Ref<Theme> loaded_theme = ResourceLoader::load(p_path); + if (loaded_theme.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("Invalid file, not a Theme resource.")); + return; + } + if (loaded_theme == edited_theme) { + EditorNode::get_singleton()->show_warning(TTR("Invalid file, same as the edited Theme resource.")); + return; + } + + import_another_theme_value->set_text(p_path); + import_other_theme_items->set_base_theme(loaded_theme); + import_other_theme_items->reset_item_tree(); +} + void ThemeItemEditorDialog::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: { @@ -553,6 +1728,11 @@ void ThemeItemEditorDialog::_notification(int p_what) { edit_items_remove_class->set_icon(get_theme_icon("Control", "EditorIcons")); edit_items_remove_custom->set_icon(get_theme_icon("ThemeRemoveCustomItems", "EditorIcons")); edit_items_remove_all->set_icon(get_theme_icon("ThemeRemoveAllItems", "EditorIcons")); + + import_another_theme_button->set_icon(get_theme_icon("Folder", "EditorIcons")); + + tc->add_theme_style_override("tab_selected", get_theme_stylebox("tab_selected_odd", "TabContainer")); + tc->add_theme_style_override("panel", get_theme_stylebox("panel_odd", "TabContainer")); } break; } } @@ -562,10 +1742,18 @@ void ThemeItemEditorDialog::set_edited_theme(const Ref<Theme> &p_theme) { } ThemeItemEditorDialog::ThemeItemEditorDialog() { - set_title(TTR("Edit Theme Items")); + set_title(TTR("Manage Theme Items")); + get_ok_button()->set_text(TTR("Close")); + set_hide_on_ok(false); // Closing may require a confirmation in some cases. + + tc = memnew(TabContainer); + tc->set_tab_align(TabContainer::TabAlign::ALIGN_LEFT); + add_child(tc); + // Edit Items tab. HSplitContainer *edit_dialog_hs = memnew(HSplitContainer); - add_child(edit_dialog_hs); + tc->add_child(edit_dialog_hs); + tc->set_tab_title(0, TTR("Edit Items")); VBoxContainer *edit_dialog_side_vb = memnew(VBoxContainer); edit_dialog_side_vb->set_custom_minimum_size(Size2(200.0, 0.0) * EDSCALE); @@ -580,33 +1768,19 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { edit_dialog_side_vb->add_child(edit_type_list); edit_type_list->connect("item_selected", callable_mp(this, &ThemeItemEditorDialog::_edited_type_selected)); - Label *edit_add_class_label = memnew(Label); - edit_add_class_label->set_text(TTR("Add Type from Class:")); - edit_dialog_side_vb->add_child(edit_add_class_label); - - HBoxContainer *edit_add_class = memnew(HBoxContainer); - edit_dialog_side_vb->add_child(edit_add_class); - edit_add_class_options = memnew(OptionButton); - edit_add_class_options->set_h_size_flags(Control::SIZE_EXPAND_FILL); - edit_add_class->add_child(edit_add_class_options); - Button *edit_add_class_button = memnew(Button); - edit_add_class_button->set_text(TTR("Add")); - edit_add_class->add_child(edit_add_class_button); - edit_add_class_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_class_type_items)); - - Label *edit_add_custom_label = memnew(Label); - edit_add_custom_label->set_text(TTR("Add Custom Type:")); - edit_dialog_side_vb->add_child(edit_add_custom_label); - - HBoxContainer *edit_add_custom = memnew(HBoxContainer); - edit_dialog_side_vb->add_child(edit_add_custom); - edit_add_custom_value = memnew(LineEdit); - edit_add_custom_value->set_h_size_flags(Control::SIZE_EXPAND_FILL); - edit_add_custom->add_child(edit_add_custom_value); - Button *edit_add_custom_button = memnew(Button); - edit_add_custom_button->set_text(TTR("Add")); - edit_add_custom->add_child(edit_add_custom_button); - edit_add_custom_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_custom_type)); + Label *edit_add_type_label = memnew(Label); + edit_add_type_label->set_text(TTR("Add Type:")); + edit_dialog_side_vb->add_child(edit_add_type_label); + + HBoxContainer *edit_add_type_hb = memnew(HBoxContainer); + edit_dialog_side_vb->add_child(edit_add_type_hb); + edit_add_type_value = memnew(LineEdit); + edit_add_type_value->set_h_size_flags(Control::SIZE_EXPAND_FILL); + edit_add_type_hb->add_child(edit_add_type_value); + Button *edit_add_type_button = memnew(Button); + edit_add_type_button->set_text(TTR("Add")); + edit_add_type_hb->add_child(edit_add_type_button); + edit_add_type_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_add_theme_type)); VBoxContainer *edit_items_vb = memnew(VBoxContainer); edit_items_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -616,7 +1790,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { edit_items_vb->add_child(edit_items_toolbar); Label *edit_items_toolbar_add_label = memnew(Label); - edit_items_toolbar_add_label->set_text(TTR("Add:")); + edit_items_toolbar_add_label->set_text(TTR("Add Item:")); edit_items_toolbar->add_child(edit_items_toolbar_add_label); edit_items_add_color = memnew(Button); @@ -664,7 +1838,7 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { edit_items_toolbar->add_child(memnew(VSeparator)); Label *edit_items_toolbar_remove_label = memnew(Label); - edit_items_toolbar_remove_label->set_text(TTR("Remove:")); + edit_items_toolbar_remove_label->set_text(TTR("Remove Items:")); edit_items_toolbar->add_child(edit_items_toolbar_remove_label); edit_items_remove_class = memnew(Button); @@ -716,576 +1890,1376 @@ ThemeItemEditorDialog::ThemeItemEditorDialog() { edit_theme_item_vb->add_child(theme_item_name); theme_item_name->connect("gui_input", callable_mp(this, &ThemeItemEditorDialog::_edit_theme_item_gui_input)); edit_theme_item_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_confirm_edit_theme_item)); -} -void ThemeEditor::edit(const Ref<Theme> &p_theme) { - theme = p_theme; - theme_edit_dialog->set_edited_theme(p_theme); - main_panel->set_theme(p_theme); - main_container->set_theme(p_theme); -} - -void ThemeEditor::_propagate_redraw(Control *p_at) { - p_at->notification(NOTIFICATION_THEME_CHANGED); - p_at->minimum_size_changed(); - p_at->update(); - for (int i = 0; i < p_at->get_child_count(); i++) { - Control *a = Object::cast_to<Control>(p_at->get_child(i)); - if (a) { - _propagate_redraw(a); - } + // Import Items tab. + TabContainer *import_tc = memnew(TabContainer); + tc->add_child(import_tc); + tc->set_tab_title(1, TTR("Import Items")); + + import_default_theme_items = memnew(ThemeItemImportTree); + import_tc->add_child(import_default_theme_items); + import_tc->set_tab_title(0, TTR("Default Theme")); + import_default_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types)); + + import_editor_theme_items = memnew(ThemeItemImportTree); + import_tc->add_child(import_editor_theme_items); + import_tc->set_tab_title(1, TTR("Editor Theme")); + import_editor_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types)); + + VBoxContainer *import_another_theme_vb = memnew(VBoxContainer); + + HBoxContainer *import_another_file_hb = memnew(HBoxContainer); + import_another_theme_vb->add_child(import_another_file_hb); + import_another_theme_value = memnew(LineEdit); + import_another_theme_value->set_h_size_flags(Control::SIZE_EXPAND_FILL); + import_another_theme_value->set_editable(false); + import_another_file_hb->add_child(import_another_theme_value); + import_another_theme_button = memnew(Button); + import_another_file_hb->add_child(import_another_theme_button); + import_another_theme_button->connect("pressed", callable_mp(this, &ThemeItemEditorDialog::_open_select_another_theme)); + + import_another_theme_dialog = memnew(EditorFileDialog); + import_another_theme_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + import_another_theme_dialog->set_title(TTR("Select Another Theme Resource:")); + List<String> ext; + ResourceLoader::get_recognized_extensions_for_type("Theme", &ext); + for (List<String>::Element *E = ext.front(); E; E = E->next()) { + import_another_theme_dialog->add_filter("*." + E->get() + "; Theme Resource"); } + import_another_file_hb->add_child(import_another_theme_dialog); + import_another_theme_dialog->connect("file_selected", callable_mp(this, &ThemeItemEditorDialog::_select_another_theme_cbk)); + + import_other_theme_items = memnew(ThemeItemImportTree); + import_other_theme_items->set_v_size_flags(Control::SIZE_EXPAND_FILL); + import_another_theme_vb->add_child(import_other_theme_items); + + import_tc->add_child(import_another_theme_vb); + import_tc->set_tab_title(2, TTR("Another Theme")); + import_other_theme_items->connect("items_imported", callable_mp(this, &ThemeItemEditorDialog::_update_edit_types)); + + confirm_closing_dialog = memnew(ConfirmationDialog); + confirm_closing_dialog->set_autowrap(true); + add_child(confirm_closing_dialog); + confirm_closing_dialog->connect("confirmed", callable_mp(this, &ThemeItemEditorDialog::_close_dialog)); } -void ThemeEditor::_refresh_interval() { - _propagate_redraw(main_panel); - _propagate_redraw(main_container); +VBoxContainer *ThemeTypeEditor::_create_item_list(Theme::DataType p_data_type) { + VBoxContainer *items_tab = memnew(VBoxContainer); + items_tab->set_custom_minimum_size(Size2(0, 160) * EDSCALE); + data_type_tabs->add_child(items_tab); + data_type_tabs->set_tab_title(data_type_tabs->get_tab_count() - 1, ""); + + ScrollContainer *items_sc = memnew(ScrollContainer); + items_sc->set_v_size_flags(SIZE_EXPAND_FILL); + items_sc->set_enable_h_scroll(false); + items_tab->add_child(items_sc); + VBoxContainer *items_list = memnew(VBoxContainer); + items_list->set_h_size_flags(SIZE_EXPAND_FILL); + items_sc->add_child(items_list); + + HBoxContainer *item_add_hb = memnew(HBoxContainer); + items_tab->add_child(item_add_hb); + LineEdit *item_add_edit = memnew(LineEdit); + item_add_edit->set_h_size_flags(SIZE_EXPAND_FILL); + item_add_hb->add_child(item_add_edit); + item_add_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_add_lineedit_cbk), varray(p_data_type, item_add_edit)); + Button *item_add_button = memnew(Button); + item_add_button->set_text(TTR("Add")); + item_add_hb->add_child(item_add_button); + item_add_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_add_cbk), varray(p_data_type, item_add_edit)); + + return items_list; } -struct _TECategory { - template <class T> - struct RefItem { - Ref<T> item; - StringName name; - bool operator<(const RefItem<T> &p) const { return item->get_instance_id() < p.item->get_instance_id(); } - }; +void ThemeTypeEditor::_update_type_list() { + ERR_FAIL_COND(edited_theme.is_null()); - template <class T> - struct Item { - T item; - String name; - bool operator<(const Item<T> &p) const { return name < p.name; } - }; - - Set<RefItem<StyleBox>> stylebox_items; - Set<RefItem<Font>> font_items; - Set<Item<int>> font_size_items; - Set<RefItem<Texture2D>> icon_items; - - Set<Item<Color>> color_items; - Set<Item<int>> constant_items; -}; - -void ThemeEditor::_save_template_cbk(String fname) { - String filename = file_dialog->get_current_path(); - - Map<String, _TECategory> categories; - - // Fill types. - List<StringName> type_list; - Theme::get_default()->get_type_list(&type_list); - for (List<StringName>::Element *E = type_list.front(); E; E = E->next()) { - categories.insert(E->get(), _TECategory()); - } - - // Fill default theme. - for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) { - _TECategory &tc = E->get(); - - List<StringName> stylebox_list; - Theme::get_default()->get_stylebox_list(E->key(), &stylebox_list); - for (List<StringName>::Element *F = stylebox_list.front(); F; F = F->next()) { - _TECategory::RefItem<StyleBox> it; - it.name = F->get(); - it.item = Theme::get_default()->get_stylebox(F->get(), E->key()); - tc.stylebox_items.insert(it); - } - - List<StringName> font_list; - Theme::get_default()->get_font_list(E->key(), &font_list); - for (List<StringName>::Element *F = font_list.front(); F; F = F->next()) { - _TECategory::RefItem<Font> it; - it.name = F->get(); - it.item = Theme::get_default()->get_font(F->get(), E->key()); - tc.font_items.insert(it); - } - - List<StringName> font_size_list; - Theme::get_default()->get_font_size_list(E->key(), &font_list); - for (List<StringName>::Element *F = font_size_list.front(); F; F = F->next()) { - _TECategory::Item<int> it; - it.name = F->get(); - it.item = Theme::get_default()->get_font_size(F->get(), E->key()); - tc.font_size_items.insert(it); - } - - List<StringName> icon_list; - Theme::get_default()->get_icon_list(E->key(), &icon_list); - for (List<StringName>::Element *F = icon_list.front(); F; F = F->next()) { - _TECategory::RefItem<Texture2D> it; - it.name = F->get(); - it.item = Theme::get_default()->get_icon(F->get(), E->key()); - tc.icon_items.insert(it); - } - - List<StringName> color_list; - Theme::get_default()->get_color_list(E->key(), &color_list); - for (List<StringName>::Element *F = color_list.front(); F; F = F->next()) { - _TECategory::Item<Color> it; - it.name = F->get(); - it.item = Theme::get_default()->get_color(F->get(), E->key()); - tc.color_items.insert(it); - } - - List<StringName> constant_list; - Theme::get_default()->get_constant_list(E->key(), &constant_list); - for (List<StringName>::Element *F = constant_list.front(); F; F = F->next()) { - _TECategory::Item<int> it; - it.name = F->get(); - it.item = Theme::get_default()->get_constant(F->get(), E->key()); - tc.constant_items.insert(it); - } - } - - FileAccess *file = FileAccess::open(filename, FileAccess::WRITE); - - ERR_FAIL_COND_MSG(!file, "Can't save theme to file '" + filename + "'."); - - file->store_line("; ******************* "); - file->store_line("; Template Theme File "); - file->store_line("; ******************* "); - file->store_line("; "); - file->store_line("; Theme Syntax: "); - file->store_line("; ------------- "); - file->store_line("; "); - file->store_line("; Must be placed in section [theme]"); - file->store_line("; "); - file->store_line("; Type.item = [value] "); - file->store_line("; "); - file->store_line("; [value] examples:"); - file->store_line("; "); - file->store_line("; Type.item = 6 ; numeric constant. "); - file->store_line("; Type.item = #FF00FF ; HTML color (magenta)."); - file->store_line("; Type.item = #FF00FF55 ; HTML color (magenta with alpha 0x55)."); - file->store_line("; Type.item = icon(image.png) ; icon in a png file (relative to theme file)."); - file->store_line("; Type.item = font(font.xres) ; font in a resource (relative to theme file)."); - file->store_line("; Type.item = sbox(stylebox.xres) ; stylebox in a resource (relative to theme file)."); - file->store_line("; Type.item = sboxf(2,#FF00FF) ; flat stylebox with margin 2."); - file->store_line("; Type.item = sboxf(2,#FF00FF,#FFFFFF) ; flat stylebox with margin 2 and border."); - file->store_line("; Type.item = sboxf(2,#FF00FF,#FFFFFF,#000000) ; flat stylebox with margin 2, light & dark borders."); - file->store_line("; Type.item = sboxt(base.png,2,2,2,2) ; textured stylebox with 3x3 stretch and stretch margins."); - file->store_line("; -Additionally, 4 extra integers can be added to sboxf and sboxt to specify custom padding of contents:"); - file->store_line("; Type.item = sboxt(base.png,2,2,2,2,5,4,2,4) ;"); - file->store_line("; -Order for all is always left, top, right, bottom."); - file->store_line("; "); - file->store_line("; Special values:"); - file->store_line("; Type.item = default ; use the value in the default theme (must exist there)."); - file->store_line("; Type.item = @somebutton_color ; reference to a library value previously defined."); - file->store_line("; "); - file->store_line("; Library Syntax: "); - file->store_line("; --------------- "); - file->store_line("; "); - file->store_line("; Must be placed in section [library], but usage is optional."); - file->store_line("; "); - file->store_line("; item = [value] ; same as Theme, but assign to library."); - file->store_line("; "); - file->store_line("; examples:"); - file->store_line("; "); - file->store_line("; [library]"); - file->store_line("; "); - file->store_line("; default_button_color = #FF00FF"); - file->store_line("; "); - file->store_line("; [theme]"); - file->store_line("; "); - file->store_line("; Button.color = @default_button_color ; used reference."); - file->store_line("; "); - file->store_line("; ******************* "); - file->store_line("; "); - file->store_line("; Template Generated Using: " + String(VERSION_FULL_BUILD)); - file->store_line("; "); - file->store_line("; "); - file->store_line(""); - file->store_line("[library]"); - file->store_line(""); - file->store_line("; place library stuff here"); - file->store_line(""); - file->store_line("[theme]"); - file->store_line(""); - file->store_line(""); + if (updating) { + return; + } + updating = true; + + Control *focused = get_focus_owner(); + if (focused) { + if (focusables.has(focused)) { + // If focus is currently on one of the internal property editors, don't update. + updating = false; + return; + } - // Write default theme. - for (Map<String, _TECategory>::Element *E = categories.front(); E; E = E->next()) { - _TECategory &tc = E->get(); + Node *focus_parent = focused->get_parent(); + while (focus_parent) { + Control *c = Object::cast_to<Control>(focus_parent); + if (c && focusables.has(c)) { + // If focus is currently on one of the internal property editors, don't update. + updating = false; + return; + } - String underline = "; "; - for (int i = 0; i < E->key().length(); i++) { - underline += "*"; + focus_parent = focus_parent->get_parent(); } + } + + List<StringName> theme_types; + edited_theme->get_type_list(&theme_types); + theme_types.sort_custom<StringName::AlphCompare>(); + + theme_type_list->clear(); - file->store_line(""); - file->store_line(underline); - file->store_line("; " + E->key()); - file->store_line(underline); + if (theme_types.size() > 0) { + theme_type_list->set_disabled(false); - if (tc.stylebox_items.size()) { - file->store_line("\n; StyleBox Items:\n"); + bool item_reselected = false; + int e_idx = 0; + for (List<StringName>::Element *E = theme_types.front(); E; E = E->next()) { + Ref<Texture2D> item_icon; + if (E->get() == "") { + item_icon = get_theme_icon("NodeDisabled", "EditorIcons"); + } else { + item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled"); + } + theme_type_list->add_icon_item(item_icon, E->get()); + + if (E->get() == edited_type) { + theme_type_list->select(e_idx); + item_reselected = true; + } + e_idx++; } - for (Set<_TECategory::RefItem<StyleBox>>::Element *F = tc.stylebox_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + if (!item_reselected) { + theme_type_list->select(0); + _list_type_selected(0); + } else { + _update_type_items(); } + } else { + theme_type_list->set_disabled(true); + theme_type_list->add_item(TTR("None")); + + edited_type = ""; + _update_type_items(); + } + + updating = false; +} + +void ThemeTypeEditor::_update_type_list_debounced() { + update_debounce_timer->start(); +} + +void ThemeTypeEditor::_update_add_type_options(const String &p_filter) { + add_type_options->clear(); - if (tc.font_items.size()) { - file->store_line("\n; Font Items:\n"); + List<StringName> names; + Theme::get_default()->get_type_list(&names); + names.sort_custom<StringName::AlphCompare>(); + + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!p_filter.is_subsequence_ofi(String(E->get()))) { + continue; } - for (Set<_TECategory::RefItem<Font>>::Element *F = tc.font_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + Ref<Texture2D> item_icon; + if (E->get() == "") { + item_icon = get_theme_icon("NodeDisabled", "EditorIcons"); + } else { + item_icon = EditorNode::get_singleton()->get_class_icon(E->get(), "NodeDisabled"); } - if (tc.font_size_items.size()) { - file->store_line("\n; Font Size Items:\n"); + add_type_options->add_item(E->get(), item_icon); + } +} + +OrderedHashMap<StringName, bool> ThemeTypeEditor::_get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default) { + OrderedHashMap<StringName, bool> items; + List<StringName> names; + + if (include_default) { + names.clear(); + (Theme::get_default().operator->()->*get_list_func)(p_type_name, &names); + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + items[E->get()] = false; } + } - for (Set<_TECategory::Item<int>>::Element *F = tc.font_size_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + { + names.clear(); + (edited_theme.operator->()->*get_list_func)(p_type_name, &names); + names.sort_custom<StringName::AlphCompare>(); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + items[E->get()] = true; } + } + + List<StringName> keys; + for (OrderedHashMap<StringName, bool>::Element E = items.front(); E; E = E.next()) { + keys.push_back(E.key()); + } + keys.sort_custom<StringName::AlphCompare>(); + + OrderedHashMap<StringName, bool> ordered_items; + for (List<StringName>::Element *E = keys.front(); E; E = E->next()) { + ordered_items[E->get()] = items[E->get()]; + } - if (tc.icon_items.size()) { - file->store_line("\n; Icon Items:\n"); + return ordered_items; +} + +HBoxContainer *ThemeTypeEditor::_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable) { + HBoxContainer *item_control = memnew(HBoxContainer); + + HBoxContainer *item_name_container = memnew(HBoxContainer); + item_name_container->set_h_size_flags(SIZE_EXPAND_FILL); + item_name_container->set_stretch_ratio(2.0); + item_control->add_child(item_name_container); + + Label *item_name = memnew(Label); + item_name->set_h_size_flags(SIZE_EXPAND_FILL); + item_name->set_clip_text(true); + item_name->set_text(p_item_name); + item_name->set_tooltip(p_item_name); + item_name_container->add_child(item_name); + + if (p_editable) { + LineEdit *item_name_edit = memnew(LineEdit); + item_name_edit->set_h_size_flags(SIZE_EXPAND_FILL); + item_name_edit->set_text(p_item_name); + item_name_container->add_child(item_name_edit); + item_name_edit->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_item_rename_entered), varray(p_data_type, p_item_name, item_name_container)); + item_name_edit->hide(); + + Button *item_rename_button = memnew(Button); + item_rename_button->set_icon(get_theme_icon("Edit", "EditorIcons")); + item_rename_button->set_tooltip(TTR("Rename Item")); + item_rename_button->set_flat(true); + item_name_container->add_child(item_rename_button); + item_rename_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_cbk), varray(p_data_type, p_item_name, item_name_container)); + + Button *item_remove_button = memnew(Button); + item_remove_button->set_icon(get_theme_icon("Remove", "EditorIcons")); + item_remove_button->set_tooltip(TTR("Remove Item")); + item_remove_button->set_flat(true); + item_name_container->add_child(item_remove_button); + item_remove_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_remove_cbk), varray(p_data_type, p_item_name)); + + Button *item_rename_confirm_button = memnew(Button); + item_rename_confirm_button->set_icon(get_theme_icon("ImportCheck", "EditorIcons")); + item_rename_confirm_button->set_tooltip(TTR("Confirm Item Rename")); + item_rename_confirm_button->set_flat(true); + item_name_container->add_child(item_rename_confirm_button); + item_rename_confirm_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_confirmed), varray(p_data_type, p_item_name, item_name_container)); + item_rename_confirm_button->hide(); + + Button *item_rename_cancel_button = memnew(Button); + item_rename_cancel_button->set_icon(get_theme_icon("ImportFail", "EditorIcons")); + item_rename_cancel_button->set_tooltip(TTR("Cancel Item Rename")); + item_rename_cancel_button->set_flat(true); + item_name_container->add_child(item_rename_cancel_button); + item_rename_cancel_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_rename_canceled), varray(p_data_type, p_item_name, item_name_container)); + item_rename_cancel_button->hide(); + } else { + item_name->add_theme_color_override("font_color", get_theme_color("disabled_font_color", "Editor")); + + Button *item_override_button = memnew(Button); + item_override_button->set_icon(get_theme_icon("Add", "EditorIcons")); + item_override_button->set_tooltip(TTR("Override Item")); + item_override_button->set_flat(true); + item_name_container->add_child(item_override_button); + item_override_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_item_override_cbk), varray(p_data_type, p_item_name)); + } + + return item_control; +} + +void ThemeTypeEditor::_add_focusable(Control *p_control) { + focusables.append(p_control); +} + +void ThemeTypeEditor::_update_type_items() { + bool show_default = show_default_items_button->is_pressed(); + List<StringName> names; + + focusables.clear(); + + // Colors. + { + for (int i = color_items_list->get_child_count() - 1; i >= 0; i--) { + Node *node = color_items_list->get_child(i); + node->queue_delete(); + color_items_list->remove_child(node); } - for (Set<_TECategory::RefItem<Texture2D>>::Element *F = tc.icon_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + OrderedHashMap<StringName, bool> color_items = _get_type_items(edited_type, &Theme::get_color_list, show_default); + for (OrderedHashMap<StringName, bool>::Element E = color_items.front(); E; E = E.next()) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_COLOR, E.key(), E.get()); + ColorPickerButton *item_editor = memnew(ColorPickerButton); + item_editor->set_h_size_flags(SIZE_EXPAND_FILL); + item_control->add_child(item_editor); + + if (E.get()) { + item_editor->set_pick_color(edited_theme->get_color(E.key(), edited_type)); + item_editor->connect("color_changed", callable_mp(this, &ThemeTypeEditor::_color_item_changed), varray(E.key())); + } else { + item_editor->set_pick_color(Theme::get_default()->get_color(E.key(), edited_type)); + item_editor->set_disabled(true); + } + + _add_focusable(item_editor); + color_items_list->add_child(item_control); } + } - if (tc.color_items.size()) { - file->store_line("\n; Color Items:\n"); + // Constants. + { + for (int i = constant_items_list->get_child_count() - 1; i >= 0; i--) { + Node *node = constant_items_list->get_child(i); + node->queue_delete(); + constant_items_list->remove_child(node); } - for (Set<_TECategory::Item<Color>>::Element *F = tc.color_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + OrderedHashMap<StringName, bool> constant_items = _get_type_items(edited_type, &Theme::get_constant_list, show_default); + for (OrderedHashMap<StringName, bool>::Element E = constant_items.front(); E; E = E.next()) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_CONSTANT, E.key(), E.get()); + SpinBox *item_editor = memnew(SpinBox); + item_editor->set_h_size_flags(SIZE_EXPAND_FILL); + item_editor->set_min(-100000); + item_editor->set_max(100000); + item_editor->set_step(1); + item_editor->set_allow_lesser(true); + item_editor->set_allow_greater(true); + item_control->add_child(item_editor); + + if (E.get()) { + item_editor->set_value(edited_theme->get_constant(E.key(), edited_type)); + item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_constant_item_changed), varray(E.key())); + } else { + item_editor->set_value(Theme::get_default()->get_constant(E.key(), edited_type)); + item_editor->set_editable(false); + } + + _add_focusable(item_editor); + constant_items_list->add_child(item_control); } + } - if (tc.constant_items.size()) { - file->store_line("\n; Constant Items:\n"); + // Fonts. + { + for (int i = font_items_list->get_child_count() - 1; i >= 0; i--) { + Node *node = font_items_list->get_child(i); + node->queue_delete(); + font_items_list->remove_child(node); } - for (Set<_TECategory::Item<int>>::Element *F = tc.constant_items.front(); F; F = F->next()) { - file->store_line(E->key() + "." + F->get().name + " = default"); + OrderedHashMap<StringName, bool> font_items = _get_type_items(edited_type, &Theme::get_font_list, show_default); + for (OrderedHashMap<StringName, bool>::Element E = font_items.front(); E; E = E.next()) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT, E.key(), E.get()); + EditorResourcePicker *item_editor = memnew(EditorResourcePicker); + item_editor->set_h_size_flags(SIZE_EXPAND_FILL); + item_editor->set_base_type("Font"); + item_control->add_child(item_editor); + + if (E.get()) { + if (edited_theme->has_font(E.key(), edited_type)) { + item_editor->set_edited_resource(edited_theme->get_font(E.key(), edited_type)); + } else { + item_editor->set_edited_resource(RES()); + } + item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_font_item_changed), varray(E.key())); + } else { + if (Theme::get_default()->has_font(E.key(), edited_type)) { + item_editor->set_edited_resource(Theme::get_default()->get_font(E.key(), edited_type)); + } else { + item_editor->set_edited_resource(RES()); + } + item_editor->set_editable(false); + } + + _add_focusable(item_editor); + font_items_list->add_child(item_control); } } - file->close(); - memdelete(file); -} - -void ThemeEditor::_theme_create_menu_cbk(int p_option) { - bool import = (p_option == POPUP_IMPORT_EDITOR_THEME); + // Fonts sizes. + { + for (int i = font_size_items_list->get_child_count() - 1; i >= 0; i--) { + Node *node = font_size_items_list->get_child(i); + node->queue_delete(); + font_size_items_list->remove_child(node); + } - Ref<Theme> base_theme; + OrderedHashMap<StringName, bool> font_size_items = _get_type_items(edited_type, &Theme::get_font_size_list, show_default); + for (OrderedHashMap<StringName, bool>::Element E = font_size_items.front(); E; E = E.next()) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_FONT_SIZE, E.key(), E.get()); + SpinBox *item_editor = memnew(SpinBox); + item_editor->set_h_size_flags(SIZE_EXPAND_FILL); + item_editor->set_min(-100000); + item_editor->set_max(100000); + item_editor->set_step(1); + item_editor->set_allow_lesser(true); + item_editor->set_allow_greater(true); + item_control->add_child(item_editor); + + if (E.get()) { + item_editor->set_value(edited_theme->get_font_size(E.key(), edited_type)); + item_editor->connect("value_changed", callable_mp(this, &ThemeTypeEditor::_font_size_item_changed), varray(E.key())); + } else { + item_editor->set_value(Theme::get_default()->get_font_size(E.key(), edited_type)); + item_editor->set_editable(false); + } - if (p_option == POPUP_CREATE_EMPTY) { - base_theme = Theme::get_default(); - } else { - base_theme = EditorNode::get_singleton()->get_theme_base()->get_theme(); + _add_focusable(item_editor); + font_size_items_list->add_child(item_control); + } } + // Icons. { - List<StringName> types; - base_theme->get_type_list(&types); + for (int i = icon_items_list->get_child_count() - 1; i >= 0; i--) { + Node *node = icon_items_list->get_child(i); + node->queue_delete(); + icon_items_list->remove_child(node); + } + + OrderedHashMap<StringName, bool> icon_items = _get_type_items(edited_type, &Theme::get_icon_list, show_default); + for (OrderedHashMap<StringName, bool>::Element E = icon_items.front(); E; E = E.next()) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_ICON, E.key(), E.get()); + EditorResourcePicker *item_editor = memnew(EditorResourcePicker); + item_editor->set_h_size_flags(SIZE_EXPAND_FILL); + item_editor->set_base_type("Texture2D"); + item_control->add_child(item_editor); + + if (E.get()) { + if (edited_theme->has_icon(E.key(), edited_type)) { + item_editor->set_edited_resource(edited_theme->get_icon(E.key(), edited_type)); + } else { + item_editor->set_edited_resource(RES()); + } + item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_icon_item_changed), varray(E.key())); + } else { + if (Theme::get_default()->has_icon(E.key(), edited_type)) { + item_editor->set_edited_resource(Theme::get_default()->get_icon(E.key(), edited_type)); + } else { + item_editor->set_edited_resource(RES()); + } + item_editor->set_editable(false); + } - for (List<StringName>::Element *T = types.front(); T; T = T->next()) { - StringName type = T->get(); + _add_focusable(item_editor); + icon_items_list->add_child(item_control); + } + } - List<StringName> icons; - base_theme->get_icon_list(type, &icons); + // Styleboxes. + { + for (int i = stylebox_items_list->get_child_count() - 1; i >= 0; i--) { + Node *node = stylebox_items_list->get_child(i); + node->queue_delete(); + stylebox_items_list->remove_child(node); + } - for (List<StringName>::Element *E = icons.front(); E; E = E->next()) { - theme->set_icon(E->get(), type, import ? base_theme->get_icon(E->get(), type) : Ref<Texture2D>()); + if (leading_stylebox.pinned) { + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, leading_stylebox.item_name, true); + EditorResourcePicker *item_editor = memnew(EditorResourcePicker); + item_editor->set_h_size_flags(SIZE_EXPAND_FILL); + item_editor->set_stretch_ratio(1.5); + item_editor->set_base_type("StyleBox"); + + Button *pin_leader_button = memnew(Button); + pin_leader_button->set_flat(true); + pin_leader_button->set_toggle_mode(true); + pin_leader_button->set_pressed(true); + pin_leader_button->set_icon(get_theme_icon("Pin", "EditorIcons")); + pin_leader_button->set_tooltip(TTR("Unpin this StyleBox as a main style.")); + item_control->add_child(pin_leader_button); + pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_unpin_leading_stylebox)); + + item_control->add_child(item_editor); + + if (leading_stylebox.stylebox.is_valid()) { + item_editor->set_edited_resource(leading_stylebox.stylebox); + } else { + item_editor->set_edited_resource(RES()); } + item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(leading_stylebox.item_name)); - List<StringName> styleboxs; - base_theme->get_stylebox_list(type, &styleboxs); + stylebox_items_list->add_child(item_control); + stylebox_items_list->add_child(memnew(HSeparator)); + } - for (List<StringName>::Element *E = styleboxs.front(); E; E = E->next()) { - theme->set_stylebox(E->get(), type, import ? base_theme->get_stylebox(E->get(), type) : Ref<StyleBox>()); + OrderedHashMap<StringName, bool> stylebox_items = _get_type_items(edited_type, &Theme::get_stylebox_list, show_default); + for (OrderedHashMap<StringName, bool>::Element E = stylebox_items.front(); E; E = E.next()) { + if (leading_stylebox.pinned && leading_stylebox.item_name == E.key()) { + continue; } - List<StringName> fonts; - base_theme->get_font_list(type, &fonts); - - for (List<StringName>::Element *E = fonts.front(); E; E = E->next()) { - theme->set_font(E->get(), type, Ref<Font>()); + HBoxContainer *item_control = _create_property_control(Theme::DATA_TYPE_STYLEBOX, E.key(), E.get()); + EditorResourcePicker *item_editor = memnew(EditorResourcePicker); + item_editor->set_h_size_flags(SIZE_EXPAND_FILL); + item_editor->set_stretch_ratio(1.5); + item_editor->set_base_type("StyleBox"); + + if (E.get()) { + Ref<StyleBox> stylebox_value; + if (edited_theme->has_stylebox(E.key(), edited_type)) { + stylebox_value = edited_theme->get_stylebox(E.key(), edited_type); + item_editor->set_edited_resource(stylebox_value); + } else { + item_editor->set_edited_resource(RES()); + } + item_editor->connect("resource_selected", callable_mp(this, &ThemeTypeEditor::_edit_resource_item)); + item_editor->connect("resource_changed", callable_mp(this, &ThemeTypeEditor::_stylebox_item_changed), varray(E.key())); + + Button *pin_leader_button = memnew(Button); + pin_leader_button->set_flat(true); + pin_leader_button->set_toggle_mode(true); + pin_leader_button->set_icon(get_theme_icon("Pin", "EditorIcons")); + pin_leader_button->set_tooltip(TTR("Pin this StyleBox as a main style. Editing its properties will update the same properties in all other StyleBoxes of this type.")); + item_control->add_child(pin_leader_button); + pin_leader_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_pin_leading_stylebox), varray(item_editor, E.key())); + } else { + if (Theme::get_default()->has_stylebox(E.key(), edited_type)) { + item_editor->set_edited_resource(Theme::get_default()->get_stylebox(E.key(), edited_type)); + } else { + item_editor->set_edited_resource(RES()); + } + item_editor->set_editable(false); } - List<StringName> font_sizes; - base_theme->get_font_size_list(type, &font_sizes); + item_control->add_child(item_editor); + _add_focusable(item_editor); + stylebox_items_list->add_child(item_control); + } + } +} + +void ThemeTypeEditor::_list_type_selected(int p_index) { + edited_type = theme_type_list->get_item_text(p_index); + _update_type_items(); +} + +void ThemeTypeEditor::_add_type_button_cbk() { + add_type_dialog->popup_centered(Size2(560, 420) * EDSCALE); + add_type_filter->grab_focus(); +} + +void ThemeTypeEditor::_add_type_filter_cbk(const String &p_value) { + _update_add_type_options(p_value); +} + +void ThemeTypeEditor::_add_type_options_cbk(int p_index) { + add_type_filter->set_text(add_type_options->get_item_text(p_index)); +} + +void ThemeTypeEditor::_add_type_dialog_confirmed() { + select_type(add_type_filter->get_text().strip_edges()); +} + +void ThemeTypeEditor::_add_type_dialog_entered(const String &p_value) { + select_type(p_value.strip_edges()); + add_type_dialog->hide(); +} + +void ThemeTypeEditor::_add_type_dialog_activated(int p_index) { + select_type(add_type_options->get_item_text(p_index)); + add_type_dialog->hide(); +} + +void ThemeTypeEditor::_add_default_type_items() { + List<StringName> names; + + updating = true; + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); - for (List<StringName>::Element *E = font_sizes.front(); E; E = E->next()) { - theme->set_font_size(E->get(), type, base_theme->get_font_size(E->get(), type)); + { + names.clear(); + Theme::get_default()->get_icon_list(edited_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!edited_theme->has_icon(E->get(), edited_type)) { + edited_theme->set_icon(E->get(), edited_type, Ref<Texture2D>()); + } + } + } + { + names.clear(); + Theme::get_default()->get_stylebox_list(edited_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!edited_theme->has_stylebox(E->get(), edited_type)) { + edited_theme->set_stylebox(E->get(), edited_type, Ref<StyleBox>()); + } + } + } + { + names.clear(); + Theme::get_default()->get_font_list(edited_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!edited_theme->has_font(E->get(), edited_type)) { + edited_theme->set_font(E->get(), edited_type, Ref<Font>()); + } + } + } + { + names.clear(); + Theme::get_default()->get_font_size_list(edited_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!edited_theme->has_font_size(E->get(), edited_type)) { + edited_theme->set_font_size(E->get(), edited_type, Theme::get_default()->get_font_size(E->get(), edited_type)); + } + } + } + { + names.clear(); + Theme::get_default()->get_color_list(edited_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!edited_theme->has_color(E->get(), edited_type)) { + edited_theme->set_color(E->get(), edited_type, Theme::get_default()->get_color(E->get(), edited_type)); + } + } + } + { + names.clear(); + Theme::get_default()->get_constant_list(edited_type, &names); + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (!edited_theme->has_constant(E->get(), edited_type)) { + edited_theme->set_constant(E->get(), edited_type, Theme::get_default()->get_constant(E->get(), edited_type)); } + } + } + + // Allow changes to be reported now that the operation is finished. + edited_theme->_unfreeze_and_propagate_changes(); + updating = false; - List<StringName> colors; - base_theme->get_color_list(type, &colors); + _update_type_items(); +} + +void ThemeTypeEditor::_item_add_cbk(int p_data_type, Control *p_control) { + LineEdit *le = Object::cast_to<LineEdit>(p_control); + if (le->get_text().strip_edges().is_empty()) { + return; + } + + String item_name = le->get_text().strip_edges(); + switch (p_data_type) { + case Theme::DATA_TYPE_COLOR: { + edited_theme->set_color(item_name, edited_type, Color()); + } break; + case Theme::DATA_TYPE_CONSTANT: { + edited_theme->set_constant(item_name, edited_type, 0); + } break; + case Theme::DATA_TYPE_FONT: { + edited_theme->set_font(item_name, edited_type, Ref<Font>()); + } break; + case Theme::DATA_TYPE_FONT_SIZE: { + edited_theme->set_font_size(item_name, edited_type, -1); + } break; + case Theme::DATA_TYPE_ICON: { + edited_theme->set_icon(item_name, edited_type, Ref<Texture2D>()); + } break; + case Theme::DATA_TYPE_STYLEBOX: { + edited_theme->set_stylebox(item_name, edited_type, Ref<StyleBox>()); + } break; + } + + le->set_text(""); +} - for (List<StringName>::Element *E = colors.front(); E; E = E->next()) { - theme->set_color(E->get(), type, import ? base_theme->get_color(E->get(), type) : Color()); +void ThemeTypeEditor::_item_add_lineedit_cbk(String p_value, int p_data_type, Control *p_control) { + _item_add_cbk(p_data_type, p_control); +} + +void ThemeTypeEditor::_item_override_cbk(int p_data_type, String p_item_name) { + switch (p_data_type) { + case Theme::DATA_TYPE_COLOR: { + edited_theme->set_color(p_item_name, edited_type, Theme::get_default()->get_color(p_item_name, edited_type)); + } break; + case Theme::DATA_TYPE_CONSTANT: { + edited_theme->set_constant(p_item_name, edited_type, Theme::get_default()->get_constant(p_item_name, edited_type)); + } break; + case Theme::DATA_TYPE_FONT: { + edited_theme->set_font(p_item_name, edited_type, Ref<Font>()); + } break; + case Theme::DATA_TYPE_FONT_SIZE: { + edited_theme->set_font_size(p_item_name, edited_type, Theme::get_default()->get_font_size(p_item_name, edited_type)); + } break; + case Theme::DATA_TYPE_ICON: { + edited_theme->set_icon(p_item_name, edited_type, Ref<Texture2D>()); + } break; + case Theme::DATA_TYPE_STYLEBOX: { + edited_theme->set_stylebox(p_item_name, edited_type, Ref<StyleBox>()); + } break; + } +} + +void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) { + switch (p_data_type) { + case Theme::DATA_TYPE_COLOR: { + edited_theme->clear_color(p_item_name, edited_type); + } break; + case Theme::DATA_TYPE_CONSTANT: { + edited_theme->clear_constant(p_item_name, edited_type); + } break; + case Theme::DATA_TYPE_FONT: { + edited_theme->clear_font(p_item_name, edited_type); + } break; + case Theme::DATA_TYPE_FONT_SIZE: { + edited_theme->clear_font_size(p_item_name, edited_type); + } break; + case Theme::DATA_TYPE_ICON: { + edited_theme->clear_icon(p_item_name, edited_type); + } break; + case Theme::DATA_TYPE_STYLEBOX: { + edited_theme->clear_stylebox(p_item_name, edited_type); + + if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) { + _unpin_leading_stylebox(); } + } break; + } +} + +void ThemeTypeEditor::_item_rename_cbk(int p_data_type, String p_item_name, Control *p_control) { + // Label + Object::cast_to<Label>(p_control->get_child(0))->hide(); + // Label buttons + Object::cast_to<Button>(p_control->get_child(2))->hide(); + Object::cast_to<Button>(p_control->get_child(3))->hide(); + + // LineEdit + Object::cast_to<LineEdit>(p_control->get_child(1))->set_text(p_item_name); + Object::cast_to<LineEdit>(p_control->get_child(1))->show(); + // LineEdit buttons + Object::cast_to<Button>(p_control->get_child(4))->show(); + Object::cast_to<Button>(p_control->get_child(5))->show(); +} + +void ThemeTypeEditor::_item_rename_confirmed(int p_data_type, String p_item_name, Control *p_control) { + LineEdit *le = Object::cast_to<LineEdit>(p_control->get_child(1)); + if (le->get_text().strip_edges().is_empty()) { + return; + } - List<StringName> constants; - base_theme->get_constant_list(type, &constants); + String new_name = le->get_text().strip_edges(); + if (new_name == p_item_name) { + _item_rename_canceled(p_data_type, p_item_name, p_control); + return; + } + + switch (p_data_type) { + case Theme::DATA_TYPE_COLOR: { + edited_theme->rename_color(p_item_name, new_name, edited_type); + } break; + case Theme::DATA_TYPE_CONSTANT: { + edited_theme->rename_constant(p_item_name, new_name, edited_type); + } break; + case Theme::DATA_TYPE_FONT: { + edited_theme->rename_font(p_item_name, new_name, edited_type); + } break; + case Theme::DATA_TYPE_FONT_SIZE: { + edited_theme->rename_font_size(p_item_name, new_name, edited_type); + } break; + case Theme::DATA_TYPE_ICON: { + edited_theme->rename_icon(p_item_name, new_name, edited_type); + } break; + case Theme::DATA_TYPE_STYLEBOX: { + edited_theme->rename_stylebox(p_item_name, new_name, edited_type); - for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - theme->set_constant(E->get(), type, base_theme->get_constant(E->get(), type)); + if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) { + leading_stylebox.item_name = new_name; } + } break; + } +} + +void ThemeTypeEditor::_item_rename_entered(String p_value, int p_data_type, String p_item_name, Control *p_control) { + _item_rename_confirmed(p_data_type, p_item_name, p_control); +} + +void ThemeTypeEditor::_item_rename_canceled(int p_data_type, String p_item_name, Control *p_control) { + // LineEdit + Object::cast_to<LineEdit>(p_control->get_child(1))->hide(); + // LineEdit buttons + Object::cast_to<Button>(p_control->get_child(4))->hide(); + Object::cast_to<Button>(p_control->get_child(5))->hide(); + + // Label + Object::cast_to<Label>(p_control->get_child(0))->show(); + // Label buttons + Object::cast_to<Button>(p_control->get_child(2))->show(); + Object::cast_to<Button>(p_control->get_child(3))->show(); +} + +void ThemeTypeEditor::_color_item_changed(Color p_value, String p_item_name) { + edited_theme->set_color(p_item_name, edited_type, p_value); +} + +void ThemeTypeEditor::_constant_item_changed(float p_value, String p_item_name) { + edited_theme->set_constant(p_item_name, edited_type, int(p_value)); +} + +void ThemeTypeEditor::_font_size_item_changed(float p_value, String p_item_name) { + edited_theme->set_font_size(p_item_name, edited_type, int(p_value)); +} + +void ThemeTypeEditor::_edit_resource_item(RES p_resource) { + EditorNode::get_singleton()->edit_resource(p_resource); +} + +void ThemeTypeEditor::_font_item_changed(Ref<Font> p_value, String p_item_name) { + edited_theme->set_font(p_item_name, edited_type, p_value); +} + +void ThemeTypeEditor::_icon_item_changed(Ref<Texture2D> p_value, String p_item_name) { + edited_theme->set_icon(p_item_name, edited_type, p_value); +} + +void ThemeTypeEditor::_stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name) { + edited_theme->set_stylebox(p_item_name, edited_type, p_value); + + if (leading_stylebox.pinned && leading_stylebox.item_name == p_item_name) { + if (leading_stylebox.stylebox.is_valid()) { + leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + } + + leading_stylebox.stylebox = p_value; + leading_stylebox.ref_stylebox = (p_value.is_valid() ? p_value->duplicate() : RES()); + if (p_value.is_valid()) { + leading_stylebox.stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); } } } -void ThemeEditor::_theme_edit_button_cbk() { - theme_edit_dialog->popup_centered(Size2(800, 640) * EDSCALE); +void ThemeTypeEditor::_pin_leading_stylebox(Control *p_editor, String p_item_name) { + if (leading_stylebox.stylebox.is_valid()) { + leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + } + + Ref<StyleBox> stylebox; + if (Object::cast_to<EditorResourcePicker>(p_editor)) { + stylebox = Object::cast_to<EditorResourcePicker>(p_editor)->get_edited_resource(); + } + + LeadingStylebox leader; + leader.pinned = true; + leader.item_name = p_item_name; + leader.stylebox = stylebox; + leader.ref_stylebox = (stylebox.is_valid() ? stylebox->duplicate() : RES()); + + leading_stylebox = leader; + if (leading_stylebox.stylebox.is_valid()) { + leading_stylebox.stylebox->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + } + + _update_type_items(); } -void ThemeEditor::_notification(int p_what) { +void ThemeTypeEditor::_unpin_leading_stylebox() { + if (leading_stylebox.stylebox.is_valid()) { + leading_stylebox.stylebox->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_stylebox_from_leading)); + } + + LeadingStylebox leader; + leader.pinned = false; + leading_stylebox = leader; + + _update_type_items(); +} + +void ThemeTypeEditor::_update_stylebox_from_leading() { + if (!leading_stylebox.pinned || leading_stylebox.stylebox.is_null()) { + return; + } + + // Prevent changes from immediatelly being reported while the operation is still ongoing. + edited_theme->_freeze_change_propagation(); + + List<StringName> names; + edited_theme->get_stylebox_list(edited_type, &names); + List<Ref<StyleBox>> styleboxes; + for (List<StringName>::Element *E = names.front(); E; E = E->next()) { + if (E->get() == leading_stylebox.item_name) { + continue; + } + + Ref<StyleBox> sb = edited_theme->get_stylebox(E->get(), edited_type); + if (sb->get_class() == leading_stylebox.stylebox->get_class()) { + styleboxes.push_back(sb); + } + } + + List<PropertyInfo> props; + leading_stylebox.stylebox->get_property_list(&props); + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + Variant value = leading_stylebox.stylebox->get(E->get().name); + Variant ref_value = leading_stylebox.ref_stylebox->get(E->get().name); + if (value == ref_value) { + continue; + } + + for (List<Ref<StyleBox>>::Element *F = styleboxes.front(); F; F = F->next()) { + Ref<StyleBox> sb = F->get(); + sb->set(E->get().name, value); + } + } + + leading_stylebox.ref_stylebox = leading_stylebox.stylebox->duplicate(); + + // Allow changes to be reported now that the operation is finished. + edited_theme->_unfreeze_and_propagate_changes(); +} + +void ThemeTypeEditor::_notification(int p_what) { switch (p_what) { - case NOTIFICATION_PROCESS: { - time_left -= get_process_delta_time(); - if (time_left < 0) { - time_left = 1.5; - _refresh_interval(); - } + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + add_type_button->set_icon(get_theme_icon("Add", "EditorIcons")); + + data_type_tabs->set_tab_icon(0, get_theme_icon("Color", "EditorIcons")); + data_type_tabs->set_tab_icon(1, get_theme_icon("MemberConstant", "EditorIcons")); + data_type_tabs->set_tab_icon(2, get_theme_icon("Font", "EditorIcons")); + data_type_tabs->set_tab_icon(3, get_theme_icon("FontSize", "EditorIcons")); + data_type_tabs->set_tab_icon(4, get_theme_icon("ImageTexture", "EditorIcons")); + data_type_tabs->set_tab_icon(5, get_theme_icon("StyleBoxFlat", "EditorIcons")); + + data_type_tabs->add_theme_style_override("tab_selected", get_theme_stylebox("tab_selected_odd", "TabContainer")); + data_type_tabs->add_theme_style_override("panel", get_theme_stylebox("panel_odd", "TabContainer")); + + _update_add_type_options(); } break; } } -void ThemeEditor::_bind_methods() { +void ThemeTypeEditor::set_edited_theme(const Ref<Theme> &p_theme) { + if (edited_theme.is_valid()) { + edited_theme->disconnect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); + } + + edited_theme = p_theme; + edited_theme->connect("changed", callable_mp(this, &ThemeTypeEditor::_update_type_list_debounced)); + _update_type_list(); +} + +void ThemeTypeEditor::select_type(String p_type_name) { + edited_type = p_type_name; + bool type_exists = false; + + for (int i = 0; i < theme_type_list->get_item_count(); i++) { + String type_name = theme_type_list->get_item_text(i); + if (type_name == edited_type) { + theme_type_list->select(i); + type_exists = true; + break; + } + } + + if (type_exists) { + _update_type_items(); + } else { + edited_theme->add_icon_type(edited_type); + edited_theme->add_stylebox_type(edited_type); + edited_theme->add_font_type(edited_type); + edited_theme->add_font_size_type(edited_type); + edited_theme->add_color_type(edited_type); + edited_theme->add_constant_type(edited_type); + + _update_type_list(); + } +} + +ThemeTypeEditor::ThemeTypeEditor() { + VBoxContainer *main_vb = memnew(VBoxContainer); + add_child(main_vb); + + HBoxContainer *type_list_hb = memnew(HBoxContainer); + main_vb->add_child(type_list_hb); + + Label *type_list_label = memnew(Label); + type_list_label->set_text(TTR("Type:")); + type_list_hb->add_child(type_list_label); + + theme_type_list = memnew(OptionButton); + theme_type_list->set_h_size_flags(SIZE_EXPAND_FILL); + type_list_hb->add_child(theme_type_list); + theme_type_list->connect("item_selected", callable_mp(this, &ThemeTypeEditor::_list_type_selected)); + + add_type_button = memnew(Button); + add_type_button->set_tooltip(TTR("Add Type")); + type_list_hb->add_child(add_type_button); + add_type_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_type_button_cbk)); + + add_type_dialog = memnew(ConfirmationDialog); + add_type_dialog->set_title(TTR("Add Item Type")); + type_list_hb->add_child(add_type_dialog); + add_type_dialog->connect("confirmed", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_confirmed)); + + VBoxContainer *add_type_vb = memnew(VBoxContainer); + add_type_dialog->add_child(add_type_vb); + + Label *add_type_filter_label = memnew(Label); + add_type_filter_label->set_text(TTR("Name:")); + add_type_vb->add_child(add_type_filter_label); + add_type_filter = memnew(LineEdit); + add_type_vb->add_child(add_type_filter); + add_type_filter->connect("text_changed", callable_mp(this, &ThemeTypeEditor::_add_type_filter_cbk)); + add_type_filter->connect("text_submitted", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_entered)); + Label *add_type_options_label = memnew(Label); + add_type_options_label->set_text(TTR("Node Types:")); + add_type_vb->add_child(add_type_options_label); + add_type_options = memnew(ItemList); + add_type_options->set_v_size_flags(SIZE_EXPAND_FILL); + add_type_vb->add_child(add_type_options); + add_type_options->connect("item_selected", callable_mp(this, &ThemeTypeEditor::_add_type_options_cbk)); + add_type_options->connect("item_activated", callable_mp(this, &ThemeTypeEditor::_add_type_dialog_activated)); + + HBoxContainer *type_controls = memnew(HBoxContainer); + main_vb->add_child(type_controls); + + show_default_items_button = memnew(CheckButton); + show_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL); + show_default_items_button->set_text(TTR("Show Default")); + show_default_items_button->set_tooltip(TTR("Show default type items alongside items that have been overridden.")); + show_default_items_button->set_pressed(true); + type_controls->add_child(show_default_items_button); + show_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_update_type_items)); + + Button *add_default_items_button = memnew(Button); + add_default_items_button->set_h_size_flags(SIZE_EXPAND_FILL); + add_default_items_button->set_text(TTR("Override All")); + add_default_items_button->set_tooltip(TTR("Override all default type items.")); + type_controls->add_child(add_default_items_button); + add_default_items_button->connect("pressed", callable_mp(this, &ThemeTypeEditor::_add_default_type_items)); + + data_type_tabs = memnew(TabContainer); + main_vb->add_child(data_type_tabs); + data_type_tabs->set_v_size_flags(SIZE_EXPAND_FILL); + data_type_tabs->set_use_hidden_tabs_for_min_size(true); + + color_items_list = _create_item_list(Theme::DATA_TYPE_COLOR); + constant_items_list = _create_item_list(Theme::DATA_TYPE_CONSTANT); + font_items_list = _create_item_list(Theme::DATA_TYPE_FONT); + font_size_items_list = _create_item_list(Theme::DATA_TYPE_FONT_SIZE); + icon_items_list = _create_item_list(Theme::DATA_TYPE_ICON); + stylebox_items_list = _create_item_list(Theme::DATA_TYPE_STYLEBOX); + + update_debounce_timer = memnew(Timer); + update_debounce_timer->set_one_shot(true); + update_debounce_timer->set_wait_time(0.5); + update_debounce_timer->connect("timeout", callable_mp(this, &ThemeTypeEditor::_update_type_list)); + add_child(update_debounce_timer); +} + +void ThemeEditor::edit(const Ref<Theme> &p_theme) { + if (theme == p_theme) { + return; + } + + theme = p_theme; + theme_type_editor->set_edited_theme(p_theme); + theme_edit_dialog->set_edited_theme(p_theme); + + for (int i = 0; i < preview_tabs_content->get_child_count(); i++) { + ThemeEditorPreview *preview_tab = Object::cast_to<ThemeEditorPreview>(preview_tabs_content->get_child(i)); + if (!preview_tab) { + continue; + } + + preview_tab->set_preview_theme(p_theme); + } + + theme_name->set_text(TTR("Theme") + ": " + theme->get_path().get_file()); +} + +Ref<Theme> ThemeEditor::get_edited_theme() { + return theme; +} + +void ThemeEditor::_theme_save_button_cbk(bool p_save_as) { + ERR_FAIL_COND_MSG(theme.is_null(), "Invalid state of the Theme Editor; the Theme resource is missing."); + + if (p_save_as) { + EditorNode::get_singleton()->save_resource_as(theme); + } else { + EditorNode::get_singleton()->save_resource(theme); + } +} + +void ThemeEditor::_theme_edit_button_cbk() { + theme_edit_dialog->popup_centered(Size2(850, 760) * EDSCALE); +} + +void ThemeEditor::_add_preview_button_cbk() { + preview_scene_dialog->popup_file_dialog(); +} + +void ThemeEditor::_preview_scene_dialog_cbk(const String &p_path) { + SceneThemeEditorPreview *preview_tab = memnew(SceneThemeEditorPreview); + if (!preview_tab->set_preview_scene(p_path)) { + return; + } + + _add_preview_tab(preview_tab, p_path.get_file(), get_theme_icon("PackedScene", "EditorIcons")); + preview_tab->connect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid), varray(preview_tab)); + preview_tab->connect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab), varray(preview_tab)); +} + +void ThemeEditor::_add_preview_tab(ThemeEditorPreview *p_preview_tab, const String &p_preview_name, const Ref<Texture2D> &p_icon) { + p_preview_tab->set_preview_theme(theme); + + preview_tabs->add_tab(p_preview_name, p_icon); + preview_tabs_content->add_child(p_preview_tab); + preview_tabs->set_tab_right_button(preview_tabs->get_tab_count() - 1, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("close", "Tabs")); + p_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked)); + + preview_tabs->set_current_tab(preview_tabs->get_tab_count() - 1); +} + +void ThemeEditor::_change_preview_tab(int p_tab) { + ERR_FAIL_INDEX_MSG(p_tab, preview_tabs_content->get_child_count(), "Attempting to open a preview tab that doesn't exist."); + + for (int i = 0; i < preview_tabs_content->get_child_count(); i++) { + Control *c = Object::cast_to<Control>(preview_tabs_content->get_child(i)); + if (!c) { + continue; + } + + c->set_visible(i == p_tab); + } +} + +void ThemeEditor::_remove_preview_tab(int p_tab) { + ERR_FAIL_INDEX_MSG(p_tab, preview_tabs_content->get_child_count(), "Attempting to remove a preview tab that doesn't exist."); + + ThemeEditorPreview *preview_tab = Object::cast_to<ThemeEditorPreview>(preview_tabs_content->get_child(p_tab)); + ERR_FAIL_COND_MSG(Object::cast_to<DefaultThemeEditorPreview>(preview_tab), "Attemptying to remove the default preview tab."); + + if (preview_tab) { + preview_tab->disconnect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked)); + if (preview_tab->is_connected("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid))) { + preview_tab->disconnect("scene_invalidated", callable_mp(this, &ThemeEditor::_remove_preview_tab_invalid)); + } + if (preview_tab->is_connected("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab))) { + preview_tab->disconnect("scene_reloaded", callable_mp(this, &ThemeEditor::_update_preview_tab)); + } + + preview_tabs_content->remove_child(preview_tab); + preview_tabs->remove_tab(p_tab); + _change_preview_tab(preview_tabs->get_current_tab()); + } +} + +void ThemeEditor::_remove_preview_tab_invalid(Node *p_tab_control) { + int tab_index = p_tab_control->get_index(); + _remove_preview_tab(tab_index); +} + +void ThemeEditor::_update_preview_tab(Node *p_tab_control) { + if (!Object::cast_to<SceneThemeEditorPreview>(p_tab_control)) { + return; + } + + int tab_index = p_tab_control->get_index(); + SceneThemeEditorPreview *scene_preview = Object::cast_to<SceneThemeEditorPreview>(p_tab_control); + preview_tabs->set_tab_title(tab_index, scene_preview->get_preview_scene_path().get_file()); +} + +void ThemeEditor::_preview_control_picked(String p_class_name) { + theme_type_editor->select_type(p_class_name); +} + +void ThemeEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + preview_tabs->add_theme_style_override("tab_selected", get_theme_stylebox("ThemeEditorPreviewFG", "EditorStyles")); + preview_tabs->add_theme_style_override("tab_unselected", get_theme_stylebox("ThemeEditorPreviewBG", "EditorStyles")); + preview_tabs_content->add_theme_style_override("panel", get_theme_stylebox("panel_odd", "TabContainer")); + + add_preview_button->set_icon(get_theme_icon("Add", "EditorIcons")); + } break; + } } ThemeEditor::ThemeEditor() { HBoxContainer *top_menu = memnew(HBoxContainer); add_child(top_menu); - top_menu->add_child(memnew(Label(TTR("Preview:")))); + theme_name = memnew(Label); + theme_name->set_text(TTR("Theme") + ": "); + top_menu->add_child(theme_name); + top_menu->add_spacer(false); - theme_create_menu = memnew(MenuButton); - theme_create_menu->set_text(TTR("Create Theme...")); - theme_create_menu->set_tooltip(TTR("Create a new Theme.")); - theme_create_menu->get_popup()->add_item(TTR("Empty Template"), POPUP_CREATE_EMPTY); - theme_create_menu->get_popup()->add_separator(); - theme_create_menu->get_popup()->add_item(TTR("Empty Editor Template"), POPUP_CREATE_EDITOR_EMPTY); - theme_create_menu->get_popup()->add_item(TTR("From Current Editor Theme"), POPUP_IMPORT_EDITOR_THEME); - top_menu->add_child(theme_create_menu); - theme_create_menu->get_popup()->connect("id_pressed", callable_mp(this, &ThemeEditor::_theme_create_menu_cbk)); - - theme_edit_button = memnew(Button); - theme_edit_button->set_text(TTR("Edit Theme Items")); - theme_edit_button->set_tooltip(TTR("Customize Theme items.")); + Button *theme_save_button = memnew(Button); + theme_save_button->set_text(TTR("Save")); + theme_save_button->set_flat(true); + theme_save_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk), varray(false)); + top_menu->add_child(theme_save_button); + + Button *theme_save_as_button = memnew(Button); + theme_save_as_button->set_text(TTR("Save As...")); + theme_save_as_button->set_flat(true); + theme_save_as_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_save_button_cbk), varray(true)); + top_menu->add_child(theme_save_as_button); + + top_menu->add_child(memnew(VSeparator)); + + Button *theme_edit_button = memnew(Button); + theme_edit_button->set_text(TTR("Manage Items...")); + theme_edit_button->set_tooltip(TTR("Add, remove, organize and import Theme items.")); theme_edit_button->set_flat(true); theme_edit_button->connect("pressed", callable_mp(this, &ThemeEditor::_theme_edit_button_cbk)); top_menu->add_child(theme_edit_button); - ScrollContainer *scroll = memnew(ScrollContainer); - add_child(scroll); - scroll->set_enable_v_scroll(true); - scroll->set_enable_h_scroll(true); - scroll->set_v_size_flags(SIZE_EXPAND_FILL); - - MarginContainer *root_container = memnew(MarginContainer); - scroll->add_child(root_container); - root_container->set_theme(Theme::get_default()); - root_container->set_clip_contents(true); - root_container->set_custom_minimum_size(Size2(700, 0) * EDSCALE); - root_container->set_v_size_flags(SIZE_EXPAND_FILL); - root_container->set_h_size_flags(SIZE_EXPAND_FILL); - - //// Preview Controls //// - - main_panel = memnew(Panel); - root_container->add_child(main_panel); - - main_container = memnew(MarginContainer); - root_container->add_child(main_container); - main_container->add_theme_constant_override("margin_right", 4 * EDSCALE); - main_container->add_theme_constant_override("margin_top", 4 * EDSCALE); - main_container->add_theme_constant_override("margin_left", 4 * EDSCALE); - main_container->add_theme_constant_override("margin_bottom", 4 * EDSCALE); - - HBoxContainer *main_hb = memnew(HBoxContainer); - main_container->add_child(main_hb); - - VBoxContainer *first_vb = memnew(VBoxContainer); - main_hb->add_child(first_vb); - first_vb->set_h_size_flags(SIZE_EXPAND_FILL); - first_vb->add_theme_constant_override("separation", 10 * EDSCALE); - - first_vb->add_child(memnew(Label("Label"))); - - first_vb->add_child(memnew(Button("Button"))); - Button *bt = memnew(Button); - bt->set_text(TTR("Toggle Button")); - bt->set_toggle_mode(true); - bt->set_pressed(true); - first_vb->add_child(bt); - bt = memnew(Button); - bt->set_text(TTR("Disabled Button")); - bt->set_disabled(true); - first_vb->add_child(bt); - Button *tb = memnew(Button); - tb->set_flat(true); - tb->set_text("Button"); - first_vb->add_child(tb); - - CheckButton *cb = memnew(CheckButton); - cb->set_text("CheckButton"); - first_vb->add_child(cb); - CheckBox *cbx = memnew(CheckBox); - cbx->set_text("CheckBox"); - first_vb->add_child(cbx); - - MenuButton *test_menu_button = memnew(MenuButton); - test_menu_button->set_text("MenuButton"); - test_menu_button->get_popup()->add_item(TTR("Item")); - test_menu_button->get_popup()->add_item(TTR("Disabled Item")); - test_menu_button->get_popup()->set_item_disabled(1, true); - test_menu_button->get_popup()->add_separator(); - test_menu_button->get_popup()->add_check_item(TTR("Check Item")); - test_menu_button->get_popup()->add_check_item(TTR("Checked Item")); - test_menu_button->get_popup()->set_item_checked(4, true); - test_menu_button->get_popup()->add_separator(); - test_menu_button->get_popup()->add_radio_check_item(TTR("Radio Item")); - test_menu_button->get_popup()->add_radio_check_item(TTR("Checked Radio Item")); - test_menu_button->get_popup()->set_item_checked(7, true); - test_menu_button->get_popup()->add_separator(TTR("Named Sep.")); - - PopupMenu *test_submenu = memnew(PopupMenu); - test_menu_button->get_popup()->add_child(test_submenu); - test_submenu->set_name("submenu"); - test_menu_button->get_popup()->add_submenu_item(TTR("Submenu"), "submenu"); - test_submenu->add_item(TTR("Subitem 1")); - test_submenu->add_item(TTR("Subitem 2")); - first_vb->add_child(test_menu_button); - - OptionButton *test_option_button = memnew(OptionButton); - test_option_button->add_item("OptionButton"); - test_option_button->add_separator(); - test_option_button->add_item(TTR("Has")); - test_option_button->add_item(TTR("Many")); - test_option_button->add_item(TTR("Options")); - first_vb->add_child(test_option_button); - first_vb->add_child(memnew(ColorPickerButton)); - - VBoxContainer *second_vb = memnew(VBoxContainer); - second_vb->set_h_size_flags(SIZE_EXPAND_FILL); - main_hb->add_child(second_vb); - second_vb->add_theme_constant_override("separation", 10 * EDSCALE); - LineEdit *le = memnew(LineEdit); - le->set_text("LineEdit"); - second_vb->add_child(le); - le = memnew(LineEdit); - le->set_text(TTR("Disabled LineEdit")); - le->set_editable(false); - second_vb->add_child(le); - TextEdit *te = memnew(TextEdit); - te->set_text("TextEdit"); - te->set_custom_minimum_size(Size2(0, 100) * EDSCALE); - second_vb->add_child(te); - second_vb->add_child(memnew(SpinBox)); - - HBoxContainer *vhb = memnew(HBoxContainer); - second_vb->add_child(vhb); - vhb->set_custom_minimum_size(Size2(0, 100) * EDSCALE); - vhb->add_child(memnew(VSlider)); - VScrollBar *vsb = memnew(VScrollBar); - vsb->set_page(25); - vhb->add_child(vsb); - vhb->add_child(memnew(VSeparator)); - VBoxContainer *hvb = memnew(VBoxContainer); - vhb->add_child(hvb); - hvb->set_alignment(ALIGN_CENTER); - hvb->set_h_size_flags(SIZE_EXPAND_FILL); - hvb->add_child(memnew(HSlider)); - HScrollBar *hsb = memnew(HScrollBar); - hsb->set_page(25); - hvb->add_child(hsb); - HSlider *hs = memnew(HSlider); - hs->set_editable(false); - hvb->add_child(hs); - hvb->add_child(memnew(HSeparator)); - ProgressBar *pb = memnew(ProgressBar); - pb->set_value(50); - hvb->add_child(pb); - - VBoxContainer *third_vb = memnew(VBoxContainer); - third_vb->set_h_size_flags(SIZE_EXPAND_FILL); - third_vb->add_theme_constant_override("separation", 10 * EDSCALE); - main_hb->add_child(third_vb); - - TabContainer *tc = memnew(TabContainer); - third_vb->add_child(tc); - tc->set_custom_minimum_size(Size2(0, 135) * EDSCALE); - Control *tcc = memnew(Control); - tcc->set_name(TTR("Tab 1")); - tc->add_child(tcc); - tcc = memnew(Control); - tcc->set_name(TTR("Tab 2")); - tc->add_child(tcc); - tcc = memnew(Control); - tcc->set_name(TTR("Tab 3")); - tc->add_child(tcc); - tc->set_tab_disabled(2, true); - - Tree *test_tree = memnew(Tree); - third_vb->add_child(test_tree); - test_tree->set_custom_minimum_size(Size2(0, 175) * EDSCALE); - test_tree->add_theme_constant_override("draw_relationship_lines", 1); - - TreeItem *item = test_tree->create_item(); - item->set_text(0, "Tree"); - item = test_tree->create_item(test_tree->get_root()); - item->set_text(0, "Item"); - item = test_tree->create_item(test_tree->get_root()); - item->set_editable(0, true); - item->set_text(0, TTR("Editable Item")); - TreeItem *sub_tree = test_tree->create_item(test_tree->get_root()); - sub_tree->set_text(0, TTR("Subtree")); - item = test_tree->create_item(sub_tree); - item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); - item->set_editable(0, true); - item->set_text(0, "Check Item"); - item = test_tree->create_item(sub_tree); - item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE); - item->set_editable(0, true); - item->set_range_config(0, 0, 20, 0.1); - item->set_range(0, 2); - item = test_tree->create_item(sub_tree); - item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE); - item->set_editable(0, true); - item->set_text(0, TTR("Has,Many,Options")); - item->set_range(0, 2); - - main_hb->add_theme_constant_override("separation", 20 * EDSCALE); - theme_edit_dialog = memnew(ThemeItemEditorDialog); theme_edit_dialog->hide(); - add_child(theme_edit_dialog); + top_menu->add_child(theme_edit_dialog); + + HSplitContainer *main_hs = memnew(HSplitContainer); + main_hs->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(main_hs); + + VBoxContainer *preview_tabs_vb = memnew(VBoxContainer); + preview_tabs_vb->set_h_size_flags(SIZE_EXPAND_FILL); + preview_tabs_vb->set_custom_minimum_size(Size2(520, 0) * EDSCALE); + preview_tabs_vb->add_theme_constant_override("separation", 2 * EDSCALE); + main_hs->add_child(preview_tabs_vb); + HBoxContainer *preview_tabbar_hb = memnew(HBoxContainer); + preview_tabs_vb->add_child(preview_tabbar_hb); + preview_tabs_content = memnew(PanelContainer); + preview_tabs_content->set_v_size_flags(SIZE_EXPAND_FILL); + preview_tabs_content->set_draw_behind_parent(true); + preview_tabs_vb->add_child(preview_tabs_content); + + preview_tabs = memnew(Tabs); + preview_tabs->set_tab_align(Tabs::ALIGN_LEFT); + preview_tabs->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)); + + HBoxContainer *add_preview_button_hb = memnew(HBoxContainer); + preview_tabbar_hb->add_child(add_preview_button_hb); + add_preview_button = memnew(Button); + add_preview_button->set_text(TTR("Add Preview")); + add_preview_button_hb->add_child(add_preview_button); + add_preview_button->connect("pressed", callable_mp(this, &ThemeEditor::_add_preview_button_cbk)); + + DefaultThemeEditorPreview *default_preview_tab = memnew(DefaultThemeEditorPreview); + preview_tabs_content->add_child(default_preview_tab); + default_preview_tab->connect("control_picked", callable_mp(this, &ThemeEditor::_preview_control_picked)); + preview_tabs->add_tab(TTR("Default Preview")); + + preview_scene_dialog = memnew(EditorFileDialog); + preview_scene_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE); + preview_scene_dialog->set_title(TTR("Select UI Scene:")); + List<String> ext; + ResourceLoader::get_recognized_extensions_for_type("PackedScene", &ext); + for (List<String>::Element *E = ext.front(); E; E = E->next()) { + preview_scene_dialog->add_filter("*." + E->get() + "; Scene"); + } + main_hs->add_child(preview_scene_dialog); + preview_scene_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_preview_scene_dialog_cbk)); - file_dialog = memnew(EditorFileDialog); - file_dialog->add_filter("*.theme ; " + TTR("Theme File")); - add_child(file_dialog); - file_dialog->connect("file_selected", callable_mp(this, &ThemeEditor::_save_template_cbk)); + theme_type_editor = memnew(ThemeTypeEditor); + main_hs->add_child(theme_type_editor); + theme_type_editor->set_custom_minimum_size(Size2(360, 0) * EDSCALE); } void ThemeEditorPlugin::edit(Object *p_node) { if (Object::cast_to<Theme>(p_node)) { theme_editor->edit(Object::cast_to<Theme>(p_node)); + } else if (Object::cast_to<Font>(p_node) || Object::cast_to<StyleBox>(p_node) || Object::cast_to<Texture2D>(p_node)) { + // Do nothing, keep editing the existing theme. } else { theme_editor->edit(Ref<Theme>()); } } bool ThemeEditorPlugin::handles(Object *p_node) const { - return p_node->is_class("Theme"); + if (Object::cast_to<Theme>(p_node)) { + return true; + } + + Ref<Theme> edited_theme = theme_editor->get_edited_theme(); + if (edited_theme.is_null()) { + return false; + } + + // If we are editing a theme already and this particular resource happens to belong to it, + // then we just keep editing it, despite not being able to directly handle it. + // This only goes one layer deep, but if required this can be extended to support, say, FontData inside of Font. + bool belongs_to_theme = false; + + if (Object::cast_to<Font>(p_node)) { + Ref<Font> font_item = Object::cast_to<Font>(p_node); + List<StringName> types; + List<StringName> names; + + edited_theme->get_font_type_list(&types); + for (List<StringName>::Element *E = types.front(); E; E = E->next()) { + names.clear(); + edited_theme->get_font_list(E->get(), &names); + + for (List<StringName>::Element *F = names.front(); F; F = F->next()) { + if (font_item == edited_theme->get_font(F->get(), E->get())) { + belongs_to_theme = true; + break; + } + } + } + } else if (Object::cast_to<StyleBox>(p_node)) { + Ref<StyleBox> stylebox_item = Object::cast_to<StyleBox>(p_node); + List<StringName> types; + List<StringName> names; + + edited_theme->get_stylebox_type_list(&types); + for (List<StringName>::Element *E = types.front(); E; E = E->next()) { + names.clear(); + edited_theme->get_stylebox_list(E->get(), &names); + + for (List<StringName>::Element *F = names.front(); F; F = F->next()) { + if (stylebox_item == edited_theme->get_stylebox(F->get(), E->get())) { + belongs_to_theme = true; + break; + } + } + } + } else if (Object::cast_to<Texture2D>(p_node)) { + Ref<Texture2D> icon_item = Object::cast_to<Texture2D>(p_node); + List<StringName> types; + List<StringName> names; + + edited_theme->get_icon_type_list(&types); + for (List<StringName>::Element *E = types.front(); E; E = E->next()) { + names.clear(); + edited_theme->get_icon_list(E->get(), &names); + + for (List<StringName>::Element *F = names.front(); F; F = F->next()) { + if (icon_item == edited_theme->get_icon(F->get(), E->get())) { + belongs_to_theme = true; + break; + } + } + } + } + + return belongs_to_theme; } void ThemeEditorPlugin::make_visible(bool p_visible) { if (p_visible) { - theme_editor->set_process(true); button->show(); editor->make_bottom_panel_item_visible(theme_editor); } else { - theme_editor->set_process(false); if (theme_editor->is_visible_in_tree()) { editor->hide_bottom_panel(); } diff --git a/editor/plugins/theme_editor_plugin.h b/editor/plugins/theme_editor_plugin.h index 0a840aecd7..cdedbbec8d 100644 --- a/editor/plugins/theme_editor_plugin.h +++ b/editor/plugins/theme_editor_plugin.h @@ -31,24 +31,160 @@ #ifndef THEME_EDITOR_PLUGIN_H #define THEME_EDITOR_PLUGIN_H -#include "scene/gui/check_box.h" -#include "scene/gui/file_dialog.h" #include "scene/gui/margin_container.h" #include "scene/gui/option_button.h" #include "scene/gui/scroll_container.h" +#include "scene/gui/tabs.h" #include "scene/gui/texture_rect.h" #include "scene/resources/theme.h" +#include "theme_editor_preview.h" #include "editor/editor_node.h" +class ThemeItemImportTree : public VBoxContainer { + GDCLASS(ThemeItemImportTree, VBoxContainer); + + Ref<Theme> edited_theme; + Ref<Theme> base_theme; + + struct ThemeItem { + String type_name; + Theme::DataType data_type; + String item_name; + + bool operator<(const ThemeItem &p_item) const { + if (type_name == p_item.type_name && data_type == p_item.data_type) { + return item_name < p_item.item_name; + } + if (type_name == p_item.type_name) { + return data_type < p_item.data_type; + } + return type_name < p_item.type_name; + } + }; + + enum ItemCheckedState { + SELECT_IMPORT_DEFINITION, + SELECT_IMPORT_FULL, + }; + + Map<ThemeItem, ItemCheckedState> selected_items; + + LineEdit *import_items_filter; + + Tree *import_items_tree; + List<TreeItem *> tree_color_items; + List<TreeItem *> tree_constant_items; + List<TreeItem *> tree_font_items; + List<TreeItem *> tree_font_size_items; + List<TreeItem *> tree_icon_items; + List<TreeItem *> tree_stylebox_items; + + bool updating_tree = false; + + enum ItemActionFlag { + IMPORT_ITEM = 1, + IMPORT_ITEM_DATA = 2, + }; + + TextureRect *select_colors_icon; + Label *select_colors_label; + Button *select_all_colors_button; + Button *select_full_colors_button; + Button *deselect_all_colors_button; + Label *total_selected_colors_label; + + TextureRect *select_constants_icon; + Label *select_constants_label; + Button *select_all_constants_button; + Button *select_full_constants_button; + Button *deselect_all_constants_button; + Label *total_selected_constants_label; + + TextureRect *select_fonts_icon; + Label *select_fonts_label; + Button *select_all_fonts_button; + Button *select_full_fonts_button; + Button *deselect_all_fonts_button; + Label *total_selected_fonts_label; + + TextureRect *select_font_sizes_icon; + Label *select_font_sizes_label; + Button *select_all_font_sizes_button; + Button *select_full_font_sizes_button; + Button *deselect_all_font_sizes_button; + Label *total_selected_font_sizes_label; + + TextureRect *select_icons_icon; + Label *select_icons_label; + Button *select_all_icons_button; + Button *select_full_icons_button; + Button *deselect_all_icons_button; + Label *total_selected_icons_label; + + TextureRect *select_styleboxes_icon; + Label *select_styleboxes_label; + Button *select_all_styleboxes_button; + Button *select_full_styleboxes_button; + Button *deselect_all_styleboxes_button; + Label *total_selected_styleboxes_label; + + HBoxContainer *select_icons_warning_hb; + TextureRect *select_icons_warning_icon; + Label *select_icons_warning; + + Button *import_collapse_types_button; + Button *import_expand_types_button; + Button *import_select_all_button; + Button *import_select_full_button; + Button *import_deselect_all_button; + + void _update_items_tree(); + void _toggle_type_items(bool p_collapse); + void _filter_text_changed(const String &p_value); + + void _store_selected_item(TreeItem *p_tree_item); + void _restore_selected_item(TreeItem *p_tree_item); + void _update_total_selected(Theme::DataType p_data_type); + + void _tree_item_edited(); + void _select_all_subitems(TreeItem *p_root_item, bool p_select_with_data); + void _deselect_all_subitems(TreeItem *p_root_item, bool p_deselect_completely); + void _update_parent_items(TreeItem *p_root_item); + + void _select_all_items_pressed(); + void _select_full_items_pressed(); + void _deselect_all_items_pressed(); + + void _select_all_data_type_pressed(int p_data_type); + void _select_full_data_type_pressed(int p_data_type); + void _deselect_all_data_type_pressed(int p_data_type); + + void _import_selected(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_edited_theme(const Ref<Theme> &p_theme); + void set_base_theme(const Ref<Theme> &p_theme); + void reset_item_tree(); + + bool has_selected_items() const; + + ThemeItemImportTree(); +}; + class ThemeItemEditorDialog : public AcceptDialog { GDCLASS(ThemeItemEditorDialog, AcceptDialog); Ref<Theme> edited_theme; + TabContainer *tc; + ItemList *edit_type_list; - OptionButton *edit_add_class_options; - LineEdit *edit_add_custom_value; + LineEdit *edit_add_type_value; String edited_item_type; Button *edit_items_add_color; @@ -83,6 +219,19 @@ class ThemeItemEditorDialog : public AcceptDialog { String edit_item_old_name; Theme::DataType edit_item_data_type = Theme::DATA_TYPE_MAX; + ThemeItemImportTree *import_default_theme_items; + ThemeItemImportTree *import_editor_theme_items; + ThemeItemImportTree *import_other_theme_items; + + LineEdit *import_another_theme_value; + Button *import_another_theme_button; + EditorFileDialog *import_another_theme_dialog; + + ConfirmationDialog *confirm_closing_dialog; + + void ok_pressed() override; + void _close_dialog(); + void _dialog_about_to_show(); void _update_edit_types(); void _edited_type_selected(int p_item_idx); @@ -90,8 +239,7 @@ class ThemeItemEditorDialog : public AcceptDialog { void _update_edit_item_tree(String p_item_type); void _item_tree_button_pressed(Object *p_item, int p_column, int p_id); - void _add_class_type_items(); - void _add_custom_type(); + void _add_theme_type(); void _add_theme_item(Theme::DataType p_data_type, String p_item_name, String p_item_type); void _remove_data_type_items(Theme::DataType p_data_type, String p_item_type); void _remove_class_items(); @@ -103,6 +251,9 @@ class ThemeItemEditorDialog : public AcceptDialog { void _confirm_edit_theme_item(); void _edit_theme_item_gui_input(const Ref<InputEvent> &p_event); + void _open_select_another_theme(); + void _select_another_theme_cbk(const String &p_path); + protected: void _notification(int p_what); @@ -112,41 +263,123 @@ public: ThemeItemEditorDialog(); }; +class ThemeTypeEditor : public MarginContainer { + GDCLASS(ThemeTypeEditor, MarginContainer); + + Ref<Theme> edited_theme; + String edited_type; + bool updating = false; + + struct LeadingStylebox { + bool pinned = false; + StringName item_name; + Ref<StyleBox> stylebox; + Ref<StyleBox> ref_stylebox; + }; + + LeadingStylebox leading_stylebox; + + OptionButton *theme_type_list; + Button *add_type_button; + ConfirmationDialog *add_type_dialog; + LineEdit *add_type_filter; + ItemList *add_type_options; + + CheckButton *show_default_items_button; + + TabContainer *data_type_tabs; + VBoxContainer *color_items_list; + VBoxContainer *constant_items_list; + VBoxContainer *font_items_list; + VBoxContainer *font_size_items_list; + VBoxContainer *icon_items_list; + VBoxContainer *stylebox_items_list; + + Vector<Control *> focusables; + Timer *update_debounce_timer; + + VBoxContainer *_create_item_list(Theme::DataType p_data_type); + void _update_type_list(); + void _update_type_list_debounced(); + void _update_add_type_options(const String &p_filter = ""); + OrderedHashMap<StringName, bool> _get_type_items(String p_type_name, void (Theme::*get_list_func)(StringName, List<StringName> *) const, bool include_default); + HBoxContainer *_create_property_control(Theme::DataType p_data_type, String p_item_name, bool p_editable); + void _add_focusable(Control *p_control); + void _update_type_items(); + + void _list_type_selected(int p_index); + void _select_type(String p_type_name); + void _add_type_button_cbk(); + void _add_type_filter_cbk(const String &p_value); + void _add_type_options_cbk(int p_index); + void _add_type_dialog_confirmed(); + void _add_type_dialog_entered(const String &p_value); + void _add_type_dialog_activated(int p_index); + void _add_default_type_items(); + + void _item_add_cbk(int p_data_type, Control *p_control); + void _item_add_lineedit_cbk(String p_value, int p_data_type, Control *p_control); + void _item_override_cbk(int p_data_type, String p_item_name); + void _item_remove_cbk(int p_data_type, String p_item_name); + void _item_rename_cbk(int p_data_type, String p_item_name, Control *p_control); + void _item_rename_confirmed(int p_data_type, String p_item_name, Control *p_control); + void _item_rename_entered(String p_value, int p_data_type, String p_item_name, Control *p_control); + void _item_rename_canceled(int p_data_type, String p_item_name, Control *p_control); + + void _color_item_changed(Color p_value, String p_item_name); + void _constant_item_changed(float p_value, String p_item_name); + void _font_size_item_changed(float p_value, String p_item_name); + void _edit_resource_item(RES p_resource); + void _font_item_changed(Ref<Font> p_value, String p_item_name); + void _icon_item_changed(Ref<Texture2D> p_value, String p_item_name); + void _stylebox_item_changed(Ref<StyleBox> p_value, String p_item_name); + void _pin_leading_stylebox(Control *p_editor, String p_item_name); + void _unpin_leading_stylebox(); + void _update_stylebox_from_leading(); + +protected: + void _notification(int p_what); + +public: + void set_edited_theme(const Ref<Theme> &p_theme); + void select_type(String p_type_name); + + ThemeTypeEditor(); +}; + class ThemeEditor : public VBoxContainer { GDCLASS(ThemeEditor, VBoxContainer); - Panel *main_panel; - MarginContainer *main_container; Ref<Theme> theme; - EditorFileDialog *file_dialog; + Tabs *preview_tabs; + PanelContainer *preview_tabs_content; + Button *add_preview_button; + EditorFileDialog *preview_scene_dialog; - double time_left = 0; + ThemeTypeEditor *theme_type_editor; - Button *theme_edit_button; - MenuButton *theme_create_menu; + Label *theme_name; ThemeItemEditorDialog *theme_edit_dialog; - enum CreatePopupMode { - POPUP_CREATE_EMPTY, - POPUP_CREATE_EDITOR_EMPTY, - POPUP_IMPORT_EDITOR_THEME, - }; - - Tree *test_tree; - - void _save_template_cbk(String fname); + void _theme_save_button_cbk(bool p_save_as); void _theme_edit_button_cbk(); - void _theme_create_menu_cbk(int p_option); - void _propagate_redraw(Control *p_at); - void _refresh_interval(); + + void _add_preview_button_cbk(); + void _preview_scene_dialog_cbk(const String &p_path); + void _add_preview_tab(ThemeEditorPreview *p_preview_tab, const String &p_preview_name, const Ref<Texture2D> &p_icon); + void _change_preview_tab(int p_tab); + void _remove_preview_tab(int p_tab); + void _remove_preview_tab_invalid(Node *p_tab_control); + void _update_preview_tab(Node *p_tab_control); + void _preview_control_picked(String p_class_name); protected: void _notification(int p_what); - static void _bind_methods(); public: void edit(const Ref<Theme> &p_theme); + Ref<Theme> get_edited_theme(); ThemeEditor(); }; diff --git a/editor/plugins/theme_editor_preview.cpp b/editor/plugins/theme_editor_preview.cpp new file mode 100644 index 0000000000..0b02150444 --- /dev/null +++ b/editor/plugins/theme_editor_preview.cpp @@ -0,0 +1,464 @@ +/*************************************************************************/ +/* theme_editor_preview.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "theme_editor_preview.h" + +#include "core/input/input.h" +#include "core/math/math_funcs.h" +#include "scene/resources/packed_scene.h" + +#include "editor/editor_scale.h" + +void ThemeEditorPreview::set_preview_theme(const Ref<Theme> &p_theme) { + preview_content->set_theme(p_theme); +} + +void ThemeEditorPreview::add_preview_overlay(Control *p_overlay) { + preview_overlay->add_child(p_overlay); + p_overlay->hide(); +} + +void ThemeEditorPreview::_propagate_redraw(Control *p_at) { + p_at->notification(NOTIFICATION_THEME_CHANGED); + p_at->minimum_size_changed(); + p_at->update(); + for (int i = 0; i < p_at->get_child_count(); i++) { + Control *a = Object::cast_to<Control>(p_at->get_child(i)); + if (a) { + _propagate_redraw(a); + } + } +} + +void ThemeEditorPreview::_refresh_interval() { + // In case the project settings have changed. + preview_bg->set_color(GLOBAL_GET("rendering/environment/defaults/default_clear_color")); + + _propagate_redraw(preview_bg); + _propagate_redraw(preview_content); +} + +void ThemeEditorPreview::_preview_visibility_changed() { + set_process(is_visible()); +} + +void ThemeEditorPreview::_picker_button_cbk() { + picker_overlay->set_visible(picker_button->is_pressed()); +} + +Control *ThemeEditorPreview::_find_hovered_control(Control *p_parent, Vector2 p_mouse_position) { + Control *found = nullptr; + + for (int i = 0; i < p_parent->get_child_count(); i++) { + Control *cc = Object::cast_to<Control>(p_parent->get_child(i)); + if (!cc || !cc->is_visible()) { + continue; + } + + Rect2 crect = cc->get_rect(); + if (crect.has_point(p_mouse_position)) { + // Check if there is a child control under mouse. + if (cc->get_child_count() > 0) { + found = _find_hovered_control(cc, p_mouse_position - cc->get_position()); + } + + // If there are no applicable children, use the control itself. + if (!found) { + found = cc; + } + break; + } + } + + return found; +} + +void ThemeEditorPreview::_draw_picker_overlay() { + if (!picker_button->is_pressed()) { + return; + } + + picker_overlay->draw_rect(Rect2(Vector2(0.0, 0.0), picker_overlay->get_size()), get_theme_color("preview_picker_overlay_color", "ThemeEditor")); + if (hovered_control) { + Rect2 highlight_rect = hovered_control->get_global_rect(); + highlight_rect.position = picker_overlay->get_global_transform().affine_inverse().xform(highlight_rect.position); + + picker_overlay->draw_style_box(get_theme_stylebox("preview_picker_overlay", "ThemeEditor"), highlight_rect); + } +} + +void ThemeEditorPreview::_gui_input_picker_overlay(const Ref<InputEvent> &p_event) { + if (!picker_button->is_pressed()) { + return; + } + + Ref<InputEventMouseButton> mb = p_event; + + if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (hovered_control) { + StringName theme_type = hovered_control->get_theme_custom_type(); + if (theme_type == StringName()) { + theme_type = hovered_control->get_class_name(); + } + + emit_signal("control_picked", theme_type); + picker_button->set_pressed(false); + picker_overlay->set_visible(false); + } + } + + Ref<InputEventMouseMotion> mm = p_event; + + if (mm.is_valid()) { + Vector2 mp = preview_content->get_local_mouse_position(); + hovered_control = _find_hovered_control(preview_content, mp); + picker_overlay->update(); + } +} + +void ThemeEditorPreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + if (is_visible_in_tree()) { + set_process(true); + } + + connect("visibility_changed", callable_mp(this, &ThemeEditorPreview::_preview_visibility_changed)); + [[fallthrough]]; + } + case NOTIFICATION_THEME_CHANGED: { + picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); + } break; + case NOTIFICATION_PROCESS: { + time_left -= get_process_delta_time(); + if (time_left < 0) { + time_left = 1.5; + _refresh_interval(); + } + } break; + } +} + +void ThemeEditorPreview::_bind_methods() { + ADD_SIGNAL(MethodInfo("control_picked", PropertyInfo(Variant::STRING, "class_name"))); +} + +ThemeEditorPreview::ThemeEditorPreview() { + preview_toolbar = memnew(HBoxContainer); + add_child(preview_toolbar); + + picker_button = memnew(Button); + preview_toolbar->add_child(picker_button); + picker_button->set_flat(true); + picker_button->set_toggle_mode(true); + picker_button->set_tooltip(TTR("Toggle the control picker, allowing to visually select control types for edit.")); + picker_button->connect("pressed", callable_mp(this, &ThemeEditorPreview::_picker_button_cbk)); + + MarginContainer *preview_body = memnew(MarginContainer); + preview_body->set_custom_minimum_size(Size2(480, 0) * EDSCALE); + preview_body->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(preview_body); + + ScrollContainer *preview_container = memnew(ScrollContainer); + preview_container->set_enable_v_scroll(true); + preview_container->set_enable_h_scroll(true); + preview_body->add_child(preview_container); + + MarginContainer *preview_root = memnew(MarginContainer); + preview_container->add_child(preview_root); + preview_root->set_theme(Theme::get_default()); + preview_root->set_clip_contents(true); + preview_root->set_custom_minimum_size(Size2(450, 0) * EDSCALE); + preview_root->set_v_size_flags(SIZE_EXPAND_FILL); + preview_root->set_h_size_flags(SIZE_EXPAND_FILL); + + preview_bg = memnew(ColorRect); + preview_bg->set_anchors_and_offsets_preset(PRESET_WIDE); + preview_bg->set_color(GLOBAL_GET("rendering/environment/defaults/default_clear_color")); + preview_root->add_child(preview_bg); + + preview_content = memnew(MarginContainer); + preview_root->add_child(preview_content); + preview_content->add_theme_constant_override("margin_right", 4 * EDSCALE); + preview_content->add_theme_constant_override("margin_top", 4 * EDSCALE); + preview_content->add_theme_constant_override("margin_left", 4 * EDSCALE); + preview_content->add_theme_constant_override("margin_bottom", 4 * EDSCALE); + + preview_overlay = memnew(MarginContainer); + preview_overlay->set_mouse_filter(MOUSE_FILTER_IGNORE); + preview_body->add_child(preview_overlay); + + picker_overlay = memnew(Control); + add_preview_overlay(picker_overlay); + picker_overlay->connect("draw", callable_mp(this, &ThemeEditorPreview::_draw_picker_overlay)); + picker_overlay->connect("gui_input", callable_mp(this, &ThemeEditorPreview::_gui_input_picker_overlay)); +} + +DefaultThemeEditorPreview::DefaultThemeEditorPreview() { + Panel *main_panel = memnew(Panel); + preview_content->add_child(main_panel); + + MarginContainer *main_mc = memnew(MarginContainer); + main_mc->add_theme_constant_override("margin_right", 4 * EDSCALE); + main_mc->add_theme_constant_override("margin_top", 4 * EDSCALE); + main_mc->add_theme_constant_override("margin_left", 4 * EDSCALE); + main_mc->add_theme_constant_override("margin_bottom", 4 * EDSCALE); + preview_content->add_child(main_mc); + + HBoxContainer *main_hb = memnew(HBoxContainer); + main_mc->add_child(main_hb); + main_hb->add_theme_constant_override("separation", 20 * EDSCALE); + + VBoxContainer *first_vb = memnew(VBoxContainer); + main_hb->add_child(first_vb); + first_vb->set_h_size_flags(SIZE_EXPAND_FILL); + first_vb->add_theme_constant_override("separation", 10 * EDSCALE); + + first_vb->add_child(memnew(Label("Label"))); + + first_vb->add_child(memnew(Button("Button"))); + Button *bt = memnew(Button); + bt->set_text(TTR("Toggle Button")); + bt->set_toggle_mode(true); + bt->set_pressed(true); + first_vb->add_child(bt); + bt = memnew(Button); + bt->set_text(TTR("Disabled Button")); + bt->set_disabled(true); + first_vb->add_child(bt); + Button *tb = memnew(Button); + tb->set_flat(true); + tb->set_text("Button"); + first_vb->add_child(tb); + + CheckButton *cb = memnew(CheckButton); + cb->set_text("CheckButton"); + first_vb->add_child(cb); + CheckBox *cbx = memnew(CheckBox); + cbx->set_text("CheckBox"); + first_vb->add_child(cbx); + + MenuButton *test_menu_button = memnew(MenuButton); + test_menu_button->set_text("MenuButton"); + test_menu_button->get_popup()->add_item(TTR("Item")); + test_menu_button->get_popup()->add_item(TTR("Disabled Item")); + test_menu_button->get_popup()->set_item_disabled(1, true); + test_menu_button->get_popup()->add_separator(); + test_menu_button->get_popup()->add_check_item(TTR("Check Item")); + test_menu_button->get_popup()->add_check_item(TTR("Checked Item")); + test_menu_button->get_popup()->set_item_checked(4, true); + test_menu_button->get_popup()->add_separator(); + test_menu_button->get_popup()->add_radio_check_item(TTR("Radio Item")); + test_menu_button->get_popup()->add_radio_check_item(TTR("Checked Radio Item")); + test_menu_button->get_popup()->set_item_checked(7, true); + test_menu_button->get_popup()->add_separator(TTR("Named Separator")); + + PopupMenu *test_submenu = memnew(PopupMenu); + test_menu_button->get_popup()->add_child(test_submenu); + test_submenu->set_name("submenu"); + test_menu_button->get_popup()->add_submenu_item(TTR("Submenu"), "submenu"); + test_submenu->add_item(TTR("Subitem 1")); + test_submenu->add_item(TTR("Subitem 2")); + first_vb->add_child(test_menu_button); + + OptionButton *test_option_button = memnew(OptionButton); + test_option_button->add_item("OptionButton"); + test_option_button->add_separator(); + test_option_button->add_item(TTR("Has")); + test_option_button->add_item(TTR("Many")); + test_option_button->add_item(TTR("Options")); + first_vb->add_child(test_option_button); + first_vb->add_child(memnew(ColorPickerButton)); + + VBoxContainer *second_vb = memnew(VBoxContainer); + second_vb->set_h_size_flags(SIZE_EXPAND_FILL); + main_hb->add_child(second_vb); + second_vb->add_theme_constant_override("separation", 10 * EDSCALE); + LineEdit *le = memnew(LineEdit); + le->set_text("LineEdit"); + second_vb->add_child(le); + le = memnew(LineEdit); + le->set_text(TTR("Disabled LineEdit")); + le->set_editable(false); + second_vb->add_child(le); + TextEdit *te = memnew(TextEdit); + te->set_text("TextEdit"); + te->set_custom_minimum_size(Size2(0, 100) * EDSCALE); + second_vb->add_child(te); + second_vb->add_child(memnew(SpinBox)); + + HBoxContainer *vhb = memnew(HBoxContainer); + second_vb->add_child(vhb); + vhb->set_custom_minimum_size(Size2(0, 100) * EDSCALE); + vhb->add_child(memnew(VSlider)); + VScrollBar *vsb = memnew(VScrollBar); + vsb->set_page(25); + vhb->add_child(vsb); + vhb->add_child(memnew(VSeparator)); + VBoxContainer *hvb = memnew(VBoxContainer); + vhb->add_child(hvb); + hvb->set_alignment(BoxContainer::ALIGN_CENTER); + hvb->set_h_size_flags(SIZE_EXPAND_FILL); + hvb->add_child(memnew(HSlider)); + HScrollBar *hsb = memnew(HScrollBar); + hsb->set_page(25); + hvb->add_child(hsb); + HSlider *hs = memnew(HSlider); + hs->set_editable(false); + hvb->add_child(hs); + hvb->add_child(memnew(HSeparator)); + ProgressBar *pb = memnew(ProgressBar); + pb->set_value(50); + hvb->add_child(pb); + + VBoxContainer *third_vb = memnew(VBoxContainer); + third_vb->set_h_size_flags(SIZE_EXPAND_FILL); + third_vb->add_theme_constant_override("separation", 10 * EDSCALE); + main_hb->add_child(third_vb); + + TabContainer *tc = memnew(TabContainer); + third_vb->add_child(tc); + tc->set_custom_minimum_size(Size2(0, 135) * EDSCALE); + Control *tcc = memnew(Control); + tcc->set_name(TTR("Tab 1")); + tc->add_child(tcc); + tcc = memnew(Control); + tcc->set_name(TTR("Tab 2")); + tc->add_child(tcc); + tcc = memnew(Control); + tcc->set_name(TTR("Tab 3")); + tc->add_child(tcc); + tc->set_tab_disabled(2, true); + + Tree *test_tree = memnew(Tree); + third_vb->add_child(test_tree); + test_tree->set_custom_minimum_size(Size2(0, 175) * EDSCALE); + + TreeItem *item = test_tree->create_item(); + item->set_text(0, "Tree"); + item = test_tree->create_item(test_tree->get_root()); + item->set_text(0, "Item"); + item = test_tree->create_item(test_tree->get_root()); + item->set_editable(0, true); + item->set_text(0, TTR("Editable Item")); + TreeItem *sub_tree = test_tree->create_item(test_tree->get_root()); + sub_tree->set_text(0, TTR("Subtree")); + item = test_tree->create_item(sub_tree); + item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + item->set_editable(0, true); + item->set_text(0, "Check Item"); + item = test_tree->create_item(sub_tree); + item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE); + item->set_editable(0, true); + item->set_range_config(0, 0, 20, 0.1); + item->set_range(0, 2); + item = test_tree->create_item(sub_tree); + item->set_cell_mode(0, TreeItem::CELL_MODE_RANGE); + item->set_editable(0, true); + item->set_text(0, TTR("Has,Many,Options")); + item->set_range(0, 2); +} + +void SceneThemeEditorPreview::_reload_scene() { + if (loaded_scene.is_null()) { + return; + } + + if (loaded_scene->get_path().is_empty() || !ResourceLoader::exists(loaded_scene->get_path())) { + EditorNode::get_singleton()->show_warning(TTR("Invalid path, the PackedScene resource was probably moved or removed.")); + emit_signal("scene_invalidated"); + return; + } + + for (int i = preview_content->get_child_count() - 1; i >= 0; i--) { + Node *node = preview_content->get_child(i); + node->queue_delete(); + preview_content->remove_child(node); + } + + Node *instance = loaded_scene->instantiate(); + if (!instance || !Object::cast_to<Control>(instance)) { + EditorNode::get_singleton()->show_warning(TTR("Invalid PackedScene resource, must have a Control node at its root.")); + emit_signal("scene_invalidated"); + return; + } + + preview_content->add_child(instance); + emit_signal("scene_reloaded"); +} + +void SceneThemeEditorPreview::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + reload_scene_button->set_icon(get_theme_icon("Reload", "EditorIcons")); + } break; + } +} + +void SceneThemeEditorPreview::_bind_methods() { + ADD_SIGNAL(MethodInfo("scene_invalidated")); + ADD_SIGNAL(MethodInfo("scene_reloaded")); +} + +bool SceneThemeEditorPreview::set_preview_scene(const String &p_path) { + loaded_scene = ResourceLoader::load(p_path); + if (loaded_scene.is_null()) { + EditorNode::get_singleton()->show_warning(TTR("Invalid file, not a PackedScene resource.")); + return false; + } + + Node *instance = loaded_scene->instantiate(); + if (!instance || !Object::cast_to<Control>(instance)) { + EditorNode::get_singleton()->show_warning(TTR("Invalid PackedScene resource, must have a Control node at its root.")); + return false; + } + + preview_content->add_child(instance); + return true; +} + +String SceneThemeEditorPreview::get_preview_scene_path() const { + if (loaded_scene.is_null()) { + return ""; + } + + return loaded_scene->get_path(); +} + +SceneThemeEditorPreview::SceneThemeEditorPreview() { + preview_toolbar->add_child(memnew(VSeparator)); + + reload_scene_button = memnew(Button); + reload_scene_button->set_flat(true); + reload_scene_button->set_tooltip(TTR("Reload the scene to reflect its most actual state.")); + preview_toolbar->add_child(reload_scene_button); + reload_scene_button->connect("pressed", callable_mp(this, &SceneThemeEditorPreview::_reload_scene)); +} diff --git a/editor/plugins/theme_editor_preview.h b/editor/plugins/theme_editor_preview.h new file mode 100644 index 0000000000..efb7e424d4 --- /dev/null +++ b/editor/plugins/theme_editor_preview.h @@ -0,0 +1,118 @@ +/*************************************************************************/ +/* theme_editor_preview.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef THEME_EDITOR_PREVIEW_H +#define THEME_EDITOR_PREVIEW_H + +#include "scene/gui/box_container.h" +#include "scene/gui/check_box.h" +#include "scene/gui/check_button.h" +#include "scene/gui/color_picker.h" +#include "scene/gui/color_rect.h" +#include "scene/gui/label.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/menu_button.h" +#include "scene/gui/option_button.h" +#include "scene/gui/panel.h" +#include "scene/gui/progress_bar.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/separator.h" +#include "scene/gui/spin_box.h" +#include "scene/gui/tab_container.h" +#include "scene/gui/text_edit.h" +#include "scene/gui/tree.h" +#include "scene/resources/theme.h" + +#include "editor/editor_node.h" + +class ThemeEditorPreview : public VBoxContainer { + GDCLASS(ThemeEditorPreview, VBoxContainer); + + ColorRect *preview_bg; + MarginContainer *preview_overlay; + Control *picker_overlay; + Control *hovered_control = nullptr; + + double time_left = 0; + + void _propagate_redraw(Control *p_at); + void _refresh_interval(); + void _preview_visibility_changed(); + + void _picker_button_cbk(); + Control *_find_hovered_control(Control *p_parent, Vector2 p_mouse_position); + + void _draw_picker_overlay(); + void _gui_input_picker_overlay(const Ref<InputEvent> &p_event); + +protected: + HBoxContainer *preview_toolbar; + MarginContainer *preview_content; + Button *picker_button; + + void add_preview_overlay(Control *p_overlay); + + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_preview_theme(const Ref<Theme> &p_theme); + + ThemeEditorPreview(); +}; + +class DefaultThemeEditorPreview : public ThemeEditorPreview { + GDCLASS(DefaultThemeEditorPreview, ThemeEditorPreview); + +public: + DefaultThemeEditorPreview(); +}; + +class SceneThemeEditorPreview : public ThemeEditorPreview { + GDCLASS(SceneThemeEditorPreview, ThemeEditorPreview); + + Ref<PackedScene> loaded_scene; + + Button *reload_scene_button; + + void _reload_scene(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + bool set_preview_scene(const String &p_path); + String get_preview_scene_path() const; + + SceneThemeEditorPreview(); +}; + +#endif // THEME_EDITOR_PREVIEW_H diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp deleted file mode 100644 index bd721244ea..0000000000 --- a/editor/plugins/tile_map_editor_plugin.cpp +++ /dev/null @@ -1,2329 +0,0 @@ -/*************************************************************************/ -/* tile_map_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "tile_map_editor_plugin.h" - -#include "canvas_item_editor_plugin.h" -#include "core/input/input.h" -#include "core/math/math_funcs.h" -#include "core/os/keyboard.h" -#include "editor/editor_scale.h" -#include "editor/editor_settings.h" -#include "scene/gui/split_container.h" - -void TileMapEditor::_node_removed(Node *p_node) { - if (p_node == node && node) { - Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed); - - if (node->is_connected("settings_changed", callable_tileset_settings_changed)) { - // Fixes #44824, which describes a situation where you can reselect a TileMap node without first de-selecting it when switching scenes. - node->disconnect("settings_changed", callable_tileset_settings_changed); - } - node = nullptr; - } -} - -void TileMapEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_PROCESS: { - if (bucket_queue.size()) { - CanvasItemEditor::get_singleton()->update_viewport(); - } - - } break; - - case NOTIFICATION_ENTER_TREE: { - get_tree()->connect("node_removed", callable_mp(this, &TileMapEditor::_node_removed)); - [[fallthrough]]; - } - - case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: { - if (is_visible_in_tree()) { - _update_palette(); - } - - paint_button->set_icon(get_theme_icon("Edit", "EditorIcons")); - line_button->set_icon(get_theme_icon("CurveLinear", "EditorIcons")); - rectangle_button->set_icon(get_theme_icon("Rectangle", "EditorIcons")); - bucket_fill_button->set_icon(get_theme_icon("Bucket", "EditorIcons")); - picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); - select_button->set_icon(get_theme_icon("ActionCopy", "EditorIcons")); - - rotate_left_button->set_icon(get_theme_icon("RotateLeft", "EditorIcons")); - rotate_right_button->set_icon(get_theme_icon("RotateRight", "EditorIcons")); - flip_horizontal_button->set_icon(get_theme_icon("MirrorX", "EditorIcons")); - flip_vertical_button->set_icon(get_theme_icon("MirrorY", "EditorIcons")); - clear_transform_button->set_icon(get_theme_icon("Clear", "EditorIcons")); - - search_box->set_right_icon(get_theme_icon("Search", "EditorIcons")); - search_box->set_clear_button_enabled(true); - - PopupMenu *p = options->get_popup(); - p->set_item_icon(p->get_item_index(OPTION_CUT), get_theme_icon("ActionCut", "EditorIcons")); - p->set_item_icon(p->get_item_index(OPTION_COPY), get_theme_icon("Duplicate", "EditorIcons")); - p->set_item_icon(p->get_item_index(OPTION_ERASE_SELECTION), get_theme_icon("Remove", "EditorIcons")); - - } break; - - case NOTIFICATION_EXIT_TREE: { - get_tree()->disconnect("node_removed", callable_mp(this, &TileMapEditor::_node_removed)); - } break; - - case NOTIFICATION_APPLICATION_FOCUS_OUT: { - if (tool == TOOL_PAINTING) { - Vector<int> ids = get_selected_tiles(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - _set_cell(over_tile, ids, flip_h, flip_v, transpose); - _finish_undo(); - - paint_undo.clear(); - } - - tool = TOOL_NONE; - _update_button_tool(); - } - - // set flag to ignore over_tile on refocus - refocus_over_tile = true; - } break; - } -} - -void TileMapEditor::_update_button_tool() { - Button *tb[6] = { paint_button, line_button, rectangle_button, bucket_fill_button, picker_button, select_button }; - - // Unpress all buttons - for (int i = 0; i < 6; i++) { - tb[i]->set_pressed(false); - } - - // Press the good button - switch (tool) { - case TOOL_NONE: - case TOOL_PAINTING: { - paint_button->set_pressed(true); - } break; - case TOOL_LINE_PAINT: { - line_button->set_pressed(true); - } break; - case TOOL_RECTANGLE_PAINT: { - rectangle_button->set_pressed(true); - } break; - case TOOL_BUCKET: { - bucket_fill_button->set_pressed(true); - } break; - case TOOL_PICKING: { - picker_button->set_pressed(true); - } break; - case TOOL_SELECTING: { - select_button->set_pressed(true); - } break; - default: - break; - } - - if (tool != TOOL_PICKING) { - last_tool = tool; - } -} - -void TileMapEditor::_button_tool_select(int p_tool) { - tool = (Tool)p_tool; - _update_button_tool(); - switch (tool) { - case TOOL_SELECTING: { - selection_active = false; - } break; - default: - break; - } - CanvasItemEditor::get_singleton()->update_viewport(); -} - -void TileMapEditor::_menu_option(int p_option) { - switch (p_option) { - case OPTION_COPY: { - _update_copydata(); - - if (selection_active) { - tool = TOOL_PASTING; - - CanvasItemEditor::get_singleton()->update_viewport(); - } - } break; - case OPTION_ERASE_SELECTION: { - if (!selection_active) { - return; - } - - _start_undo(TTR("Erase Selection")); - _erase_selection(); - _finish_undo(); - - selection_active = false; - copydata.clear(); - - CanvasItemEditor::get_singleton()->update_viewport(); - } break; - case OPTION_FIX_INVALID: { - undo_redo->create_action(TTR("Fix Invalid Tiles")); - undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data")); - node->fix_invalid_tiles(); - undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data")); - undo_redo->commit_action(); - - } break; - case OPTION_CUT: { - if (selection_active) { - _update_copydata(); - - _start_undo(TTR("Cut Selection")); - _erase_selection(); - _finish_undo(); - - selection_active = false; - - tool = TOOL_PASTING; - - CanvasItemEditor::get_singleton()->update_viewport(); - } - } break; - } - _update_button_tool(); -} - -void TileMapEditor::_palette_selected(int index) { - _update_palette(); -} - -void TileMapEditor::_palette_multi_selected(int index, bool selected) { - _update_palette(); -} - -void TileMapEditor::_palette_input(const Ref<InputEvent> &p_event) { - const Ref<InputEventMouseButton> mb = p_event; - - // Zoom in/out using Ctrl + mouse wheel. - if (mb.is_valid() && mb->is_pressed() && mb->get_command()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { - size_slider->set_value(size_slider->get_value() + 0.2); - } - - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { - size_slider->set_value(size_slider->get_value() - 0.2); - } - } -} - -void TileMapEditor::_canvas_mouse_enter() { - mouse_over = true; - CanvasItemEditor::get_singleton()->update_viewport(); -} - -void TileMapEditor::_canvas_mouse_exit() { - mouse_over = false; - CanvasItemEditor::get_singleton()->update_viewport(); -} - -Vector<int> TileMapEditor::get_selected_tiles() const { - Vector<int> items = palette->get_selected_items(); - - if (items.size() == 0) { - items.push_back(TileMap::INVALID_CELL); - return items; - } - - for (int i = items.size() - 1; i >= 0; i--) { - items.write[i] = palette->get_item_metadata(items[i]); - } - return items; -} - -void TileMapEditor::set_selected_tiles(Vector<int> p_tiles) { - palette->deselect_all(); - - for (int i = p_tiles.size() - 1; i >= 0; i--) { - int idx = palette->find_metadata(p_tiles[i]); - - if (idx >= 0) { - palette->select(idx, false); - } - } - - palette->ensure_current_is_visible(); -} - -Dictionary TileMapEditor::_create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord) { - Dictionary cell; - - cell["id"] = tile; - cell["flip_h"] = flip_x; - cell["flip_y"] = flip_y; - cell["transpose"] = transpose; - cell["auto_coord"] = autotile_coord; - - return cell; -} - -void TileMapEditor::_create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new) { - Dictionary cell_old = _create_cell_dictionary(p_cell_old.idx, p_cell_old.xf, p_cell_old.yf, p_cell_old.tr, p_cell_old.ac); - Dictionary cell_new = _create_cell_dictionary(p_cell_new.idx, p_cell_new.xf, p_cell_new.yf, p_cell_new.tr, p_cell_new.ac); - - undo_redo->add_undo_method(node, "_set_celld", p_vec, cell_old); - undo_redo->add_do_method(node, "_set_celld", p_vec, cell_new); -} - -void TileMapEditor::_start_undo(const String &p_action) { - undo_data.clear(); - undo_redo->create_action(p_action); -} - -void TileMapEditor::_finish_undo() { - if (undo_data.size()) { - for (Map<Point2i, CellOp>::Element *E = undo_data.front(); E; E = E->next()) { - _create_set_cell_undo_redo(E->key(), E->get(), _get_op_from_cell(E->key())); - } - - undo_data.clear(); - } - - undo_redo->commit_action(); -} - -void TileMapEditor::_set_cell(const Point2i &p_pos, Vector<int> p_values, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord) { - ERR_FAIL_COND(!node); - - if (p_values.size() == 0) { - return; - } - - int p_value = p_values[Math::rand() % p_values.size()]; - int prev_val = node->get_cell(p_pos.x, p_pos.y); - - bool prev_flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y); - bool prev_flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y); - bool prev_transpose = node->is_cell_transposed(p_pos.x, p_pos.y); - Vector2 prev_position = node->get_cell_autotile_coord(p_pos.x, p_pos.y); - - Vector2 position; - int current = manual_palette->get_current(); - if (current != -1) { - if (tool != TOOL_PASTING) { - position = manual_palette->get_item_metadata(current); - } else { - position = p_autotile_coord; - } - } else { - // If there is no manual tile selected, that either means that - // autotiling is enabled, or the given tile is not autotiling. Either - // way, the coordinate of the tile does not matter, so assigning it to - // the coordinate of the existing tile works fine. - position = prev_position; - } - - if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose && prev_position == position) { - return; // Check that it's actually different. - } - - for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) { - for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) { - Point2i p = Point2i(x, y); - if (!undo_data.has(p)) { - undo_data[p] = _get_op_from_cell(p); - } - } - } - - node->_set_celld(p_pos, _create_cell_dictionary(p_value, p_flip_h, p_flip_v, p_transpose, p_autotile_coord)); - - if (tool == TOOL_PASTING) { - return; - } - - if (manual_autotile || (p_value != -1 && node->get_tileset()->tile_get_tile_mode(p_value) == TileSet::ATLAS_TILE)) { - if (current != -1) { - node->set_cell_autotile_coord(p_pos.x, p_pos.y, position); - } else if (node->get_tileset()->tile_get_tile_mode(p_value) == TileSet::ATLAS_TILE && priority_atlastile) { - // BIND_CENTER is used to indicate that bitmask should not update for this tile cell. - node->get_tileset()->autotile_set_bitmask(p_value, Vector2(p_pos.x, p_pos.y), TileSet::BIND_CENTER); - node->update_cell_bitmask(p_pos.x, p_pos.y); - } - } else { - node->update_bitmask_area(Point2(p_pos)); - } -} - -void TileMapEditor::_manual_toggled(bool p_enabled) { - manual_autotile = p_enabled; - _update_palette(); -} - -void TileMapEditor::_priority_toggled(bool p_enabled) { - priority_atlastile = p_enabled; - _update_palette(); -} - -void TileMapEditor::_text_entered(const String &p_text) { - canvas_item_editor_viewport->grab_focus(); -} - -void TileMapEditor::_text_changed(const String &p_text) { - _update_palette(); -} - -void TileMapEditor::_sbox_input(const Ref<InputEvent> &p_ie) { - Ref<InputEventKey> k = p_ie; - - if (k.is_valid() && (k->get_keycode() == KEY_UP || - k->get_keycode() == KEY_DOWN || - k->get_keycode() == KEY_PAGEUP || - k->get_keycode() == KEY_PAGEDOWN)) { - palette->call("_gui_input", k); - search_box->accept_event(); - } -} - -// Implementation detail of TileMapEditor::_update_palette(); -// In modern C++ this could have been inside its body. -namespace { -struct _PaletteEntry { - int id = 0; - String name; - - bool operator<(const _PaletteEntry &p_rhs) const { - // Natural no case comparison will compare strings based on CharType - // order (except digits) and on numbers that start on the same position. - return name.naturalnocasecmp_to(p_rhs.name) < 0; - } -}; -} // namespace - -void TileMapEditor::_update_palette() { - if (!node) { - return; - } - - // Update the clear button. - clear_transform_button->set_disabled(!flip_h && !flip_v && !transpose); - - // Update the palette. - Vector<int> selected = get_selected_tiles(); - int selected_single = palette->get_current(); - int selected_manual = manual_palette->get_current(); - palette->clear(); - manual_palette->clear(); - manual_palette->hide(); - - Ref<TileSet> tileset = node->get_tileset(); - if (tileset.is_null()) { - search_box->set_text(""); - search_box->set_editable(false); - info_message->show(); - return; - } - - search_box->set_editable(true); - info_message->hide(); - - List<int> tiles; - tileset->get_tile_list(&tiles); - if (tiles.is_empty()) { - return; - } - - float min_size = EDITOR_DEF("editors/tile_map/preview_size", 64); - min_size *= EDSCALE; - int hseparation = EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8); - bool show_tile_names = bool(EDITOR_DEF("editors/tile_map/show_tile_names", true)); - bool show_tile_ids = bool(EDITOR_DEF("editors/tile_map/show_tile_ids", false)); - bool sort_by_name = bool(EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true)); - - palette->add_theme_constant_override("hseparation", hseparation * EDSCALE); - - palette->set_fixed_icon_size(Size2(min_size, min_size)); - palette->set_fixed_column_width(min_size * MAX(size_slider->get_value(), 1)); - palette->set_same_column_width(true); - manual_palette->set_fixed_icon_size(Size2(min_size, min_size)); - manual_palette->set_same_column_width(true); - - String filter = search_box->get_text().strip_edges(); - - Vector<_PaletteEntry> entries; - - for (List<int>::Element *E = tiles.front(); E; E = E->next()) { - String name = tileset->tile_get_name(E->get()); - - if (name != "") { - if (show_tile_ids) { - if (sort_by_name) { - name = name + " - " + itos(E->get()); - } else { - name = itos(E->get()) + " - " + name; - } - } - } else { - name = "#" + itos(E->get()); - } - - if (filter != "" && !filter.is_subsequence_ofi(name)) { - continue; - } - - const _PaletteEntry entry = { E->get(), name }; - entries.push_back(entry); - } - - if (sort_by_name) { - entries.sort(); - } - - for (int i = 0; i < entries.size(); i++) { - if (show_tile_names) { - palette->add_item(entries[i].name); - } else { - palette->add_item(String()); - } - - Ref<Texture2D> tex = tileset->tile_get_texture(entries[i].id); - - if (tex.is_valid()) { - Rect2 region = tileset->tile_get_region(entries[i].id); - - if (tileset->tile_get_tile_mode(entries[i].id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(entries[i].id) == TileSet::ATLAS_TILE) { - int spacing = tileset->autotile_get_spacing(entries[i].id); - region.size = tileset->autotile_get_size(entries[i].id); - region.position += (region.size + Vector2(spacing, spacing)) * tileset->autotile_get_icon_coordinate(entries[i].id); - } - - // Transpose and flip. - palette->set_item_icon_transposed(palette->get_item_count() - 1, transpose); - if (flip_h) { - region.size.x = -region.size.x; - } - if (flip_v) { - region.size.y = -region.size.y; - } - - // Set region. - if (region.size != Size2()) { - palette->set_item_icon_region(palette->get_item_count() - 1, region); - } - - // Set icon. - palette->set_item_icon(palette->get_item_count() - 1, tex); - - // Modulation. - Color color = tileset->tile_get_modulate(entries[i].id); - palette->set_item_icon_modulate(palette->get_item_count() - 1, color); - } - - palette->set_item_metadata(palette->get_item_count() - 1, entries[i].id); - } - - int sel_tile = selected.get(0); - if (selected.get(0) != TileMap::INVALID_CELL) { - set_selected_tiles(selected); - sel_tile = selected.get(Math::rand() % selected.size()); - } else if (palette->get_item_count() > 0) { - palette->select(0); - sel_tile = palette->get_selected_items().get(0); - } - - if (sel_tile != TileMap::INVALID_CELL && ((manual_autotile && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) || (!priority_atlastile && tileset->tile_get_tile_mode(sel_tile) == TileSet::ATLAS_TILE))) { - const Map<Vector2, uint32_t> &tiles2 = tileset->autotile_get_bitmask_map(sel_tile); - - Vector<Vector2> entries2; - for (const Map<Vector2, uint32_t>::Element *E = tiles2.front(); E; E = E->next()) { - entries2.push_back(E->key()); - } - // Sort tiles in row-major order. - struct SwapComparator { - _FORCE_INLINE_ bool operator()(const Vector2 &v_l, const Vector2 &v_r) const { - return v_l.y != v_r.y ? v_l.y < v_r.y : v_l.x < v_r.x; - } - }; - entries2.sort_custom<SwapComparator>(); - - Ref<Texture2D> tex = tileset->tile_get_texture(sel_tile); - - for (int i = 0; i < entries2.size(); i++) { - manual_palette->add_item(String()); - - if (tex.is_valid()) { - Rect2 region = tileset->tile_get_region(sel_tile); - int spacing = tileset->autotile_get_spacing(sel_tile); - region.size = tileset->autotile_get_size(sel_tile); // !! - region.position += (region.size + Vector2(spacing, spacing)) * entries2[i]; - - if (!region.has_no_area()) { - manual_palette->set_item_icon_region(manual_palette->get_item_count() - 1, region); - } - - manual_palette->set_item_icon(manual_palette->get_item_count() - 1, tex); - } - - manual_palette->set_item_metadata(manual_palette->get_item_count() - 1, entries2[i]); - } - } - - if (manual_palette->get_item_count() > 0) { - // Only show the manual palette if at least tile exists in it. - if (selected_manual == -1 || selected_single != palette->get_current()) { - selected_manual = 0; - } - if (selected_manual < manual_palette->get_item_count()) { - manual_palette->set_current(selected_manual); - } - manual_palette->show(); - } - - if (sel_tile != TileMap::INVALID_CELL && tileset->tile_get_tile_mode(sel_tile) == TileSet::AUTO_TILE) { - manual_button->show(); - priority_button->hide(); - } else { - manual_button->hide(); - priority_button->show(); - } -} - -void TileMapEditor::_pick_tile(const Point2 &p_pos) { - int id = node->get_cell(p_pos.x, p_pos.y); - - if (id == TileMap::INVALID_CELL) { - return; - } - - if (search_box->get_text() != "") { - search_box->set_text(""); - _update_palette(); - } - - flip_h = node->is_cell_x_flipped(p_pos.x, p_pos.y); - flip_v = node->is_cell_y_flipped(p_pos.x, p_pos.y); - transpose = node->is_cell_transposed(p_pos.x, p_pos.y); - autotile_coord = node->get_cell_autotile_coord(p_pos.x, p_pos.y); - - Vector<int> selected; - selected.push_back(id); - set_selected_tiles(selected); - _update_palette(); - - if ((manual_autotile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::AUTO_TILE) || (!priority_atlastile && node->get_tileset()->tile_get_tile_mode(id) == TileSet::ATLAS_TILE)) { - manual_palette->select(manual_palette->find_metadata((Point2)autotile_coord)); - } - - CanvasItemEditor::get_singleton()->update_viewport(); -} - -Vector<Vector2> TileMapEditor::_bucket_fill(const Point2i &p_start, bool erase, bool preview) { - int prev_id = node->get_cell(p_start.x, p_start.y); - Vector<int> ids; - ids.push_back(TileMap::INVALID_CELL); - if (!erase) { - ids = get_selected_tiles(); - - if (ids.size() == 0 || ids[0] == TileMap::INVALID_CELL) { - return Vector<Vector2>(); - } - } else if (prev_id == TileMap::INVALID_CELL) { - return Vector<Vector2>(); - } - - // Check if the tile variation is the same - Vector2 prev_position = node->get_cell_autotile_coord(p_start.x, p_start.y); - if (ids.size() == 1 && ids[0] == prev_id) { - int current = manual_palette->get_current(); - Vector2 position = manual_palette->get_item_metadata(current); - if (prev_position == position) { - // Same ID and variation, nothing to change - return Vector<Vector2>(); - } - } - - Rect2i r = node->get_used_rect(); - - int area = r.get_area(); - if (preview) { - // Test if we can re-use the result from preview bucket fill - bool invalidate_cache = false; - // Area changed - if (r != bucket_cache_rect) { - _clear_bucket_cache(); - } - // Cache grid is not initialized - if (bucket_cache_visited == nullptr) { - bucket_cache_visited = new bool[area]; - invalidate_cache = true; - } - // Tile ID changed or position wasn't visited by the previous fill - const int loc = (p_start.x - r.position.x) + (p_start.y - r.position.y) * r.get_size().x; - const bool in_range = 0 <= loc && loc < area; - if (prev_id != bucket_cache_tile || (in_range && !bucket_cache_visited[loc])) { - invalidate_cache = true; - } - if (invalidate_cache) { - for (int i = 0; i < area; ++i) { - bucket_cache_visited[i] = false; - } - bucket_cache = Vector<Vector2>(); - bucket_cache_tile = prev_id; - bucket_cache_rect = r; - bucket_queue.clear(); - } - } - - Vector<Vector2> points; - Vector<Vector2> non_preview_cache; - int count = 0; - int limit = 0; - - if (preview) { - limit = 1024; - } else { - bucket_queue.clear(); - } - - bucket_queue.push_back(p_start); - - while (bucket_queue.size()) { - Point2i n = bucket_queue.front()->get(); - bucket_queue.pop_front(); - - if (!r.has_point(n)) { - continue; - } - - if (node->get_cell(n.x, n.y) == prev_id) { - if (preview) { - int loc = (n.x - r.position.x) + (n.y - r.position.y) * r.get_size().x; - if (bucket_cache_visited[loc]) { - continue; - } - bucket_cache_visited[loc] = true; - bucket_cache.push_back(n); - } else { - if (non_preview_cache.find(n) >= 0) { - continue; - } - points.push_back(n); - non_preview_cache.push_back(n); - } - - bucket_queue.push_back(Point2i(n.x, n.y + 1)); - bucket_queue.push_back(Point2i(n.x, n.y - 1)); - bucket_queue.push_back(Point2i(n.x + 1, n.y)); - bucket_queue.push_back(Point2i(n.x - 1, n.y)); - count++; - } - - if (limit > 0 && count >= limit) { - break; - } - } - - return preview ? bucket_cache : points; -} - -void TileMapEditor::_fill_points(const Vector<Vector2> &p_points, const Dictionary &p_op) { - int len = p_points.size(); - const Vector2 *pr = p_points.ptr(); - - Vector<int> ids = p_op["id"]; - bool xf = p_op["flip_h"]; - bool yf = p_op["flip_v"]; - bool tr = p_op["transpose"]; - - for (int i = 0; i < len; i++) { - _set_cell(pr[i], ids, xf, yf, tr); - node->make_bitmask_area_dirty(pr[i]); - } - if (!manual_autotile) { - node->update_dirty_bitmask(); - } -} - -void TileMapEditor::_erase_points(const Vector<Vector2> &p_points) { - int len = p_points.size(); - const Vector2 *pr = p_points.ptr(); - - for (int i = 0; i < len; i++) { - _set_cell(pr[i], invalid_cell); - } -} - -void TileMapEditor::_select(const Point2i &p_from, const Point2i &p_to) { - Point2i begin = p_from; - Point2i end = p_to; - - if (begin.x > end.x) { - SWAP(begin.x, end.x); - } - if (begin.y > end.y) { - SWAP(begin.y, end.y); - } - - rectangle.position = begin; - rectangle.size = end - begin; - - CanvasItemEditor::get_singleton()->update_viewport(); -} - -void TileMapEditor::_erase_selection() { - if (!selection_active) { - return; - } - - for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { - for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { - _set_cell(Point2i(j, i), invalid_cell, false, false, false); - } - } -} - -void TileMapEditor::_draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) { - Ref<Texture2D> t = node->get_tileset()->tile_get_texture(p_cell); - - if (t.is_null()) { - return; - } - - Vector2 tile_ofs = node->get_tileset()->tile_get_texture_offset(p_cell); - - Rect2 r = node->get_tileset()->tile_get_region(p_cell); - if (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::AUTO_TILE || node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::ATLAS_TILE) { - Vector2 offset; - if (tool != TOOL_PASTING) { - int selected = manual_palette->get_current(); - if ((manual_autotile || (node->get_tileset()->tile_get_tile_mode(p_cell) == TileSet::ATLAS_TILE && !priority_atlastile)) && selected != -1) { - offset = manual_palette->get_item_metadata(selected); - } else { - offset = node->get_tileset()->autotile_get_icon_coordinate(p_cell); - } - } else { - offset = p_autotile_coord; - } - - int spacing = node->get_tileset()->autotile_get_spacing(p_cell); - r.size = node->get_tileset()->autotile_get_size(p_cell); - r.position += (r.size + Vector2(spacing, spacing)) * offset; - } - Size2 cell_size = node->get_cell_size(); - bool centered_texture = node->is_centered_textures_enabled(); - bool compatibility_mode_enabled = node->is_compatibility_mode_enabled(); - Rect2 rect = Rect2(); - rect.position = node->map_to_world(p_point) + node->get_cell_draw_offset(); - - if (r.has_no_area()) { - rect.size = t->get_size(); - } else { - rect.size = r.size; - } - - if (compatibility_mode_enabled && !centered_texture) { - if (rect.size.y > rect.size.x) { - if ((p_flip_h && (p_flip_v || p_transpose)) || (p_flip_v && !p_transpose)) { - tile_ofs.y += rect.size.y - rect.size.x; - } - } else if (rect.size.y < rect.size.x) { - if ((p_flip_v && (p_flip_h || p_transpose)) || (p_flip_h && !p_transpose)) { - tile_ofs.x += rect.size.x - rect.size.y; - } - } - } - - if (p_transpose) { - SWAP(tile_ofs.x, tile_ofs.y); - if (centered_texture) { - rect.position.x += cell_size.x / 2 - rect.size.y / 2; - rect.position.y += cell_size.y / 2 - rect.size.x / 2; - } - } else if (centered_texture) { - rect.position += cell_size / 2 - rect.size / 2; - } - - if (p_flip_h) { - rect.size.x *= -1.0; - tile_ofs.x *= -1.0; - } - - if (p_flip_v) { - rect.size.y *= -1.0; - tile_ofs.y *= -1.0; - } - - if (compatibility_mode_enabled && !centered_texture) { - if (node->get_tile_origin() == TileMap::TILE_ORIGIN_TOP_LEFT) { - rect.position += tile_ofs; - } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_BOTTOM_LEFT) { - rect.position += tile_ofs; - - if (p_transpose) { - if (p_flip_h) { - rect.position.x -= cell_size.x; - } else { - rect.position.x += cell_size.x; - } - } else { - if (p_flip_v) { - rect.position.y -= cell_size.y; - } else { - rect.position.y += cell_size.y; - } - } - - } else if (node->get_tile_origin() == TileMap::TILE_ORIGIN_CENTER) { - rect.position += tile_ofs; - - if (p_flip_h) { - rect.position.x -= cell_size.x / 2; - } else { - rect.position.x += cell_size.x / 2; - } - - if (p_flip_v) { - rect.position.y -= cell_size.y / 2; - } else { - rect.position.y += cell_size.y / 2; - } - } - } else { - rect.position += tile_ofs; - } - - Color modulate = node->get_tileset()->tile_get_modulate(p_cell); - modulate.a = 0.5; - - Transform2D old_transform = p_viewport->get_viewport_transform(); - p_viewport->draw_set_transform_matrix(p_xform); // Take into account TileMap transformation when displaying cell - if (r.has_no_area()) { - p_viewport->draw_texture_rect(t, rect, false, modulate, p_transpose); - } else { - p_viewport->draw_texture_rect_region(t, rect, r, modulate, p_transpose); - } - p_viewport->draw_set_transform_matrix(old_transform); -} - -void TileMapEditor::_draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform) { - Vector<Vector2> points = _bucket_fill(p_point, false, true); - const Vector2 *pr = points.ptr(); - int len = points.size(); - - for (int i = 0; i < len; ++i) { - _draw_cell(p_viewport, p_cell, pr[i], p_flip_h, p_flip_v, p_transpose, p_autotile_coord, p_xform); - } -} - -void TileMapEditor::_clear_bucket_cache() { - if (bucket_cache_visited) { - delete[] bucket_cache_visited; - bucket_cache_visited = nullptr; - } -} - -void TileMapEditor::_update_copydata() { - copydata.clear(); - - if (!selection_active) { - return; - } - - for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { - for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { - TileData tcd; - - tcd.cell = node->get_cell(j, i); - if (tcd.cell != TileMap::INVALID_CELL) { - tcd.pos = Point2i(j, i); - tcd.flip_h = node->is_cell_x_flipped(j, i); - tcd.flip_v = node->is_cell_y_flipped(j, i); - tcd.transpose = node->is_cell_transposed(j, i); - tcd.autotile_coord = node->get_cell_autotile_coord(j, i); - - copydata.push_back(tcd); - } - } - } -} - -static inline Vector<Point2i> line(int x0, int x1, int y0, int y1) { - Vector<Point2i> points; - - float dx = ABS(x1 - x0); - float dy = ABS(y1 - y0); - - int x = x0; - int y = y0; - - int sx = x0 > x1 ? -1 : 1; - int sy = y0 > y1 ? -1 : 1; - - if (dx > dy) { - float err = dx / 2; - - for (; x != x1; x += sx) { - points.push_back(Vector2(x, y)); - - err -= dy; - if (err < 0) { - y += sy; - err += dx; - } - } - } else { - float err = dy / 2; - - for (; y != y1; y += sy) { - points.push_back(Vector2(x, y)); - - err -= dx; - if (err < 0) { - x += sx; - err += dy; - } - } - } - - points.push_back(Vector2(x, y)); - - return points; -} - -bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) { - if (!node || !node->get_tileset().is_valid() || !node->is_visible_in_tree() || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) { - return false; - } - - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform(); - Transform2D xform_inv = xform.affine_inverse(); - - Ref<InputEventMouseButton> mb = p_event; - - if (mb.is_valid()) { - if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (mb->is_pressed()) { - if (Input::get_singleton()->is_key_pressed(KEY_SPACE)) { - return false; // Drag. - } - - if (tool == TOOL_NONE) { - tool = TOOL_PAINTING; - _update_button_tool(); - - if (mb->get_command()) { - tool = TOOL_PICKING; - _pick_tile(over_tile); - _update_button_tool(); - - return true; - } - } - - if (tool == TOOL_LINE_PAINT || tool == TOOL_RECTANGLE_PAINT) { - selection_active = false; - rectangle_begin = over_tile; - - mouse_down = true; - } else if (tool == TOOL_PAINTING) { - Vector<int> ids = get_selected_tiles(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - tool = TOOL_PAINTING; - - _start_undo(TTR("Paint TileMap")); - } - } else if (tool == TOOL_PICKING) { - _pick_tile(over_tile); - } else if (tool == TOOL_SELECTING) { - selection_active = true; - rectangle_begin = over_tile; - } - - _update_button_tool(); - return true; - - } else { - // Mousebutton was released. - if (tool != TOOL_NONE) { - if (tool == TOOL_PAINTING) { - Vector<int> ids = get_selected_tiles(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - _set_cell(over_tile, ids, flip_h, flip_v, transpose); - _finish_undo(); - - paint_undo.clear(); - } - } else if (tool == TOOL_LINE_PAINT) { - if (!mouse_down) { - return true; - } - - Vector<int> ids = get_selected_tiles(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - _start_undo(TTR("Line Draw")); - for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - _set_cell(E->key(), ids, flip_h, flip_v, transpose); - } - _finish_undo(); - - paint_undo.clear(); - - CanvasItemEditor::get_singleton()->update_viewport(); - - mouse_down = false; - return true; - } - - mouse_down = false; - } else if (tool == TOOL_RECTANGLE_PAINT) { - if (!mouse_down) { - return true; - } - - Vector<int> ids = get_selected_tiles(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - _start_undo(TTR("Rectangle Paint")); - for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { - for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { - _set_cell(Point2i(j, i), ids, flip_h, flip_v, transpose); - } - } - _finish_undo(); - - CanvasItemEditor::get_singleton()->update_viewport(); - - mouse_down = false; - return true; - } - - mouse_down = false; - } else if (tool == TOOL_PASTING) { - Point2 ofs = over_tile - rectangle.position; - Vector<int> ids; - - _start_undo(TTR("Paste")); - ids.push_back(0); - for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) { - ids.write[0] = E->get().cell; - _set_cell(E->get().pos + ofs, ids, E->get().flip_h, E->get().flip_v, E->get().transpose, E->get().autotile_coord); - } - _finish_undo(); - - CanvasItemEditor::get_singleton()->update_viewport(); - - return true; // We want to keep the Pasting tool. - } else if (tool == TOOL_SELECTING) { - CanvasItemEditor::get_singleton()->update_viewport(); - - } else if (tool == TOOL_BUCKET) { - Vector<Vector2> points = _bucket_fill(over_tile); - - if (points.size() == 0) { - return false; - } - - _start_undo(TTR("Bucket Fill")); - - Dictionary op; - op["id"] = get_selected_tiles(); - op["flip_h"] = flip_h; - op["flip_v"] = flip_v; - op["transpose"] = transpose; - - _fill_points(points, op); - - _finish_undo(); - - // So the fill preview is cleared right after the click. - CanvasItemEditor::get_singleton()->update_viewport(); - - // We want to keep the bucket-tool active. - return true; - } - - tool = TOOL_NONE; - _update_button_tool(); - - return true; - } - } - } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - if (mb->is_pressed()) { - if (tool == TOOL_SELECTING || selection_active) { - tool = TOOL_NONE; - selection_active = false; - - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - - if (tool == TOOL_PASTING) { - tool = TOOL_NONE; - - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - - if (tool == TOOL_LINE_PAINT) { - tool = TOOL_LINE_ERASE; - mouse_down = true; - rectangle_begin = over_tile; - - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - - if (tool == TOOL_RECTANGLE_PAINT) { - tool = TOOL_RECTANGLE_ERASE; - mouse_down = true; - rectangle_begin = over_tile; - copydata.clear(); - - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - - if (tool == TOOL_NONE) { - paint_undo.clear(); - - Point2 local = node->world_to_map(xform_inv.xform(mb->get_position())); - - _start_undo(TTR("Erase TileMap")); - tool = TOOL_ERASING; - _set_cell(local, invalid_cell); - - _update_button_tool(); - return true; - } - - } else { - if (tool == TOOL_LINE_ERASE) { - if (!mouse_down) { - return true; - } - - tool = TOOL_LINE_PAINT; - _update_button_tool(); - - Vector<int> ids = get_selected_tiles(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - _start_undo(TTR("Line Erase")); - for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - _set_cell(E->key(), invalid_cell, flip_h, flip_v, transpose); - } - _finish_undo(); - paint_undo.clear(); - - CanvasItemEditor::get_singleton()->update_viewport(); - - mouse_down = false; - return true; - } - - mouse_down = false; - } else if (tool == TOOL_RECTANGLE_ERASE) { - if (!mouse_down) { - return true; - } - - tool = TOOL_RECTANGLE_PAINT; - _update_button_tool(); - - Vector<int> ids = get_selected_tiles(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - _start_undo(TTR("Rectangle Erase")); - for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { - for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { - _set_cell(Point2i(j, i), invalid_cell, flip_h, flip_v, transpose); - } - } - _finish_undo(); - paint_undo.clear(); - - CanvasItemEditor::get_singleton()->update_viewport(); - - mouse_down = false; - return true; - } - - mouse_down = false; - tool = TOOL_RECTANGLE_PAINT; - } - - if (tool == TOOL_ERASING) { - tool = TOOL_NONE; - _update_button_tool(); - - return true; - } else if (tool == TOOL_BUCKET) { - Vector<int> ids; - ids.push_back(node->get_cell(over_tile.x, over_tile.y)); - Dictionary pop; - pop["id"] = ids; - pop["flip_h"] = node->is_cell_x_flipped(over_tile.x, over_tile.y); - pop["flip_v"] = node->is_cell_y_flipped(over_tile.x, over_tile.y); - pop["transpose"] = node->is_cell_transposed(over_tile.x, over_tile.y); - - Vector<Vector2> points = _bucket_fill(over_tile, true); - - if (points.size() == 0) { - return false; - } - - undo_redo->create_action(TTR("Bucket Fill")); - - undo_redo->add_do_method(this, "_erase_points", points); - undo_redo->add_undo_method(this, "_fill_points", points, pop); - - undo_redo->commit_action(); - } - } - } - } - - Ref<InputEventMouseMotion> mm = p_event; - - if (mm.is_valid()) { - Point2i new_over_tile = node->world_to_map(xform_inv.xform(mm->get_position())); - Point2i old_over_tile = over_tile; - - if (new_over_tile != over_tile) { - over_tile = new_over_tile; - CanvasItemEditor::get_singleton()->update_viewport(); - } - - if (refocus_over_tile) { - // editor lost focus; forget last tile position - old_over_tile = new_over_tile; - refocus_over_tile = false; - } - - int tile_under = node->get_cell(over_tile.x, over_tile.y); - String tile_name = "none"; - - if (node->get_tileset()->has_tile(tile_under)) { - tile_name = node->get_tileset()->tile_get_name(tile_under); - } - tile_info->show(); - tile_info->set_text(String::num(over_tile.x) + ", " + String::num(over_tile.y) + " [" + tile_name + "]"); - - if (tool == TOOL_PAINTING) { - // Paint using bresenham line to prevent holes in painting if the user moves fast. - - Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y); - Vector<int> ids = get_selected_tiles(); - - for (int i = 0; i < points.size(); ++i) { - Point2i pos = points[i]; - - if (!paint_undo.has(pos)) { - paint_undo[pos] = _get_op_from_cell(pos); - } - - _set_cell(pos, ids, flip_h, flip_v, transpose); - } - - return true; - } - - if (tool == TOOL_ERASING) { - // Erase using bresenham line to prevent holes in painting if the user moves fast. - - Vector<Point2i> points = line(old_over_tile.x, over_tile.x, old_over_tile.y, over_tile.y); - - for (int i = 0; i < points.size(); ++i) { - Point2i pos = points[i]; - - _set_cell(pos, invalid_cell); - } - - return true; - } - - if (tool == TOOL_SELECTING) { - _select(rectangle_begin, over_tile); - - return true; - } - - if (tool == TOOL_LINE_PAINT || tool == TOOL_LINE_ERASE) { - Vector<int> ids = get_selected_tiles(); - Vector<int> tmp_cell; - bool erasing = (tool == TOOL_LINE_ERASE); - - if (!mouse_down) { - return true; - } - - tmp_cell.push_back(0); - if (erasing && paint_undo.size()) { - for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - tmp_cell.write[0] = E->get().idx; - _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr); - } - } - - paint_undo.clear(); - - if (ids.size() > 0 && ids[0] != TileMap::INVALID_CELL) { - Vector<Point2i> points = line(rectangle_begin.x, over_tile.x, rectangle_begin.y, over_tile.y); - - for (int i = 0; i < points.size(); i++) { - paint_undo[points[i]] = _get_op_from_cell(points[i]); - - if (erasing) { - _set_cell(points[i], invalid_cell); - } - } - - CanvasItemEditor::get_singleton()->update_viewport(); - } - - return true; - } - if (tool == TOOL_RECTANGLE_PAINT || tool == TOOL_RECTANGLE_ERASE) { - Vector<int> tmp_cell; - tmp_cell.push_back(0); - - Point2i end_tile = over_tile; - - if (!mouse_down) { - return true; - } - - if (mm->get_shift()) { - int size = fmax(ABS(end_tile.x - rectangle_begin.x), ABS(end_tile.y - rectangle_begin.y)); - int xDirection = MAX(MIN(end_tile.x - rectangle_begin.x, 1), -1); - int yDirection = MAX(MIN(end_tile.y - rectangle_begin.y, 1), -1); - end_tile = rectangle_begin + Point2i(xDirection * size, yDirection * size); - } - - _select(rectangle_begin, end_tile); - - if (tool == TOOL_RECTANGLE_ERASE) { - if (paint_undo.size()) { - for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - tmp_cell.write[0] = E->get().idx; - _set_cell(E->key(), tmp_cell, E->get().xf, E->get().yf, E->get().tr); - } - } - - paint_undo.clear(); - - for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { - for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { - Point2i tile = Point2i(j, i); - paint_undo[tile] = _get_op_from_cell(tile); - - _set_cell(tile, invalid_cell); - } - } - } - - return true; - } - if (tool == TOOL_PICKING && Input::get_singleton()->is_mouse_button_pressed(MOUSE_BUTTON_LEFT)) { - _pick_tile(over_tile); - - return true; - } - } - - Ref<InputEventKey> k = p_event; - - if (k.is_valid() && k->is_pressed()) { - if (last_tool == TOOL_NONE && tool == TOOL_PICKING && k->get_keycode() == KEY_SHIFT && k->get_command()) { - // trying to draw a rectangle with the painting tool, so change to the correct tool - tool = last_tool; - - CanvasItemEditor::get_singleton()->update_viewport(); - _update_button_tool(); - } - - if (k->get_keycode() == KEY_ESCAPE) { - if (tool == TOOL_PASTING) { - copydata.clear(); - } else if (tool == TOOL_SELECTING || selection_active) { - selection_active = false; - } - - tool = TOOL_NONE; - - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - - if (!mouse_over) { - // Editor shortcuts should not fire if mouse not in viewport. - return false; - } - - if (ED_IS_SHORTCUT("tile_map_editor/paint_tile", p_event)) { - // NOTE: We do not set tool = TOOL_PAINTING as this begins painting - // immediately without pressing the left mouse button first. - tool = TOOL_NONE; - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/line_fill", p_event)) { - tool = TOOL_LINE_PAINT; - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/rectangle_fill", p_event)) { - tool = TOOL_RECTANGLE_PAINT; - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/bucket_fill", p_event)) { - tool = TOOL_BUCKET; - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/erase_selection", p_event)) { - _menu_option(OPTION_ERASE_SELECTION); - - _update_button_tool(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/select", p_event)) { - tool = TOOL_SELECTING; - selection_active = false; - - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/copy_selection", p_event)) { - _update_copydata(); - - if (selection_active) { - tool = TOOL_PASTING; - - CanvasItemEditor::get_singleton()->update_viewport(); - - _update_button_tool(); - return true; - } - } - if (ED_IS_SHORTCUT("tile_map_editor/cut_selection", p_event)) { - if (selection_active) { - _update_copydata(); - - _start_undo(TTR("Cut Selection")); - _erase_selection(); - _finish_undo(); - - selection_active = false; - - tool = TOOL_PASTING; - - CanvasItemEditor::get_singleton()->update_viewport(); - _update_button_tool(); - return true; - } - } - if (ED_IS_SHORTCUT("tile_map_editor/find_tile", p_event)) { - search_box->select_all(); - search_box->grab_focus(); - - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/rotate_left", p_event)) { - _rotate(-1); - CanvasItemEditor::get_singleton()->update_viewport(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/rotate_right", p_event)) { - _rotate(1); - CanvasItemEditor::get_singleton()->update_viewport(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/flip_horizontal", p_event)) { - _flip_horizontal(); - CanvasItemEditor::get_singleton()->update_viewport(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/flip_vertical", p_event)) { - _flip_vertical(); - CanvasItemEditor::get_singleton()->update_viewport(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/clear_transform", p_event)) { - _clear_transform(); - CanvasItemEditor::get_singleton()->update_viewport(); - return true; - } - if (ED_IS_SHORTCUT("tile_map_editor/transpose", p_event)) { - transpose = !transpose; - _update_palette(); - CanvasItemEditor::get_singleton()->update_viewport(); - return true; - } - } else if (k.is_valid()) { // Release event. - - if (tool == TOOL_NONE) { - if (k->get_keycode() == KEY_SHIFT && k->get_command()) { - tool = TOOL_PICKING; - _update_button_tool(); - } - } else if (tool == TOOL_PICKING) { -#ifdef APPLE_STYLE_KEYS - if (k->get_keycode() == KEY_META) { -#else - if (k->get_keycode() == KEY_CONTROL) { -#endif - // Go back to that last tool if KEY_CONTROL was released. - tool = last_tool; - - CanvasItemEditor::get_singleton()->update_viewport(); - _update_button_tool(); - } - } - } - return false; -} - -void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { - if (!node || CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) { - return; - } - - Transform2D cell_xf = node->get_cell_transform(); - Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * node->get_global_transform(); - Transform2D xform_inv = xform.affine_inverse(); - - Size2 screen_size = p_overlay->get_size(); - { - Rect2 aabb; - aabb.position = node->world_to_map(xform_inv.xform(Vector2())); - aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(0, screen_size.height)))); - aabb.expand_to(node->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0)))); - aabb.expand_to(node->world_to_map(xform_inv.xform(screen_size))); - Rect2i si = aabb.grow(1.0); - - if (node->get_half_offset() != TileMap::HALF_OFFSET_X && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_X) { - int max_lines = 2000; //avoid crash if size too small - - for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) { - Vector2 from = xform.xform(node->map_to_world(Vector2(i, si.position.y))); - Vector2 to = xform.xform(node->map_to_world(Vector2(i, si.position.y + si.size.y + 1))); - - Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); - p_overlay->draw_line(from, to, col, 1); - if (max_lines-- == 0) { - break; - } - } - } else { - int max_lines = 10000; //avoid crash if size too small - - for (int i = (si.position.x) - 1; i <= (si.position.x + si.size.x); i++) { - for (int j = (si.position.y) - 1; j <= (si.position.y + si.size.y); j++) { - Vector2 ofs; - if (ABS(j) & 1) { - ofs = cell_xf[0] * (node->get_half_offset() == TileMap::HALF_OFFSET_X ? 0.5 : -0.5); - } - - Vector2 from = xform.xform(node->map_to_world(Vector2(i, j), true) + ofs); - Vector2 to = xform.xform(node->map_to_world(Vector2(i, j + 1), true) + ofs); - - Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); - p_overlay->draw_line(from, to, col, 1); - - if (--max_lines == 0) { - break; - } - } - if (max_lines == 0) { - break; - } - } - } - - int max_lines = 10000; //avoid crash if size too small - - if (node->get_half_offset() != TileMap::HALF_OFFSET_Y && node->get_half_offset() != TileMap::HALF_OFFSET_NEGATIVE_Y) { - for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) { - Vector2 from = xform.xform(node->map_to_world(Vector2(si.position.x, i))); - Vector2 to = xform.xform(node->map_to_world(Vector2(si.position.x + si.size.x + 1, i))); - - Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); - p_overlay->draw_line(from, to, col, 1); - - if (max_lines-- == 0) { - break; - } - } - } else { - for (int i = (si.position.y) - 1; i <= (si.position.y + si.size.y); i++) { - for (int j = (si.position.x) - 1; j <= (si.position.x + si.size.x); j++) { - Vector2 ofs; - if (ABS(j) & 1) { - ofs = cell_xf[1] * (node->get_half_offset() == TileMap::HALF_OFFSET_Y ? 0.5 : -0.5); - } - - Vector2 from = xform.xform(node->map_to_world(Vector2(j, i), true) + ofs); - Vector2 to = xform.xform(node->map_to_world(Vector2(j + 1, i), true) + ofs); - - Color col = i == 0 ? Color(1, 0.8, 0.2, 0.5) : Color(1, 0.3, 0.1, 0.2); - p_overlay->draw_line(from, to, col, 1); - - if (--max_lines == 0) { - break; - } - } - if (max_lines == 0) { - break; - } - } - } - } - - if (selection_active) { - Vector<Vector2> points; - points.push_back(xform.xform(node->map_to_world((rectangle.position)))); - points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, 0))))); - points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(rectangle.size.x + 1, rectangle.size.y + 1))))); - points.push_back(xform.xform(node->map_to_world((rectangle.position + Point2(0, rectangle.size.y + 1))))); - - p_overlay->draw_colored_polygon(points, Color(0.2, 0.8, 1, 0.4)); - } - - if (mouse_over && node->get_tileset().is_valid()) { - Vector2 endpoints[4] = { - node->map_to_world(over_tile, true), - node->map_to_world((over_tile + Point2(1, 0)), true), - node->map_to_world((over_tile + Point2(1, 1)), true), - node->map_to_world((over_tile + Point2(0, 1)), true) - }; - - for (int i = 0; i < 4; i++) { - if (node->get_half_offset() == TileMap::HALF_OFFSET_X && ABS(over_tile.y) & 1) { - endpoints[i] += cell_xf[0] * 0.5; - } - if (node->get_half_offset() == TileMap::HALF_OFFSET_NEGATIVE_X && ABS(over_tile.y) & 1) { - endpoints[i] += cell_xf[0] * -0.5; - } - if (node->get_half_offset() == TileMap::HALF_OFFSET_Y && ABS(over_tile.x) & 1) { - endpoints[i] += cell_xf[1] * 0.5; - } - if (node->get_half_offset() == TileMap::HALF_OFFSET_NEGATIVE_Y && ABS(over_tile.x) & 1) { - endpoints[i] += cell_xf[1] * -0.5; - } - endpoints[i] = xform.xform(endpoints[i]); - } - Color col; - if (node->get_cell(over_tile.x, over_tile.y) != TileMap::INVALID_CELL) { - col = Color(0.2, 0.8, 1.0, 0.8); - } else { - col = Color(1.0, 0.4, 0.2, 0.8); - } - - for (int i = 0; i < 4; i++) { - p_overlay->draw_line(endpoints[i], endpoints[(i + 1) % 4], col, 2); - } - - bool bucket_preview = EditorSettings::get_singleton()->get("editors/tile_map/bucket_fill_preview"); - if (tool == TOOL_SELECTING || tool == TOOL_PICKING || !bucket_preview) { - return; - } - - if (tool == TOOL_LINE_PAINT) { - if (!mouse_down) { - return; - } - - if (paint_undo.is_empty()) { - return; - } - - Vector<int> ids = get_selected_tiles(); - - if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) { - return; - } - - for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) { - _draw_cell(p_overlay, ids[0], E->key(), flip_h, flip_v, transpose, autotile_coord, xform); - } - - } else if (tool == TOOL_RECTANGLE_PAINT) { - if (!mouse_down) { - return; - } - - Vector<int> ids = get_selected_tiles(); - - if (ids.size() == 1 && ids[0] == TileMap::INVALID_CELL) { - return; - } - - for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) { - for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) { - _draw_cell(p_overlay, ids[0], Point2i(j, i), flip_h, flip_v, transpose, autotile_coord, xform); - } - } - } else if (tool == TOOL_PASTING) { - if (copydata.is_empty()) { - return; - } - - Ref<TileSet> ts = node->get_tileset(); - - if (ts.is_null()) { - return; - } - - Point2 ofs = over_tile - rectangle.position; - - for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) { - if (!ts->has_tile(E->get().cell)) { - continue; - } - - TileData tcd = E->get(); - - _draw_cell(p_overlay, tcd.cell, tcd.pos + ofs, tcd.flip_h, tcd.flip_v, tcd.transpose, tcd.autotile_coord, xform); - } - - Rect2i duplicate = rectangle; - duplicate.position = over_tile; - - Vector<Vector2> points; - points.push_back(xform.xform(node->map_to_world(duplicate.position))); - points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, 0))))); - points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(duplicate.size.x + 1, duplicate.size.y + 1))))); - points.push_back(xform.xform(node->map_to_world((duplicate.position + Point2(0, duplicate.size.y + 1))))); - - p_overlay->draw_colored_polygon(points, Color(0.2, 1.0, 0.8, 0.2)); - - } else if (tool == TOOL_BUCKET) { - Vector<int> tiles = get_selected_tiles(); - _draw_fill_preview(p_overlay, tiles[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform); - - } else { - Vector<int> st = get_selected_tiles(); - - if (st.size() == 1 && st[0] == TileMap::INVALID_CELL) { - return; - } - - _draw_cell(p_overlay, st[0], over_tile, flip_h, flip_v, transpose, autotile_coord, xform); - } - } -} - -void TileMapEditor::edit(Node *p_tile_map) { - search_box->set_text(""); - - if (!canvas_item_editor_viewport) { - canvas_item_editor_viewport = CanvasItemEditor::get_singleton()->get_viewport_control(); - } - - if (node) { - Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed); - - if (node->is_connected("settings_changed", callable_tileset_settings_changed)) { - node->disconnect("settings_changed", callable_tileset_settings_changed); - } - } - if (p_tile_map) { - node = Object::cast_to<TileMap>(p_tile_map); - if (!canvas_item_editor_viewport->is_connected("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter))) { - canvas_item_editor_viewport->connect("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter)); - } - if (!canvas_item_editor_viewport->is_connected("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit))) { - canvas_item_editor_viewport->connect("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit)); - } - - _update_palette(); - - } else { - node = nullptr; - - if (canvas_item_editor_viewport->is_connected("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter))) { - canvas_item_editor_viewport->disconnect("mouse_entered", callable_mp(this, &TileMapEditor::_canvas_mouse_enter)); - } - if (canvas_item_editor_viewport->is_connected("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit))) { - canvas_item_editor_viewport->disconnect("mouse_exited", callable_mp(this, &TileMapEditor::_canvas_mouse_exit)); - } - - _update_palette(); - } - - if (node) { - Callable callable_tileset_settings_changed = callable_mp(this, &TileMapEditor::_tileset_settings_changed); - - if (!node->is_connected("settings_changed", callable_tileset_settings_changed)) { - node->connect("settings_changed", callable_tileset_settings_changed); - } - } - - _clear_bucket_cache(); -} - -void TileMapEditor::_tileset_settings_changed() { - _update_palette(); - CanvasItemEditor::get_singleton()->update_viewport(); -} - -void TileMapEditor::_icon_size_changed(float p_value) { - if (node) { - palette->set_icon_scale(p_value); - manual_palette->set_icon_scale(p_value); - _update_palette(); - } -} - -void TileMapEditor::_bind_methods() { - ClassDB::bind_method(D_METHOD("_fill_points"), &TileMapEditor::_fill_points); - ClassDB::bind_method(D_METHOD("_erase_points"), &TileMapEditor::_erase_points); -} - -TileMapEditor::CellOp TileMapEditor::_get_op_from_cell(const Point2i &p_pos) { - CellOp op; - op.idx = node->get_cell(p_pos.x, p_pos.y); - if (op.idx != TileMap::INVALID_CELL) { - if (node->is_cell_x_flipped(p_pos.x, p_pos.y)) { - op.xf = true; - } - if (node->is_cell_y_flipped(p_pos.x, p_pos.y)) { - op.yf = true; - } - if (node->is_cell_transposed(p_pos.x, p_pos.y)) { - op.tr = true; - } - op.ac = node->get_cell_autotile_coord(p_pos.x, p_pos.y); - } - return op; -} - -void TileMapEditor::_rotate(int steps) { - const bool normal_rotation_matrix[][3] = { - { false, false, false }, - { true, true, false }, - { false, true, true }, - { true, false, true } - }; - - const bool mirrored_rotation_matrix[][3] = { - { false, true, false }, - { true, true, true }, - { false, false, true }, - { true, false, false } - }; - - if (transpose ^ flip_h ^ flip_v) { - // Odd number of flags activated = mirrored rotation - for (int i = 0; i < 4; i++) { - if (transpose == mirrored_rotation_matrix[i][0] && - flip_h == mirrored_rotation_matrix[i][1] && - flip_v == mirrored_rotation_matrix[i][2]) { - int new_id = Math::wrapi(i + steps, 0, 4); - transpose = mirrored_rotation_matrix[new_id][0]; - flip_h = mirrored_rotation_matrix[new_id][1]; - flip_v = mirrored_rotation_matrix[new_id][2]; - break; - } - } - } else { - // Even number of flags activated = normal rotation - for (int i = 0; i < 4; i++) { - if (transpose == normal_rotation_matrix[i][0] && - flip_h == normal_rotation_matrix[i][1] && - flip_v == normal_rotation_matrix[i][2]) { - int new_id = Math::wrapi(i + steps, 0, 4); - transpose = normal_rotation_matrix[new_id][0]; - flip_h = normal_rotation_matrix[new_id][1]; - flip_v = normal_rotation_matrix[new_id][2]; - break; - } - } - } - - _update_palette(); -} - -void TileMapEditor::_flip_horizontal() { - flip_h = !flip_h; - _update_palette(); -} - -void TileMapEditor::_flip_vertical() { - flip_v = !flip_v; - _update_palette(); -} - -void TileMapEditor::_clear_transform() { - transpose = false; - flip_h = false; - flip_v = false; - _update_palette(); -} - -TileMapEditor::TileMapEditor(EditorNode *p_editor) { - node = nullptr; - manual_autotile = false; - priority_atlastile = false; - manual_position = Vector2(0, 0); - canvas_item_editor_viewport = nullptr; - editor = p_editor; - undo_redo = EditorNode::get_undo_redo(); - - tool = TOOL_NONE; - selection_active = false; - mouse_over = false; - mouse_down = false; - - flip_h = false; - flip_v = false; - transpose = false; - - bucket_cache_tile = -1; - bucket_cache_visited = nullptr; - - invalid_cell.resize(1); - invalid_cell.write[0] = TileMap::INVALID_CELL; - - ED_SHORTCUT("tile_map_editor/erase_selection", TTR("Erase Selection"), KEY_DELETE); - ED_SHORTCUT("tile_map_editor/find_tile", TTR("Find Tile"), KEY_MASK_CMD + KEY_F); - ED_SHORTCUT("tile_map_editor/transpose", TTR("Transpose"), KEY_T); - - HBoxContainer *tool_hb = memnew(HBoxContainer); - add_child(tool_hb); - - manual_button = memnew(CheckBox); - manual_button->set_text(TTR("Disable Autotile")); - manual_button->connect("toggled", callable_mp(this, &TileMapEditor::_manual_toggled)); - add_child(manual_button); - - priority_button = memnew(CheckBox); - priority_button->set_text(TTR("Enable Priority")); - priority_button->connect("toggled", callable_mp(this, &TileMapEditor::_priority_toggled)); - add_child(priority_button); - - search_box = memnew(LineEdit); - search_box->set_placeholder(TTR("Filter tiles")); - search_box->set_h_size_flags(SIZE_EXPAND_FILL); - search_box->connect("text_entered", callable_mp(this, &TileMapEditor::_text_entered)); - search_box->connect("text_changed", callable_mp(this, &TileMapEditor::_text_changed)); - search_box->connect("gui_input", callable_mp(this, &TileMapEditor::_sbox_input)); - add_child(search_box); - - size_slider = memnew(HSlider); - size_slider->set_h_size_flags(SIZE_EXPAND_FILL); - size_slider->set_min(0.1f); - size_slider->set_max(4.0f); - size_slider->set_step(0.1f); - size_slider->set_value(1.0f); - size_slider->connect("value_changed", callable_mp(this, &TileMapEditor::_icon_size_changed)); - add_child(size_slider); - - int mw = EDITOR_DEF("editors/tile_map/palette_min_width", 80); - - VSplitContainer *palette_container = memnew(VSplitContainer); - palette_container->set_v_size_flags(SIZE_EXPAND_FILL); - palette_container->set_custom_minimum_size(Size2(mw, 0)); - add_child(palette_container); - - // Add tile palette. - palette = memnew(ItemList); - palette->set_h_size_flags(SIZE_EXPAND_FILL); - palette->set_v_size_flags(SIZE_EXPAND_FILL); - palette->set_max_columns(0); - palette->set_icon_mode(ItemList::ICON_MODE_TOP); - palette->set_max_text_lines(2); - palette->set_select_mode(ItemList::SELECT_MULTI); - palette->add_theme_constant_override("vseparation", 8 * EDSCALE); - palette->connect("item_selected", callable_mp(this, &TileMapEditor::_palette_selected)); - palette->connect("multi_selected", callable_mp(this, &TileMapEditor::_palette_multi_selected)); - palette->connect("gui_input", callable_mp(this, &TileMapEditor::_palette_input)); - palette_container->add_child(palette); - - // Add message for when no texture is selected. - info_message = memnew(Label); - info_message->set_text(TTR("Give a TileSet resource to this TileMap to use its tiles.")); - info_message->set_valign(Label::VALIGN_CENTER); - info_message->set_align(Label::ALIGN_CENTER); - info_message->set_autowrap(true); - info_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - info_message->set_anchors_and_offsets_preset(PRESET_WIDE, PRESET_MODE_KEEP_SIZE, 8 * EDSCALE); - palette->add_child(info_message); - - // Add autotile override palette. - manual_palette = memnew(ItemList); - manual_palette->set_h_size_flags(SIZE_EXPAND_FILL); - manual_palette->set_v_size_flags(SIZE_EXPAND_FILL); - manual_palette->set_max_columns(0); - manual_palette->set_icon_mode(ItemList::ICON_MODE_TOP); - manual_palette->set_max_text_lines(2); - manual_palette->hide(); - palette_container->add_child(manual_palette); - - // Add menu items. - toolbar = memnew(HBoxContainer); - toolbar->hide(); - CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar); - - toolbar->add_child(memnew(VSeparator)); - - // Tools. - paint_button = memnew(Button); - paint_button->set_flat(true); - paint_button->set_shortcut(ED_SHORTCUT("tile_map_editor/paint_tile", TTR("Paint Tile"), KEY_P)); - paint_button->set_shortcut_context(this); - paint_button->set_tooltip(TTR("RMB: Erase")); - paint_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_NONE)); - paint_button->set_toggle_mode(true); - toolbar->add_child(paint_button); - - line_button = memnew(Button); - line_button->set_flat(true); - line_button->set_shortcut(ED_SHORTCUT("tile_map_editor/line_fill", TTR("Line Fill"), KEY_L)); - line_button->set_shortcut_context(this); - line_button->set_tooltip(TTR("RMB: Erase")); - line_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_LINE_PAINT)); - line_button->set_toggle_mode(true); - toolbar->add_child(line_button); - - rectangle_button = memnew(Button); - rectangle_button->set_flat(true); - rectangle_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rectangle_fill", TTR("Rectangle Fill"), KEY_O)); - rectangle_button->set_shortcut_context(this); - rectangle_button->set_tooltip(TTR("Shift+LMB: Keep 1:1 proporsions\nRMB: Erase")); - rectangle_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_RECTANGLE_PAINT)); - rectangle_button->set_toggle_mode(true); - toolbar->add_child(rectangle_button); - - bucket_fill_button = memnew(Button); - bucket_fill_button->set_flat(true); - bucket_fill_button->set_shortcut(ED_SHORTCUT("tile_map_editor/bucket_fill", TTR("Bucket Fill"), KEY_B)); - bucket_fill_button->set_shortcut_context(this); - bucket_fill_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_BUCKET)); - bucket_fill_button->set_toggle_mode(true); - toolbar->add_child(bucket_fill_button); - - picker_button = memnew(Button); - picker_button->set_flat(true); - picker_button->set_shortcut(ED_SHORTCUT("tile_map_editor/pick_tile", TTR("Pick Tile"), KEY_I)); - picker_button->set_shortcut_context(this); - picker_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_PICKING)); - picker_button->set_toggle_mode(true); - toolbar->add_child(picker_button); - - select_button = memnew(Button); - select_button->set_flat(true); - select_button->set_shortcut(ED_SHORTCUT("tile_map_editor/select", TTR("Select"), KEY_M)); - select_button->set_shortcut_context(this); - select_button->connect("pressed", callable_mp(this, &TileMapEditor::_button_tool_select), make_binds(TOOL_SELECTING)); - select_button->set_toggle_mode(true); - toolbar->add_child(select_button); - - _update_button_tool(); - - // Container to the right of the toolbar. - toolbar_right = memnew(HBoxContainer); - toolbar_right->hide(); - toolbar_right->set_h_size_flags(SIZE_EXPAND_FILL); - toolbar_right->set_alignment(BoxContainer::ALIGN_END); - CanvasItemEditor::get_singleton()->add_control_to_menu_panel(toolbar_right); - - // Tile position. - tile_info = memnew(Label); - tile_info->set_modulate(Color(1, 1, 1, 0.8)); - tile_info->set_mouse_filter(MOUSE_FILTER_IGNORE); - tile_info->add_theme_font_override("font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts")); - tile_info->add_theme_font_size_override("font_size", EditorNode::get_singleton()->get_gui_base()->get_theme_font_size("main_size", "EditorFonts")); - // The tile info is only displayed after a tile has been hovered. - tile_info->hide(); - CanvasItemEditor::get_singleton()->add_control_to_info_overlay(tile_info); - - // Menu. - options = memnew(MenuButton); - options->set_shortcut_context(this); - options->set_text("TileMap"); - options->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("TileMap", "EditorIcons")); - toolbar_right->add_child(options); - - PopupMenu *p = options->get_popup(); - p->add_shortcut(ED_SHORTCUT("tile_map_editor/cut_selection", TTR("Cut Selection"), KEY_MASK_CMD + KEY_X), OPTION_CUT); - p->add_shortcut(ED_SHORTCUT("tile_map_editor/copy_selection", TTR("Copy Selection"), KEY_MASK_CMD + KEY_C), OPTION_COPY); - p->add_shortcut(ED_GET_SHORTCUT("tile_map_editor/erase_selection"), OPTION_ERASE_SELECTION); - p->add_separator(); - p->add_item(TTR("Fix Invalid Tiles"), OPTION_FIX_INVALID); - p->connect("id_pressed", callable_mp(this, &TileMapEditor::_menu_option)); - - rotate_left_button = memnew(Button); - rotate_left_button->set_flat(true); - rotate_left_button->set_tooltip(TTR("Rotate Left")); - rotate_left_button->set_focus_mode(FOCUS_NONE); - rotate_left_button->connect("pressed", callable_mp(this, &TileMapEditor::_rotate), varray(-1)); - rotate_left_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_left", TTR("Rotate Left"), KEY_A)); - rotate_left_button->set_shortcut_context(this); - tool_hb->add_child(rotate_left_button); - - rotate_right_button = memnew(Button); - rotate_right_button->set_flat(true); - rotate_right_button->set_tooltip(TTR("Rotate Right")); - rotate_right_button->set_focus_mode(FOCUS_NONE); - rotate_right_button->connect("pressed", callable_mp(this, &TileMapEditor::_rotate), varray(1)); - rotate_right_button->set_shortcut(ED_SHORTCUT("tile_map_editor/rotate_right", TTR("Rotate Right"), KEY_S)); - rotate_right_button->set_shortcut_context(this); - tool_hb->add_child(rotate_right_button); - - flip_horizontal_button = memnew(Button); - flip_horizontal_button->set_flat(true); - flip_horizontal_button->set_tooltip(TTR("Flip Horizontally")); - flip_horizontal_button->set_focus_mode(FOCUS_NONE); - flip_horizontal_button->connect("pressed", callable_mp(this, &TileMapEditor::_flip_horizontal)); - flip_horizontal_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_horizontal", TTR("Flip Horizontally"), KEY_X)); - flip_horizontal_button->set_shortcut_context(this); - tool_hb->add_child(flip_horizontal_button); - - flip_vertical_button = memnew(Button); - flip_vertical_button->set_flat(true); - flip_vertical_button->set_tooltip(TTR("Flip Vertically")); - flip_vertical_button->set_focus_mode(FOCUS_NONE); - flip_vertical_button->connect("pressed", callable_mp(this, &TileMapEditor::_flip_vertical)); - flip_vertical_button->set_shortcut(ED_SHORTCUT("tile_map_editor/flip_vertical", TTR("Flip Vertically"), KEY_Z)); - flip_vertical_button->set_shortcut_context(this); - tool_hb->add_child(flip_vertical_button); - - clear_transform_button = memnew(Button); - clear_transform_button->set_flat(true); - clear_transform_button->set_tooltip(TTR("Clear Transform")); - clear_transform_button->set_focus_mode(FOCUS_NONE); - clear_transform_button->connect("pressed", callable_mp(this, &TileMapEditor::_clear_transform)); - clear_transform_button->set_shortcut(ED_SHORTCUT("tile_map_editor/clear_transform", TTR("Clear Transform"), KEY_W)); - clear_transform_button->set_shortcut_context(this); - tool_hb->add_child(clear_transform_button); - - clear_transform_button->set_disabled(true); -} - -TileMapEditor::~TileMapEditor() { - _clear_bucket_cache(); - copydata.clear(); -} - -/////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////// - -void TileMapEditorPlugin::_notification(int p_what) { - if (p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) { - switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) { - case 0: { // Left. - CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 0); - } break; - case 1: { // Right. - CanvasItemEditor::get_singleton()->get_palette_split()->move_child(tile_map_editor, 1); - } break; - } - } -} - -void TileMapEditorPlugin::edit(Object *p_object) { - tile_map_editor->edit(Object::cast_to<Node>(p_object)); -} - -bool TileMapEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("TileMap"); -} - -void TileMapEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - tile_map_editor->show(); - tile_map_editor->get_toolbar()->show(); - tile_map_editor->get_toolbar_right()->show(); - // `tile_info` isn't shown here, as it's displayed after a tile has been hovered. - // Otherwise, a translucent black rectangle would be visible as there would be an - // empty Label in the CanvasItemEditor's info overlay. - - // Change to TOOL_SELECT when TileMap node is selected, to prevent accidental movement. - CanvasItemEditor::get_singleton()->set_current_tool(CanvasItemEditor::TOOL_SELECT); - } else { - tile_map_editor->hide(); - tile_map_editor->get_toolbar()->hide(); - tile_map_editor->get_toolbar_right()->hide(); - tile_map_editor->get_tile_info()->hide(); - tile_map_editor->edit(nullptr); - } -} - -TileMapEditorPlugin::TileMapEditorPlugin(EditorNode *p_node) { - EDITOR_DEF("editors/tile_map/preview_size", 64); - EDITOR_DEF("editors/tile_map/palette_item_hseparation", 8); - EDITOR_DEF("editors/tile_map/show_tile_names", true); - EDITOR_DEF("editors/tile_map/show_tile_ids", false); - EDITOR_DEF("editors/tile_map/sort_tiles_by_name", true); - EDITOR_DEF("editors/tile_map/bucket_fill_preview", true); - EDITOR_DEF("editors/tile_map/editor_side", 1); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::INT, "editors/tile_map/editor_side", PROPERTY_HINT_ENUM, "Left,Right")); - - tile_map_editor = memnew(TileMapEditor(p_node)); - switch ((int)EditorSettings::get_singleton()->get("editors/tile_map/editor_side")) { - case 0: { // Left. - add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_LEFT, tile_map_editor); - } break; - case 1: { // Right. - add_control_to_container(CONTAINER_CANVAS_EDITOR_SIDE_RIGHT, tile_map_editor); - } break; - } - tile_map_editor->hide(); -} - -TileMapEditorPlugin::~TileMapEditorPlugin() { -} diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h deleted file mode 100644 index 421a3b3f68..0000000000 --- a/editor/plugins/tile_map_editor_plugin.h +++ /dev/null @@ -1,242 +0,0 @@ -/*************************************************************************/ -/* tile_map_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef TILE_MAP_EDITOR_PLUGIN_H -#define TILE_MAP_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" -#include "editor/editor_plugin.h" -#include "scene/2d/tile_map.h" -#include "scene/gui/check_box.h" -#include "scene/gui/label.h" -#include "scene/gui/line_edit.h" -#include "scene/gui/menu_button.h" - -class TileMapEditor : public VBoxContainer { - GDCLASS(TileMapEditor, VBoxContainer); - - enum Tool { - TOOL_NONE, - TOOL_PAINTING, - TOOL_ERASING, - TOOL_RECTANGLE_PAINT, - TOOL_RECTANGLE_ERASE, - TOOL_LINE_PAINT, - TOOL_LINE_ERASE, - TOOL_SELECTING, - TOOL_BUCKET, - TOOL_PICKING, - TOOL_PASTING - }; - - enum Options { - OPTION_COPY, - OPTION_ERASE_SELECTION, - OPTION_FIX_INVALID, - OPTION_CUT - }; - - TileMap *node; - bool manual_autotile; - bool priority_atlastile; - Vector2 manual_position; - - EditorNode *editor; - UndoRedo *undo_redo; - Control *canvas_item_editor_viewport; - - LineEdit *search_box; - HSlider *size_slider; - ItemList *palette; - ItemList *manual_palette; - - Label *info_message; - - HBoxContainer *toolbar; - HBoxContainer *toolbar_right; - - Label *tile_info; - MenuButton *options; - - Button *paint_button; - Button *line_button; - Button *rectangle_button; - Button *bucket_fill_button; - Button *picker_button; - Button *select_button; - - Button *flip_horizontal_button; - Button *flip_vertical_button; - Button *rotate_left_button; - Button *rotate_right_button; - Button *clear_transform_button; - - CheckBox *manual_button; - CheckBox *priority_button; - - Tool tool; - Tool last_tool; - - bool selection_active; - bool mouse_over; - bool mouse_down; - - bool flip_h; - bool flip_v; - bool transpose; - Point2i autotile_coord; - - Point2i rectangle_begin; - Rect2i rectangle; - - Point2i over_tile; - bool refocus_over_tile = false; - - bool *bucket_cache_visited; - Rect2i bucket_cache_rect; - int bucket_cache_tile; - Vector<Vector2> bucket_cache; - List<Point2i> bucket_queue; - - struct CellOp { - int idx = TileMap::INVALID_CELL; - bool xf = false; - bool yf = false; - bool tr = false; - Vector2 ac; - }; - - Map<Point2i, CellOp> paint_undo; - - struct TileData { - Point2i pos; - int cell = TileMap::INVALID_CELL; - bool flip_h = false; - bool flip_v = false; - bool transpose = false; - Point2i autotile_coord; - }; - - List<TileData> copydata; - - Map<Point2i, CellOp> undo_data; - Vector<int> invalid_cell; - - void _pick_tile(const Point2 &p_pos); - - Vector<Vector2> _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false); - - void _fill_points(const Vector<Vector2> &p_points, const Dictionary &p_op); - void _erase_points(const Vector<Vector2> &p_points); - - void _select(const Point2i &p_from, const Point2i &p_to); - void _erase_selection(); - - void _draw_cell(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform); - void _draw_fill_preview(Control *p_viewport, int p_cell, const Point2i &p_point, bool p_flip_h, bool p_flip_v, bool p_transpose, const Point2i &p_autotile_coord, const Transform2D &p_xform); - void _clear_bucket_cache(); - - void _update_copydata(); - - Vector<int> get_selected_tiles() const; - void set_selected_tiles(Vector<int> p_tile); - - void _manual_toggled(bool p_enabled); - void _priority_toggled(bool p_enabled); - void _text_entered(const String &p_text); - void _text_changed(const String &p_text); - void _sbox_input(const Ref<InputEvent> &p_ie); - void _update_palette(); - void _update_button_tool(); - void _button_tool_select(int p_tool); - void _menu_option(int p_option); - void _palette_selected(int index); - void _palette_multi_selected(int index, bool selected); - void _palette_input(const Ref<InputEvent> &p_event); - - Dictionary _create_cell_dictionary(int tile, bool flip_x, bool flip_y, bool transpose, Vector2 autotile_coord); - void _start_undo(const String &p_action); - void _finish_undo(); - void _create_set_cell_undo_redo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new); - void _set_cell(const Point2i &p_pos, Vector<int> p_values, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, const Point2i &p_autotile_coord = Point2()); - - void _canvas_mouse_enter(); - void _canvas_mouse_exit(); - void _tileset_settings_changed(); - void _icon_size_changed(float p_value); - - void _clear_transform(); - void _flip_horizontal(); - void _flip_vertical(); - void _rotate(int steps); - -protected: - void _notification(int p_what); - void _node_removed(Node *p_node); - static void _bind_methods(); - CellOp _get_op_from_cell(const Point2i &p_pos); - -public: - HBoxContainer *get_toolbar() const { return toolbar; } - HBoxContainer *get_toolbar_right() const { return toolbar_right; } - Label *get_tile_info() const { return tile_info; } - - bool forward_gui_input(const Ref<InputEvent> &p_event); - void forward_canvas_draw_over_viewport(Control *p_overlay); - - void edit(Node *p_tile_map); - - TileMapEditor(EditorNode *p_editor); - ~TileMapEditor(); -}; - -class TileMapEditorPlugin : public EditorPlugin { - GDCLASS(TileMapEditorPlugin, EditorPlugin); - - TileMapEditor *tile_map_editor; - -protected: - void _notification(int p_what); - -public: - virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tile_map_editor->forward_gui_input(p_event); } - virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tile_map_editor->forward_canvas_draw_over_viewport(p_overlay); } - - virtual String get_name() const override { return "TileMap"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_object) override; - virtual bool handles(Object *p_object) const override; - virtual void make_visible(bool p_visible) override; - - TileMapEditorPlugin(EditorNode *p_node); - ~TileMapEditorPlugin(); -}; - -#endif // TILE_MAP_EDITOR_PLUGIN_H diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp deleted file mode 100644 index f683c4b10d..0000000000 --- a/editor/plugins/tile_set_editor_plugin.cpp +++ /dev/null @@ -1,3680 +0,0 @@ -/*************************************************************************/ -/* tile_set_editor_plugin.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#include "tile_set_editor_plugin.h" - -#include "core/input/input.h" -#include "core/os/keyboard.h" -#include "editor/editor_scale.h" -#include "editor/plugins/canvas_item_editor_plugin.h" -#include "scene/2d/physics_body_2d.h" -#include "scene/2d/sprite_2d.h" - -void TileSetEditor::edit(const Ref<TileSet> &p_tileset) { - tileset = p_tileset; - - texture_list->clear(); - texture_map.clear(); - update_texture_list(); -} - -void TileSetEditor::_import_node(Node *p_node, Ref<TileSet> p_library) { - for (int i = 0; i < p_node->get_child_count(); i++) { - Node *child = p_node->get_child(i); - - if (!Object::cast_to<Sprite2D>(child)) { - if (child->get_child_count() > 0) { - _import_node(child, p_library); - } - - continue; - } - - Sprite2D *mi = Object::cast_to<Sprite2D>(child); - Ref<Texture2D> texture = mi->get_texture(); - Ref<ShaderMaterial> material = mi->get_material(); - - if (texture.is_null()) { - continue; - } - - int id = p_library->find_tile_by_name(mi->get_name()); - if (id < 0) { - id = p_library->get_last_unused_tile_id(); - p_library->create_tile(id); - p_library->tile_set_name(id, mi->get_name()); - } - - p_library->tile_set_texture(id, texture); - p_library->tile_set_material(id, material); - - p_library->tile_set_modulate(id, mi->get_modulate()); - - Vector2 phys_offset; - Size2 s; - - if (mi->is_region_enabled()) { - s = mi->get_region_rect().size; - p_library->tile_set_region(id, mi->get_region_rect()); - } else { - const int frame = mi->get_frame(); - const int hframes = mi->get_hframes(); - s = texture->get_size() / Size2(hframes, mi->get_vframes()); - p_library->tile_set_region(id, Rect2(Vector2(frame % hframes, frame / hframes) * s, s)); - } - - if (mi->is_centered()) { - phys_offset += -s / 2; - } - - Vector<TileSet::ShapeData> collisions; - Ref<NavigationPolygon> nav_poly; - Ref<OccluderPolygon2D> occluder; - bool found_collisions = false; - - for (int j = 0; j < mi->get_child_count(); j++) { - Node *child2 = mi->get_child(j); - - if (Object::cast_to<NavigationRegion2D>(child2)) { - nav_poly = Object::cast_to<NavigationRegion2D>(child2)->get_navigation_polygon(); - } - - if (Object::cast_to<LightOccluder2D>(child2)) { - occluder = Object::cast_to<LightOccluder2D>(child2)->get_occluder_polygon(); - } - - if (!Object::cast_to<StaticBody2D>(child2)) { - continue; - } - - found_collisions = true; - - StaticBody2D *sb = Object::cast_to<StaticBody2D>(child2); - - List<uint32_t> shapes; - sb->get_shape_owners(&shapes); - - for (List<uint32_t>::Element *E = shapes.front(); E; E = E->next()) { - if (sb->is_shape_owner_disabled(E->get())) { - continue; - } - - Transform2D shape_transform = sb->get_transform() * sb->shape_owner_get_transform(E->get()); - bool one_way = sb->is_shape_owner_one_way_collision_enabled(E->get()); - - shape_transform[2] -= phys_offset; - - for (int k = 0; k < sb->shape_owner_get_shape_count(E->get()); k++) { - Ref<Shape2D> shape = sb->shape_owner_get_shape(E->get(), k); - TileSet::ShapeData shape_data; - shape_data.shape = shape; - shape_data.shape_transform = shape_transform; - shape_data.one_way_collision = one_way; - collisions.push_back(shape_data); - } - } - } - - if (found_collisions) { - p_library->tile_set_shapes(id, collisions); - } - - p_library->tile_set_texture_offset(id, mi->get_offset()); - p_library->tile_set_navigation_polygon(id, nav_poly); - p_library->tile_set_light_occluder(id, occluder); - p_library->tile_set_occluder_offset(id, -phys_offset); - p_library->tile_set_navigation_polygon_offset(id, -phys_offset); - p_library->tile_set_z_index(id, mi->get_z_index()); - } -} - -void TileSetEditor::_import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge) { - if (!p_merge) { - p_library->clear(); - } - - _import_node(p_scene, p_library); -} - -void TileSetEditor::_undo_redo_import_scene(Node *p_scene, bool p_merge) { - _import_scene(p_scene, tileset, p_merge); -} - -Error TileSetEditor::update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge) { - _import_scene(p_base_scene, ml, p_merge); - return OK; -} - -Variant TileSetEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) { - return false; -} - -bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { - Dictionary d = p_data; - - if (!d.has("type")) { - return false; - } - - if (d.has("from") && (Object *)(d["from"]) == texture_list) { - return false; - } - - if (String(d["type"]) == "resource" && d.has("resource")) { - RES r = d["resource"]; - - Ref<Texture2D> texture = r; - - if (texture.is_valid()) { - return true; - } - } - - if (String(d["type"]) == "files") { - Vector<String> files = d["files"]; - - if (files.size() == 0) { - return false; - } - - for (int i = 0; i < files.size(); i++) { - String file = files[i]; - String ftype = EditorFileSystem::get_singleton()->get_file_type(file); - - if (!ClassDB::is_parent_class(ftype, "Texture")) { - return false; - } - } - - return true; - } - return false; -} - -void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { - if (!can_drop_data_fw(p_point, p_data, p_from)) { - return; - } - - Dictionary d = p_data; - - if (!d.has("type")) { - return; - } - - if (String(d["type"]) == "resource" && d.has("resource")) { - RES r = d["resource"]; - - Ref<Texture2D> texture = r; - - if (texture.is_valid()) { - add_texture(texture); - } - - if (texture_list->get_item_count() > 0) { - update_texture_list_icon(); - texture_list->select(texture_list->get_item_count() - 1); - _on_texture_list_selected(texture_list->get_item_count() - 1); - } - } - - if (String(d["type"]) == "files") { - Vector<String> files = d["files"]; - - _on_textures_added(files); - } -} - -void TileSetEditor::_bind_methods() { - ClassDB::bind_method("_undo_redo_import_scene", &TileSetEditor::_undo_redo_import_scene); - ClassDB::bind_method("_on_workspace_process", &TileSetEditor::_on_workspace_process); // Still used by some connect_compat. - ClassDB::bind_method("_set_snap_step", &TileSetEditor::_set_snap_step); - ClassDB::bind_method("_set_snap_off", &TileSetEditor::_set_snap_off); - ClassDB::bind_method("_set_snap_sep", &TileSetEditor::_set_snap_sep); - ClassDB::bind_method("_validate_current_tile_id", &TileSetEditor::_validate_current_tile_id); - ClassDB::bind_method("_select_edited_shape_coord", &TileSetEditor::_select_edited_shape_coord); - ClassDB::bind_method("_sort_tiles", &TileSetEditor::_sort_tiles); - - ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &TileSetEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetEditor::drop_data_fw); - - ClassDB::bind_method("edit", &TileSetEditor::edit); - ClassDB::bind_method("add_texture", &TileSetEditor::add_texture); - ClassDB::bind_method("remove_texture", &TileSetEditor::remove_texture); - ClassDB::bind_method("update_texture_list_icon", &TileSetEditor::update_texture_list_icon); - ClassDB::bind_method("update_workspace_minsize", &TileSetEditor::update_workspace_minsize); -} - -void TileSetEditor::_notification(int p_what) { - switch (p_what) { - case NOTIFICATION_READY: { - add_theme_constant_override("autohide", 1); // Fixes the dragger always showing up. - } break; - case NOTIFICATION_TRANSLATION_CHANGED: - case NOTIFICATION_LAYOUT_DIRECTION_CHANGED: - case NOTIFICATION_ENTER_TREE: - case NOTIFICATION_THEME_CHANGED: { - tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_icon(get_theme_icon("ToolAddNode", "EditorIcons")); - tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_icon(get_theme_icon("Remove", "EditorIcons")); - tileset_toolbar_tools->set_icon(get_theme_icon("Tools", "EditorIcons")); - - tool_workspacemode[WORKSPACE_EDIT]->set_icon(get_theme_icon("Edit", "EditorIcons")); - tool_workspacemode[WORKSPACE_CREATE_SINGLE]->set_icon(get_theme_icon("AddSingleTile", "EditorIcons")); - tool_workspacemode[WORKSPACE_CREATE_AUTOTILE]->set_icon(get_theme_icon("AddAutotile", "EditorIcons")); - tool_workspacemode[WORKSPACE_CREATE_ATLAS]->set_icon(get_theme_icon("AddAtlasTile", "EditorIcons")); - - tools[TOOL_SELECT]->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); - tools[BITMASK_COPY]->set_icon(get_theme_icon("Duplicate", "EditorIcons")); - tools[BITMASK_PASTE]->set_icon(get_theme_icon("Override", "EditorIcons")); - tools[BITMASK_CLEAR]->set_icon(get_theme_icon("Clear", "EditorIcons")); - tools[SHAPE_NEW_POLYGON]->set_icon(get_theme_icon("CollisionPolygon2D", "EditorIcons")); - tools[SHAPE_NEW_RECTANGLE]->set_icon(get_theme_icon("CollisionShape2D", "EditorIcons")); - if (is_layout_rtl()) { - tools[SELECT_PREVIOUS]->set_icon(get_theme_icon("ArrowLeft", "EditorIcons")); - tools[SELECT_NEXT]->set_icon(get_theme_icon("ArrowRight", "EditorIcons")); - } else { - tools[SELECT_PREVIOUS]->set_icon(get_theme_icon("ArrowRight", "EditorIcons")); - tools[SELECT_NEXT]->set_icon(get_theme_icon("ArrowLeft", "EditorIcons")); - } - tools[SHAPE_DELETE]->set_icon(get_theme_icon("Remove", "EditorIcons")); - tools[SHAPE_KEEP_INSIDE_TILE]->set_icon(get_theme_icon("Snap", "EditorIcons")); - tools[TOOL_GRID_SNAP]->set_icon(get_theme_icon("SnapGrid", "EditorIcons")); - tools[ZOOM_OUT]->set_icon(get_theme_icon("ZoomLess", "EditorIcons")); - tools[ZOOM_1]->set_icon(get_theme_icon("ZoomReset", "EditorIcons")); - tools[ZOOM_IN]->set_icon(get_theme_icon("ZoomMore", "EditorIcons")); - tools[VISIBLE_INFO]->set_icon(get_theme_icon("InformationSign", "EditorIcons")); - _update_toggle_shape_button(); - - tool_editmode[EDITMODE_REGION]->set_icon(get_theme_icon("RegionEdit", "EditorIcons")); - tool_editmode[EDITMODE_COLLISION]->set_icon(get_theme_icon("StaticBody2D", "EditorIcons")); - tool_editmode[EDITMODE_OCCLUSION]->set_icon(get_theme_icon("LightOccluder2D", "EditorIcons")); - tool_editmode[EDITMODE_NAVIGATION]->set_icon(get_theme_icon("Navigation2D", "EditorIcons")); - tool_editmode[EDITMODE_BITMASK]->set_icon(get_theme_icon("PackedDataContainer", "EditorIcons")); - tool_editmode[EDITMODE_PRIORITY]->set_icon(get_theme_icon("MaterialPreviewLight1", "EditorIcons")); - tool_editmode[EDITMODE_ICON]->set_icon(get_theme_icon("Image", "EditorIcons")); - tool_editmode[EDITMODE_Z_INDEX]->set_icon(get_theme_icon("Sort", "EditorIcons")); - - scroll->add_theme_style_override("bg", get_theme_stylebox("bg", "Tree")); - } break; - } -} - -TileSetEditor::TileSetEditor(EditorNode *p_editor) { - editor = p_editor; - undo_redo = EditorNode::get_undo_redo(); - current_tile = -1; - - VBoxContainer *left_container = memnew(VBoxContainer); - add_child(left_container); - - texture_list = memnew(ItemList); - left_container->add_child(texture_list); - texture_list->set_v_size_flags(SIZE_EXPAND_FILL); - texture_list->set_custom_minimum_size(Size2(200, 0)); - texture_list->connect("item_selected", callable_mp(this, &TileSetEditor::_on_texture_list_selected)); - texture_list->set_drag_forwarding(this); - - HBoxContainer *tileset_toolbar_container = memnew(HBoxContainer); - left_container->add_child(tileset_toolbar_container); - - tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE] = memnew(Button); - tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_flat(true); - tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_ADD_TEXTURE)); - tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]); - tileset_toolbar_buttons[TOOL_TILESET_ADD_TEXTURE]->set_tooltip(TTR("Add Texture(s) to TileSet.")); - - tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE] = memnew(Button); - tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_flat(true); - tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed), varray(TOOL_TILESET_REMOVE_TEXTURE)); - tileset_toolbar_container->add_child(tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]); - tileset_toolbar_buttons[TOOL_TILESET_REMOVE_TEXTURE]->set_tooltip(TTR("Remove selected Texture from TileSet.")); - - Control *toolbar_separator = memnew(Control); - toolbar_separator->set_h_size_flags(Control::SIZE_EXPAND_FILL); - tileset_toolbar_container->add_child(toolbar_separator); - - tileset_toolbar_tools = memnew(MenuButton); - tileset_toolbar_tools->set_text(TTR("Tools")); - tileset_toolbar_tools->get_popup()->add_item(TTR("Create from Scene"), TOOL_TILESET_CREATE_SCENE); - tileset_toolbar_tools->get_popup()->add_item(TTR("Merge from Scene"), TOOL_TILESET_MERGE_SCENE); - - tileset_toolbar_tools->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_button_pressed)); - tileset_toolbar_container->add_child(tileset_toolbar_tools); - - //--------------- - VBoxContainer *right_container = memnew(VBoxContainer); - right_container->set_v_size_flags(SIZE_EXPAND_FILL); - add_child(right_container); - - dragging_point = -1; - creating_shape = false; - snap_step = Vector2(32, 32); - snap_offset = WORKSPACE_MARGIN; - - set_custom_minimum_size(Size2(0, 150)); - - VBoxContainer *main_vb = memnew(VBoxContainer); - right_container->add_child(main_vb); - main_vb->set_v_size_flags(SIZE_EXPAND_FILL); - - HBoxContainer *tool_hb = memnew(HBoxContainer); - Ref<ButtonGroup> g(memnew(ButtonGroup)); - - String workspace_label[WORKSPACE_MODE_MAX] = { - TTR("Edit"), - TTR("New Single Tile"), - TTR("New Autotile"), - TTR("New Atlas") - }; - for (int i = 0; i < (int)WORKSPACE_MODE_MAX; i++) { - tool_workspacemode[i] = memnew(Button); - tool_workspacemode[i]->set_text(workspace_label[i]); - tool_workspacemode[i]->set_toggle_mode(true); - tool_workspacemode[i]->set_button_group(g); - tool_workspacemode[i]->connect("pressed", callable_mp(this, &TileSetEditor::_on_workspace_mode_changed), varray(i)); - tool_hb->add_child(tool_workspacemode[i]); - } - - Control *spacer = memnew(Control); - spacer->set_h_size_flags(Control::SIZE_EXPAND_FILL); - tool_hb->add_child(spacer); - tool_hb->move_child(spacer, WORKSPACE_CREATE_SINGLE); - - tools[SELECT_NEXT] = memnew(Button); - tool_hb->add_child(tools[SELECT_NEXT]); - tool_hb->move_child(tools[SELECT_NEXT], WORKSPACE_CREATE_SINGLE); - tools[SELECT_NEXT]->set_flat(true); - tools[SELECT_NEXT]->set_shortcut(ED_SHORTCUT("tileset_editor/next_shape", TTR("Next Coordinate"), KEY_PAGEDOWN)); - tools[SELECT_NEXT]->set_shortcut_context(this); - tools[SELECT_NEXT]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_NEXT)); - tools[SELECT_NEXT]->set_tooltip(TTR("Select the next shape, subtile, or Tile.")); - tools[SELECT_PREVIOUS] = memnew(Button); - tool_hb->add_child(tools[SELECT_PREVIOUS]); - tool_hb->move_child(tools[SELECT_PREVIOUS], WORKSPACE_CREATE_SINGLE); - tools[SELECT_PREVIOUS]->set_flat(true); - tools[SELECT_PREVIOUS]->set_shortcut(ED_SHORTCUT("tileset_editor/previous_shape", TTR("Previous Coordinate"), KEY_PAGEUP)); - tools[SELECT_PREVIOUS]->set_shortcut_context(this); - tools[SELECT_PREVIOUS]->set_tooltip(TTR("Select the previous shape, subtile, or Tile.")); - tools[SELECT_PREVIOUS]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SELECT_PREVIOUS)); - - VSeparator *separator_shape_selection = memnew(VSeparator); - tool_hb->add_child(separator_shape_selection); - tool_hb->move_child(separator_shape_selection, WORKSPACE_CREATE_SINGLE); - - tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); - workspace_mode = WORKSPACE_EDIT; - - main_vb->add_child(tool_hb); - main_vb->add_child(memnew(HSeparator)); - - tool_hb = memnew(HBoxContainer); - - g = Ref<ButtonGroup>(memnew(ButtonGroup)); - String label[EDITMODE_MAX] = { - TTR("Region"), - TTR("Collision"), - TTR("Occlusion"), - TTR("Navigation"), - TTR("Bitmask"), - TTR("Priority"), - TTR("Icon"), - TTR("Z Index") - }; - for (int i = 0; i < (int)EDITMODE_MAX; i++) { - tool_editmode[i] = memnew(Button); - tool_editmode[i]->set_text(label[i]); - tool_editmode[i]->set_toggle_mode(true); - tool_editmode[i]->set_button_group(g); - tool_editmode[i]->connect("pressed", callable_mp(this, &TileSetEditor::_on_edit_mode_changed), varray(i)); - tool_hb->add_child(tool_editmode[i]); - } - tool_editmode[EDITMODE_COLLISION]->set_pressed(true); - edit_mode = EDITMODE_COLLISION; - - tool_editmode[EDITMODE_REGION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_region", TTR("Region Mode"), KEY_1)); - tool_editmode[EDITMODE_COLLISION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_collision", TTR("Collision Mode"), KEY_2)); - tool_editmode[EDITMODE_OCCLUSION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_occlusion", TTR("Occlusion Mode"), KEY_3)); - tool_editmode[EDITMODE_NAVIGATION]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_navigation", TTR("Navigation Mode"), KEY_4)); - tool_editmode[EDITMODE_BITMASK]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_bitmask", TTR("Bitmask Mode"), KEY_5)); - tool_editmode[EDITMODE_PRIORITY]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_priority", TTR("Priority Mode"), KEY_6)); - tool_editmode[EDITMODE_ICON]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_icon", TTR("Icon Mode"), KEY_7)); - tool_editmode[EDITMODE_Z_INDEX]->set_shortcut(ED_SHORTCUT("tileset_editor/editmode_z_index", TTR("Z Index Mode"), KEY_8)); - - tool_editmode[EDITMODE_REGION]->set_shortcut_context(this); - tool_editmode[EDITMODE_REGION]->set_shortcut_context(this); - tool_editmode[EDITMODE_COLLISION]->set_shortcut_context(this); - tool_editmode[EDITMODE_OCCLUSION]->set_shortcut_context(this); - tool_editmode[EDITMODE_NAVIGATION]->set_shortcut_context(this); - tool_editmode[EDITMODE_BITMASK]->set_shortcut_context(this); - tool_editmode[EDITMODE_PRIORITY]->set_shortcut_context(this); - tool_editmode[EDITMODE_ICON]->set_shortcut_context(this); - tool_editmode[EDITMODE_Z_INDEX]->set_shortcut_context(this); - - main_vb->add_child(tool_hb); - separator_editmode = memnew(HSeparator); - main_vb->add_child(separator_editmode); - - toolbar = memnew(HBoxContainer); - Ref<ButtonGroup> tg(memnew(ButtonGroup)); - - tools[TOOL_SELECT] = memnew(Button); - toolbar->add_child(tools[TOOL_SELECT]); - tools[TOOL_SELECT]->set_flat(true); - tools[TOOL_SELECT]->set_toggle_mode(true); - tools[TOOL_SELECT]->set_button_group(tg); - tools[TOOL_SELECT]->set_pressed(true); - tools[TOOL_SELECT]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(TOOL_SELECT)); - - separator_bitmask = memnew(VSeparator); - toolbar->add_child(separator_bitmask); - tools[BITMASK_COPY] = memnew(Button); - tools[BITMASK_COPY]->set_flat(true); - tools[BITMASK_COPY]->set_tooltip(TTR("Copy bitmask.")); - tools[BITMASK_COPY]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_COPY)); - toolbar->add_child(tools[BITMASK_COPY]); - tools[BITMASK_PASTE] = memnew(Button); - tools[BITMASK_PASTE]->set_flat(true); - tools[BITMASK_PASTE]->set_tooltip(TTR("Paste bitmask.")); - tools[BITMASK_PASTE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_PASTE)); - toolbar->add_child(tools[BITMASK_PASTE]); - tools[BITMASK_CLEAR] = memnew(Button); - tools[BITMASK_CLEAR]->set_flat(true); - tools[BITMASK_CLEAR]->set_tooltip(TTR("Erase bitmask.")); - tools[BITMASK_CLEAR]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(BITMASK_CLEAR)); - toolbar->add_child(tools[BITMASK_CLEAR]); - - tools[SHAPE_NEW_RECTANGLE] = memnew(Button); - toolbar->add_child(tools[SHAPE_NEW_RECTANGLE]); - tools[SHAPE_NEW_RECTANGLE]->set_flat(true); - tools[SHAPE_NEW_RECTANGLE]->set_toggle_mode(true); - tools[SHAPE_NEW_RECTANGLE]->set_button_group(tg); - tools[SHAPE_NEW_RECTANGLE]->set_tooltip(TTR("Create a new rectangle.")); - tools[SHAPE_NEW_RECTANGLE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_NEW_RECTANGLE)); - tools[SHAPE_NEW_RECTANGLE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_rectangle", TTR("New Rectangle"), KEY_MASK_SHIFT | KEY_R)); - - tools[SHAPE_NEW_POLYGON] = memnew(Button); - toolbar->add_child(tools[SHAPE_NEW_POLYGON]); - tools[SHAPE_NEW_POLYGON]->set_flat(true); - tools[SHAPE_NEW_POLYGON]->set_toggle_mode(true); - tools[SHAPE_NEW_POLYGON]->set_button_group(tg); - tools[SHAPE_NEW_POLYGON]->set_tooltip(TTR("Create a new polygon.")); - tools[SHAPE_NEW_POLYGON]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_NEW_POLYGON)); - tools[SHAPE_NEW_POLYGON]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_new_polygon", TTR("New Polygon"), KEY_MASK_SHIFT | KEY_P)); - - separator_shape_toggle = memnew(VSeparator); - toolbar->add_child(separator_shape_toggle); - tools[SHAPE_TOGGLE_TYPE] = memnew(Button); - tools[SHAPE_TOGGLE_TYPE]->set_flat(true); - tools[SHAPE_TOGGLE_TYPE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_TOGGLE_TYPE)); - toolbar->add_child(tools[SHAPE_TOGGLE_TYPE]); - - separator_delete = memnew(VSeparator); - toolbar->add_child(separator_delete); - tools[SHAPE_DELETE] = memnew(Button); - tools[SHAPE_DELETE]->set_flat(true); - tools[SHAPE_DELETE]->connect("pressed", callable_mp(this, &TileSetEditor::_on_tool_clicked), varray(SHAPE_DELETE)); - tools[SHAPE_DELETE]->set_shortcut(ED_SHORTCUT("tileset_editor/shape_delete", TTR("Delete Selected Shape"), KEY_MASK_SHIFT | KEY_BACKSPACE)); - toolbar->add_child(tools[SHAPE_DELETE]); - - spin_priority = memnew(SpinBox); - spin_priority->set_min(1); - spin_priority->set_max(255); - spin_priority->set_step(1); - spin_priority->set_custom_minimum_size(Size2(100, 0)); - spin_priority->connect("value_changed", callable_mp(this, &TileSetEditor::_on_priority_changed)); - spin_priority->hide(); - toolbar->add_child(spin_priority); - - spin_z_index = memnew(SpinBox); - spin_z_index->set_min(RS::CANVAS_ITEM_Z_MIN); - spin_z_index->set_max(RS::CANVAS_ITEM_Z_MAX); - spin_z_index->set_step(1); - spin_z_index->set_custom_minimum_size(Size2(100, 0)); - spin_z_index->connect("value_changed", callable_mp(this, &TileSetEditor::_on_z_index_changed)); - spin_z_index->hide(); - toolbar->add_child(spin_z_index); - - separator_grid = memnew(VSeparator); - toolbar->add_child(separator_grid); - tools[SHAPE_KEEP_INSIDE_TILE] = memnew(Button); - tools[SHAPE_KEEP_INSIDE_TILE]->set_flat(true); - tools[SHAPE_KEEP_INSIDE_TILE]->set_toggle_mode(true); - tools[SHAPE_KEEP_INSIDE_TILE]->set_pressed(true); - tools[SHAPE_KEEP_INSIDE_TILE]->set_tooltip(TTR("Keep polygon inside region Rect.")); - toolbar->add_child(tools[SHAPE_KEEP_INSIDE_TILE]); - tools[TOOL_GRID_SNAP] = memnew(Button); - tools[TOOL_GRID_SNAP]->set_flat(true); - tools[TOOL_GRID_SNAP]->set_toggle_mode(true); - tools[TOOL_GRID_SNAP]->set_tooltip(TTR("Enable snap and show grid (configurable via the Inspector).")); - tools[TOOL_GRID_SNAP]->connect("toggled", callable_mp(this, &TileSetEditor::_on_grid_snap_toggled)); - toolbar->add_child(tools[TOOL_GRID_SNAP]); - - Control *separator = memnew(Control); - separator->set_h_size_flags(SIZE_EXPAND_FILL); - toolbar->add_child(separator); - - tools[ZOOM_OUT] = memnew(Button); - tools[ZOOM_OUT]->set_flat(true); - tools[ZOOM_OUT]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_out)); - toolbar->add_child(tools[ZOOM_OUT]); - tools[ZOOM_OUT]->set_tooltip(TTR("Zoom Out")); - tools[ZOOM_1] = memnew(Button); - tools[ZOOM_1]->set_flat(true); - tools[ZOOM_1]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_reset)); - toolbar->add_child(tools[ZOOM_1]); - tools[ZOOM_1]->set_tooltip(TTR("Zoom Reset")); - tools[ZOOM_IN] = memnew(Button); - tools[ZOOM_IN]->set_flat(true); - tools[ZOOM_IN]->connect("pressed", callable_mp(this, &TileSetEditor::_zoom_in)); - toolbar->add_child(tools[ZOOM_IN]); - tools[ZOOM_IN]->set_tooltip(TTR("Zoom In")); - - tools[VISIBLE_INFO] = memnew(Button); - tools[VISIBLE_INFO]->set_flat(true); - tools[VISIBLE_INFO]->set_toggle_mode(true); - tools[VISIBLE_INFO]->set_tooltip(TTR("Display Tile Names (Hold Alt Key)")); - toolbar->add_child(tools[VISIBLE_INFO]); - - main_vb->add_child(toolbar); - - scroll = memnew(ScrollContainer); - main_vb->add_child(scroll); - scroll->set_v_size_flags(SIZE_EXPAND_FILL); - scroll->connect("gui_input", callable_mp(this, &TileSetEditor::_on_scroll_container_input)); - scroll->set_clip_contents(true); - - empty_message = memnew(Label); - empty_message->set_text(TTR("Add or select a texture on the left panel to edit the tiles bound to it.")); - empty_message->set_valign(Label::VALIGN_CENTER); - empty_message->set_align(Label::ALIGN_CENTER); - empty_message->set_autowrap(true); - empty_message->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - empty_message->set_v_size_flags(SIZE_EXPAND_FILL); - main_vb->add_child(empty_message); - - workspace_container = memnew(Control); - scroll->add_child(workspace_container); - - workspace_overlay = memnew(Control); - workspace_overlay->connect("draw", callable_mp(this, &TileSetEditor::_on_workspace_overlay_draw)); - workspace_container->add_child(workspace_overlay); - - workspace = memnew(Control); - workspace->set_focus_mode(FOCUS_ALL); - workspace->connect("draw", callable_mp(this, &TileSetEditor::_on_workspace_draw)); - workspace->connect("gui_input", callable_mp(this, &TileSetEditor::_on_workspace_input)); - workspace->set_draw_behind_parent(true); - workspace_overlay->add_child(workspace); - - preview = memnew(Sprite2D); - workspace->add_child(preview); - preview->set_centered(false); - preview->set_draw_behind_parent(true); - preview->set_position(WORKSPACE_MARGIN); - - //--------------- - cd = memnew(ConfirmationDialog); - add_child(cd); - cd->connect("confirmed", callable_mp(this, &TileSetEditor::_on_tileset_toolbar_confirm)); - - //--------------- - err_dialog = memnew(AcceptDialog); - add_child(err_dialog); - - //--------------- - texture_dialog = memnew(EditorFileDialog); - texture_dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); - texture_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES); - texture_dialog->clear_filters(); - List<String> extensions; - - ResourceLoader::get_recognized_extensions_for_type("Texture2D", &extensions); - for (List<String>::Element *E = extensions.front(); E; E = E->next()) { - texture_dialog->add_filter("*." + E->get() + " ; " + E->get().to_upper()); - } - add_child(texture_dialog); - texture_dialog->connect("files_selected", callable_mp(this, &TileSetEditor::_on_textures_added)); - - //--------------- - helper = memnew(TilesetEditorContext(this)); - tile_names_visible = false; - - // Config scale. - max_scale = 16.0f; - min_scale = 0.01f; - scale_ratio = 1.2f; -} - -TileSetEditor::~TileSetEditor() { - if (helper) { - memdelete(helper); - } -} - -void TileSetEditor::_on_tileset_toolbar_button_pressed(int p_index) { - option = p_index; - switch (option) { - case TOOL_TILESET_ADD_TEXTURE: { - texture_dialog->popup_file_dialog(); - } break; - case TOOL_TILESET_REMOVE_TEXTURE: { - if (get_current_texture().is_valid()) { - cd->set_text(TTR("Remove selected texture? This will remove all tiles which use it.")); - cd->popup_centered(Size2(300, 60)); - } else { - err_dialog->set_text(TTR("You haven't selected a texture to remove.")); - err_dialog->popup_centered(Size2(300, 60)); - } - } break; - case TOOL_TILESET_CREATE_SCENE: { - cd->set_text(TTR("Create from scene? This will overwrite all current tiles.")); - cd->popup_centered(Size2(300, 60)); - } break; - case TOOL_TILESET_MERGE_SCENE: { - cd->set_text(TTR("Merge from scene?")); - cd->popup_centered(Size2(300, 60)); - } break; - } -} - -void TileSetEditor::_on_tileset_toolbar_confirm() { - switch (option) { - case TOOL_TILESET_REMOVE_TEXTURE: { - RID current_rid = get_current_texture()->get_rid(); - List<int> ids; - tileset->get_tile_list(&ids); - - undo_redo->create_action(TTR("Remove Texture")); - for (List<int>::Element *E = ids.front(); E; E = E->next()) { - if (tileset->tile_get_texture(E->get())->get_rid() == current_rid) { - undo_redo->add_do_method(tileset.ptr(), "remove_tile", E->get()); - _undo_tile_removal(E->get()); - } - } - undo_redo->add_do_method(this, "remove_texture", get_current_texture()); - undo_redo->add_undo_method(this, "add_texture", get_current_texture()); - undo_redo->add_undo_method(this, "update_texture_list_icon"); - undo_redo->commit_action(); - } break; - case TOOL_TILESET_MERGE_SCENE: - case TOOL_TILESET_CREATE_SCENE: { - EditorNode *en = editor; - Node *scene = en->get_edited_scene(); - if (!scene) { - break; - } - - List<int> ids; - tileset->get_tile_list(&ids); - - undo_redo->create_action(option == TOOL_TILESET_MERGE_SCENE ? TTR("Merge Tileset from Scene") : TTR("Create Tileset from Scene")); - undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE); - undo_redo->add_undo_method(tileset.ptr(), "clear"); - for (List<int>::Element *E = ids.front(); E; E = E->next()) { - _undo_tile_removal(E->get()); - } - undo_redo->add_do_method(this, "edit", tileset); - undo_redo->add_undo_method(this, "edit", tileset); - undo_redo->commit_action(); - } break; - } -} - -void TileSetEditor::_on_texture_list_selected(int p_index) { - if (get_current_texture().is_valid()) { - current_item_index = p_index; - preview->set_texture(get_current_texture()); - update_workspace_tile_mode(); - update_workspace_minsize(); - } else { - current_item_index = -1; - preview->set_texture(nullptr); - workspace->set_custom_minimum_size(Size2i()); - update_workspace_tile_mode(); - } - - set_current_tile(-1); - workspace->update(); -} - -void TileSetEditor::_on_textures_added(const PackedStringArray &p_paths) { - int invalid_count = 0; - for (int i = 0; i < p_paths.size(); i++) { - Ref<Texture2D> t = Ref<Texture2D>(ResourceLoader::load(p_paths[i])); - - ERR_CONTINUE_MSG(!t.is_valid(), "'" + p_paths[i] + "' is not a valid texture."); - - if (texture_map.has(t->get_rid())) { - invalid_count++; - } else { - add_texture(t); - } - } - - if (texture_list->get_item_count() > 0) { - update_texture_list_icon(); - texture_list->select(texture_list->get_item_count() - 1); - _on_texture_list_selected(texture_list->get_item_count() - 1); - } - - if (invalid_count > 0) { - err_dialog->set_text(vformat(TTR("%s file(s) were not added because was already on the list."), String::num(invalid_count, 0))); - err_dialog->popup_centered(Size2(300, 60)); - } -} - -void TileSetEditor::_on_edit_mode_changed(int p_edit_mode) { - draw_handles = false; - creating_shape = false; - edit_mode = (EditMode)p_edit_mode; - switch (edit_mode) { - case EDITMODE_REGION: { - tools[TOOL_SELECT]->show(); - - separator_bitmask->hide(); - tools[BITMASK_COPY]->hide(); - tools[BITMASK_PASTE]->hide(); - tools[BITMASK_CLEAR]->hide(); - tools[SHAPE_NEW_POLYGON]->hide(); - tools[SHAPE_NEW_RECTANGLE]->hide(); - - if (workspace_mode == WORKSPACE_EDIT) { - separator_delete->show(); - tools[SHAPE_DELETE]->show(); - } else { - separator_delete->hide(); - tools[SHAPE_DELETE]->hide(); - } - - separator_grid->show(); - tools[SHAPE_KEEP_INSIDE_TILE]->hide(); - tools[TOOL_GRID_SNAP]->show(); - - tools[TOOL_SELECT]->set_pressed(true); - tools[TOOL_SELECT]->set_tooltip(TTR("Drag handles to edit Rect.\nClick on another Tile to edit it.")); - tools[SHAPE_DELETE]->set_tooltip(TTR("Delete selected Rect.")); - spin_priority->hide(); - spin_z_index->hide(); - } break; - case EDITMODE_COLLISION: - case EDITMODE_OCCLUSION: - case EDITMODE_NAVIGATION: { - tools[TOOL_SELECT]->show(); - - separator_bitmask->hide(); - tools[BITMASK_COPY]->hide(); - tools[BITMASK_PASTE]->hide(); - tools[BITMASK_CLEAR]->hide(); - tools[SHAPE_NEW_POLYGON]->show(); - tools[SHAPE_NEW_RECTANGLE]->show(); - - separator_delete->show(); - tools[SHAPE_DELETE]->show(); - - separator_grid->show(); - tools[SHAPE_KEEP_INSIDE_TILE]->show(); - tools[TOOL_GRID_SNAP]->show(); - - tools[TOOL_SELECT]->set_tooltip(TTR("Select current edited sub-tile.\nClick on another Tile to edit it.")); - tools[SHAPE_DELETE]->set_tooltip(TTR("Delete polygon.")); - spin_priority->hide(); - spin_z_index->hide(); - - _select_edited_shape_coord(); - } break; - case EDITMODE_BITMASK: { - tools[TOOL_SELECT]->show(); - - separator_bitmask->show(); - tools[BITMASK_COPY]->show(); - tools[BITMASK_PASTE]->show(); - tools[BITMASK_CLEAR]->show(); - tools[SHAPE_NEW_POLYGON]->hide(); - tools[SHAPE_NEW_RECTANGLE]->hide(); - - separator_delete->hide(); - tools[SHAPE_DELETE]->hide(); - - tools[SHAPE_KEEP_INSIDE_TILE]->hide(); - - tools[TOOL_SELECT]->set_pressed(true); - tools[TOOL_SELECT]->set_tooltip(TTR("LMB: Set bit on.\nRMB: Set bit off.\nShift+LMB: Set wildcard bit.\nClick on another Tile to edit it.")); - spin_priority->hide(); - } break; - case EDITMODE_Z_INDEX: - case EDITMODE_PRIORITY: - case EDITMODE_ICON: { - tools[TOOL_SELECT]->show(); - - separator_bitmask->hide(); - tools[BITMASK_COPY]->hide(); - tools[BITMASK_PASTE]->hide(); - tools[BITMASK_CLEAR]->hide(); - tools[SHAPE_NEW_POLYGON]->hide(); - tools[SHAPE_NEW_RECTANGLE]->hide(); - - separator_delete->hide(); - tools[SHAPE_DELETE]->hide(); - - separator_grid->show(); - tools[SHAPE_KEEP_INSIDE_TILE]->hide(); - tools[TOOL_GRID_SNAP]->show(); - - if (edit_mode == EDITMODE_ICON) { - tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to use as icon, this will be also used on invalid autotile bindings.\nClick on another Tile to edit it.")); - spin_priority->hide(); - spin_z_index->hide(); - } else if (edit_mode == EDITMODE_PRIORITY) { - tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its priority.\nClick on another Tile to edit it.")); - spin_priority->show(); - spin_z_index->hide(); - } else { - tools[TOOL_SELECT]->set_tooltip(TTR("Select sub-tile to change its z index.\nClick on another Tile to edit it.")); - spin_priority->hide(); - spin_z_index->show(); - } - } break; - default: { - } - } - _update_toggle_shape_button(); - workspace->update(); -} - -void TileSetEditor::_on_workspace_mode_changed(int p_workspace_mode) { - workspace_mode = (WorkspaceMode)p_workspace_mode; - if (p_workspace_mode == WORKSPACE_EDIT) { - update_workspace_tile_mode(); - } else { - for (int i = 0; i < EDITMODE_MAX; i++) { - tool_editmode[i]->hide(); - } - tool_editmode[EDITMODE_REGION]->show(); - tool_editmode[EDITMODE_REGION]->set_pressed(true); - _on_edit_mode_changed(EDITMODE_REGION); - separator_editmode->show(); - } -} - -void TileSetEditor::_on_workspace_draw() { - if (tileset.is_null() || !get_current_texture().is_valid()) { - return; - } - - const Color COLOR_AUTOTILE = Color(0.3, 0.6, 1); - const Color COLOR_SINGLE = Color(1, 1, 0.3); - const Color COLOR_ATLAS = Color(0.8, 0.8, 0.8); - const Color COLOR_SUBDIVISION = Color(0.3, 0.7, 0.6); - - draw_handles = false; - - draw_highlight_current_tile(); - - draw_grid_snap(); - if (get_current_tile() >= 0) { - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->autotile_get_size(get_current_tile()); - Rect2i region = tileset->tile_get_region(get_current_tile()); - - switch (edit_mode) { - case EDITMODE_ICON: { - Vector2 coord = tileset->autotile_get_icon_coordinate(get_current_tile()); - draw_highlight_subtile(coord); - } break; - case EDITMODE_BITMASK: { - Color c(1, 0, 0, 0.5); - Color ci(0.3, 0.6, 1, 0.5); - for (int x = 0; x < region.size.x / (spacing + size.x); x++) { - for (int y = 0; y < region.size.y / (spacing + size.y); y++) { - Vector2 coord(x, y); - Point2 anchor(coord.x * (spacing + size.x), coord.y * (spacing + size.y)); - anchor += WORKSPACE_MARGIN; - anchor += region.position; - uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), coord); - if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) { - if (mask & TileSet::BIND_IGNORE_TOPLEFT) { - workspace->draw_rect(Rect2(anchor, size / 4), ci); - workspace->draw_rect(Rect2(anchor + size / 4, size / 4), ci); - } else if (mask & TileSet::BIND_TOPLEFT) { - workspace->draw_rect(Rect2(anchor, size / 2), c); - } - if (mask & TileSet::BIND_IGNORE_TOPRIGHT) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 4), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x * 3 / 4, size.y / 4), size / 4), ci); - } else if (mask & TileSet::BIND_TOPRIGHT) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, 0), size / 2), c); - } - if (mask & TileSet::BIND_IGNORE_BOTTOMLEFT) { - workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 4), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 4, size.y * 3 / 4), size / 4), ci); - } else if (mask & TileSet::BIND_BOTTOMLEFT) { - workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 2), size / 2), c); - } - if (mask & TileSet::BIND_IGNORE_BOTTOMRIGHT) { - workspace->draw_rect(Rect2(anchor + size / 2, size / 4), ci); - workspace->draw_rect(Rect2(anchor + size * 3 / 4, size / 4), ci); - } else if (mask & TileSet::BIND_BOTTOMRIGHT) { - workspace->draw_rect(Rect2(anchor + size / 2, size / 2), c); - } - } else { - if (mask & TileSet::BIND_IGNORE_TOPLEFT) { - workspace->draw_rect(Rect2(anchor, size / 6), ci); - workspace->draw_rect(Rect2(anchor + size / 6, size / 6), ci); - } else if (mask & TileSet::BIND_TOPLEFT) { - workspace->draw_rect(Rect2(anchor, size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_TOP) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 6), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y / 6), size / 6), ci); - } else if (mask & TileSet::BIND_TOP) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, 0), size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_TOPRIGHT) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, 0), size / 6), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 6), size / 6), ci); - } else if (mask & TileSet::BIND_TOPRIGHT) { - workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, 0), size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_LEFT) { - workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 6), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y / 2), size / 6), ci); - } else if (mask & TileSet::BIND_LEFT) { - workspace->draw_rect(Rect2(anchor + Vector2(0, size.y / 3), size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_CENTER) { - workspace->draw_rect(Rect2(anchor + size / 3, size / 6), ci); - workspace->draw_rect(Rect2(anchor + size / 2, size / 6), ci); - } else if (mask & TileSet::BIND_CENTER) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y / 3), size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_RIGHT) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x * 4 / 6, size.y / 3), size / 6), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x * 5 / 6, size.y / 2), size / 6), ci); - } else if (mask & TileSet::BIND_RIGHT) { - workspace->draw_rect(Rect2(anchor + Vector2((size.x / 3) * 2, size.y / 3), size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_BOTTOMLEFT) { - workspace->draw_rect(Rect2(anchor + Vector2(0, size.y * 4 / 6), size / 6), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 6, size.y * 5 / 6), size / 6), ci); - } else if (mask & TileSet::BIND_BOTTOMLEFT) { - workspace->draw_rect(Rect2(anchor + Vector2(0, (size.y / 3) * 2), size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_BOTTOM) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, size.y * 4 / 6), size / 6), ci); - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 2, size.y * 5 / 6), size / 6), ci); - } else if (mask & TileSet::BIND_BOTTOM) { - workspace->draw_rect(Rect2(anchor + Vector2(size.x / 3, (size.y / 3) * 2), size / 3), c); - } - if (mask & TileSet::BIND_IGNORE_BOTTOMRIGHT) { - workspace->draw_rect(Rect2(anchor + size * 4 / 6, size / 6), ci); - workspace->draw_rect(Rect2(anchor + size * 5 / 6, size / 6), ci); - } else if (mask & TileSet::BIND_BOTTOMRIGHT) { - workspace->draw_rect(Rect2(anchor + (size / 3) * 2, size / 3), c); - } - } - } - } - } break; - case EDITMODE_COLLISION: - case EDITMODE_OCCLUSION: - case EDITMODE_NAVIGATION: { - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - draw_highlight_subtile(edited_shape_coord); - } - draw_polygon_shapes(); - draw_grid_snap(); - } break; - case EDITMODE_PRIORITY: { - spin_priority->set_value(tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)); - uint32_t mask = tileset->autotile_get_bitmask(get_current_tile(), edited_shape_coord); - Vector<Vector2> queue_others; - int total = 0; - for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) { - if (E->value() == mask) { - total += tileset->autotile_get_subtile_priority(get_current_tile(), E->key()); - if (E->key() != edited_shape_coord) { - queue_others.push_back(E->key()); - } - } - } - spin_priority->set_suffix(" / " + String::num(total, 0)); - draw_highlight_subtile(edited_shape_coord, queue_others); - } break; - case EDITMODE_Z_INDEX: { - spin_z_index->set_value(tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)); - draw_highlight_subtile(edited_shape_coord); - } break; - default: { - } - } - } - - RID current_texture_rid = get_current_texture()->get_rid(); - List<int> *tiles = new List<int>(); - tileset->get_tile_list(tiles); - for (List<int>::Element *E = tiles->front(); E; E = E->next()) { - int t_id = E->get(); - if (tileset->tile_get_texture(t_id)->get_rid() == current_texture_rid && (t_id != get_current_tile() || edit_mode != EDITMODE_REGION || workspace_mode != WORKSPACE_EDIT)) { - Rect2i region = tileset->tile_get_region(t_id); - region.position += WORKSPACE_MARGIN; - Color c; - if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) { - c = COLOR_SINGLE; - } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) { - c = COLOR_AUTOTILE; - } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) { - c = COLOR_ATLAS; - } - draw_tile_subdivision(t_id, COLOR_SUBDIVISION); - workspace->draw_rect(region, c, false); - } - } - delete tiles; - - if (edit_mode == EDITMODE_REGION) { - if (workspace_mode != WORKSPACE_EDIT) { - Rect2i region = edited_region; - Color c; - if (workspace_mode == WORKSPACE_CREATE_SINGLE) { - c = COLOR_SINGLE; - } else if (workspace_mode == WORKSPACE_CREATE_AUTOTILE) { - c = COLOR_AUTOTILE; - } else if (workspace_mode == WORKSPACE_CREATE_ATLAS) { - c = COLOR_ATLAS; - } - workspace->draw_rect(region, c, false); - draw_edited_region_subdivision(); - } else { - int t_id = get_current_tile(); - if (t_id < 0) { - return; - } - - Rect2i region; - if (draw_edited_region) { - region = edited_region; - } else { - region = tileset->tile_get_region(t_id); - region.position += WORKSPACE_MARGIN; - } - - if (draw_edited_region) { - draw_edited_region_subdivision(); - } else { - draw_tile_subdivision(t_id, COLOR_SUBDIVISION); - } - - Color c; - if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) { - c = COLOR_SINGLE; - } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) { - c = COLOR_AUTOTILE; - } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) { - c = COLOR_ATLAS; - } - workspace->draw_rect(region, c, false); - } - } - - workspace_overlay->update(); -} - -void TileSetEditor::_on_workspace_process() { - if (Input::get_singleton()->is_key_pressed(KEY_ALT) || tools[VISIBLE_INFO]->is_pressed()) { - if (!tile_names_visible) { - tile_names_visible = true; - workspace_overlay->update(); - } - } else if (tile_names_visible) { - tile_names_visible = false; - workspace_overlay->update(); - } -} - -void TileSetEditor::_on_workspace_overlay_draw() { - if (!tileset.is_valid() || !get_current_texture().is_valid()) { - return; - } - - const Color COLOR_AUTOTILE = Color(0.266373, 0.565288, 0.988281); - const Color COLOR_SINGLE = Color(0.988281, 0.909323, 0.266373); - const Color COLOR_ATLAS = Color(0.78653, 0.812835, 0.832031); - - if (tile_names_visible) { - RID current_texture_rid = get_current_texture()->get_rid(); - List<int> *tiles = new List<int>(); - tileset->get_tile_list(tiles); - for (List<int>::Element *E = tiles->front(); E; E = E->next()) { - int t_id = E->get(); - if (tileset->tile_get_texture(t_id)->get_rid() != current_texture_rid) { - continue; - } - - Rect2 region = tileset->tile_get_region(t_id); - region.position += WORKSPACE_MARGIN; - region.position *= workspace->get_scale().x; - Color c; - if (tileset->tile_get_tile_mode(t_id) == TileSet::SINGLE_TILE) { - c = COLOR_SINGLE; - } else if (tileset->tile_get_tile_mode(t_id) == TileSet::AUTO_TILE) { - c = COLOR_AUTOTILE; - } else if (tileset->tile_get_tile_mode(t_id) == TileSet::ATLAS_TILE) { - c = COLOR_ATLAS; - } - String tile_id_name = String::num(t_id, 0) + ": " + tileset->tile_get_name(t_id); - Ref<Font> font = get_theme_font("font", "Label"); - int font_size = get_theme_font_size("font_size", "Label"); - region.set_size(font->get_string_size(tile_id_name, font_size)); - workspace_overlay->draw_rect(region, c); - region.position.y += region.size.y - 2; - c = Color(0.1, 0.1, 0.1); - workspace_overlay->draw_string(font, region.position, tile_id_name, HALIGN_LEFT, -1, font_size, c); - } - delete tiles; - } - - int t_id = get_current_tile(); - if (t_id < 0) { - return; - } - - Ref<Texture2D> handle = get_theme_icon("EditorHandle", "EditorIcons"); - if (draw_handles) { - for (int i = 0; i < current_shape.size(); i++) { - workspace_overlay->draw_texture(handle, current_shape[i] * workspace->get_scale().x - handle->get_size() * 0.5); - } - } -} - -int TileSetEditor::get_grabbed_point(const Vector2 &p_mouse_pos, real_t p_grab_threshold) { - Transform2D xform = workspace->get_transform(); - - int grabbed_point = -1; - real_t min_distance = 1e10; - - for (int i = 0; i < current_shape.size(); i++) { - const real_t distance = xform.xform(current_shape[i]).distance_to(xform.xform(p_mouse_pos)); - if (distance < p_grab_threshold && distance < min_distance) { - min_distance = distance; - grabbed_point = i; - } - } - - return grabbed_point; -} - -bool TileSetEditor::is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold) { - Transform2D xform = workspace->get_transform(); - - const real_t distance = xform.xform(current_shape[0]).distance_to(xform.xform(p_pos)); - - return distance < p_grab_threshold; -} - -void TileSetEditor::_on_scroll_container_input(const Ref<InputEvent> &p_event) { - const Ref<InputEventMouseButton> mb = p_event; - - if (mb.is_valid()) { - // Zoom in/out using Ctrl + mouse wheel. This is done on the ScrollContainer - // to allow performing this action anywhere, even if the cursor isn't - // hovering the texture in the workspace. - if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_pressed() && mb->get_control()) { - print_line("zooming in"); - _zoom_in(); - // Don't scroll up after zooming in. - accept_event(); - } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_pressed() && mb->get_control()) { - print_line("zooming out"); - _zoom_out(); - // Don't scroll down after zooming out. - accept_event(); - } - } -} - -void TileSetEditor::_on_workspace_input(const Ref<InputEvent> &p_ie) { - if (tileset.is_null() || !get_current_texture().is_valid()) { - return; - } - - static bool dragging; - static bool erasing; - static bool alternative; - draw_edited_region = false; - - Rect2 current_tile_region = Rect2(); - if (get_current_tile() >= 0) { - current_tile_region = tileset->tile_get_region(get_current_tile()); - } - current_tile_region.position += WORKSPACE_MARGIN; - - const Ref<InputEventMouseButton> mb = p_ie; - const Ref<InputEventMouseMotion> mm = p_ie; - - if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && !creating_shape) { - if (!current_tile_region.has_point(mb->get_position())) { - List<int> *tiles = new List<int>(); - tileset->get_tile_list(tiles); - for (List<int>::Element *E = tiles->front(); E; E = E->next()) { - int t_id = E->get(); - if (get_current_texture()->get_rid() == tileset->tile_get_texture(t_id)->get_rid()) { - Rect2 r = tileset->tile_get_region(t_id); - r.position += WORKSPACE_MARGIN; - if (r.has_point(mb->get_position())) { - set_current_tile(t_id); - workspace->update(); - workspace_overlay->update(); - delete tiles; - return; - } - } - } - delete tiles; - } - } - } - // Drag Middle Mouse - if (mm.is_valid()) { - if (mm->get_button_mask() & MOUSE_BUTTON_MASK_MIDDLE) { - Vector2 dragged(mm->get_relative().x, mm->get_relative().y); - scroll->set_h_scroll(scroll->get_h_scroll() - dragged.x * workspace->get_scale().x); - scroll->set_v_scroll(scroll->get_v_scroll() - dragged.y * workspace->get_scale().x); - } - } - - if (edit_mode == EDITMODE_REGION) { - if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (get_current_tile() >= 0 || workspace_mode != WORKSPACE_EDIT) { - dragging = true; - region_from = mb->get_position(); - edited_region = Rect2(region_from, Size2()); - workspace->update(); - workspace_overlay->update(); - return; - } - } else if (dragging && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - dragging = false; - edited_region = Rect2(); - workspace->update(); - workspace_overlay->update(); - return; - } else if (dragging && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - dragging = false; - update_edited_region(mb->get_position()); - edited_region.position -= WORKSPACE_MARGIN; - if (!edited_region.has_no_area()) { - if (get_current_tile() >= 0 && workspace_mode == WORKSPACE_EDIT) { - undo_redo->create_action(TTR("Set Tile Region")); - undo_redo->add_do_method(tileset.ptr(), "tile_set_region", get_current_tile(), edited_region); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", get_current_tile(), tileset->tile_get_region(get_current_tile())); - - Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2; - Size2 workspace_minsize = workspace->get_custom_minimum_size(); - // If the new region is bigger, just directly change the workspace size to avoid checking all other tiles. - if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) { - Size2 max_workspace_size = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y)); - undo_redo->add_do_method(workspace, "set_custom_minimum_size", max_workspace_size); - undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize); - undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", max_workspace_size); - undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize); - undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", max_workspace_size); - undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize); - } else if (workspace_minsize.x > get_current_texture()->get_size().x + WORKSPACE_MARGIN.x * 2 || workspace_minsize.y > get_current_texture()->get_size().y + WORKSPACE_MARGIN.y * 2) { - undo_redo->add_do_method(this, "update_workspace_minsize"); - undo_redo->add_undo_method(this, "update_workspace_minsize"); - } - - edited_region = Rect2(); - - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->add_do_method(workspace_overlay, "update"); - undo_redo->add_undo_method(workspace_overlay, "update"); - undo_redo->commit_action(); - } else { - int t_id = tileset->get_last_unused_tile_id(); - undo_redo->create_action(TTR("Create Tile")); - undo_redo->add_do_method(tileset.ptr(), "create_tile", t_id); - undo_redo->add_undo_method(tileset.ptr(), "remove_tile", t_id); - undo_redo->add_undo_method(this, "_validate_current_tile_id"); - undo_redo->add_do_method(tileset.ptr(), "tile_set_texture", t_id, get_current_texture()); - undo_redo->add_do_method(tileset.ptr(), "tile_set_region", t_id, edited_region); - undo_redo->add_do_method(tileset.ptr(), "tile_set_name", t_id, get_current_texture()->get_path().get_file() + " " + String::num(t_id, 0)); - if (workspace_mode != WORKSPACE_CREATE_SINGLE) { - undo_redo->add_do_method(tileset.ptr(), "autotile_set_size", t_id, snap_step); - undo_redo->add_do_method(tileset.ptr(), "autotile_set_spacing", t_id, snap_separation.x); - undo_redo->add_do_method(tileset.ptr(), "tile_set_tile_mode", t_id, workspace_mode == WORKSPACE_CREATE_AUTOTILE ? TileSet::AUTO_TILE : TileSet::ATLAS_TILE); - } - - tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); - tool_editmode[EDITMODE_COLLISION]->set_pressed(true); - edit_mode = EDITMODE_COLLISION; - - Size2 tile_workspace_size = edited_region.position + edited_region.size + WORKSPACE_MARGIN * 2; - Size2 workspace_minsize = workspace->get_custom_minimum_size(); - if (tile_workspace_size.x > workspace_minsize.x || tile_workspace_size.y > workspace_minsize.y) { - Size2 new_workspace_minsize = Size2(MAX(tile_workspace_size.x, workspace_minsize.x), MAX(tile_workspace_size.y, workspace_minsize.y)); - undo_redo->add_do_method(workspace, "set_custom_minimum_size", new_workspace_minsize); - undo_redo->add_undo_method(workspace, "set_custom_minimum_size", workspace_minsize); - undo_redo->add_do_method(workspace_container, "set_custom_minimum_size", new_workspace_minsize); - undo_redo->add_undo_method(workspace_container, "set_custom_minimum_size", workspace_minsize); - undo_redo->add_do_method(workspace_overlay, "set_custom_minimum_size", new_workspace_minsize); - undo_redo->add_undo_method(workspace_overlay, "set_custom_minimum_size", workspace_minsize); - } - - edited_region = Rect2(); - - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->add_do_method(workspace_overlay, "update"); - undo_redo->add_undo_method(workspace_overlay, "update"); - undo_redo->commit_action(); - - set_current_tile(t_id); - _on_workspace_mode_changed(WORKSPACE_EDIT); - } - } else { - edited_region = Rect2(); - workspace->update(); - workspace_overlay->update(); - } - return; - } - } else if (mm.is_valid()) { - if (dragging) { - update_edited_region(mm->get_position()); - draw_edited_region = true; - workspace->update(); - workspace_overlay->update(); - return; - } - } - } - - if (workspace_mode == WORKSPACE_EDIT) { - if (get_current_tile() >= 0) { - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->autotile_get_size(get_current_tile()); - switch (edit_mode) { - case EDITMODE_ICON: { - if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT && current_tile_region.has_point(mb->get_position())) { - Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y))); - undo_redo->create_action(TTR("Set Tile Icon")); - undo_redo->add_do_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), coord); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", get_current_tile(), tileset->autotile_get_icon_coordinate(get_current_tile())); - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->commit_action(); - } - } - } break; - case EDITMODE_BITMASK: { - if (mb.is_valid()) { - if (mb->is_pressed()) { - if (dragging) { - return; - } - if ((mb->get_button_index() == MOUSE_BUTTON_RIGHT || mb->get_button_index() == MOUSE_BUTTON_LEFT) && current_tile_region.has_point(mb->get_position())) { - dragging = true; - erasing = (mb->get_button_index() == MOUSE_BUTTON_RIGHT); - alternative = Input::get_singleton()->is_key_pressed(KEY_SHIFT); - Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y))); - Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y)); - pos = mb->get_position() - (pos + current_tile_region.position); - uint32_t bit = 0; - if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) { - if (pos.x < size.x / 2) { - if (pos.y < size.y / 2) { - bit = TileSet::BIND_TOPLEFT; - } else { - bit = TileSet::BIND_BOTTOMLEFT; - } - } else { - if (pos.y < size.y / 2) { - bit = TileSet::BIND_TOPRIGHT; - } else { - bit = TileSet::BIND_BOTTOMRIGHT; - } - } - } else { - if (pos.x < size.x / 3) { - if (pos.y < size.y / 3) { - bit = TileSet::BIND_TOPLEFT; - } else if (pos.y > (size.y / 3) * 2) { - bit = TileSet::BIND_BOTTOMLEFT; - } else { - bit = TileSet::BIND_LEFT; - } - } else if (pos.x > (size.x / 3) * 2) { - if (pos.y < size.y / 3) { - bit = TileSet::BIND_TOPRIGHT; - } else if (pos.y > (size.y / 3) * 2) { - bit = TileSet::BIND_BOTTOMRIGHT; - } else { - bit = TileSet::BIND_RIGHT; - } - } else { - if (pos.y < size.y / 3) { - bit = TileSet::BIND_TOP; - } else if (pos.y > (size.y / 3) * 2) { - bit = TileSet::BIND_BOTTOM; - } else { - bit = TileSet::BIND_CENTER; - } - } - } - - uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord); - uint32_t new_mask = old_mask; - if (alternative) { - new_mask &= ~bit; - new_mask |= (bit << 16); - } else if (erasing) { - new_mask &= ~bit; - new_mask &= ~(bit << 16); - } else { - new_mask |= bit; - new_mask &= ~(bit << 16); - } - - if (old_mask != new_mask) { - undo_redo->create_action(TTR("Edit Tile Bitmask")); - undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask); - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->commit_action(); - } - } - } else { - if ((erasing && mb->get_button_index() == MOUSE_BUTTON_RIGHT) || (!erasing && mb->get_button_index() == MOUSE_BUTTON_LEFT)) { - dragging = false; - erasing = false; - alternative = false; - } - } - } - if (mm.is_valid()) { - if (dragging && current_tile_region.has_point(mm->get_position())) { - Vector2 coord((int)((mm->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mm->get_position().y - current_tile_region.position.y) / (spacing + size.y))); - Vector2 pos(coord.x * (spacing + size.x), coord.y * (spacing + size.y)); - pos = mm->get_position() - (pos + current_tile_region.position); - uint32_t bit = 0; - if (tileset->autotile_get_bitmask_mode(get_current_tile()) == TileSet::BITMASK_2X2) { - if (pos.x < size.x / 2) { - if (pos.y < size.y / 2) { - bit = TileSet::BIND_TOPLEFT; - } else { - bit = TileSet::BIND_BOTTOMLEFT; - } - } else { - if (pos.y < size.y / 2) { - bit = TileSet::BIND_TOPRIGHT; - } else { - bit = TileSet::BIND_BOTTOMRIGHT; - } - } - } else { - if (pos.x < size.x / 3) { - if (pos.y < size.y / 3) { - bit = TileSet::BIND_TOPLEFT; - } else if (pos.y > (size.y / 3) * 2) { - bit = TileSet::BIND_BOTTOMLEFT; - } else { - bit = TileSet::BIND_LEFT; - } - } else if (pos.x > (size.x / 3) * 2) { - if (pos.y < size.y / 3) { - bit = TileSet::BIND_TOPRIGHT; - } else if (pos.y > (size.y / 3) * 2) { - bit = TileSet::BIND_BOTTOMRIGHT; - } else { - bit = TileSet::BIND_RIGHT; - } - } else { - if (pos.y < size.y / 3) { - bit = TileSet::BIND_TOP; - } else if (pos.y > (size.y / 3) * 2) { - bit = TileSet::BIND_BOTTOM; - } else { - bit = TileSet::BIND_CENTER; - } - } - } - - uint32_t old_mask = tileset->autotile_get_bitmask(get_current_tile(), coord); - uint32_t new_mask = old_mask; - if (alternative) { - new_mask &= ~bit; - new_mask |= (bit << 16); - } else if (erasing) { - new_mask &= ~bit; - new_mask &= ~(bit << 16); - } else { - new_mask |= bit; - new_mask &= ~(bit << 16); - } - if (old_mask != new_mask) { - undo_redo->create_action(TTR("Edit Tile Bitmask")); - undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, new_mask); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), coord, old_mask); - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->commit_action(); - } - } - } - } break; - case EDITMODE_COLLISION: - case EDITMODE_OCCLUSION: - case EDITMODE_NAVIGATION: - case EDITMODE_PRIORITY: - case EDITMODE_Z_INDEX: { - Vector2 shape_anchor = Vector2(0, 0); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - shape_anchor = edited_shape_coord; - shape_anchor.x *= (size.x + spacing); - shape_anchor.y *= (size.y + spacing); - } - - const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); - shape_anchor += current_tile_region.position; - if (tools[TOOL_SELECT]->is_pressed()) { - if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (edit_mode != EDITMODE_PRIORITY && current_shape.size() > 0) { - int grabbed_point = get_grabbed_point(mb->get_position(), grab_threshold); - - if (grabbed_point >= 0) { - dragging_point = grabbed_point; - workspace->update(); - return; - } - } - if ((tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) && current_tile_region.has_point(mb->get_position())) { - Vector2 coord((int)((mb->get_position().x - current_tile_region.position.x) / (spacing + size.x)), (int)((mb->get_position().y - current_tile_region.position.y) / (spacing + size.y))); - if (edited_shape_coord != coord) { - edited_shape_coord = coord; - _select_edited_shape_coord(); - } - } - workspace->update(); - } else if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (edit_mode == EDITMODE_COLLISION) { - if (dragging_point >= 0) { - dragging_point = -1; - - Vector<Vector2> points; - - for (int i = 0; i < current_shape.size(); i++) { - Vector2 p = current_shape[i]; - if (tools[TOOL_GRID_SNAP]->is_pressed() || tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) { - p = snap_point(p); - } - points.push_back(p - shape_anchor); - } - - undo_redo->create_action(TTR("Edit Collision Polygon")); - _set_edited_shape_points(points); - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } - } else if (edit_mode == EDITMODE_OCCLUSION) { - if (dragging_point >= 0) { - dragging_point = -1; - - Vector<Vector2> polygon; - polygon.resize(current_shape.size()); - Vector2 *w = polygon.ptrw(); - - for (int i = 0; i < current_shape.size(); i++) { - w[i] = current_shape[i] - shape_anchor; - } - - undo_redo->create_action(TTR("Edit Occlusion Polygon")); - undo_redo->add_do_method(edited_occlusion_shape.ptr(), "set_polygon", polygon); - undo_redo->add_undo_method(edited_occlusion_shape.ptr(), "set_polygon", edited_occlusion_shape->get_polygon()); - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } - } else if (edit_mode == EDITMODE_NAVIGATION) { - if (dragging_point >= 0) { - dragging_point = -1; - - Vector<Vector2> polygon; - Vector<int> indices; - polygon.resize(current_shape.size()); - Vector2 *w = polygon.ptrw(); - - for (int i = 0; i < current_shape.size(); i++) { - w[i] = current_shape[i] - shape_anchor; - indices.push_back(i); - } - - undo_redo->create_action(TTR("Edit Navigation Polygon")); - undo_redo->add_do_method(edited_navigation_shape.ptr(), "set_vertices", polygon); - undo_redo->add_undo_method(edited_navigation_shape.ptr(), "set_vertices", edited_navigation_shape->get_vertices()); - undo_redo->add_do_method(edited_navigation_shape.ptr(), "clear_polygons"); - undo_redo->add_undo_method(edited_navigation_shape.ptr(), "clear_polygons"); - undo_redo->add_do_method(edited_navigation_shape.ptr(), "add_polygon", indices); - undo_redo->add_undo_method(edited_navigation_shape.ptr(), "add_polygon", edited_navigation_shape->get_polygon(0)); - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } - } - } - } else if (mm.is_valid()) { - if (dragging_point >= 0) { - current_shape.set(dragging_point, snap_point(mm->get_position())); - workspace->update(); - } - } - } else if (tools[SHAPE_NEW_POLYGON]->is_pressed()) { - if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - Vector2 pos = mb->get_position(); - pos = snap_point(pos); - if (creating_shape) { - if (current_shape.size() > 2) { - if (is_within_grabbing_distance_of_first_point(mb->get_position(), grab_threshold)) { - close_shape(shape_anchor); - workspace->update(); - return; - } - } - current_shape.push_back(pos); - workspace->update(); - } else { - creating_shape = true; - _set_edited_collision_shape(Ref<ConvexPolygonShape2D>()); - current_shape.resize(0); - current_shape.push_back(snap_point(pos)); - workspace->update(); - } - } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - if (creating_shape) { - creating_shape = false; - _select_edited_shape_coord(); - workspace->update(); - } - } - } else if (mm.is_valid()) { - if (creating_shape) { - workspace->update(); - } - } - } else if (tools[SHAPE_NEW_RECTANGLE]->is_pressed()) { - if (mb.is_valid()) { - if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - _set_edited_collision_shape(Ref<ConvexPolygonShape2D>()); - current_shape.resize(0); - Vector2 pos = mb->get_position(); - pos = snap_point(pos); - current_shape.push_back(pos); - current_shape.push_back(pos); - current_shape.push_back(pos); - current_shape.push_back(pos); - creating_shape = true; - workspace->update(); - return; - } else if (mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_RIGHT) { - if (creating_shape) { - creating_shape = false; - _select_edited_shape_coord(); - workspace->update(); - } - } else if (!mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { - if (creating_shape) { - // if the first two corners are within grabbing distance of one another, expand the rect to fill the tile - if (is_within_grabbing_distance_of_first_point(current_shape[1], grab_threshold)) { - current_shape.set(0, snap_point(shape_anchor)); - current_shape.set(1, snap_point(shape_anchor + Vector2(current_tile_region.size.x, 0))); - current_shape.set(2, snap_point(shape_anchor + current_tile_region.size)); - current_shape.set(3, snap_point(shape_anchor + Vector2(0, current_tile_region.size.y))); - } - - close_shape(shape_anchor); - workspace->update(); - return; - } - } - } else if (mm.is_valid()) { - if (creating_shape) { - Vector2 pos = mm->get_position(); - pos = snap_point(pos); - Vector2 p = current_shape[2]; - current_shape.set(3, snap_point(Vector2(pos.x, p.y))); - current_shape.set(0, snap_point(pos)); - current_shape.set(1, snap_point(Vector2(p.x, pos.y))); - workspace->update(); - } - } - } - } break; - default: { - } - } - } - } -} - -void TileSetEditor::_on_tool_clicked(int p_tool) { - if (p_tool == BITMASK_COPY) { - bitmask_map_copy = tileset->autotile_get_bitmask_map(get_current_tile()); - } else if (p_tool == BITMASK_PASTE) { - undo_redo->create_action(TTR("Paste Tile Bitmask")); - undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile()); - undo_redo->add_undo_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile()); - for (Map<Vector2, uint32_t>::Element *E = bitmask_map_copy.front(); E; E = E->next()) { - undo_redo->add_do_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value()); - } - for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) { - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value()); - } - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->commit_action(); - } else if (p_tool == BITMASK_CLEAR) { - undo_redo->create_action(TTR("Clear Tile Bitmask")); - undo_redo->add_do_method(tileset.ptr(), "autotile_clear_bitmask_map", get_current_tile()); - for (Map<Vector2, uint32_t>::Element *E = tileset->autotile_get_bitmask_map(get_current_tile()).front(); E; E = E->next()) { - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", get_current_tile(), E->key(), E->value()); - } - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->commit_action(); - } else if (p_tool == SHAPE_TOGGLE_TYPE) { - if (edited_collision_shape.is_valid()) { - Ref<ConvexPolygonShape2D> convex = edited_collision_shape; - Ref<ConcavePolygonShape2D> concave = edited_collision_shape; - Ref<Shape2D> previous_shape = edited_collision_shape; - Array sd = tileset->call("tile_get_shapes", get_current_tile()); - - if (convex.is_valid()) { - // Make concave. - undo_redo->create_action(TTR("Make Polygon Concave")); - Ref<ConcavePolygonShape2D> _concave = memnew(ConcavePolygonShape2D); - edited_collision_shape = _concave; - _set_edited_shape_points(_get_collision_shape_points(convex)); - } else if (concave.is_valid()) { - // Make convex. - undo_redo->create_action(TTR("Make Polygon Convex")); - Ref<ConvexPolygonShape2D> _convex = memnew(ConvexPolygonShape2D); - edited_collision_shape = _convex; - _set_edited_shape_points(_get_collision_shape_points(concave)); - } - for (int i = 0; i < sd.size(); i++) { - if (sd[i].get("shape") == previous_shape) { - undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate()); - sd.remove(i); - break; - } - } - - undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D(), false, edited_shape_coord); - } else { - undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), edited_collision_shape, Transform2D()); - } - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - - _update_toggle_shape_button(); - workspace->update(); - workspace_container->update(); - helper->notify_property_list_changed(); - } - } else if (p_tool == SELECT_NEXT) { - _select_next_shape(); - } else if (p_tool == SELECT_PREVIOUS) { - _select_previous_shape(); - } else if (p_tool == SHAPE_DELETE) { - if (creating_shape) { - creating_shape = false; - current_shape.resize(0); - workspace->update(); - } else { - switch (edit_mode) { - case EDITMODE_REGION: { - int t_id = get_current_tile(); - if (workspace_mode == WORKSPACE_EDIT && t_id >= 0) { - undo_redo->create_action(TTR("Remove Tile")); - undo_redo->add_do_method(tileset.ptr(), "remove_tile", t_id); - _undo_tile_removal(t_id); - undo_redo->add_do_method(this, "_validate_current_tile_id"); - - Rect2 tile_region = tileset->tile_get_region(get_current_tile()); - Size2 tile_workspace_size = tile_region.position + tile_region.size; - if (tile_workspace_size.x > get_current_texture()->get_size().x || tile_workspace_size.y > get_current_texture()->get_size().y) { - undo_redo->add_do_method(this, "update_workspace_minsize"); - undo_redo->add_undo_method(this, "update_workspace_minsize"); - } - - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->add_do_method(workspace_overlay, "update"); - undo_redo->add_undo_method(workspace_overlay, "update"); - undo_redo->commit_action(); - } - tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); - workspace_mode = WORKSPACE_EDIT; - update_workspace_tile_mode(); - } break; - case EDITMODE_COLLISION: { - if (!edited_collision_shape.is_null()) { - // Necessary to get the version that returns a Array instead of a Vector. - Array sd = tileset->call("tile_get_shapes", get_current_tile()); - for (int i = 0; i < sd.size(); i++) { - if (sd[i].get("shape") == edited_collision_shape) { - undo_redo->create_action(TTR("Remove Collision Polygon")); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate()); - sd.remove(i); - undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd); - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - break; - } - } - } - } break; - case EDITMODE_OCCLUSION: { - if (!edited_occlusion_shape.is_null()) { - undo_redo->create_action(TTR("Remove Occlusion Polygon")); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>()); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile())); - } else { - undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), Ref<OccluderPolygon2D>(), edited_shape_coord); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord); - } - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } - } break; - case EDITMODE_NAVIGATION: { - if (!edited_navigation_shape.is_null()) { - undo_redo->create_action(TTR("Remove Navigation Polygon")); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>()); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile())); - } else { - undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), Ref<NavigationPolygon>(), edited_shape_coord); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord); - } - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } - } break; - default: { - } - } - } - } else if (p_tool == TOOL_SELECT || p_tool == SHAPE_NEW_POLYGON || p_tool == SHAPE_NEW_RECTANGLE) { - if (creating_shape) { - // Cancel Creation - creating_shape = false; - current_shape.resize(0); - workspace->update(); - } - } -} - -void TileSetEditor::_on_priority_changed(float val) { - if ((int)val == tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)) { - return; - } - - undo_redo->create_action(TTR("Edit Tile Priority")); - undo_redo->add_do_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, (int)val); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", get_current_tile(), edited_shape_coord, tileset->autotile_get_subtile_priority(get_current_tile(), edited_shape_coord)); - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->commit_action(); -} - -void TileSetEditor::_on_z_index_changed(float val) { - if ((int)val == tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)) { - return; - } - - undo_redo->create_action(TTR("Edit Tile Z Index")); - undo_redo->add_do_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, (int)val); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", get_current_tile(), edited_shape_coord, tileset->autotile_get_z_index(get_current_tile(), edited_shape_coord)); - undo_redo->add_do_method(workspace, "update"); - undo_redo->add_undo_method(workspace, "update"); - undo_redo->commit_action(); -} - -void TileSetEditor::_on_grid_snap_toggled(bool p_val) { - helper->set_snap_options_visible(p_val); - workspace->update(); -} - -Vector<Vector2> TileSetEditor::_get_collision_shape_points(const Ref<Shape2D> &p_shape) { - Ref<ConvexPolygonShape2D> convex = p_shape; - Ref<ConcavePolygonShape2D> concave = p_shape; - if (convex.is_valid()) { - return convex->get_points(); - } else if (concave.is_valid()) { - Vector<Vector2> points; - for (int i = 0; i < concave->get_segments().size(); i += 2) { - points.push_back(concave->get_segments()[i]); - } - return points; - } else { - return Vector<Vector2>(); - } -} - -Vector<Vector2> TileSetEditor::_get_edited_shape_points() { - return _get_collision_shape_points(edited_collision_shape); -} - -void TileSetEditor::_set_edited_shape_points(const Vector<Vector2> &points) { - Ref<ConvexPolygonShape2D> convex = edited_collision_shape; - Ref<ConcavePolygonShape2D> concave = edited_collision_shape; - if (convex.is_valid()) { - undo_redo->add_do_method(convex.ptr(), "set_points", points); - undo_redo->add_undo_method(convex.ptr(), "set_points", _get_edited_shape_points()); - } else if (concave.is_valid() && points.size() > 1) { - PackedVector2Array segments; - for (int i = 0; i < points.size() - 1; i++) { - segments.push_back(points[i]); - segments.push_back(points[i + 1]); - } - segments.push_back(points[points.size() - 1]); - segments.push_back(points[0]); - undo_redo->add_do_method(concave.ptr(), "set_segments", segments); - undo_redo->add_undo_method(concave.ptr(), "set_segments", concave->get_segments()); - } -} - -void TileSetEditor::_update_tile_data() { - current_tile_data.clear(); - if (get_current_tile() < 0) { - return; - } - - Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile()); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - SubtileData data; - for (int i = 0; i < sd.size(); i++) { - data.collisions.push_back(sd[i].shape); - } - data.navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile()); - data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile()); - current_tile_data[Vector2i()] = data; - } else { - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->tile_get_region(get_current_tile()).size; - Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor(); - for (int y = 0; y < cell_count.y; y++) { - for (int x = 0; x < cell_count.x; x++) { - SubtileData data; - Vector2i coord(x, y); - for (int i = 0; i < sd.size(); i++) { - if (sd[i].autotile_coord == coord) { - data.collisions.push_back(sd[i].shape); - } - } - data.navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord); - data.occlusion_shape = tileset->tile_get_light_occluder(get_current_tile()); - current_tile_data[coord] = data; - } - } - } -} - -void TileSetEditor::_update_toggle_shape_button() { - Ref<ConvexPolygonShape2D> convex = edited_collision_shape; - Ref<ConcavePolygonShape2D> concave = edited_collision_shape; - separator_shape_toggle->show(); - tools[SHAPE_TOGGLE_TYPE]->show(); - if (edit_mode != EDITMODE_COLLISION || !edited_collision_shape.is_valid()) { - separator_shape_toggle->hide(); - tools[SHAPE_TOGGLE_TYPE]->hide(); - } else if (concave.is_valid()) { - tools[SHAPE_TOGGLE_TYPE]->set_icon(get_theme_icon("ConvexPolygonShape2D", "EditorIcons")); - tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Convex")); - } else if (convex.is_valid()) { - tools[SHAPE_TOGGLE_TYPE]->set_icon(get_theme_icon("ConcavePolygonShape2D", "EditorIcons")); - tools[SHAPE_TOGGLE_TYPE]->set_text(TTR("Make Concave")); - } else { - // Shouldn't happen - separator_shape_toggle->hide(); - tools[SHAPE_TOGGLE_TYPE]->hide(); - } -} - -void TileSetEditor::_select_next_tile() { - Array tiles = _get_tiles_in_current_texture(true); - if (tiles.size() == 0) { - set_current_tile(-1); - } else if (get_current_tile() == -1) { - set_current_tile(tiles[0]); - } else { - int index = tiles.find(get_current_tile()); - if (index < 0) { - set_current_tile(tiles[0]); - } else if (index == tiles.size() - 1) { - set_current_tile(tiles[0]); - } else { - set_current_tile(tiles[index + 1]); - } - } - if (get_current_tile() == -1) { - return; - } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - return; - } else { - switch (edit_mode) { - case EDITMODE_COLLISION: - case EDITMODE_OCCLUSION: - case EDITMODE_NAVIGATION: - case EDITMODE_PRIORITY: - case EDITMODE_Z_INDEX: { - edited_shape_coord = Vector2(); - _select_edited_shape_coord(); - } break; - default: { - } - } - } -} - -void TileSetEditor::_select_previous_tile() { - Array tiles = _get_tiles_in_current_texture(true); - if (tiles.size() == 0) { - set_current_tile(-1); - } else if (get_current_tile() == -1) { - set_current_tile(tiles[tiles.size() - 1]); - } else { - int index = tiles.find(get_current_tile()); - if (index <= 0) { - set_current_tile(tiles[tiles.size() - 1]); - } else { - set_current_tile(tiles[index - 1]); - } - } - if (get_current_tile() == -1) { - return; - } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - return; - } else { - switch (edit_mode) { - case EDITMODE_COLLISION: - case EDITMODE_OCCLUSION: - case EDITMODE_NAVIGATION: - case EDITMODE_PRIORITY: - case EDITMODE_Z_INDEX: { - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->tile_get_region(get_current_tile()).size; - Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor(); - cell_count -= Vector2(1, 1); - edited_shape_coord = cell_count; - _select_edited_shape_coord(); - } break; - default: { - } - } - } -} - -Array TileSetEditor::_get_tiles_in_current_texture(bool sorted) { - Array a; - List<int> all_tiles; - if (!get_current_texture().is_valid()) { - return a; - } - tileset->get_tile_list(&all_tiles); - for (int i = 0; i < all_tiles.size(); i++) { - if (tileset->tile_get_texture(all_tiles[i]) == get_current_texture()) { - a.push_back(all_tiles[i]); - } - } - if (sorted) { - a.sort_custom(callable_mp(this, &TileSetEditor::_sort_tiles)); - } - return a; -} - -bool TileSetEditor::_sort_tiles(Variant p_a, Variant p_b) { - int a = p_a; - int b = p_b; - - Vector2 pos_a = tileset->tile_get_region(a).position; - Vector2 pos_b = tileset->tile_get_region(b).position; - if (pos_a.y < pos_b.y) { - return true; - - } else if (pos_a.y == pos_b.y) { - return (pos_a.x < pos_b.x); - } else { - return false; - } -} - -void TileSetEditor::_select_next_subtile() { - if (get_current_tile() == -1) { - _select_next_tile(); - return; - } - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - _select_next_tile(); - } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) { - _select_next_tile(); - } else { - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->tile_get_region(get_current_tile()).size; - Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor(); - if (edited_shape_coord.x >= cell_count.x - 1 && edited_shape_coord.y >= cell_count.y - 1) { - _select_next_tile(); - } else { - edited_shape_coord.x++; - if (edited_shape_coord.x >= cell_count.x) { - edited_shape_coord.x = 0; - edited_shape_coord.y++; - } - _select_edited_shape_coord(); - } - } -} - -void TileSetEditor::_select_previous_subtile() { - if (get_current_tile() == -1) { - _select_previous_tile(); - return; - } - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - _select_previous_tile(); - } else if (edit_mode == EDITMODE_REGION || edit_mode == EDITMODE_BITMASK || edit_mode == EDITMODE_ICON) { - _select_previous_tile(); - } else { - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->tile_get_region(get_current_tile()).size; - Vector2 cell_count = (size / (tileset->autotile_get_size(get_current_tile()) + Vector2(spacing, spacing))).floor(); - if (edited_shape_coord.x <= 0 && edited_shape_coord.y <= 0) { - _select_previous_tile(); - } else { - edited_shape_coord.x--; - if (edited_shape_coord.x == -1) { - edited_shape_coord.x = cell_count.x - 1; - edited_shape_coord.y--; - } - _select_edited_shape_coord(); - } - } -} - -void TileSetEditor::_select_next_shape() { - if (get_current_tile() == -1) { - _select_next_subtile(); - } else if (edit_mode != EDITMODE_COLLISION) { - _select_next_subtile(); - } else { - Vector2i edited_coord = Vector2i(); - if (tileset->tile_get_tile_mode(get_current_tile()) != TileSet::SINGLE_TILE) { - edited_coord = Vector2i(edited_shape_coord); - } - SubtileData data = current_tile_data[edited_coord]; - if (data.collisions.size() == 0) { - _select_next_subtile(); - } else { - int index = data.collisions.find(edited_collision_shape); - if (index < 0) { - _set_edited_collision_shape(data.collisions[0]); - } else if (index == data.collisions.size() - 1) { - _select_next_subtile(); - } else { - _set_edited_collision_shape(data.collisions[index + 1]); - } - } - current_shape.resize(0); - Rect2 current_tile_region = tileset->tile_get_region(get_current_tile()); - current_tile_region.position += WORKSPACE_MARGIN; - - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->autotile_get_size(get_current_tile()); - Vector2 shape_anchor = edited_shape_coord; - shape_anchor.x *= (size.x + spacing); - shape_anchor.y *= (size.y + spacing); - current_tile_region.position += shape_anchor; - - if (edited_collision_shape.is_valid()) { - for (int i = 0; i < _get_edited_shape_points().size(); i++) { - current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position); - } - } - workspace->update(); - workspace_container->update(); - helper->notify_property_list_changed(); - } -} - -void TileSetEditor::_select_previous_shape() { - if (get_current_tile() == -1) { - _select_previous_subtile(); - if (get_current_tile() != -1 && edit_mode == EDITMODE_COLLISION) { - SubtileData data = current_tile_data[Vector2i(edited_shape_coord)]; - if (data.collisions.size() > 1) { - _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); - } - } else { - return; - } - } else if (edit_mode != EDITMODE_COLLISION) { - _select_previous_subtile(); - } else { - Vector2i edited_coord = Vector2i(); - if (tileset->tile_get_tile_mode(get_current_tile()) != TileSet::SINGLE_TILE) { - edited_coord = Vector2i(edited_shape_coord); - } - SubtileData data = current_tile_data[edited_coord]; - if (data.collisions.size() == 0) { - _select_previous_subtile(); - data = current_tile_data[Vector2i(edited_shape_coord)]; - if (data.collisions.size() > 1) { - _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); - } - } else { - int index = data.collisions.find(edited_collision_shape); - if (index < 0) { - _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); - } else if (index == 0) { - _select_previous_subtile(); - data = current_tile_data[Vector2i(edited_shape_coord)]; - if (data.collisions.size() > 1) { - _set_edited_collision_shape(data.collisions[data.collisions.size() - 1]); - } - } else { - _set_edited_collision_shape(data.collisions[index - 1]); - } - } - - current_shape.resize(0); - Rect2 current_tile_region = tileset->tile_get_region(get_current_tile()); - current_tile_region.position += WORKSPACE_MARGIN; - - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->autotile_get_size(get_current_tile()); - Vector2 shape_anchor = edited_shape_coord; - shape_anchor.x *= (size.x + spacing); - shape_anchor.y *= (size.y + spacing); - current_tile_region.position += shape_anchor; - - if (edited_collision_shape.is_valid()) { - for (int i = 0; i < _get_edited_shape_points().size(); i++) { - current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position); - } - } - workspace->update(); - workspace_container->update(); - helper->notify_property_list_changed(); - } -} - -void TileSetEditor::_set_edited_collision_shape(const Ref<Shape2D> &p_shape) { - edited_collision_shape = p_shape; - _update_toggle_shape_button(); -} - -void TileSetEditor::_set_snap_step(Vector2 p_val) { - snap_step.x = CLAMP(p_val.x, 1, 256); - snap_step.y = CLAMP(p_val.y, 1, 256); - workspace->update(); -} - -void TileSetEditor::_set_snap_off(Vector2 p_val) { - snap_offset.x = CLAMP(p_val.x, 0, 256 + WORKSPACE_MARGIN.x); - snap_offset.y = CLAMP(p_val.y, 0, 256 + WORKSPACE_MARGIN.y); - workspace->update(); -} - -void TileSetEditor::_set_snap_sep(Vector2 p_val) { - snap_separation.x = CLAMP(p_val.x, 0, 256); - snap_separation.y = CLAMP(p_val.y, 0, 256); - workspace->update(); -} - -void TileSetEditor::_validate_current_tile_id() { - if (get_current_tile() >= 0 && !tileset->has_tile(get_current_tile())) { - set_current_tile(-1); - } -} - -void TileSetEditor::_select_edited_shape_coord() { - select_coord(edited_shape_coord); -} - -void TileSetEditor::_undo_tile_removal(int p_id) { - undo_redo->add_undo_method(tileset.ptr(), "create_tile", p_id); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_name", p_id, tileset->tile_get_name(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture_offset", p_id, tileset->tile_get_texture_offset(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_material", p_id, tileset->tile_get_material(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_modulate", p_id, tileset->tile_get_modulate(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_occluder_offset", p_id, tileset->tile_get_occluder_offset(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon_offset", p_id, tileset->tile_get_navigation_polygon_offset(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_offset", p_id, 0, tileset->tile_get_shape_offset(p_id, 0)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_shape_transform", p_id, 0, tileset->tile_get_shape_transform(p_id, 0)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_z_index", p_id, tileset->tile_get_z_index(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_texture", p_id, tileset->tile_get_texture(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_region", p_id, tileset->tile_get_region(p_id)); - // Necessary to get the version that returns a Array instead of a Vector. - undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", p_id, tileset->call("tile_get_shapes", p_id)); - if (tileset->tile_get_tile_mode(p_id) == TileSet::SINGLE_TILE) { - undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", p_id, tileset->tile_get_light_occluder(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", p_id, tileset->tile_get_navigation_polygon(p_id)); - } else { - Map<Vector2, Ref<OccluderPolygon2D>> oclusion_map = tileset->autotile_get_light_oclusion_map(p_id); - for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = oclusion_map.front(); E; E = E->next()) { - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", p_id, E->value(), E->key()); - } - Map<Vector2, Ref<NavigationPolygon>> navigation_map = tileset->autotile_get_navigation_map(p_id); - for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = navigation_map.front(); E; E = E->next()) { - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", p_id, E->value(), E->key()); - } - Map<Vector2, uint32_t> bitmask_map = tileset->autotile_get_bitmask_map(p_id); - for (Map<Vector2, uint32_t>::Element *E = bitmask_map.front(); E; E = E->next()) { - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask", p_id, E->key(), E->value()); - } - Map<Vector2, int> priority_map = tileset->autotile_get_priority_map(p_id); - for (Map<Vector2, int>::Element *E = priority_map.front(); E; E = E->next()) { - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_subtile_priority", p_id, E->key(), E->value()); - } - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_icon_coordinate", p_id, tileset->autotile_get_icon_coordinate(p_id)); - Map<Vector2, int> z_map = tileset->autotile_get_z_index_map(p_id); - for (Map<Vector2, int>::Element *E = z_map.front(); E; E = E->next()) { - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_z_index", p_id, E->key(), E->value()); - } - undo_redo->add_undo_method(tileset.ptr(), "tile_set_tile_mode", p_id, tileset->tile_get_tile_mode(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_size", p_id, tileset->autotile_get_size(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_spacing", p_id, tileset->autotile_get_spacing(p_id)); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_bitmask_mode", p_id, tileset->autotile_get_bitmask_mode(p_id)); - } -} - -void TileSetEditor::_zoom_in() { - float scale = workspace->get_scale().x; - if (scale < max_scale) { - scale *= scale_ratio; - workspace->set_scale(Vector2(scale, scale)); - workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale); - workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale); - } -} - -void TileSetEditor::_zoom_out() { - float scale = workspace->get_scale().x; - if (scale > min_scale) { - scale /= scale_ratio; - workspace->set_scale(Vector2(scale, scale)); - workspace_container->set_custom_minimum_size(workspace->get_rect().size * scale); - workspace_overlay->set_custom_minimum_size(workspace->get_rect().size * scale); - } -} - -void TileSetEditor::_zoom_reset() { - workspace->set_scale(Vector2(1, 1)); - workspace_container->set_custom_minimum_size(workspace->get_rect().size); - workspace_overlay->set_custom_minimum_size(workspace->get_rect().size); -} - -void TileSetEditor::draw_highlight_current_tile() { - Color shadow_color = Color(0.3, 0.3, 0.3, 0.3); - if ((workspace_mode == WORKSPACE_EDIT && get_current_tile() >= 0) || !edited_region.has_no_area()) { - Rect2 region; - if (edited_region.has_no_area()) { - region = tileset->tile_get_region(get_current_tile()); - region.position += WORKSPACE_MARGIN; - } else { - region = edited_region; - } - - if (region.position.y >= 0) { - workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, region.position.y), shadow_color); - } - if (region.position.x >= 0) { - workspace->draw_rect(Rect2(0, MAX(0, region.position.y), region.position.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color); - } - if (region.position.x + region.size.x <= workspace->get_rect().size.x) { - workspace->draw_rect(Rect2(region.position.x + region.size.x, MAX(0, region.position.y), workspace->get_rect().size.x - region.position.x - region.size.x, MIN(workspace->get_rect().size.y - region.position.y, MIN(region.size.y, region.position.y + region.size.y))), shadow_color); - } - if (region.position.y + region.size.y <= workspace->get_rect().size.y) { - workspace->draw_rect(Rect2(0, region.position.y + region.size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - region.size.y - region.position.y), shadow_color); - } - } else { - workspace->draw_rect(Rect2(Point2(0, 0), workspace->get_rect().size), shadow_color); - } -} - -void TileSetEditor::draw_highlight_subtile(Vector2 coord, const Vector<Vector2> &other_highlighted) { - Color shadow_color = Color(0.3, 0.3, 0.3, 0.3); - Vector2 size = tileset->autotile_get_size(get_current_tile()); - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Rect2 region = tileset->tile_get_region(get_current_tile()); - coord.x *= (size.x + spacing); - coord.y *= (size.y + spacing); - coord += region.position; - coord += WORKSPACE_MARGIN; - - if (coord.y >= 0) { - workspace->draw_rect(Rect2(0, 0, workspace->get_rect().size.x, coord.y), shadow_color); - } - if (coord.x >= 0) { - workspace->draw_rect(Rect2(0, MAX(0, coord.y), coord.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color); - } - if (coord.x + size.x <= workspace->get_rect().size.x) { - workspace->draw_rect(Rect2(coord.x + size.x, MAX(0, coord.y), workspace->get_rect().size.x - coord.x - size.x, MIN(workspace->get_rect().size.y - coord.y, MIN(size.y, coord.y + size.y))), shadow_color); - } - if (coord.y + size.y <= workspace->get_rect().size.y) { - workspace->draw_rect(Rect2(0, coord.y + size.y, workspace->get_rect().size.x, workspace->get_rect().size.y - size.y - coord.y), shadow_color); - } - - coord += Vector2(1, 1) / workspace->get_scale().x; - workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0, 0), false); - for (int i = 0; i < other_highlighted.size(); i++) { - coord = other_highlighted[i]; - coord.x *= (size.x + spacing); - coord.y *= (size.y + spacing); - coord += region.position; - coord += WORKSPACE_MARGIN; - coord += Vector2(1, 1) / workspace->get_scale().x; - workspace->draw_rect(Rect2(coord, size - Vector2(2, 2) / workspace->get_scale().x), Color(1, 0.5, 0.5), false); - } -} - -void TileSetEditor::draw_tile_subdivision(int p_id, Color p_color) const { - Color c = p_color; - if (tileset->tile_get_tile_mode(p_id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == TileSet::ATLAS_TILE) { - Rect2 region = tileset->tile_get_region(p_id); - Size2 size = tileset->autotile_get_size(p_id); - int spacing = tileset->autotile_get_spacing(p_id); - float j = size.x; - - while (j < region.size.x) { - if (spacing <= 0) { - workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(j, 0), region.position + WORKSPACE_MARGIN + Point2(j, region.size.y), c); - } else { - workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(j, 0), Size2(spacing, region.size.y)), c); - } - j += spacing + size.x; - } - j = size.y; - while (j < region.size.y) { - if (spacing <= 0) { - workspace->draw_line(region.position + WORKSPACE_MARGIN + Point2(0, j), region.position + WORKSPACE_MARGIN + Point2(region.size.x, j), c); - } else { - workspace->draw_rect(Rect2(region.position + WORKSPACE_MARGIN + Point2(0, j), Size2(region.size.x, spacing)), c); - } - j += spacing + size.y; - } - } -} - -void TileSetEditor::draw_edited_region_subdivision() const { - Color c = Color(0.3, 0.7, 0.6); - Rect2 region = edited_region; - Size2 size; - int spacing; - bool draw; - - if (workspace_mode == WORKSPACE_EDIT) { - int p_id = get_current_tile(); - size = tileset->autotile_get_size(p_id); - spacing = tileset->autotile_get_spacing(p_id); - draw = tileset->tile_get_tile_mode(p_id) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(p_id) == TileSet::ATLAS_TILE; - } else { - size = snap_step; - spacing = snap_separation.x; - draw = workspace_mode != WORKSPACE_CREATE_SINGLE; - } - - if (draw) { - float j = size.x; - while (j < region.size.x) { - if (spacing <= 0) { - workspace->draw_line(region.position + Point2(j, 0), region.position + Point2(j, region.size.y), c); - } else { - workspace->draw_rect(Rect2(region.position + Point2(j, 0), Size2(spacing, region.size.y)), c); - } - j += spacing + size.x; - } - j = size.y; - while (j < region.size.y) { - if (spacing <= 0) { - workspace->draw_line(region.position + Point2(0, j), region.position + Point2(region.size.x, j), c); - } else { - workspace->draw_rect(Rect2(region.position + Point2(0, j), Size2(region.size.x, spacing)), c); - } - j += spacing + size.y; - } - } -} - -void TileSetEditor::draw_grid_snap() { - if (tools[TOOL_GRID_SNAP]->is_pressed()) { - Color grid_color = Color(0.4, 0, 1); - Size2 s = workspace->get_size(); - - int width_count = Math::floor((s.width - WORKSPACE_MARGIN.x) / (snap_step.x + snap_separation.x)); - int height_count = Math::floor((s.height - WORKSPACE_MARGIN.y) / (snap_step.y + snap_separation.y)); - - int last_p = 0; - if (snap_step.x != 0) { - for (int i = 0; i <= width_count; i++) { - if (i == 0 && snap_offset.x != 0) { - last_p = snap_offset.x; - } - if (snap_separation.x != 0) { - if (i != 0) { - workspace->draw_rect(Rect2(last_p, 0, snap_separation.x, s.height), grid_color); - last_p += snap_separation.x; - } else { - workspace->draw_rect(Rect2(last_p, 0, -snap_separation.x, s.height), grid_color); - } - } else { - workspace->draw_line(Point2(last_p, 0), Point2(last_p, s.height), grid_color); - } - last_p += snap_step.x; - } - } - last_p = 0; - if (snap_step.y != 0) { - for (int i = 0; i <= height_count; i++) { - if (i == 0 && snap_offset.y != 0) { - last_p = snap_offset.y; - } - if (snap_separation.y != 0) { - if (i != 0) { - workspace->draw_rect(Rect2(0, last_p, s.width, snap_separation.y), grid_color); - last_p += snap_separation.y; - } else { - workspace->draw_rect(Rect2(0, last_p, s.width, -snap_separation.y), grid_color); - } - } else { - workspace->draw_line(Point2(0, last_p), Point2(s.width, last_p), grid_color); - } - last_p += snap_step.y; - } - } - } -} - -void TileSetEditor::draw_polygon_shapes() { - int t_id = get_current_tile(); - if (t_id < 0) { - return; - } - - switch (edit_mode) { - case EDITMODE_COLLISION: { - Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(t_id); - for (int i = 0; i < sd.size(); i++) { - Vector2 coord = Vector2(0, 0); - Vector2 anchor = Vector2(0, 0); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - coord = sd[i].autotile_coord; - anchor = tileset->autotile_get_size(t_id); - anchor.x += tileset->autotile_get_spacing(t_id); - anchor.y += tileset->autotile_get_spacing(t_id); - anchor.x *= coord.x; - anchor.y *= coord.y; - } - anchor += WORKSPACE_MARGIN; - anchor += tileset->tile_get_region(t_id).position; - Ref<Shape2D> shape = sd[i].shape; - if (shape.is_valid()) { - Color c_bg; - Color c_border; - Ref<ConvexPolygonShape2D> convex = shape; - bool is_convex = convex.is_valid(); - if ((tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE || coord == edited_shape_coord) && sd[i].shape == edited_collision_shape) { - if (is_convex) { - c_bg = Color(0, 1, 1, 0.5); - c_border = Color(0, 1, 1); - } else { - c_bg = Color(0.8, 0, 1, 0.5); - c_border = Color(0.8, 0, 1); - } - } else { - if (is_convex) { - c_bg = Color(0.9, 0.7, 0.07, 0.5); - c_border = Color(0.9, 0.7, 0.07, 1); - - } else { - c_bg = Color(0.9, 0.45, 0.075, 0.5); - c_border = Color(0.9, 0.45, 0.075); - } - } - Vector<Vector2> polygon; - Vector<Color> colors; - if (!creating_shape && shape == edited_collision_shape && current_shape.size() > 2) { - for (int j = 0; j < current_shape.size(); j++) { - polygon.push_back(current_shape[j]); - colors.push_back(c_bg); - } - } else { - for (int j = 0; j < _get_collision_shape_points(shape).size(); j++) { - polygon.push_back(_get_collision_shape_points(shape)[j] + anchor); - colors.push_back(c_bg); - } - } - - if (polygon.size() < 3) { - continue; - } - - workspace->draw_polygon(polygon, colors); - - if (coord == edited_shape_coord || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - if (!creating_shape && polygon.size() > 1) { - for (int j = 0; j < polygon.size() - 1; j++) { - workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1); - } - workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1); - } - if (shape == edited_collision_shape) { - draw_handles = true; - } - } - } - } - } break; - case EDITMODE_OCCLUSION: { - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - Ref<OccluderPolygon2D> shape = edited_occlusion_shape; - if (shape.is_valid()) { - Color c_bg = Color(0, 1, 1, 0.5); - Color c_border = Color(0, 1, 1); - - Vector<Vector2> polygon; - Vector<Color> colors; - Vector2 anchor = WORKSPACE_MARGIN; - anchor += tileset->tile_get_region(get_current_tile()).position; - if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) { - for (int j = 0; j < current_shape.size(); j++) { - polygon.push_back(current_shape[j]); - colors.push_back(c_bg); - } - } else { - for (int j = 0; j < shape->get_polygon().size(); j++) { - polygon.push_back(shape->get_polygon()[j] + anchor); - colors.push_back(c_bg); - } - } - workspace->draw_polygon(polygon, colors); - - if (!creating_shape && polygon.size() > 1) { - for (int j = 0; j < polygon.size() - 1; j++) { - workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1); - } - workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1); - } - if (shape == edited_occlusion_shape) { - draw_handles = true; - } - } - } else { - Map<Vector2, Ref<OccluderPolygon2D>> map = tileset->autotile_get_light_oclusion_map(t_id); - for (Map<Vector2, Ref<OccluderPolygon2D>>::Element *E = map.front(); E; E = E->next()) { - Vector2 coord = E->key(); - Vector2 anchor = tileset->autotile_get_size(t_id); - anchor.x += tileset->autotile_get_spacing(t_id); - anchor.y += tileset->autotile_get_spacing(t_id); - anchor.x *= coord.x; - anchor.y *= coord.y; - anchor += WORKSPACE_MARGIN; - anchor += tileset->tile_get_region(t_id).position; - Ref<OccluderPolygon2D> shape = E->value(); - if (shape.is_valid()) { - Color c_bg; - Color c_border; - if (coord == edited_shape_coord && shape == edited_occlusion_shape) { - c_bg = Color(0, 1, 1, 0.5); - c_border = Color(0, 1, 1); - } else { - c_bg = Color(0.9, 0.7, 0.07, 0.5); - c_border = Color(0.9, 0.7, 0.07, 1); - } - Vector<Vector2> polygon; - Vector<Color> colors; - if (!creating_shape && shape == edited_occlusion_shape && current_shape.size() > 2) { - for (int j = 0; j < current_shape.size(); j++) { - polygon.push_back(current_shape[j]); - colors.push_back(c_bg); - } - } else { - for (int j = 0; j < shape->get_polygon().size(); j++) { - polygon.push_back(shape->get_polygon()[j] + anchor); - colors.push_back(c_bg); - } - } - workspace->draw_polygon(polygon, colors); - - if (coord == edited_shape_coord) { - if (!creating_shape && polygon.size() > 1) { - for (int j = 0; j < polygon.size() - 1; j++) { - workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1); - } - workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1); - } - if (shape == edited_occlusion_shape) { - draw_handles = true; - } - } - } - } - } - } break; - case EDITMODE_NAVIGATION: { - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - Ref<NavigationPolygon> shape = edited_navigation_shape; - - if (shape.is_valid()) { - Color c_bg = Color(0, 1, 1, 0.5); - Color c_border = Color(0, 1, 1); - - Vector<Vector2> polygon; - Vector<Color> colors; - Vector2 anchor = WORKSPACE_MARGIN; - anchor += tileset->tile_get_region(get_current_tile()).position; - if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) { - for (int j = 0; j < current_shape.size(); j++) { - polygon.push_back(current_shape[j]); - colors.push_back(c_bg); - } - } else { - Vector<Vector2> vertices = shape->get_vertices(); - for (int j = 0; j < shape->get_polygon(0).size(); j++) { - polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor); - colors.push_back(c_bg); - } - } - workspace->draw_polygon(polygon, colors); - - if (!creating_shape && polygon.size() > 1) { - for (int j = 0; j < polygon.size() - 1; j++) { - workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1); - } - workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1); - } - if (shape == edited_navigation_shape) { - draw_handles = true; - } - } - } else { - Map<Vector2, Ref<NavigationPolygon>> map = tileset->autotile_get_navigation_map(t_id); - for (Map<Vector2, Ref<NavigationPolygon>>::Element *E = map.front(); E; E = E->next()) { - Vector2 coord = E->key(); - Vector2 anchor = tileset->autotile_get_size(t_id); - anchor.x += tileset->autotile_get_spacing(t_id); - anchor.y += tileset->autotile_get_spacing(t_id); - anchor.x *= coord.x; - anchor.y *= coord.y; - anchor += WORKSPACE_MARGIN; - anchor += tileset->tile_get_region(t_id).position; - Ref<NavigationPolygon> shape = E->value(); - if (shape.is_valid()) { - Color c_bg; - Color c_border; - if (coord == edited_shape_coord && shape == edited_navigation_shape) { - c_bg = Color(0, 1, 1, 0.5); - c_border = Color(0, 1, 1); - } else { - c_bg = Color(0.9, 0.7, 0.07, 0.5); - c_border = Color(0.9, 0.7, 0.07, 1); - } - Vector<Vector2> polygon; - Vector<Color> colors; - if (!creating_shape && shape == edited_navigation_shape && current_shape.size() > 2) { - for (int j = 0; j < current_shape.size(); j++) { - polygon.push_back(current_shape[j]); - colors.push_back(c_bg); - } - } else { - Vector<Vector2> vertices = shape->get_vertices(); - for (int j = 0; j < shape->get_polygon(0).size(); j++) { - polygon.push_back(vertices[shape->get_polygon(0)[j]] + anchor); - colors.push_back(c_bg); - } - } - workspace->draw_polygon(polygon, colors); - - if (coord == edited_shape_coord) { - if (!creating_shape && polygon.size() > 1) { - for (int j = 0; j < polygon.size() - 1; j++) { - workspace->draw_line(polygon[j], polygon[j + 1], c_border, 1); - } - workspace->draw_line(polygon[polygon.size() - 1], polygon[0], c_border, 1); - } - if (shape == edited_navigation_shape) { - draw_handles = true; - } - } - } - } - } - } break; - default: { - } - } - - if (creating_shape && current_shape.size() > 1) { - for (int j = 0; j < current_shape.size() - 1; j++) { - workspace->draw_line(current_shape[j], current_shape[j + 1], Color(0, 1, 1), 1); - } - workspace->draw_line(current_shape[current_shape.size() - 1], snap_point(workspace->get_local_mouse_position()), Color(0, 1, 1), 1); - draw_handles = true; - } -} - -void TileSetEditor::close_shape(const Vector2 &shape_anchor) { - creating_shape = false; - - if (edit_mode == EDITMODE_COLLISION) { - if (current_shape.size() >= 3) { - Ref<ConvexPolygonShape2D> shape = memnew(ConvexPolygonShape2D); - - Vector<Vector2> points; - float p_total = 0; - - for (int i = 0; i < current_shape.size(); i++) { - points.push_back(current_shape[i] - shape_anchor); - - if (i != current_shape.size() - 1) { - p_total += ((current_shape[i + 1].x - current_shape[i].x) * (-current_shape[i + 1].y + (-current_shape[i].y))); - } else { - p_total += ((current_shape[0].x - current_shape[i].x) * (-current_shape[0].y + (-current_shape[i].y))); - } - } - - if (p_total < 0) { - points.reverse(); - } - - shape->set_points(points); - - undo_redo->create_action(TTR("Create Collision Polygon")); - // Necessary to get the version that returns a Array instead of a Vector. - Array sd = tileset->call("tile_get_shapes", get_current_tile()); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd.duplicate()); - for (int i = 0; i < sd.size(); i++) { - if (sd[i].get("shape") == edited_collision_shape) { - sd.remove(i); - break; - } - } - undo_redo->add_do_method(tileset.ptr(), "tile_set_shapes", get_current_tile(), sd); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D(), false, edited_shape_coord); - } else { - undo_redo->add_do_method(tileset.ptr(), "tile_add_shape", get_current_tile(), shape, Transform2D()); - } - tools[TOOL_SELECT]->set_pressed(true); - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } else { - tools[TOOL_SELECT]->set_pressed(true); - workspace->update(); - } - } else if (edit_mode == EDITMODE_OCCLUSION) { - Ref<OccluderPolygon2D> shape = memnew(OccluderPolygon2D); - - Vector<Vector2> polygon; - polygon.resize(current_shape.size()); - Vector2 *w = polygon.ptrw(); - - for (int i = 0; i < current_shape.size(); i++) { - w[i] = current_shape[i] - shape_anchor; - } - - shape->set_polygon(polygon); - - undo_redo->create_action(TTR("Create Occlusion Polygon")); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - undo_redo->add_do_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), shape, edited_shape_coord); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_light_occluder", get_current_tile(), tileset->autotile_get_light_occluder(get_current_tile(), edited_shape_coord), edited_shape_coord); - } else { - undo_redo->add_do_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), shape); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_light_occluder", get_current_tile(), tileset->tile_get_light_occluder(get_current_tile())); - } - tools[TOOL_SELECT]->set_pressed(true); - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } else if (edit_mode == EDITMODE_NAVIGATION) { - Ref<NavigationPolygon> shape = memnew(NavigationPolygon); - - Vector<Vector2> polygon; - Vector<int> indices; - polygon.resize(current_shape.size()); - Vector2 *w = polygon.ptrw(); - - for (int i = 0; i < current_shape.size(); i++) { - w[i] = current_shape[i] - shape_anchor; - indices.push_back(i); - } - - shape->set_vertices(polygon); - shape->add_polygon(indices); - - undo_redo->create_action(TTR("Create Navigation Polygon")); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE || tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - undo_redo->add_do_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), shape, edited_shape_coord); - undo_redo->add_undo_method(tileset.ptr(), "autotile_set_navigation_polygon", get_current_tile(), tileset->autotile_get_navigation_polygon(get_current_tile(), edited_shape_coord), edited_shape_coord); - } else { - undo_redo->add_do_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), shape); - undo_redo->add_undo_method(tileset.ptr(), "tile_set_navigation_polygon", get_current_tile(), tileset->tile_get_navigation_polygon(get_current_tile())); - } - tools[TOOL_SELECT]->set_pressed(true); - undo_redo->add_do_method(this, "_select_edited_shape_coord"); - undo_redo->add_undo_method(this, "_select_edited_shape_coord"); - undo_redo->commit_action(); - } - tileset->notify_property_list_changed(); -} - -void TileSetEditor::select_coord(const Vector2 &coord) { - _update_tile_data(); - current_shape = PackedVector2Array(); - if (get_current_tile() == -1) { - return; - } - Rect2 current_tile_region = tileset->tile_get_region(get_current_tile()); - current_tile_region.position += WORKSPACE_MARGIN; - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - if (edited_collision_shape != tileset->tile_get_shape(get_current_tile(), 0)) { - _set_edited_collision_shape(tileset->tile_get_shape(get_current_tile(), 0)); - } - if (edited_occlusion_shape != tileset->tile_get_light_occluder(get_current_tile())) { - edited_occlusion_shape = tileset->tile_get_light_occluder(get_current_tile()); - } - if (edited_navigation_shape != tileset->tile_get_navigation_polygon(get_current_tile())) { - edited_navigation_shape = tileset->tile_get_navigation_polygon(get_current_tile()); - } - - if (edit_mode == EDITMODE_COLLISION) { - current_shape.resize(0); - if (edited_collision_shape.is_valid()) { - for (int i = 0; i < _get_edited_shape_points().size(); i++) { - current_shape.push_back(_get_edited_shape_points()[i] + current_tile_region.position); - } - } - } else if (edit_mode == EDITMODE_OCCLUSION) { - current_shape.resize(0); - if (edited_occlusion_shape.is_valid()) { - for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) { - current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + current_tile_region.position); - } - } - } else if (edit_mode == EDITMODE_NAVIGATION) { - current_shape.resize(0); - if (edited_navigation_shape.is_valid()) { - if (edited_navigation_shape->get_polygon_count() > 0) { - Vector<Vector2> vertices = edited_navigation_shape->get_vertices(); - for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) { - current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + current_tile_region.position); - } - } - } - } - } else { - Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(get_current_tile()); - bool found_collision_shape = false; - for (int i = 0; i < sd.size(); i++) { - if (sd[i].autotile_coord == coord) { - if (edited_collision_shape != sd[i].shape) { - _set_edited_collision_shape(sd[i].shape); - } - found_collision_shape = true; - break; - } - } - if (!found_collision_shape) { - _set_edited_collision_shape(Ref<ConvexPolygonShape2D>(nullptr)); - } - if (edited_occlusion_shape != tileset->autotile_get_light_occluder(get_current_tile(), coord)) { - edited_occlusion_shape = tileset->autotile_get_light_occluder(get_current_tile(), coord); - } - if (edited_navigation_shape != tileset->autotile_get_navigation_polygon(get_current_tile(), coord)) { - edited_navigation_shape = tileset->autotile_get_navigation_polygon(get_current_tile(), coord); - } - - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 size = tileset->autotile_get_size(get_current_tile()); - Vector2 shape_anchor = coord; - shape_anchor.x *= (size.x + spacing); - shape_anchor.y *= (size.y + spacing); - shape_anchor += current_tile_region.position; - if (edit_mode == EDITMODE_COLLISION) { - current_shape.resize(0); - if (edited_collision_shape.is_valid()) { - for (int j = 0; j < _get_edited_shape_points().size(); j++) { - current_shape.push_back(_get_edited_shape_points()[j] + shape_anchor); - } - } - } else if (edit_mode == EDITMODE_OCCLUSION) { - current_shape.resize(0); - if (edited_occlusion_shape.is_valid()) { - for (int i = 0; i < edited_occlusion_shape->get_polygon().size(); i++) { - current_shape.push_back(edited_occlusion_shape->get_polygon()[i] + shape_anchor); - } - } - } else if (edit_mode == EDITMODE_NAVIGATION) { - current_shape.resize(0); - if (edited_navigation_shape.is_valid()) { - if (edited_navigation_shape->get_polygon_count() > 0) { - Vector<Vector2> vertices = edited_navigation_shape->get_vertices(); - for (int i = 0; i < edited_navigation_shape->get_polygon(0).size(); i++) { - current_shape.push_back(vertices[edited_navigation_shape->get_polygon(0)[i]] + shape_anchor); - } - } - } - } - } - workspace->update(); - workspace_container->update(); - helper->notify_property_list_changed(); -} - -Vector2 TileSetEditor::snap_point(const Vector2 &point) { - Vector2 p = point; - Vector2 coord = edited_shape_coord; - Vector2 tile_size = tileset->autotile_get_size(get_current_tile()); - int spacing = tileset->autotile_get_spacing(get_current_tile()); - Vector2 anchor = coord; - anchor.x *= (tile_size.x + spacing); - anchor.y *= (tile_size.y + spacing); - anchor += tileset->tile_get_region(get_current_tile()).position; - anchor += WORKSPACE_MARGIN; - Rect2 region(anchor, tile_size); - Rect2 tile_region(tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN, tileset->tile_get_region(get_current_tile()).size); - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - region.position = tileset->tile_get_region(get_current_tile()).position + WORKSPACE_MARGIN; - region.size = tileset->tile_get_region(get_current_tile()).size; - } - - if (tools[TOOL_GRID_SNAP]->is_pressed()) { - p.x = Math::snap_scalar_separation(snap_offset.x, snap_step.x, p.x, snap_separation.x); - p.y = Math::snap_scalar_separation(snap_offset.y, snap_step.y, p.y, snap_separation.y); - } - - if (tools[SHAPE_KEEP_INSIDE_TILE]->is_pressed()) { - if (p.x < region.position.x) { - p.x = region.position.x; - } - if (p.y < region.position.y) { - p.y = region.position.y; - } - if (p.x > region.position.x + region.size.x) { - p.x = region.position.x + region.size.x; - } - if (p.y > region.position.y + region.size.y) { - p.y = region.position.y + region.size.y; - } - } - - if (p.x < tile_region.position.x) { - p.x = tile_region.position.x; - } - if (p.y < tile_region.position.y) { - p.y = tile_region.position.y; - } - if (p.x > (tile_region.position.x + tile_region.size.x)) { - p.x = (tile_region.position.x + tile_region.size.x); - } - if (p.y > (tile_region.position.y + tile_region.size.y)) { - p.y = (tile_region.position.y + tile_region.size.y); - } - - return p; -} - -void TileSetEditor::add_texture(Ref<Texture2D> p_texture) { - texture_list->add_item(p_texture->get_path().get_file()); - texture_map.insert(p_texture->get_rid(), p_texture); - texture_list->set_item_metadata(texture_list->get_item_count() - 1, p_texture->get_rid()); -} - -void TileSetEditor::remove_texture(Ref<Texture2D> p_texture) { - texture_list->remove_item(texture_list->find_metadata(p_texture->get_rid())); - texture_map.erase(p_texture->get_rid()); - - _validate_current_tile_id(); - - if (!get_current_texture().is_valid()) { - _on_texture_list_selected(-1); - workspace_overlay->update(); - } -} - -void TileSetEditor::update_texture_list() { - Ref<Texture2D> selected_texture = get_current_texture(); - - helper->set_tileset(tileset); - - List<int> ids; - tileset->get_tile_list(&ids); - Vector<int> ids_to_remove; - for (List<int>::Element *E = ids.front(); E; E = E->next()) { - // Clear tiles referencing gone textures (user has been already given the chance to fix broken deps) - if (!tileset->tile_get_texture(E->get()).is_valid()) { - ids_to_remove.push_back(E->get()); - ERR_CONTINUE(!tileset->tile_get_texture(E->get()).is_valid()); - } - - if (!texture_map.has(tileset->tile_get_texture(E->get())->get_rid())) { - add_texture(tileset->tile_get_texture(E->get())); - } - } - for (int i = 0; i < ids_to_remove.size(); i++) { - tileset->remove_tile(ids_to_remove[i]); - } - - if (texture_list->get_item_count() > 0 && selected_texture.is_valid()) { - texture_list->select(texture_list->find_metadata(selected_texture->get_rid())); - if (texture_list->get_selected_items().size() > 0) { - _on_texture_list_selected(texture_list->get_selected_items()[0]); - } - } else if (get_current_texture().is_valid()) { - _on_texture_list_selected(texture_list->find_metadata(get_current_texture()->get_rid())); - } else { - _validate_current_tile_id(); - _on_texture_list_selected(-1); - workspace_overlay->update(); - } - update_texture_list_icon(); - helper->notify_property_list_changed(); -} - -void TileSetEditor::update_texture_list_icon() { - for (int current_idx = 0; current_idx < texture_list->get_item_count(); current_idx++) { - RID rid = texture_list->get_item_metadata(current_idx); - texture_list->set_item_icon(current_idx, texture_map[rid]); - Size2 texture_size = texture_map[rid]->get_size(); - texture_list->set_item_icon_region(current_idx, Rect2(0, 0, MIN(texture_size.x, 150), MIN(texture_size.y, 100))); - } - texture_list->update(); -} - -void TileSetEditor::update_workspace_tile_mode() { - if (!get_current_texture().is_valid()) { - tool_workspacemode[WORKSPACE_EDIT]->set_pressed(true); - workspace_mode = WORKSPACE_EDIT; - for (int i = 1; i < WORKSPACE_MODE_MAX; i++) { - tool_workspacemode[i]->set_disabled(true); - } - tools[SELECT_NEXT]->set_disabled(true); - tools[SELECT_PREVIOUS]->set_disabled(true); - - tools[ZOOM_OUT]->hide(); - tools[ZOOM_1]->hide(); - tools[ZOOM_IN]->hide(); - tools[VISIBLE_INFO]->hide(); - - scroll->hide(); - empty_message->show(); - } else { - for (int i = 1; i < WORKSPACE_MODE_MAX; i++) { - tool_workspacemode[i]->set_disabled(false); - } - tools[SELECT_NEXT]->set_disabled(false); - tools[SELECT_PREVIOUS]->set_disabled(false); - - tools[ZOOM_OUT]->show(); - tools[ZOOM_1]->show(); - tools[ZOOM_IN]->show(); - tools[VISIBLE_INFO]->show(); - - scroll->show(); - empty_message->hide(); - } - - if (workspace_mode != WORKSPACE_EDIT) { - for (int i = 0; i < EDITMODE_MAX; i++) { - tool_editmode[i]->hide(); - } - tool_editmode[EDITMODE_REGION]->show(); - tool_editmode[EDITMODE_REGION]->set_pressed(true); - _on_edit_mode_changed(EDITMODE_REGION); - separator_editmode->show(); - return; - } - - if (get_current_tile() < 0) { - for (int i = 0; i < EDITMODE_MAX; i++) { - tool_editmode[i]->hide(); - } - for (int i = TOOL_SELECT; i < ZOOM_OUT; i++) { - tools[i]->hide(); - } - - separator_editmode->hide(); - separator_bitmask->hide(); - separator_delete->hide(); - separator_grid->hide(); - return; - } - - for (int i = 0; i < EDITMODE_MAX; i++) { - tool_editmode[i]->show(); - } - separator_editmode->show(); - - if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::SINGLE_TILE) { - if (tool_editmode[EDITMODE_ICON]->is_pressed() || tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed() || tool_editmode[EDITMODE_Z_INDEX]->is_pressed()) { - tool_editmode[EDITMODE_COLLISION]->set_pressed(true); - edit_mode = EDITMODE_COLLISION; - } - select_coord(Vector2(0, 0)); - - tool_editmode[EDITMODE_ICON]->hide(); - tool_editmode[EDITMODE_BITMASK]->hide(); - tool_editmode[EDITMODE_PRIORITY]->hide(); - tool_editmode[EDITMODE_Z_INDEX]->hide(); - } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::AUTO_TILE) { - if (edit_mode == EDITMODE_ICON) { - select_coord(tileset->autotile_get_icon_coordinate(get_current_tile())); - } else { - _select_edited_shape_coord(); - } - } else if (tileset->tile_get_tile_mode(get_current_tile()) == TileSet::ATLAS_TILE) { - if (tool_editmode[EDITMODE_PRIORITY]->is_pressed() || tool_editmode[EDITMODE_BITMASK]->is_pressed()) { - tool_editmode[EDITMODE_COLLISION]->set_pressed(true); - edit_mode = EDITMODE_COLLISION; - } - if (edit_mode == EDITMODE_ICON) { - select_coord(tileset->autotile_get_icon_coordinate(get_current_tile())); - } else { - _select_edited_shape_coord(); - } - - tool_editmode[EDITMODE_BITMASK]->hide(); - } - _on_edit_mode_changed(edit_mode); -} - -void TileSetEditor::update_workspace_minsize() { - Size2 workspace_min_size = get_current_texture()->get_size(); - RID current_texture_rid = get_current_texture()->get_rid(); - List<int> *tiles = new List<int>(); - tileset->get_tile_list(tiles); - for (List<int>::Element *E = tiles->front(); E; E = E->next()) { - if (tileset->tile_get_texture(E->get())->get_rid() != current_texture_rid) { - continue; - } - - Rect2i region = tileset->tile_get_region(E->get()); - if (region.position.x + region.size.x > workspace_min_size.x) { - workspace_min_size.x = region.position.x + region.size.x; - } - if (region.position.y + region.size.y > workspace_min_size.y) { - workspace_min_size.y = region.position.y + region.size.y; - } - } - delete tiles; - - workspace->set_custom_minimum_size(workspace_min_size + WORKSPACE_MARGIN * 2); - workspace_container->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2); - workspace_overlay->set_custom_minimum_size(workspace_min_size * workspace->get_scale() + WORKSPACE_MARGIN * 2); -} - -void TileSetEditor::update_edited_region(const Vector2 &end_point) { - edited_region = Rect2(region_from, Size2()); - if (tools[TOOL_GRID_SNAP]->is_pressed()) { - Vector2 grid_coord; - grid_coord = ((region_from - snap_offset) / (snap_step + snap_separation)).floor(); - grid_coord *= (snap_step + snap_separation); - grid_coord += snap_offset; - edited_region.expand_to(grid_coord); - grid_coord += snap_step; - edited_region.expand_to(grid_coord); - - grid_coord = ((end_point - snap_offset) / (snap_step + snap_separation)).floor(); - grid_coord *= (snap_step + snap_separation); - grid_coord += snap_offset; - edited_region.expand_to(grid_coord); - grid_coord += snap_step; - edited_region.expand_to(grid_coord); - } else { - edited_region.expand_to(end_point); - } -} - -int TileSetEditor::get_current_tile() const { - return current_tile; -} - -void TileSetEditor::set_current_tile(int p_id) { - if (current_tile != p_id) { - current_tile = p_id; - helper->notify_property_list_changed(); - select_coord(Vector2(0, 0)); - update_workspace_tile_mode(); - if (p_id == -1) { - editor->get_inspector()->edit(tileset.ptr()); - } else { - editor->get_inspector()->edit(helper); - } - } -} - -Ref<Texture2D> TileSetEditor::get_current_texture() { - if (texture_list->get_selected_items().size() == 0) { - return Ref<Texture2D>(); - } else { - return texture_map[texture_list->get_item_metadata(texture_list->get_selected_items()[0])]; - } -} - -void TilesetEditorContext::set_tileset(const Ref<TileSet> &p_tileset) { - tileset = p_tileset; -} - -void TilesetEditorContext::set_snap_options_visible(bool p_visible) { - snap_options_visible = p_visible; - notify_property_list_changed(); -} - -bool TilesetEditorContext::_set(const StringName &p_name, const Variant &p_value) { - String name = p_name.operator String(); - - if (name == "options_offset") { - Vector2 snap = p_value; - tileset_editor->_set_snap_off(snap + WORKSPACE_MARGIN); - return true; - } else if (name == "options_step") { - Vector2 snap = p_value; - tileset_editor->_set_snap_step(snap); - return true; - } else if (name == "options_separation") { - Vector2 snap = p_value; - tileset_editor->_set_snap_sep(snap); - return true; - } else if (p_name.operator String().left(5) == "tile_") { - String name2 = p_name.operator String().right(5); - bool v = false; - - if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) { - return false; - } - - if (name2 == "autotile_bitmask_mode") { - tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", p_value, &v); - } else if (name2 == "subtile_size") { - tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", p_value, &v); - } else if (name2 == "subtile_spacing") { - tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", p_value, &v); - } else { - tileset->set(String::num(tileset_editor->get_current_tile(), 0) + "/" + name2, p_value, &v); - } - if (v) { - tileset->notify_property_list_changed(); - tileset_editor->workspace->update(); - tileset_editor->workspace_overlay->update(); - } - return v; - } else if (name == "tileset_script") { - tileset->set_script(p_value); - return true; - } else if (name == "selected_collision_one_way") { - Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); - for (int index = 0; index < sd.size(); index++) { - if (sd[index].shape == tileset_editor->edited_collision_shape) { - tileset->tile_set_shape_one_way(tileset_editor->get_current_tile(), index, p_value); - return true; - } - } - return false; - } else if (name == "selected_collision_one_way_margin") { - Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); - for (int index = 0; index < sd.size(); index++) { - if (sd[index].shape == tileset_editor->edited_collision_shape) { - tileset->tile_set_shape_one_way_margin(tileset_editor->get_current_tile(), index, p_value); - return true; - } - } - return false; - } - - tileset_editor->err_dialog->set_text(TTR("This property can't be changed.")); - tileset_editor->err_dialog->popup_centered(Size2(300, 60)); - return false; -} - -bool TilesetEditorContext::_get(const StringName &p_name, Variant &r_ret) const { - String name = p_name.operator String(); - bool v = false; - - if (name == "options_offset") { - r_ret = tileset_editor->snap_offset - WORKSPACE_MARGIN; - v = true; - } else if (name == "options_step") { - r_ret = tileset_editor->snap_step; - v = true; - } else if (name == "options_separation") { - r_ret = tileset_editor->snap_separation; - v = true; - } else if (name.left(5) == "tile_") { - name = name.right(5); - - if (tileset_editor->get_current_tile() < 0 || tileset.is_null()) { - return false; - } - if (!tileset->has_tile(tileset_editor->get_current_tile())) { - return false; - } - - if (name == "autotile_bitmask_mode") { - r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/bitmask_mode", &v); - } else if (name == "subtile_size") { - r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/tile_size", &v); - } else if (name == "subtile_spacing") { - r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/autotile/spacing", &v); - } else { - r_ret = tileset->get(String::num(tileset_editor->get_current_tile(), 0) + "/" + name, &v); - } - return v; - } else if (name == "selected_collision") { - r_ret = tileset_editor->edited_collision_shape; - v = true; - } else if (name == "selected_collision_one_way") { - Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); - for (int index = 0; index < sd.size(); index++) { - if (sd[index].shape == tileset_editor->edited_collision_shape) { - r_ret = sd[index].one_way_collision; - v = true; - break; - } - } - } else if (name == "selected_collision_one_way_margin") { - Vector<TileSet::ShapeData> sd = tileset->tile_get_shapes(tileset_editor->get_current_tile()); - for (int index = 0; index < sd.size(); index++) { - if (sd[index].shape == tileset_editor->edited_collision_shape) { - r_ret = sd[index].one_way_collision_margin; - v = true; - break; - } - } - } else if (name == "selected_navigation") { - r_ret = tileset_editor->edited_navigation_shape; - v = true; - } else if (name == "selected_occlusion") { - r_ret = tileset_editor->edited_occlusion_shape; - v = true; - } else if (name == "tileset_script") { - r_ret = tileset->get_script(); - v = true; - } - return v; -} - -void TilesetEditorContext::_get_property_list(List<PropertyInfo> *p_list) const { - if (snap_options_visible) { - p_list->push_back(PropertyInfo(Variant::NIL, "Snap Options", PROPERTY_HINT_NONE, "options_", PROPERTY_USAGE_GROUP)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_offset")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_step")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "options_separation")); - } - if (tileset_editor->get_current_tile() >= 0 && !tileset.is_null()) { - int id = tileset_editor->get_current_tile(); - p_list->push_back(PropertyInfo(Variant::NIL, "Selected Tile", PROPERTY_HINT_NONE, "tile_", PROPERTY_USAGE_GROUP)); - p_list->push_back(PropertyInfo(Variant::STRING, "tile_name")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_tex_offset")); - p_list->push_back(PropertyInfo(Variant::OBJECT, "tile_material", PROPERTY_HINT_RESOURCE_TYPE, "ShaderMaterial")); - p_list->push_back(PropertyInfo(Variant::COLOR, "tile_modulate")); - p_list->push_back(PropertyInfo(Variant::INT, "tile_tile_mode", PROPERTY_HINT_ENUM, "SINGLE_TILE,AUTO_TILE,ATLAS_TILE")); - if (tileset->tile_get_tile_mode(id) == TileSet::AUTO_TILE) { - p_list->push_back(PropertyInfo(Variant::INT, "tile_autotile_bitmask_mode", PROPERTY_HINT_ENUM, "2X2,3X3 (minimal),3X3")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size")); - p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 256, 1")); - } else if (tileset->tile_get_tile_mode(id) == TileSet::ATLAS_TILE) { - p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_subtile_size")); - p_list->push_back(PropertyInfo(Variant::INT, "tile_subtile_spacing", PROPERTY_HINT_RANGE, "0, 256, 1")); - } - p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_occluder_offset")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_navigation_offset")); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - p_list->push_back(PropertyInfo(Variant::VECTOR2, "tile_shape_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR)); - p_list->push_back(PropertyInfo(Variant::INT, "tile_z_index", PROPERTY_HINT_RANGE, itos(RS::CANVAS_ITEM_Z_MIN) + "," + itos(RS::CANVAS_ITEM_Z_MAX) + ",1")); - } - if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_COLLISION && tileset_editor->edited_collision_shape.is_valid()) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_collision", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_collision_shape->get_class())); - if (tileset_editor->edited_collision_shape.is_valid()) { - p_list->push_back(PropertyInfo(Variant::BOOL, "selected_collision_one_way", PROPERTY_HINT_NONE)); - p_list->push_back(PropertyInfo(Variant::FLOAT, "selected_collision_one_way_margin", PROPERTY_HINT_NONE)); - } - } - if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_NAVIGATION && tileset_editor->edited_navigation_shape.is_valid()) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_navigation", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_navigation_shape->get_class())); - } - if (tileset_editor->edit_mode == TileSetEditor::EDITMODE_OCCLUSION && tileset_editor->edited_occlusion_shape.is_valid()) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "selected_occlusion", PROPERTY_HINT_RESOURCE_TYPE, tileset_editor->edited_occlusion_shape->get_class())); - } - if (!tileset.is_null()) { - p_list->push_back(PropertyInfo(Variant::OBJECT, "tileset_script", PROPERTY_HINT_RESOURCE_TYPE, "Script")); - } -} - -void TilesetEditorContext::_bind_methods() { - ClassDB::bind_method("_hide_script_from_inspector", &TilesetEditorContext::_hide_script_from_inspector); -} - -TilesetEditorContext::TilesetEditorContext(TileSetEditor *p_tileset_editor) { - tileset_editor = p_tileset_editor; - snap_options_visible = false; -} - -void TileSetEditorPlugin::edit(Object *p_node) { - if (Object::cast_to<TileSet>(p_node)) { - tileset_editor->edit(Object::cast_to<TileSet>(p_node)); - } -} - -bool TileSetEditorPlugin::handles(Object *p_node) const { - return p_node->is_class("TileSet") || p_node->is_class("TilesetEditorContext"); -} - -void TileSetEditorPlugin::make_visible(bool p_visible) { - if (p_visible) { - tileset_editor_button->show(); - editor->make_bottom_panel_item_visible(tileset_editor); - get_tree()->connect("idle_frame", Callable(tileset_editor, "_on_workspace_process")); - } else { - editor->hide_bottom_panel(); - tileset_editor_button->hide(); - get_tree()->disconnect("idle_frame", Callable(tileset_editor, "_on_workspace_process")); - } -} - -Dictionary TileSetEditorPlugin::get_state() const { - Dictionary state; - state["snap_offset"] = tileset_editor->snap_offset; - state["snap_step"] = tileset_editor->snap_step; - state["snap_separation"] = tileset_editor->snap_separation; - state["snap_enabled"] = tileset_editor->tools[TileSetEditor::TOOL_GRID_SNAP]->is_pressed(); - state["keep_inside_tile"] = tileset_editor->tools[TileSetEditor::SHAPE_KEEP_INSIDE_TILE]->is_pressed(); - state["show_information"] = tileset_editor->tools[TileSetEditor::VISIBLE_INFO]->is_pressed(); - return state; -} - -void TileSetEditorPlugin::set_state(const Dictionary &p_state) { - Dictionary state = p_state; - if (state.has("snap_step")) { - tileset_editor->_set_snap_step(state["snap_step"]); - } - - if (state.has("snap_offset")) { - tileset_editor->_set_snap_off(state["snap_offset"]); - } - - if (state.has("snap_separation")) { - tileset_editor->_set_snap_sep(state["snap_separation"]); - } - - if (state.has("snap_enabled")) { - tileset_editor->tools[TileSetEditor::TOOL_GRID_SNAP]->set_pressed(state["snap_enabled"]); - if (tileset_editor->helper) { - tileset_editor->_on_grid_snap_toggled(state["snap_enabled"]); - } - } - - if (state.has("keep_inside_tile")) { - tileset_editor->tools[TileSetEditor::SHAPE_KEEP_INSIDE_TILE]->set_pressed(state["keep_inside_tile"]); - } - - if (state.has("show_information")) { - tileset_editor->tools[TileSetEditor::VISIBLE_INFO]->set_pressed(state["show_information"]); - } -} - -TileSetEditorPlugin::TileSetEditorPlugin(EditorNode *p_node) { - editor = p_node; - tileset_editor = memnew(TileSetEditor(p_node)); - - tileset_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); - tileset_editor->hide(); - - tileset_editor_button = p_node->add_bottom_panel_item(TTR("TileSet"), tileset_editor); - tileset_editor_button->hide(); -} diff --git a/editor/plugins/tile_set_editor_plugin.h b/editor/plugins/tile_set_editor_plugin.h deleted file mode 100644 index e778c18f44..0000000000 --- a/editor/plugins/tile_set_editor_plugin.h +++ /dev/null @@ -1,298 +0,0 @@ -/*************************************************************************/ -/* tile_set_editor_plugin.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ -/* */ -/* Permission is hereby granted, free of charge, to any person obtaining */ -/* a copy of this software and associated documentation files (the */ -/* "Software"), to deal in the Software without restriction, including */ -/* without limitation the rights to use, copy, modify, merge, publish, */ -/* distribute, sublicense, and/or sell copies of the Software, and to */ -/* permit persons to whom the Software is furnished to do so, subject to */ -/* the following conditions: */ -/* */ -/* The above copyright notice and this permission notice shall be */ -/* included in all copies or substantial portions of the Software. */ -/* */ -/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ -/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ -/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ -/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ -/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ -/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ -/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/*************************************************************************/ - -#ifndef TILE_SET_EDITOR_PLUGIN_H -#define TILE_SET_EDITOR_PLUGIN_H - -#include "editor/editor_node.h" -#include "scene/2d/sprite_2d.h" -#include "scene/resources/concave_polygon_shape_2d.h" -#include "scene/resources/convex_polygon_shape_2d.h" -#include "scene/resources/tile_set.h" - -#define WORKSPACE_MARGIN Vector2(10, 10) -class TilesetEditorContext; - -class TileSetEditor : public HSplitContainer { - friend class TileSetEditorPlugin; - friend class TilesetEditorContext; - - GDCLASS(TileSetEditor, HSplitContainer); - - enum TextureButtons { - TOOL_TILESET_ADD_TEXTURE, - TOOL_TILESET_REMOVE_TEXTURE, - TOOL_TILESET_CREATE_SCENE, - TOOL_TILESET_MERGE_SCENE, - TOOL_TILESET_MAX - }; - - enum WorkspaceMode { - WORKSPACE_EDIT, - WORKSPACE_CREATE_SINGLE, - WORKSPACE_CREATE_AUTOTILE, - WORKSPACE_CREATE_ATLAS, - WORKSPACE_MODE_MAX - }; - - enum EditMode { - EDITMODE_REGION, - EDITMODE_COLLISION, - EDITMODE_OCCLUSION, - EDITMODE_NAVIGATION, - EDITMODE_BITMASK, - EDITMODE_PRIORITY, - EDITMODE_ICON, - EDITMODE_Z_INDEX, - EDITMODE_MAX - }; - - enum TileSetTools { - SELECT_PREVIOUS, - SELECT_NEXT, - TOOL_SELECT, - BITMASK_COPY, - BITMASK_PASTE, - BITMASK_CLEAR, - SHAPE_NEW_POLYGON, - SHAPE_NEW_RECTANGLE, - SHAPE_TOGGLE_TYPE, - SHAPE_DELETE, - SHAPE_KEEP_INSIDE_TILE, - TOOL_GRID_SNAP, - ZOOM_OUT, - ZOOM_1, - ZOOM_IN, - VISIBLE_INFO, - TOOL_MAX - }; - - struct SubtileData { - Array collisions; - Ref<OccluderPolygon2D> occlusion_shape; - Ref<NavigationPolygon> navigation_shape; - }; - - Ref<TileSet> tileset; - TilesetEditorContext *helper; - EditorNode *editor; - UndoRedo *undo_redo; - - ConfirmationDialog *cd; - AcceptDialog *err_dialog; - EditorFileDialog *texture_dialog; - - ItemList *texture_list; - int option; - Button *tileset_toolbar_buttons[TOOL_TILESET_MAX]; - MenuButton *tileset_toolbar_tools; - Map<RID, Ref<Texture2D>> texture_map; - - bool creating_shape; - int dragging_point; - bool tile_names_visible; - Vector2 region_from; - Rect2 edited_region; - bool draw_edited_region; - Vector2 edited_shape_coord; - PackedVector2Array current_shape; - Map<Vector2i, SubtileData> current_tile_data; - Map<Vector2, uint32_t> bitmask_map_copy; - - Vector2 snap_step; - Vector2 snap_offset; - Vector2 snap_separation; - - Ref<Shape2D> edited_collision_shape; - Ref<OccluderPolygon2D> edited_occlusion_shape; - Ref<NavigationPolygon> edited_navigation_shape; - - int current_item_index; - Sprite2D *preview; - ScrollContainer *scroll; - Label *empty_message; - Control *workspace_container; - bool draw_handles; - Control *workspace_overlay; - Control *workspace; - Button *tool_workspacemode[WORKSPACE_MODE_MAX]; - Button *tool_editmode[EDITMODE_MAX]; - HSeparator *separator_editmode; - HBoxContainer *toolbar; - Button *tools[TOOL_MAX]; - VSeparator *separator_shape_toggle; - VSeparator *separator_bitmask; - VSeparator *separator_delete; - VSeparator *separator_grid; - SpinBox *spin_priority; - SpinBox *spin_z_index; - WorkspaceMode workspace_mode; - EditMode edit_mode; - int current_tile; - - float max_scale; - float min_scale; - float scale_ratio; - - void update_texture_list(); - void update_texture_list_icon(); - - void add_texture(Ref<Texture2D> p_texture); - void remove_texture(Ref<Texture2D> p_texture); - - Ref<Texture2D> get_current_texture(); - - static void _import_node(Node *p_node, Ref<TileSet> p_library); - static void _import_scene(Node *p_scene, Ref<TileSet> p_library, bool p_merge); - void _undo_redo_import_scene(Node *p_scene, bool p_merge); - - bool _is_drop_valid(const Dictionary &p_drag_data, const Dictionary &p_item_data) const; - Variant get_drag_data_fw(const Point2 &p_point, Control *p_from); - bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; - void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); - void _file_load_request(const Vector<String> &p_path, int p_at_pos = -1); - -protected: - static void _bind_methods(); - void _notification(int p_what); - -public: - void edit(const Ref<TileSet> &p_tileset); - static Error update_library_file(Node *p_base_scene, Ref<TileSet> ml, bool p_merge = true); - - TileSetEditor(EditorNode *p_editor); - ~TileSetEditor(); - -private: - void _on_tileset_toolbar_button_pressed(int p_index); - void _on_tileset_toolbar_confirm(); - void _on_texture_list_selected(int p_index); - void _on_textures_added(const PackedStringArray &p_paths); - void _on_edit_mode_changed(int p_edit_mode); - void _on_workspace_mode_changed(int p_workspace_mode); - void _on_workspace_overlay_draw(); - void _on_workspace_draw(); - void _on_workspace_process(); - void _on_scroll_container_input(const Ref<InputEvent> &p_event); - void _on_workspace_input(const Ref<InputEvent> &p_ie); - void _on_tool_clicked(int p_tool); - void _on_priority_changed(float val); - void _on_z_index_changed(float val); - void _on_grid_snap_toggled(bool p_val); - Vector<Vector2> _get_collision_shape_points(const Ref<Shape2D> &p_shape); - Vector<Vector2> _get_edited_shape_points(); - void _set_edited_shape_points(const Vector<Vector2> &points); - void _update_tile_data(); - void _update_toggle_shape_button(); - void _select_next_tile(); - void _select_previous_tile(); - Array _get_tiles_in_current_texture(bool sorted = false); - bool _sort_tiles(Variant p_a, Variant p_b); - void _select_next_subtile(); - void _select_previous_subtile(); - void _select_next_shape(); - void _select_previous_shape(); - void _set_edited_collision_shape(const Ref<Shape2D> &p_shape); - void _set_snap_step(Vector2 p_val); - void _set_snap_off(Vector2 p_val); - void _set_snap_sep(Vector2 p_val); - - void _validate_current_tile_id(); - void _select_edited_shape_coord(); - void _undo_tile_removal(int p_id); - - void _zoom_in(); - void _zoom_out(); - void _zoom_reset(); - - void draw_highlight_current_tile(); - void draw_highlight_subtile(Vector2 coord, const Vector<Vector2> &other_highlighted = Vector<Vector2>()); - void draw_tile_subdivision(int p_id, Color p_color) const; - void draw_edited_region_subdivision() const; - void draw_grid_snap(); - void draw_polygon_shapes(); - void close_shape(const Vector2 &shape_anchor); - void select_coord(const Vector2 &coord); - Vector2 snap_point(const Vector2 &point); - void update_workspace_tile_mode(); - void update_workspace_minsize(); - void update_edited_region(const Vector2 &end_point); - int get_grabbed_point(const Vector2 &p_mouse_pos, real_t grab_threshold); - bool is_within_grabbing_distance_of_first_point(const Vector2 &p_pos, real_t p_grab_threshold); - - int get_current_tile() const; - void set_current_tile(int p_id); -}; - -class TilesetEditorContext : public Object { - friend class TileSetEditor; - GDCLASS(TilesetEditorContext, Object); - - Ref<TileSet> tileset; - TileSetEditor *tileset_editor; - bool snap_options_visible; - -public: - bool _hide_script_from_inspector() { return true; } - void set_tileset(const Ref<TileSet> &p_tileset); - -private: - void set_snap_options_visible(bool p_visible); - -protected: - bool _set(const StringName &p_name, const Variant &p_value); - bool _get(const StringName &p_name, Variant &r_ret) const; - void _get_property_list(List<PropertyInfo> *p_list) const; - static void _bind_methods(); - -public: - TilesetEditorContext(TileSetEditor *p_tileset_editor); -}; - -class TileSetEditorPlugin : public EditorPlugin { - GDCLASS(TileSetEditorPlugin, EditorPlugin); - - TileSetEditor *tileset_editor; - Button *tileset_editor_button; - EditorNode *editor; - -public: - virtual String get_name() const override { return "TileSet"; } - bool has_main_screen() const override { return false; } - virtual void edit(Object *p_node) override; - virtual bool handles(Object *p_node) const override; - virtual void make_visible(bool p_visible) override; - void set_state(const Dictionary &p_state) override; - Dictionary get_state() const override; - - TileSetEditorPlugin(EditorNode *p_node); -}; - -#endif // TILE_SET_EDITOR_PLUGIN_H diff --git a/editor/plugins/tiles/SCsub b/editor/plugins/tiles/SCsub new file mode 100644 index 0000000000..359d04e5df --- /dev/null +++ b/editor/plugins/tiles/SCsub @@ -0,0 +1,5 @@ +#!/usr/bin/env python + +Import("env") + +env.add_source_files(env.editor_sources, "*.cpp") diff --git a/editor/plugins/tiles/tile_atlas_view.cpp b/editor/plugins/tiles/tile_atlas_view.cpp new file mode 100644 index 0000000000..374a255df3 --- /dev/null +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -0,0 +1,680 @@ +/*************************************************************************/ +/* tile_atlas_view.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_atlas_view.h" + +#include "core/input/input.h" +#include "core/os/keyboard.h" +#include "scene/gui/box_container.h" +#include "scene/gui/label.h" +#include "scene/gui/panel.h" +#include "scene/gui/texture_rect.h" + +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +void TileAtlasView::_gui_input(const Ref<InputEvent> &p_event) { + bool ctrl = Input::get_singleton()->is_key_pressed(KEY_CTRL); + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + drag_type = DRAG_TYPE_NONE; + if (ctrl && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + // Zoom out + zoom_widget->set_zoom_by_increments(-2); + emit_signal("transform_changed", zoom_widget->get_zoom(), panning); + _update_zoom_and_panning(true); + accept_event(); + } + + if (ctrl && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + // Zoom in + zoom_widget->set_zoom_by_increments(2); + emit_signal("transform_changed", zoom_widget->get_zoom(), panning); + _update_zoom_and_panning(true); + accept_event(); + } + + if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE || mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->is_pressed()) { + drag_type = DRAG_TYPE_PAN; + } else { + drag_type = DRAG_TYPE_NONE; + } + accept_event(); + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (drag_type == DRAG_TYPE_PAN) { + panning += mm->get_relative(); + _update_zoom_and_panning(); + emit_signal("transform_changed", zoom_widget->get_zoom(), panning); + accept_event(); + } + } +} + +Size2i TileAtlasView::_compute_base_tiles_control_size() { + // Update the texture. + Vector2i size; + Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); + 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; +} + +Size2i TileAtlasView::_compute_alternative_tiles_control_size() { + Vector2i 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); + int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id); + Vector2i line_size; + Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size; + for (int j = 1; j < alternatives_count; j++) { + int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j); + bool transposed = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id))->get_transpose(); + line_size.x += transposed ? texture_region_size.y : texture_region_size.x; + line_size.y = MAX(line_size.y, transposed ? texture_region_size.x : texture_region_size.y); + } + size.x = MAX(size.x, line_size.x); + size.y += line_size.y; + } + + return size; +} + +void TileAtlasView::_update_zoom_and_panning(bool p_zoom_on_mouse_pos) { + float zoom = zoom_widget->get_zoom(); + + // Compute the minimum sizes. + Size2i base_tiles_control_size = _compute_base_tiles_control_size(); + base_tiles_root_control->set_custom_minimum_size(Vector2(base_tiles_control_size) * zoom); + + Size2i alternative_tiles_control_size = _compute_alternative_tiles_control_size(); + alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * zoom); + + // Set the texture for the base tiles. + Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); + + // Set the scales. + if (base_tiles_control_size.x > 0 && base_tiles_control_size.y > 0) { + base_tiles_drawing_root->set_scale(Vector2(zoom, zoom)); + } else { + base_tiles_drawing_root->set_scale(Vector2(1, 1)); + } + if (alternative_tiles_control_size.x > 0 && alternative_tiles_control_size.y > 0) { + alternative_tiles_drawing_root->set_scale(Vector2(zoom, zoom)); + } else { + alternative_tiles_drawing_root->set_scale(Vector2(1, 1)); + } + + // Update the margin container's margins. + const char *constants[] = { "margin_left", "margin_top", "margin_right", "margin_bottom" }; + for (int i = 0; i < 4; i++) { + margin_container->add_theme_constant_override(constants[i], margin_container_paddings[i] * zoom); + } + + // Update the backgrounds. + background_left->update(); + background_right->update(); + + // Zoom on the position. + if (p_zoom_on_mouse_pos) { + // Offset the panning relative to the center of panel. + Vector2 relative_mpos = get_local_mouse_position() - get_size() / 2; + panning = (panning - relative_mpos) * zoom / previous_zoom + relative_mpos; + } else { + // Center of panel. + panning = panning * zoom / previous_zoom; + } + button_center_view->set_disabled(panning.is_equal_approx(Vector2())); + + previous_zoom = zoom; + + center_container->set_begin(panning - center_container->get_minimum_size() / 2); + center_container->set_size(center_container->get_minimum_size()); +} + +void TileAtlasView::_zoom_widget_changed() { + _update_zoom_and_panning(); + emit_signal("transform_changed", zoom_widget->get_zoom(), panning); +} + +void TileAtlasView::_center_view() { + panning = Vector2(); + button_center_view->set_disabled(true); + _update_zoom_and_panning(); + emit_signal("transform_changed", zoom_widget->get_zoom(), panning); +} + +void TileAtlasView::_base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) { + base_tiles_root_control->set_tooltip(""); + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + Transform2D xform = base_tiles_drawing_root->get_transform().affine_inverse(); + Vector2i coords = get_atlas_tile_coords_at_pos(xform.xform(mm->get_position())); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + coords = tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + base_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: 0"), source_id, coords)); + } + } + } +} + +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 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(); + 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); + } + } + } + + // 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); + // Bottom + int bottom_border = margins.y + (grid_size.y * texture_region_size.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); + } + // Left + rect.position = Vector2i(0, margins.y); + rect.set_end(Vector2i(margins.x, margins.y + (grid_size.y * texture_region_size.y))); + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + // Right. + int right_border = margins.x + (grid_size.x * texture_region_size.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))); + base_tiles_draw->draw_texture_rect_region(texture, rect, rect); + } + + // Draw actual tiles, using their properties (modulation, etc...) + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i); + + // Update the y to max value. + Vector2i offset_pos = (margins + (atlas_coords * texture_region_size) + tile_set_atlas_source->get_tile_texture_region(atlas_coords).size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, 0)); + + // Draw the tile. + TileSetPluginAtlasRendering::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0); + } + } +} + +void TileAtlasView::_draw_base_tiles_texture_grid() { + 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) { + if (base_tile_coords == Vector2i(x, y)) { + // Draw existing tile. + Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(base_tile_coords); + Vector2 region_size = texture_region_size * size_in_atlas + separation * (size_in_atlas - Vector2i(1, 1)); + base_tiles_texture_grid->draw_rect(Rect2i(origin, region_size), Color(1.0, 1.0, 1.0, 0.8), false); + } + } else { + // Draw the grid. + base_tiles_texture_grid->draw_rect(Rect2i(origin, texture_region_size), Color(0.7, 0.7, 0.7, 0.1), false); + } + } + } + } +} + +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"); + Vector2i tile_shape_size = tile_set->get_tile_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); + Vector2 in_tile_base_offset = tile_set_atlas_source->get_tile_effective_texture_offset(tile_id, 0); + Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(tile_id); + Vector2 origin = texture_region.position + (texture_region.size - tile_shape_size) / 2 + in_tile_base_offset; + + // Draw only if the tile shape fits in the texture region + tile_set->draw_tile_shape(base_tiles_shape_grid, Rect2(origin, tile_shape_size), grid_color); + } +} + +void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event) { + alternative_tiles_root_control->set_tooltip(""); + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + Transform2D xform = alternative_tiles_drawing_root->get_transform().affine_inverse(); + Vector3i coords3 = get_alternative_tile_at_pos(xform.xform(mm->get_position())); + Vector2i coords = Vector2i(coords3.x, coords3.y); + int alternative_id = coords3.z; + if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative_id != TileSetSource::INVALID_TILE_ALTERNATIVE) { + alternative_tiles_root_control->set_tooltip(vformat(TTR("Source: %d\nAtlas coordinates: %s\nAlternative: %d"), source_id, coords, alternative_id)); + } + } +} + +void TileAtlasView::_draw_alternatives() { + // Draw the alternative tiles. + Ref<Texture2D> texture = tile_set_atlas_source->get_texture(); + if (texture.is_valid()) { + Vector2 current_pos; + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i); + current_pos.x = 0; + int y_increment = 0; + Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(atlas_coords); + int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(atlas_coords); + for (int j = 1; j < alternatives_count; j++) { + int alternative_id = tile_set_atlas_source->get_alternative_tile_id(atlas_coords, j); + TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id)); + bool transposed = tile_data->get_transpose(); + + // Update the y to max value. + Vector2i offset_pos = current_pos; + if (transposed) { + offset_pos = (current_pos + Vector2(texture_region.size.y, texture_region.size.x) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); + y_increment = MAX(y_increment, texture_region.size.x); + } else { + offset_pos = (current_pos + texture_region.size / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(atlas_coords, alternative_id)); + y_increment = MAX(y_increment, texture_region.size.y); + } + + // Draw the tile. + TileSetPluginAtlasRendering::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id); + + // Increment the x position. + current_pos.x += transposed ? texture_region.size.y : texture_region.size.x; + } + if (alternatives_count > 1) { + current_pos.y += y_increment; + } + } + } +} + +void TileAtlasView::_draw_background_left() { + Ref<Texture2D> texture = get_theme_icon("Checkerboard", "EditorIcons"); + background_left->set_size(base_tiles_root_control->get_custom_minimum_size()); + background_left->draw_texture_rect(texture, Rect2(Vector2(), background_left->get_size()), true); +} + +void TileAtlasView::_draw_background_right() { + Ref<Texture2D> texture = get_theme_icon("Checkerboard", "EditorIcons"); + background_right->set_size(alternative_tiles_root_control->get_custom_minimum_size()); + background_right->draw_texture_rect(texture, Rect2(Vector2(), background_right->get_size()), true); +} + +void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) { + ERR_FAIL_COND(!p_tile_set); + ERR_FAIL_COND(!p_tile_set_atlas_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); + + tile_set = p_tile_set; + tile_set_atlas_source = p_tile_set_atlas_source; + source_id = p_source_id; + + // Show or hide the view. + bool valid = tile_set_atlas_source->get_texture().is_valid(); + hbox->set_visible(valid); + missing_source_label->set_visible(!valid); + + // Update the rect cache. + _update_alternative_tiles_rect_cache(); + + // Update everything. + _update_zoom_and_panning(); + + // Change children control size. + Size2i base_tiles_control_size = _compute_base_tiles_control_size(); + for (int i = 0; i < base_tiles_drawing_root->get_child_count(); i++) { + Control *control = Object::cast_to<Control>(base_tiles_drawing_root->get_child(i)); + if (control) { + control->set_size(base_tiles_control_size); + } + } + + Size2i alternative_control_size = _compute_alternative_tiles_control_size(); + for (int i = 0; i < alternative_tiles_drawing_root->get_child_count(); i++) { + Control *control = Object::cast_to<Control>(alternative_tiles_drawing_root->get_child(i)); + if (control) { + control->set_size(alternative_control_size); + } + } + + // 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(); +} + +float TileAtlasView::get_zoom() const { + return zoom_widget->get_zoom(); +}; + +void TileAtlasView::set_transform(float p_zoom, Vector2i p_panning) { + zoom_widget->set_zoom(p_zoom); + panning = p_panning; + _update_zoom_and_panning(); +}; + +void TileAtlasView::set_padding(Side p_side, int p_padding) { + ERR_FAIL_COND(p_padding < 0); + margin_container_paddings[p_side] = p_padding; +} + +Vector2i TileAtlasView::get_atlas_tile_coords_at_pos(const Vector2 p_pos) const { + 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(); + + // Compute index in atlas + Vector2 pos = p_pos - margins; + Vector2i ret = (pos / (texture_region_size + separation)).floor(); + + return ret; + } + + return TileSetSource::INVALID_ATLAS_COORDS; +} + +void TileAtlasView::_update_alternative_tiles_rect_cache() { + alternative_tiles_rect_cache.clear(); + + Rect2i current; + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); + int alternatives_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id); + Size2i texture_region_size = tile_set_atlas_source->get_tile_texture_region(tile_id).size; + int line_height = 0; + for (int j = 1; j < alternatives_count; j++) { + int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j); + TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(tile_id, alternative_id)); + bool transposed = tile_data->get_transpose(); + current.size = transposed ? Vector2i(texture_region_size.y, texture_region_size.x) : texture_region_size; + + // Update the rect. + if (!alternative_tiles_rect_cache.has(tile_id)) { + alternative_tiles_rect_cache[tile_id] = Map<int, Rect2i>(); + } + alternative_tiles_rect_cache[tile_id][alternative_id] = current; + + current.position.x += transposed ? texture_region_size.y : texture_region_size.x; + line_height = MAX(line_height, transposed ? texture_region_size.x : texture_region_size.y); + } + + current.position.x = 0; + current.position.y += line_height; + } +} + +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()); + } + } + } + + return Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); +} + +Rect2i TileAtlasView::get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile) { + ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache.has(p_coords), Rect2i(), vformat("No cached rect for tile coords:%s", p_coords)); + ERR_FAIL_COND_V_MSG(!alternative_tiles_rect_cache[p_coords].has(p_alternative_tile), Rect2i(), vformat("No cached rect for tile coords:%s alternative_id:%d", p_coords, p_alternative_tile)); + + return alternative_tiles_rect_cache[p_coords][p_alternative_tile]; +} + +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(); +} + +void TileAtlasView::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: + button_center_view->set_icon(get_theme_icon("CenterView", "EditorIcons")); + break; + } +} + +void TileAtlasView::_bind_methods() { + ClassDB::bind_method("_gui_input", &TileAtlasView::_gui_input); + + ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll"))); +} + +TileAtlasView::TileAtlasView() { + set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + + Panel *panel = memnew(Panel); + panel->set_clip_contents(true); + panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + panel->set_h_size_flags(SIZE_EXPAND_FILL); + panel->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(panel); + + // Scrollingsc + zoom_widget = memnew(EditorZoomWidget); + add_child(zoom_widget); + zoom_widget->set_anchors_and_offsets_preset(Control::PRESET_TOP_LEFT, Control::PRESET_MODE_MINSIZE, 2 * EDSCALE); + zoom_widget->connect("zoom_changed", callable_mp(this, &TileAtlasView::_zoom_widget_changed).unbind(1)); + + button_center_view = memnew(Button); + button_center_view->set_icon(get_theme_icon("CenterView", "EditorIcons")); + button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5); + button_center_view->connect("pressed", callable_mp(this, &TileAtlasView::_center_view)); + button_center_view->set_flat(true); + button_center_view->set_disabled(true); + add_child(button_center_view); + + center_container = memnew(CenterContainer); + center_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + center_container->set_anchors_preset(Control::PRESET_CENTER); + center_container->connect("gui_input", callable_mp(this, &TileAtlasView::_gui_input)); + panel->add_child(center_container); + + missing_source_label = memnew(Label); + missing_source_label->set_text(TTR("No atlas source with a valid texture selected.")); + center_container->add_child(missing_source_label); + + margin_container = memnew(MarginContainer); + margin_container->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + center_container->add_child(margin_container); + + hbox = memnew(HBoxContainer); + hbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + hbox->add_theme_constant_override("separation", 10); + hbox->hide(); + margin_container->add_child(hbox); + + VBoxContainer *left_vbox = memnew(VBoxContainer); + left_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + hbox->add_child(left_vbox); + + VBoxContainer *right_vbox = memnew(VBoxContainer); + right_vbox->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + hbox->add_child(right_vbox); + + // Base tiles. + Label *base_tile_label = memnew(Label); + base_tile_label->set_mouse_filter(Control::MOUSE_FILTER_PASS); + base_tile_label->set_text(TTR("Base Tiles")); + base_tile_label->set_align(Label::ALIGN_CENTER); + left_vbox->add_child(base_tile_label); + + base_tiles_root_control = memnew(Control); + base_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); + base_tiles_root_control->set_v_size_flags(Control::SIZE_EXPAND_FILL); + base_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_base_tiles_root_control_gui_input)); + left_vbox->add_child(base_tiles_root_control); + + background_left = memnew(Control); + background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + background_left->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); + background_left->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_left)); + base_tiles_root_control->add_child(background_left); + + base_tiles_drawing_root = memnew(Control); + base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_drawing_root->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST); + base_tiles_root_control->add_child(base_tiles_drawing_root); + + base_tiles_draw = memnew(Control); + base_tiles_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_draw->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles)); + base_tiles_drawing_root->add_child(base_tiles_draw); + + base_tiles_texture_grid = memnew(Control); + base_tiles_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_texture_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_texture_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_texture_grid)); + base_tiles_drawing_root->add_child(base_tiles_texture_grid); + + base_tiles_shape_grid = memnew(Control); + base_tiles_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_shape_grid->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_shape_grid->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_shape_grid)); + base_tiles_drawing_root->add_child(base_tiles_shape_grid); + + base_tiles_dark = memnew(Control); + base_tiles_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_dark->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_dark->connect("draw", callable_mp(this, &TileAtlasView::_draw_base_tiles_dark)); + base_tiles_drawing_root->add_child(base_tiles_dark); + + // Alternative tiles. + Label *alternative_tiles_label = memnew(Label); + alternative_tiles_label->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + alternative_tiles_label->set_text(TTR("Alternative Tiles")); + alternative_tiles_label->set_align(Label::ALIGN_CENTER); + right_vbox->add_child(alternative_tiles_label); + + alternative_tiles_root_control = memnew(Control); + alternative_tiles_root_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); + alternative_tiles_root_control->connect("gui_input", callable_mp(this, &TileAtlasView::_alternative_tiles_root_control_gui_input)); + right_vbox->add_child(alternative_tiles_root_control); + + background_right = memnew(Control); + background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + background_right->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); + background_right->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_right)); + + alternative_tiles_root_control->add_child(background_right); + + alternative_tiles_drawing_root = memnew(Control); + alternative_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST); + alternative_tiles_root_control->add_child(alternative_tiles_drawing_root); + + alternatives_draw = memnew(Control); + alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives)); + alternative_tiles_drawing_root->add_child(alternatives_draw); +} diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h new file mode 100644 index 0000000000..bafc2b3985 --- /dev/null +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -0,0 +1,160 @@ +/*************************************************************************/ +/* tile_atlas_view.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_ATLAS_VIEW_H +#define TILE_ATLAS_VIEW_H + +#include "editor/editor_zoom_widget.h" +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/center_container.h" +#include "scene/gui/label.h" +#include "scene/gui/margin_container.h" +#include "scene/gui/scroll_container.h" +#include "scene/gui/texture_rect.h" +#include "scene/resources/tile_set.h" + +class TileAtlasView : public Control { + GDCLASS(TileAtlasView, Control); + +private: + TileSet *tile_set; + TileSetAtlasSource *tile_set_atlas_source; + int source_id = -1; + + enum DragType { + DRAG_TYPE_NONE, + DRAG_TYPE_PAN, + }; + DragType drag_type = DRAG_TYPE_NONE; + float previous_zoom = 1.0; + EditorZoomWidget *zoom_widget; + Button *button_center_view; + CenterContainer *center_container; + Vector2 panning; + void _update_zoom_and_panning(bool p_zoom_on_mouse_pos = false); + void _zoom_widget_changed(); + void _center_view(); + void _gui_input(const Ref<InputEvent> &p_event); + + Map<Vector2, Map<int, Rect2i>> alternative_tiles_rect_cache; + void _update_alternative_tiles_rect_cache(); + + MarginContainer *margin_container; + int margin_container_paddings[4] = { 0, 0, 0, 0 }; + HBoxContainer *hbox; + Label *missing_source_label; + + // Background + Control *background_left; + void _draw_background_left(); + Control *background_right; + void _draw_background_right(); + + // Left side. + Control *base_tiles_root_control; + void _base_tiles_root_control_gui_input(const Ref<InputEvent> &p_event); + + Control *base_tiles_drawing_root; + + Control *base_tiles_draw; + void _draw_base_tiles(); + + Control *base_tiles_texture_grid; + void _draw_base_tiles_texture_grid(); + + 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. + Control *alternative_tiles_root_control; + void _alternative_tiles_root_control_gui_input(const Ref<InputEvent> &p_event); + + Control *alternative_tiles_drawing_root; + + Control *alternatives_draw; + void _draw_alternatives(); + + Size2i _compute_alternative_tiles_control_size(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + // Global. + void set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + + float get_zoom() const; + void set_transform(float p_zoom, Vector2i p_panning); + + void set_padding(Side p_side, int p_padding); + + // 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; + + void add_control_over_atlas_tiles(Control *p_control, bool scaled = true) { + if (scaled) { + base_tiles_drawing_root->add_child(p_control); + } else { + base_tiles_root_control->add_child(p_control); + } + p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); + }; + + // Right side. + Vector3i get_alternative_tile_at_pos(const Vector2 p_pos) const; + Rect2i get_alternative_tile_rect(const Vector2i p_coords, int p_alternative_tile); + + void add_control_over_alternative_tiles(Control *p_control, bool scaled = true) { + if (scaled) { + alternative_tiles_drawing_root->add_child(p_control); + } else { + alternative_tiles_root_control->add_child(p_control); + } + p_control->set_mouse_filter(Control::MOUSE_FILTER_PASS); + }; + + // Update everything. + void update(); + + TileAtlasView(); +}; + +#endif // TILE_ATLAS_VIEW diff --git a/editor/plugins/tiles/tile_data_editors.cpp b/editor/plugins/tiles/tile_data_editors.cpp new file mode 100644 index 0000000000..d9d0e48fb3 --- /dev/null +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -0,0 +1,2467 @@ +/*************************************************************************/ +/* tile_data_editors.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_data_editors.h" + +#include "tile_set_editor.h" + +#include "core/math/geometry_2d.h" +#include "core/os/keyboard.h" + +#include "editor/editor_properties.h" +#include "editor/editor_scale.h" + +void TileDataEditor::_call_tile_set_changed() { + _tile_set_changed(); +} + +TileData *TileDataEditor::_get_tile_data(TileMapCell p_cell) { + ERR_FAIL_COND_V(!tile_set.is_valid(), nullptr); + ERR_FAIL_COND_V(!tile_set->has_source(p_cell.source_id), nullptr); + + TileData *td = nullptr; + TileSetSource *source = *tile_set->get_source(p_cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + ERR_FAIL_COND_V(!atlas_source->has_tile(p_cell.get_atlas_coords()), nullptr); + ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_cell.get_atlas_coords(), p_cell.alternative_tile), nullptr); + td = Object::cast_to<TileData>(atlas_source->get_tile_data(p_cell.get_atlas_coords(), p_cell.alternative_tile)); + } + + return td; +} + +void TileDataEditor::_bind_methods() { + 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 = p_tile_set; + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileDataEditor::_call_tile_set_changed)); + } + _call_tile_set_changed(); +} + +bool DummyObject::_set(const StringName &p_name, const Variant &p_value) { + if (properties.has(p_name)) { + properties[p_name] = p_value; + return true; + } + return false; +} + +bool DummyObject::_get(const StringName &p_name, Variant &r_ret) const { + if (properties.has(p_name)) { + r_ret = properties[p_name]; + return true; + } + return false; +} + +bool DummyObject::has_dummy_property(StringName p_name) { + return properties.has(p_name); +} + +void DummyObject::add_dummy_property(StringName p_name) { + ERR_FAIL_COND(properties.has(p_name)); + properties[p_name] = Variant(); +} + +void DummyObject::remove_dummy_property(StringName p_name) { + ERR_FAIL_COND(!properties.has(p_name)); + properties.erase(p_name); +} + +void DummyObject::clear_dummy_properties() { + properties.clear(); +} + +void GenericTilePolygonEditor::_base_control_draw() { + ERR_FAIL_COND(!tile_set.is_valid()); + + real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + const Ref<Texture2D> handle = get_theme_icon("EditorPathSharpHandle", "EditorIcons"); + const Ref<Texture2D> add_handle = get_theme_icon("EditorHandleAdd", "EditorIcons"); + + Size2 tile_size = tile_set->get_tile_size(); + + Transform2D xform; + xform.set_origin(base_control->get_size() / 2 + panning); + xform.set_scale(Vector2(editor_zoom_widget->get_zoom(), editor_zoom_widget->get_zoom())); + base_control->draw_set_transform_matrix(xform); + + // Draw the tile shape filled. + tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), Color(1.0, 1.0, 1.0, 0.3), true); + + // Draw the background. + if (background_texture.is_valid()) { + base_control->draw_texture_rect_region(background_texture, Rect2(-background_region.size / 2 - background_offset, background_region.size), background_region, background_modulate, background_transpose); + } + + // Draw the polygons. + for (unsigned int i = 0; i < polygons.size(); i++) { + const Vector<Vector2> &polygon = polygons[i]; + Color color = polygon_color; + if (!in_creation_polygon.is_empty()) { + color = color.darkened(0.3); + } + color.a = 0.5; + Vector<Color> v_color; + v_color.push_back(color); + base_control->draw_polygon(polygon, v_color); + + color.a = 0.7; + for (int j = 0; j < polygon.size(); j++) { + base_control->draw_line(polygon[j], polygon[(j + 1) % polygon.size()], color); + } + } + + // Draw the polygon in creation. + if (!in_creation_polygon.is_empty()) { + for (int i = 0; i < in_creation_polygon.size() - 1; i++) { + base_control->draw_line(in_creation_polygon[i], in_creation_polygon[i + 1], Color(1.0, 1.0, 1.0)); + } + } + + Point2 in_creation_point = xform.affine_inverse().xform(base_control->get_local_mouse_position()); + float in_creation_distance = grab_threshold * 2.0; + _snap_to_tile_shape(in_creation_point, in_creation_distance, grab_threshold / editor_zoom_widget->get_zoom()); + if (button_pixel_snap->is_pressed()) { + _snap_to_half_pixel(in_creation_point); + } + + if (drag_type == DRAG_TYPE_CREATE_POINT && !in_creation_polygon.is_empty()) { + base_control->draw_line(in_creation_polygon[in_creation_polygon.size() - 1], in_creation_point, Color(1.0, 1.0, 1.0)); + } + + // Draw the handles. + int tinted_polygon_index = -1; + int tinted_point_index = -1; + if (drag_type == DRAG_TYPE_DRAG_POINT) { + tinted_polygon_index = drag_polygon_index; + tinted_point_index = drag_point_index; + } else if (hovered_point_index >= 0) { + tinted_polygon_index = hovered_polygon_index; + tinted_point_index = hovered_point_index; + } + + base_control->draw_set_transform_matrix(Transform2D()); + if (!in_creation_polygon.is_empty()) { + for (int i = 0; i < in_creation_polygon.size(); i++) { + base_control->draw_texture(handle, xform.xform(in_creation_polygon[i]) - handle->get_size() / 2); + } + } else { + for (int i = 0; i < (int)polygons.size(); i++) { + const Vector<Vector2> &polygon = polygons[i]; + for (int j = 0; j < polygon.size(); j++) { + const Color modulate = (tinted_polygon_index == i && tinted_point_index == j) ? Color(0.5, 1, 2) : Color(1, 1, 1); + base_control->draw_texture(handle, xform.xform(polygon[j]) - handle->get_size() / 2, modulate); + } + } + } + + // Draw the text on top of the selected point. + if (tinted_polygon_index >= 0) { + Ref<Font> font = get_theme_font("font", "Label"); + int font_size = get_theme_font_size("font_size", "Label"); + String text = multiple_polygon_mode ? vformat("%d:%d", tinted_polygon_index, tinted_point_index) : vformat("%d", tinted_point_index); + Size2 text_size = font->get_string_size(text, font_size); + base_control->draw_string(font, xform.xform(polygons[tinted_polygon_index][tinted_point_index]) - text_size * 0.5, text, HALIGN_LEFT, -1, font_size, Color(1.0, 1.0, 1.0, 0.5)); + } + + if (drag_type == DRAG_TYPE_CREATE_POINT) { + base_control->draw_texture(handle, xform.xform(in_creation_point) - handle->get_size() / 2, Color(0.5, 1, 2)); + } + + // Draw the point creation preview in edit mode. + if (hovered_segment_index >= 0) { + base_control->draw_texture(add_handle, xform.xform(hovered_segment_point) - add_handle->get_size() / 2); + } + + // Draw the tile shape line. + base_control->draw_set_transform_matrix(xform); + tile_set->draw_tile_shape(base_control, Rect2(-tile_size / 2, tile_size), grid_color, false); + base_control->draw_set_transform_matrix(Transform2D()); +} + +void GenericTilePolygonEditor::_center_view() { + panning = Vector2(); + base_control->update(); + button_center_view->set_disabled(true); +} + +void GenericTilePolygonEditor::_zoom_changed() { + base_control->update(); +} + +void GenericTilePolygonEditor::_advanced_menu_item_pressed(int p_item_pressed) { + switch (p_item_pressed) { + 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()); + 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"); + for (unsigned int i = 0; i < polygons.size(); i++) { + undo_redo->add_undo_method(this, "add_polygon", polygons[i]); + } + undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); + undo_redo->commit_action(true); + break; + 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"); + undo_redo->add_do_method(this, "emit_signal", "polygons_changed"); + undo_redo->add_undo_method(this, "clear_polygons"); + for (unsigned int i = 0; i < polygons.size(); i++) { + undo_redo->add_undo_method(this, "add_polygon", polygons[i]); + } + undo_redo->add_undo_method(base_control, "update"); + undo_redo->add_undo_method(this, "emit_signal", "polygons_changed"); + undo_redo->commit_action(true); + break; + default: + break; + } +} + +void GenericTilePolygonEditor::_grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index) { + const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + r_polygon_index = -1; + r_point_index = -1; + float closest_distance = grab_threshold + 1.0; + for (unsigned int i = 0; i < polygons.size(); i++) { + const Vector<Vector2> &polygon = polygons[i]; + for (int j = 0; j < polygon.size(); j++) { + float distance = p_pos.distance_to(p_polygon_xform.xform(polygon[j])); + if (distance < grab_threshold && distance < closest_distance) { + r_polygon_index = i; + r_point_index = j; + closest_distance = distance; + } + } + } +} + +void GenericTilePolygonEditor::_grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point) { + const real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + + Point2 point = p_polygon_xform.affine_inverse().xform(p_pos); + r_polygon_index = -1; + r_segment_index = -1; + float closest_distance = grab_threshold * 2.0; + for (unsigned int i = 0; i < polygons.size(); i++) { + const Vector<Vector2> &polygon = polygons[i]; + for (int j = 0; j < polygon.size(); j++) { + Vector2 segment[2] = { polygon[j], polygon[(j + 1) % polygon.size()] }; + Vector2 closest_point = Geometry2D::get_closest_point_to_segment(point, segment); + float distance = closest_point.distance_to(point); + if (distance < grab_threshold / editor_zoom_widget->get_zoom() && distance < closest_distance) { + r_polygon_index = i; + r_segment_index = j; + r_point = closest_point; + closest_distance = distance; + } + } + } +} + +void GenericTilePolygonEditor::_snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist) { + ERR_FAIL_COND(!tile_set.is_valid()); + + Vector<Point2> polygon = tile_set->get_tile_shape_polygon(); + Point2 snapped_point = r_point; + + // Snap to polygon vertices. + bool snapped = false; + for (int i = 0; i < polygon.size(); i++) { + float distance = r_point.distance_to(polygon[i]); + if (distance < p_snap_dist && distance < r_current_snapped_dist) { + snapped_point = polygon[i]; + r_current_snapped_dist = distance; + snapped = true; + } + } + + // Snap to edges if we did not snap to vertices. + if (!snapped) { + for (int i = 0; i < polygon.size(); i++) { + Point2 segment[2] = { polygon[i], polygon[(i + 1) % polygon.size()] }; + Point2 point = Geometry2D::get_closest_point_to_segment(r_point, segment); + float distance = r_point.distance_to(point); + if (distance < p_snap_dist && distance < r_current_snapped_dist) { + snapped_point = point; + r_current_snapped_dist = distance; + } + } + } + + r_point = snapped_point; +} + +void GenericTilePolygonEditor::_snap_to_half_pixel(Point2 &r_point) { + r_point = (r_point * 2).round() / 2.0; +} + +void GenericTilePolygonEditor::_base_control_gui_input(Ref<InputEvent> p_event) { + real_t grab_threshold = EDITOR_GET("editors/poly_editor/point_grab_radius"); + + hovered_polygon_index = -1; + hovered_point_index = -1; + hovered_segment_index = -1; + hovered_segment_point = Vector2(); + + Transform2D xform; + xform.set_origin(base_control->get_size() / 2 + panning); + xform.set_scale(Vector2(editor_zoom_widget->get_zoom(), editor_zoom_widget->get_zoom())); + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (drag_type == DRAG_TYPE_DRAG_POINT) { + ERR_FAIL_INDEX(drag_polygon_index, (int)polygons.size()); + ERR_FAIL_INDEX(drag_point_index, polygons[drag_polygon_index].size()); + Point2 point = xform.affine_inverse().xform(mm->get_position()); + float distance = grab_threshold * 2.0; + _snap_to_tile_shape(point, distance, grab_threshold / editor_zoom_widget->get_zoom()); + if (button_pixel_snap->is_pressed()) { + _snap_to_half_pixel(point); + } + polygons[drag_polygon_index].write[drag_point_index] = point; + } else if (drag_type == DRAG_TYPE_PAN) { + panning += mm->get_position() - drag_last_pos; + drag_last_pos = mm->get_position(); + button_center_view->set_disabled(panning.is_equal_approx(Vector2())); + } else { + // Update hovered point. + _grab_polygon_point(mm->get_position(), xform, hovered_polygon_index, hovered_point_index); + + // If we have no hovered point, check if we hover a segment. + if (hovered_point_index == -1) { + _grab_polygon_segment_point(mm->get_position(), xform, hovered_polygon_index, hovered_segment_index, hovered_segment_point); + } + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP && mb->is_ctrl_pressed()) { + editor_zoom_widget->set_zoom_by_increments(1); + _zoom_changed(); + accept_event(); + } else if (mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN && mb->is_ctrl_pressed()) { + editor_zoom_widget->set_zoom_by_increments(-1); + _zoom_changed(); + accept_event(); + } else if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + if (tools_button_group->get_pressed_button() != button_create) { + in_creation_polygon.clear(); + } + if (tools_button_group->get_pressed_button() == button_create) { + // Create points. + if (in_creation_polygon.size() >= 3 && mb->get_position().distance_to(xform.xform(in_creation_polygon[0])) < grab_threshold) { + // Closes and create polygon. + if (!multiple_polygon_mode) { + clear_polygons(); + } + int added = add_polygon(in_creation_polygon); + + in_creation_polygon.clear(); + button_edit->set_pressed(true); + undo_redo->create_action(TTR("Edit Polygons")); + if (!multiple_polygon_mode) { + undo_redo->add_do_method(this, "clear_polygons"); + } + undo_redo->add_do_method(this, "add_polygon", in_creation_polygon); + undo_redo->add_do_method(base_control, "update"); + undo_redo->add_undo_method(this, "remove_polygon", added); + undo_redo->add_undo_method(base_control, "update"); + undo_redo->commit_action(false); + emit_signal("polygons_changed"); + } else { + // Create a new point. + drag_type = DRAG_TYPE_CREATE_POINT; + } + } else if (tools_button_group->get_pressed_button() == button_edit) { + // Edit points. + int closest_polygon; + int closest_point; + _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); + if (closest_polygon >= 0) { + drag_type = DRAG_TYPE_DRAG_POINT; + drag_polygon_index = closest_polygon; + drag_point_index = closest_point; + drag_old_polygon = polygons[drag_polygon_index]; + } else { + // Create a point. + Vector2 point_to_create; + _grab_polygon_segment_point(mb->get_position(), xform, closest_polygon, closest_point, point_to_create); + if (closest_polygon >= 0) { + polygons[closest_polygon].insert(closest_point + 1, point_to_create); + drag_type = DRAG_TYPE_DRAG_POINT; + drag_polygon_index = closest_polygon; + drag_point_index = closest_point + 1; + drag_old_polygon = polygons[closest_polygon]; + } + } + } else if (tools_button_group->get_pressed_button() == button_delete) { + // Remove point. + int closest_polygon; + int closest_point; + _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); + if (closest_polygon >= 0) { + PackedVector2Array old_polygon = polygons[closest_polygon]; + polygons[closest_polygon].remove(closest_point); + undo_redo->create_action(TTR("Edit Polygons")); + if (polygons[closest_polygon].size() < 3) { + remove_polygon(closest_polygon); + undo_redo->add_do_method(this, "remove_polygon", closest_polygon); + undo_redo->add_undo_method(this, "add_polygon", old_polygon, closest_polygon); + } else { + undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]); + undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon); + } + undo_redo->add_do_method(base_control, "update"); + undo_redo->add_undo_method(base_control, "update"); + undo_redo->commit_action(false); + emit_signal("polygons_changed"); + } + } + } else { + if (drag_type == DRAG_TYPE_DRAG_POINT) { + undo_redo->create_action(TTR("Edit Polygons")); + undo_redo->add_do_method(this, "set_polygon", drag_polygon_index, polygons[drag_polygon_index]); + undo_redo->add_do_method(base_control, "update"); + undo_redo->add_undo_method(this, "set_polygon", drag_polygon_index, drag_old_polygon); + undo_redo->add_undo_method(base_control, "update"); + undo_redo->commit_action(false); + emit_signal("polygons_changed"); + } else if (drag_type == DRAG_TYPE_CREATE_POINT) { + Point2 point = xform.affine_inverse().xform(mb->get_position()); + float distance = grab_threshold * 2; + _snap_to_tile_shape(point, distance, grab_threshold / editor_zoom_widget->get_zoom()); + if (button_pixel_snap->is_pressed()) { + _snap_to_half_pixel(point); + } + in_creation_polygon.push_back(point); + } + drag_type = DRAG_TYPE_NONE; + drag_point_index = -1; + } + + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->is_pressed()) { + if (tools_button_group->get_pressed_button() == button_edit) { + // Remove point or pan. + int closest_polygon; + int closest_point; + _grab_polygon_point(mb->get_position(), xform, closest_polygon, closest_point); + if (closest_polygon >= 0) { + PackedVector2Array old_polygon = polygons[closest_polygon]; + polygons[closest_polygon].remove(closest_point); + undo_redo->create_action(TTR("Edit Polygons")); + if (polygons[closest_polygon].size() < 3) { + remove_polygon(closest_polygon); + undo_redo->add_do_method(this, "remove_polygon", closest_polygon); + undo_redo->add_undo_method(this, "add_polygon", old_polygon, closest_polygon); + } else { + undo_redo->add_do_method(this, "set_polygon", closest_polygon, polygons[closest_polygon]); + undo_redo->add_undo_method(this, "set_polygon", closest_polygon, old_polygon); + } + undo_redo->add_do_method(base_control, "update"); + undo_redo->add_undo_method(base_control, "update"); + undo_redo->commit_action(false); + emit_signal("polygons_changed"); + } else { + drag_type = DRAG_TYPE_PAN; + drag_last_pos = mb->get_position(); + } + } else { + drag_type = DRAG_TYPE_PAN; + drag_last_pos = mb->get_position(); + } + } else { + drag_type = DRAG_TYPE_NONE; + } + } else if (mb->get_button_index() == MOUSE_BUTTON_MIDDLE) { + if (mb->is_pressed()) { + drag_type = DRAG_TYPE_PAN; + drag_last_pos = mb->get_position(); + } else { + drag_type = DRAG_TYPE_NONE; + } + } + } + + base_control->update(); +} + +void GenericTilePolygonEditor::set_tile_set(Ref<TileSet> p_tile_set) { + if (tile_set != p_tile_set) { + // Set the default tile shape + clear_polygons(); + if (p_tile_set.is_valid()) { + add_polygon(p_tile_set->get_tile_shape_polygon()); + } + } + tile_set = p_tile_set; +} + +void GenericTilePolygonEditor::set_background(Ref<Texture2D> p_texture, Rect2 p_region, Vector2 p_offset, bool p_flip_h, bool p_flip_v, bool p_transpose, Color p_modulate) { + background_texture = p_texture; + background_region = p_region; + background_offset = p_offset; + background_h_flip = p_flip_h; + background_v_flip = p_flip_v; + background_transpose = p_transpose; + background_modulate = p_modulate; + base_control->update(); +} + +int GenericTilePolygonEditor::get_polygon_count() { + return polygons.size(); +} + +int GenericTilePolygonEditor::add_polygon(Vector<Point2> p_polygon, int p_index) { + ERR_FAIL_COND_V(p_polygon.size() < 3, -1); + ERR_FAIL_COND_V(!multiple_polygon_mode && polygons.size() >= 1, -1); + + if (p_index < 0) { + polygons.push_back(p_polygon); + base_control->update(); + button_edit->set_pressed(true); + return polygons.size() - 1; + } else { + polygons.insert(p_index, p_polygon); + button_edit->set_pressed(true); + base_control->update(); + return p_index; + } +} + +void GenericTilePolygonEditor::remove_polygon(int p_index) { + ERR_FAIL_INDEX(p_index, (int)polygons.size()); + polygons.remove(p_index); + + if (polygons.size() == 0) { + button_create->set_pressed(true); + } + base_control->update(); +} + +void GenericTilePolygonEditor::clear_polygons() { + polygons.clear(); + base_control->update(); +} + +void GenericTilePolygonEditor::set_polygon(int p_polygon_index, Vector<Point2> p_polygon) { + ERR_FAIL_INDEX(p_polygon_index, (int)polygons.size()); + ERR_FAIL_COND(p_polygon.size() < 3); + polygons[p_polygon_index] = p_polygon; + button_edit->set_pressed(true); + base_control->update(); +} + +Vector<Point2> GenericTilePolygonEditor::get_polygon(int p_polygon_index) { + ERR_FAIL_INDEX_V(p_polygon_index, (int)polygons.size(), Vector<Point2>()); + return polygons[p_polygon_index]; +} + +void GenericTilePolygonEditor::set_polygons_color(Color p_color) { + polygon_color = p_color; + base_control->update(); +} + +void GenericTilePolygonEditor::set_multiple_polygon_mode(bool p_multiple_polygon_mode) { + multiple_polygon_mode = p_multiple_polygon_mode; +} + +void GenericTilePolygonEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_READY: + button_create->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveCreate", "EditorIcons")); + button_edit->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveEdit", "EditorIcons")); + button_delete->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CurveDelete", "EditorIcons")); + button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CenterView", "EditorIcons")); + button_pixel_snap->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Snap", "EditorIcons")); + button_advanced_menu->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("GuiTabMenu", "EditorIcons")); + break; + } +} + +void GenericTilePolygonEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_polygon_count"), &GenericTilePolygonEditor::get_polygon_count); + ClassDB::bind_method(D_METHOD("add_polygon", "polygon", "index"), &GenericTilePolygonEditor::add_polygon, DEFVAL(-1)); + ClassDB::bind_method(D_METHOD("remove_polygon", "index"), &GenericTilePolygonEditor::remove_polygon); + ClassDB::bind_method(D_METHOD("clear_polygons"), &GenericTilePolygonEditor::clear_polygons); + ClassDB::bind_method(D_METHOD("set_polygon", "index", "polygon"), &GenericTilePolygonEditor::set_polygon); + ClassDB::bind_method(D_METHOD("get_polygon", "index"), &GenericTilePolygonEditor::set_polygon); + + ADD_SIGNAL(MethodInfo("polygons_changed")); +} + +GenericTilePolygonEditor::GenericTilePolygonEditor() { + toolbar = memnew(HBoxContainer); + add_child(toolbar); + + tools_button_group.instantiate(); + + button_create = memnew(Button); + button_create->set_flat(true); + button_create->set_toggle_mode(true); + button_create->set_button_group(tools_button_group); + button_create->set_pressed(true); + toolbar->add_child(button_create); + + button_edit = memnew(Button); + button_edit->set_flat(true); + button_edit->set_toggle_mode(true); + button_edit->set_button_group(tools_button_group); + toolbar->add_child(button_edit); + + button_delete = memnew(Button); + button_delete->set_flat(true); + button_delete->set_toggle_mode(true); + button_delete->set_button_group(tools_button_group); + toolbar->add_child(button_delete); + + button_advanced_menu = memnew(MenuButton); + button_advanced_menu->set_flat(true); + button_advanced_menu->set_toggle_mode(true); + button_advanced_menu->get_popup()->add_item(TTR("Reset to default tile shape"), RESET_TO_DEFAULT_TILE); + button_advanced_menu->get_popup()->add_item(TTR("Clear"), CLEAR_TILE); + button_advanced_menu->get_popup()->connect("id_pressed", callable_mp(this, &GenericTilePolygonEditor::_advanced_menu_item_pressed)); + toolbar->add_child(button_advanced_menu); + + toolbar->add_child(memnew(VSeparator)); + + button_pixel_snap = memnew(Button); + button_pixel_snap->set_flat(true); + button_pixel_snap->set_toggle_mode(true); + button_pixel_snap->set_pressed(true); + toolbar->add_child(button_pixel_snap); + + Control *root = memnew(Control); + root->set_h_size_flags(Control::SIZE_EXPAND_FILL); + root->set_custom_minimum_size(Size2(0, 200 * EDSCALE)); + root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + add_child(root); + + panel = memnew(Panel); + panel->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + panel->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + root->add_child(panel); + + base_control = memnew(Control); + base_control->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + base_control->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_control->connect("draw", callable_mp(this, &GenericTilePolygonEditor::_base_control_draw)); + base_control->connect("gui_input", callable_mp(this, &GenericTilePolygonEditor::_base_control_gui_input)); + base_control->set_clip_contents(true); + root->add_child(base_control); + + editor_zoom_widget = memnew(EditorZoomWidget); + editor_zoom_widget->set_position(Vector2(5, 5)); + editor_zoom_widget->connect("zoom_changed", callable_mp(this, &GenericTilePolygonEditor::_zoom_changed).unbind(1)); + root->add_child(editor_zoom_widget); + + button_center_view = memnew(Button); + button_center_view->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("CenterView", "EditorIcons")); + button_center_view->set_anchors_and_offsets_preset(Control::PRESET_TOP_RIGHT, Control::PRESET_MODE_MINSIZE, 5); + button_center_view->connect("pressed", callable_mp(this, &GenericTilePolygonEditor::_center_view)); + button_center_view->set_flat(true); + button_center_view->set_disabled(true); + root->add_child(button_center_view); +} + +void TileDataDefaultEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { + ERR_FAIL_COND(!dummy_object); + dummy_object->set(p_property, p_value); +} + +Variant TileDataDefaultEditor::_get_painted_value() { + ERR_FAIL_COND_V(!dummy_object, Variant()); + return dummy_object->get(property); +} + +void TileDataDefaultEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + Variant value = tile_data->get(property); + dummy_object->set(property, value); + if (property_editor) { + property_editor->update_property(); + } +} + +void TileDataDefaultEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + tile_data->set(property, p_value); +} + +Variant TileDataDefaultEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND_V(!tile_data, Variant()); + return tile_data->get(property); +} + +void TileDataDefaultEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { + for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { + Vector2i coords = E->key().get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), E->get()); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/%s", coords.x, coords.y, E->key().alternative_tile, property), p_new_value); + } +} + +void TileDataDefaultEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) { + if (drag_type == DRAG_TYPE_PAINT_RECT) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + + p_canvas_item->draw_set_transform_matrix(p_transform); + + Rect2i rect; + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); + rect = rect.abs(); + + Set<TileMapCell> edited; + for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { + for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + edited.insert(cell); + } + } + } + + for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { + Vector2i coords = E->get().get_atlas_coords(); + p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false); + } + p_canvas_item->draw_set_transform_matrix(Transform2D()); + } +}; + +void TileDataDefaultEditor::forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){ + +}; + +void TileDataDefaultEditor::forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (drag_type == DRAG_TYPE_PAINT) { + Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); + for (int i = 0; i < line.size(); i++) { + Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + if (!drag_modified.has(cell)) { + drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0); + } + _set_value(p_tile_set_atlas_source, coords, 0, drag_painted_value); + } + } + drag_last_pos = mm->get_position(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + if (picker_button->is_pressed()) { + Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + _set_painted_value(p_tile_set_atlas_source, coords, 0); + picker_button->set_pressed(false); + } + } else if (mb->is_ctrl_pressed()) { + drag_type = DRAG_TYPE_PAINT_RECT; + drag_modified.clear(); + drag_painted_value = _get_painted_value(); + drag_start_pos = mb->get_position(); + } else { + drag_type = DRAG_TYPE_PAINT; + drag_modified.clear(); + drag_painted_value = _get_painted_value(); + Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0); + _set_value(p_tile_set_atlas_source, coords, 0, drag_painted_value); + } + drag_last_pos = mb->get_position(); + } + } else { + if (drag_type == DRAG_TYPE_PAINT_RECT) { + Rect2i rect; + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); + rect = rect.abs(); + + drag_modified.clear(); + for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { + for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, 0); + } + } + } + undo_redo->create_action(TTR("Painting Tiles Property")); + _setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value); + undo_redo->commit_action(true); + drag_type = DRAG_TYPE_NONE; + } else if (drag_type == DRAG_TYPE_PAINT) { + undo_redo->create_action(TTR("Painting Tiles Property")); + _setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value); + undo_redo->commit_action(false); + drag_type = DRAG_TYPE_NONE; + } + } + } + } +} + +void TileDataDefaultEditor::forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (drag_type == DRAG_TYPE_PAINT) { + Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position()); + Vector2i coords = Vector2i(tile.x, tile.y); + int alternative_tile = tile.z; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + if (!drag_modified.has(cell)) { + drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, alternative_tile); + } + _set_value(p_tile_set_atlas_source, coords, alternative_tile, drag_painted_value); + } + + drag_last_pos = mm->get_position(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + if (picker_button->is_pressed()) { + Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); + Vector2i coords = Vector2i(tile.x, tile.y); + int alternative_tile = tile.z; + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + _set_painted_value(p_tile_set_atlas_source, coords, alternative_tile); + picker_button->set_pressed(false); + } + } else { + drag_type = DRAG_TYPE_PAINT; + drag_modified.clear(); + drag_painted_value = _get_painted_value(); + + Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); + Vector2i coords = Vector2i(tile.x, tile.y); + int alternative_tile = tile.z; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + drag_modified[cell] = _get_value(p_tile_set_atlas_source, coords, alternative_tile); + _set_value(p_tile_set_atlas_source, coords, alternative_tile, drag_painted_value); + } + drag_last_pos = mb->get_position(); + } + } else { + undo_redo->create_action(TTR("Painting Tiles Property")); + _setup_undo_redo_action(p_tile_set_atlas_source, drag_modified, drag_painted_value); + undo_redo->commit_action(false); + drag_type = DRAG_TYPE_NONE; + } + } + } +} + +void TileDataDefaultEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + bool valid; + Variant value = tile_data->get(property, &valid); + if (!valid) { + return; + } + + if (value.get_type() == Variant::BOOL) { + Ref<Texture2D> texture = (bool)value ? tile_bool_checked : tile_bool_unchecked; + int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3; + Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size))); + p_canvas_item->draw_texture_rect(texture, rect); + } else if (value.get_type() == Variant::COLOR) { + int size = MIN(tile_set->get_tile_size().x, tile_set->get_tile_size().y) / 3; + Rect2 rect = p_transform.xform(Rect2(Vector2(-size / 2, -size / 2), Vector2(size, size))); + p_canvas_item->draw_rect(rect, value); + } else { + Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); + String text; + switch (value.get_type()) { + case Variant::INT: + text = vformat("%d", value); + break; + case Variant::FLOAT: + text = vformat("%.2f", value); + break; + case Variant::STRING: + case Variant::STRING_NAME: + text = value; + break; + default: + return; + break; + } + + Color color = Color(1, 1, 1); + if (p_selected) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + selection_color.set_v(0.9); + color = selection_color; + } else if (is_visible_in_tree()) { + Variant painted_value = _get_painted_value(); + bool equal = (painted_value.get_type() == Variant::FLOAT && value.get_type() == Variant::FLOAT) ? Math::is_equal_approx(float(painted_value), float(value)) : painted_value == value; + if (equal) { + color = Color(0.7, 0.7, 0.7); + } + } + + Vector2 string_size = font->get_string_size(text); + p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1)); + } +} + +void TileDataDefaultEditor::setup_property_editor(Variant::Type p_type, String p_property, String p_label, Variant p_default_value) { + ERR_FAIL_COND_MSG(!property.is_empty(), "Cannot setup TileDataDefaultEditor twice"); + property = p_property; + + // Update everything. + if (property_editor) { + property_editor->queue_delete(); + } + + // Update the dummy object. + dummy_object->add_dummy_property(p_property); + + // Get the default value for the type. + if (p_default_value == Variant()) { + Callable::CallError error; + Variant painted_value; + Variant::construct(p_type, painted_value, nullptr, 0, error); + dummy_object->set(p_property, painted_value); + } else { + dummy_object->set(p_property, p_default_value); + } + + // Create and setup the property editor. + property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, p_type, p_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + property_editor->set_object_and_property(dummy_object, p_property); + if (p_label.is_empty()) { + property_editor->set_label(p_property); + } else { + property_editor->set_label(p_label); + } + property_editor->connect("property_changed", callable_mp(this, &TileDataDefaultEditor::_property_value_changed).unbind(1)); + property_editor->update_property(); + add_child(property_editor); +} + +void TileDataDefaultEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); + tile_bool_checked = get_theme_icon("TileChecked", "EditorIcons"); + tile_bool_unchecked = get_theme_icon("TileUnchecked", "EditorIcons"); + break; + default: + break; + } +} + +TileDataDefaultEditor::TileDataDefaultEditor() { + label = memnew(Label); + label->set_text("Painting:"); + add_child(label); + + toolbar->add_child(memnew(VSeparator)); + + picker_button = memnew(Button); + picker_button->set_flat(true); + picker_button->set_toggle_mode(true); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + toolbar->add_child(picker_button); +} + +TileDataDefaultEditor::~TileDataDefaultEditor() { + toolbar->queue_delete(); + memdelete(dummy_object); +} + +void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + Vector2i tile_set_tile_size = tile_set->get_tile_size(); + Rect2i rect = Rect2i(-tile_set_tile_size / 2, tile_set_tile_size); + Color color = Color(1.0, 0.0, 0.0); + if (p_selected) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + color = selection_color; + } + tile_set->draw_tile_shape(p_canvas_item, p_transform.xform(rect), color); +} + +void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + bool valid; + Variant value = tile_data->get(property, &valid); + if (!valid) { + return; + } + ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I && value.get_type() != Variant::VECTOR2); + + Color color = Color(1.0, 1.0, 1.0); + if (p_selected) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + color = selection_color; + } + Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons"); + p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(value)) - position_icon->get_size() / 2, color); +} + +void TileDataYSortEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + Color color = Color(1.0, 1.0, 1.0); + if (p_selected) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + color = selection_color; + } + Ref<Texture2D> position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons"); + p_canvas_item->draw_texture(position_icon, p_transform.xform(Vector2(0, tile_data->get_y_sort_origin())) - position_icon->get_size() / 2, color); +} + +void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + Color color = grid_color.darkened(0.2); + if (p_selected) { + color = selection_color.darkened(0.2); + } + color.a *= 0.5; + + Vector<Color> debug_occlusion_color; + debug_occlusion_color.push_back(color); + + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + Ref<OccluderPolygon2D> occluder = tile_data->get_occluder(occlusion_layer); + if (occluder.is_valid() && occluder->get_polygon().size() >= 3) { + p_canvas_item->draw_polygon(Variant(occluder->get_polygon()), debug_occlusion_color); + } + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} + +Variant TileDataOcclusionShapeEditor::_get_painted_value() { + Ref<OccluderPolygon2D> occluder_polygon; + occluder_polygon.instantiate(); + if (polygon_editor->get_polygon_count() >= 1) { + occluder_polygon->set_polygon(polygon_editor->get_polygon(0)); + } + return occluder_polygon; +} + +void TileDataOcclusionShapeEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + + Ref<OccluderPolygon2D> occluder_polygon = tile_data->get_occluder(occlusion_layer); + polygon_editor->clear_polygons(); + if (occluder_polygon.is_valid()) { + polygon_editor->add_polygon(occluder_polygon->get_polygon()); + } + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +void TileDataOcclusionShapeEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + Ref<OccluderPolygon2D> occluder_polygon = p_value; + tile_data->set_occluder(occlusion_layer, occluder_polygon); + + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +Variant TileDataOcclusionShapeEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND_V(!tile_data, Variant()); + return tile_data->get_occluder(occlusion_layer); +} + +void TileDataOcclusionShapeEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { + for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { + Vector2i coords = E->key().get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), E->get()); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/occlusion_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, occlusion_layer), p_new_value); + } +} + +void TileDataOcclusionShapeEditor::_tile_set_changed() { + polygon_editor->set_tile_set(tile_set); +} + +void TileDataOcclusionShapeEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); + break; + default: + break; + } +} + +TileDataOcclusionShapeEditor::TileDataOcclusionShapeEditor() { + polygon_editor = memnew(GenericTilePolygonEditor); + add_child(polygon_editor); +} + +void TileDataCollisionEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { + dummy_object->set(p_property, p_value); +} + +void TileDataCollisionEditor::_polygons_changed() { + // Update the dummy object properties and their editors. + for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { + StringName one_way_property = vformat("polygon_%d_one_way", i); + StringName one_way_margin_property = vformat("polygon_%d_one_way_margin", i); + + if (!dummy_object->has_dummy_property(one_way_property)) { + dummy_object->add_dummy_property(one_way_property); + dummy_object->set(one_way_property, false); + } + + if (!dummy_object->has_dummy_property(one_way_margin_property)) { + dummy_object->add_dummy_property(one_way_margin_property); + dummy_object->set(one_way_margin_property, 1.0); + } + + if (!property_editors.has(one_way_property)) { + EditorProperty *one_way_property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::BOOL, one_way_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + one_way_property_editor->set_object_and_property(dummy_object, one_way_property); + one_way_property_editor->set_label(one_way_property); + one_way_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + one_way_property_editor->update_property(); + add_child(one_way_property_editor); + property_editors[one_way_property] = one_way_property_editor; + } + + if (!property_editors.has(one_way_margin_property)) { + EditorProperty *one_way_margin_property_editor = EditorInspectorDefaultPlugin::get_editor_for_property(dummy_object, Variant::FLOAT, one_way_margin_property, PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); + one_way_margin_property_editor->set_object_and_property(dummy_object, one_way_margin_property); + one_way_margin_property_editor->set_label(one_way_margin_property); + one_way_margin_property_editor->connect("property_changed", callable_mp(this, &TileDataCollisionEditor::_property_value_changed).unbind(1)); + one_way_margin_property_editor->update_property(); + add_child(one_way_margin_property_editor); + property_editors[one_way_margin_property] = one_way_margin_property_editor; + } + } + + // Remove uneeded properties and their editors. + for (int i = polygon_editor->get_polygon_count(); dummy_object->has_dummy_property(vformat("polygon_%d_one_way", i)); i++) { + dummy_object->remove_dummy_property(vformat("polygon_%d_one_way", i)); + } + for (int i = polygon_editor->get_polygon_count(); dummy_object->has_dummy_property(vformat("polygon_%d_one_way_margin", i)); i++) { + dummy_object->remove_dummy_property(vformat("polygon_%d_one_way_margin", i)); + } + for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way", i)); i++) { + property_editors[vformat("polygon_%d_one_way", i)]->queue_delete(); + property_editors.erase(vformat("polygon_%d_one_way", i)); + } + for (int i = polygon_editor->get_polygon_count(); property_editors.has(vformat("polygon_%d_one_way_margin", i)); i++) { + property_editors[vformat("polygon_%d_one_way_margin", i)]->queue_delete(); + property_editors.erase(vformat("polygon_%d_one_way_margin", i)); + } +} + +Variant TileDataCollisionEditor::_get_painted_value() { + 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); + } + + return array; +} + +void TileDataCollisionEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + + polygon_editor->clear_polygons(); + for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { + Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i); + if (polygon.size() >= 3) { + polygon_editor->add_polygon(polygon); + } + } + + _polygons_changed(); + 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(); + } + + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +void TileDataCollisionEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + + Array array = p_value; + 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"]); + } + + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +Variant TileDataCollisionEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND_V(!tile_data, Variant()); + + 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); + } + return array; +} + +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(); + + 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_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"]); + } + } +} + +void TileDataCollisionEditor::_tile_set_changed() { + polygon_editor->set_tile_set(tile_set); + _polygons_changed(); +} + +void TileDataCollisionEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + polygon_editor->set_polygons_color(get_tree()->get_debug_collisions_color()); + break; + default: + break; + } +} + +TileDataCollisionEditor::TileDataCollisionEditor() { + polygon_editor = memnew(GenericTilePolygonEditor); + polygon_editor->set_multiple_polygon_mode(true); + polygon_editor->connect("polygons_changed", callable_mp(this, &TileDataCollisionEditor::_polygons_changed)); + add_child(polygon_editor); + + _polygons_changed(); +} + +TileDataCollisionEditor::~TileDataCollisionEditor() { + memdelete(dummy_object); +} + +void TileDataCollisionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + // Draw all shapes. + Vector<Color> color; + if (p_selected) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + selection_color.a = 0.7; + color.push_back(selection_color); + } else { + Color debug_collision_color = p_canvas_item->get_tree()->get_debug_collisions_color(); + color.push_back(debug_collision_color); + } + + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + for (int i = 0; i < tile_data->get_collision_polygons_count(physics_layer); i++) { + Vector<Vector2> polygon = tile_data->get_collision_polygon_points(physics_layer, i); + if (polygon.size() >= 3) { + p_canvas_item->draw_polygon(polygon, color); + } + } + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} + +void TileDataTerrainsEditor::_update_terrain_selector() { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Update the terrain set selector. + Vector<String> options; + options.push_back(String(TTR("No terrains")) + String(":-1")); + for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) { + options.push_back(vformat("Terrain Set %d", i)); + } + terrain_set_property_editor->setup(options); + terrain_set_property_editor->update_property(); + + // Update the terrain selector. + int terrain_set = int(dummy_object->get("terrain_set")); + if (terrain_set == -1) { + terrain_property_editor->hide(); + } else { + options.clear(); + Vector<Vector<Ref<Texture2D>>> icons = tile_set->generate_terrains_icons(Size2(16, 16) * EDSCALE); + options.push_back(String(TTR("No terrain")) + String(":-1")); + for (int i = 0; i < tile_set->get_terrains_count(terrain_set); i++) { + String name = tile_set->get_terrain_name(terrain_set, i); + if (name.is_empty()) { + options.push_back(vformat("Terrain %d", i)); + } else { + options.push_back(name); + } + } + terrain_property_editor->setup(options); + terrain_property_editor->update_property(); + + // Kind of a hack to set icons. + // We could provide a way to modify that in the EditorProperty. + OptionButton *option_button = Object::cast_to<OptionButton>(terrain_property_editor->get_child(0)); + for (int terrain = 0; terrain < tile_set->get_terrains_count(terrain_set); terrain++) { + option_button->set_item_icon(terrain + 1, icons[terrain_set][terrain]); + } + terrain_property_editor->show(); + } +} + +void TileDataTerrainsEditor::_property_value_changed(StringName p_property, Variant p_value, StringName p_field) { + Variant old_value = dummy_object->get(p_property); + dummy_object->set(p_property, p_value); + if (p_property == "terrain_set") { + if (p_value != old_value) { + dummy_object->set("terrain", -1); + } + _update_terrain_selector(); + } + emit_signal("needs_redraw"); +} + +void TileDataTerrainsEditor::_tile_set_changed() { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Fix if wrong values are selected. + if (int(dummy_object->get("terrain_set")) > tile_set->get_terrain_sets_count()) { + dummy_object->set("terrain_set", -1); + } + int terrain_set = int(dummy_object->get("terrain")); + if (terrain_set >= 0) { + if (int(dummy_object->get("terrain")) > tile_set->get_terrains_count(terrain_set)) { + dummy_object->set("terrain", -1); + } + } + + _update_terrain_selector(); +} + +void TileDataTerrainsEditor::forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Draw the hovered terrain bit, or the whole tile if it has the wrong terrain set. + Vector2i hovered_coords = TileSetSource::INVALID_ATLAS_COORDS; + if (drag_type == DRAG_TYPE_NONE) { + Vector2i mouse_pos = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()); + hovered_coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_pos); + hovered_coords = p_tile_set_atlas_source->get_tile_at_coords(hovered_coords); + if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, 0)); + int terrain_set = tile_data->get_terrain_set(); + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(hovered_coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, 0); + + if (terrain_set >= 0 && terrain_set == int(dummy_object->get("terrain_set"))) { + // Draw hovered bit. + Transform2D xform; + xform.set_origin(position); + + Vector<Color> color; + color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { + p_canvas_item->draw_set_transform_matrix(p_transform * xform); + p_canvas_item->draw_polygon(polygon, color); + } + } + } + } else { + // Draw hovered tile. + Vector2i tile_size = tile_set->get_tile_size(); + Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size)); + tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true); + } + } + } + + // Dim terrains with wrong terrain set. + Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); + for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i coords = p_tile_set_atlas_source->get_tile_id(i); + if (coords != hovered_coords) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { + // Dimming + p_canvas_item->draw_set_transform_matrix(p_transform); + Rect2i rect = p_tile_set_atlas_source->get_tile_texture_region(coords); + p_canvas_item->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.3)); + + // Text + p_canvas_item->draw_set_transform_matrix(Transform2D()); + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + + Color color = Color(1, 1, 1); + String text; + if (tile_data->get_terrain_set() >= 0) { + text = vformat("%d", tile_data->get_terrain_set()); + } else { + text = "-"; + } + Vector2 string_size = font->get_string_size(text); + p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1)); + } + } + } + p_canvas_item->draw_set_transform_matrix(Transform2D()); + + if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) { + // Draw selection rectangle. + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + + p_canvas_item->draw_set_transform_matrix(p_transform); + + Rect2i rect; + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); + rect = rect.abs(); + + Set<TileMapCell> edited; + for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { + for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + edited.insert(cell); + } + } + } + + for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { + Vector2i coords = E->get().get_atlas_coords(); + p_canvas_item->draw_rect(p_tile_set_atlas_source->get_tile_texture_region(coords), selection_color, false); + } + p_canvas_item->draw_set_transform_matrix(Transform2D()); + } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) { + // Highlight selected peering bits. + Dictionary painted = Dictionary(drag_painted_value); + int terrain_set = int(painted["terrain_set"]); + + Rect2i rect; + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()))); + rect = rect.abs(); + + Set<TileMapCell> edited; + for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { + for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + if (tile_data->get_terrain_set() == terrain_set) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + edited.insert(cell); + } + } + } + } + + Vector2 end = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()); + Vector<Point2> mouse_pos_rect_polygon; + mouse_pos_rect_polygon.push_back(drag_start_pos); + mouse_pos_rect_polygon.push_back(Vector2(end.x, drag_start_pos.y)); + mouse_pos_rect_polygon.push_back(end); + mouse_pos_rect_polygon.push_back(Vector2(drag_start_pos.x, end.y)); + + Vector<Color> color; + color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + + p_canvas_item->draw_set_transform_matrix(p_transform); + + for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { + Vector2i coords = E->get().get_atlas_coords(); + + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + for (int j = 0; j < polygon.size(); j++) { + polygon.write[j] += position; + } + if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) { + // Draw bit. + p_canvas_item->draw_polygon(polygon, color); + } + } + } + } + + p_canvas_item->draw_set_transform_matrix(Transform2D()); + } +} + +void TileDataTerrainsEditor::forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Draw the hovered terrain bit, or the whole tile if it has the wrong terrain set. + Vector2i hovered_coords = TileSetSource::INVALID_ATLAS_COORDS; + int hovered_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; + if (drag_type == DRAG_TYPE_NONE) { + Vector2i mouse_pos = p_transform.affine_inverse().xform(p_canvas_item->get_local_mouse_position()); + Vector3i hovered = p_tile_atlas_view->get_alternative_tile_at_pos(mouse_pos); + hovered_coords = Vector2i(hovered.x, hovered.y); + hovered_alternative = hovered.z; + if (hovered_coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(hovered_coords, hovered_alternative)); + int terrain_set = tile_data->get_terrain_set(); + Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(hovered_coords, hovered_alternative); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(hovered_coords, hovered_alternative); + + if (terrain_set == int(dummy_object->get("terrain_set"))) { + // Draw hovered bit. + Transform2D xform; + xform.set_origin(position); + + Vector<Color> color; + color.push_back(Color(1.0, 1.0, 1.0, 0.5)); + + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(xform.affine_inverse().xform(mouse_pos), polygon)) { + p_canvas_item->draw_set_transform_matrix(p_transform * xform); + p_canvas_item->draw_polygon(polygon, color); + } + } + } + } else { + // Draw hovered tile. + Vector2i tile_size = tile_set->get_tile_size(); + Rect2i rect = p_transform.xform(Rect2i(position - tile_size / 2, tile_size)); + tile_set->draw_tile_shape(p_canvas_item, rect, Color(1.0, 1.0, 1.0, 0.5), true); + } + } + } + + // Dim terrains with wrong terrain set. + Ref<Font> font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); + for (int i = 0; i < p_tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i coords = p_tile_set_atlas_source->get_tile_id(i); + for (int j = 1; j < p_tile_set_atlas_source->get_alternative_tiles_count(coords); j++) { + int alternative_tile = p_tile_set_atlas_source->get_alternative_tile_id(coords, j); + if (coords != hovered_coords || alternative_tile != hovered_alternative) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + if (tile_data->get_terrain_set() != int(dummy_object->get("terrain_set"))) { + // Dimming + p_canvas_item->draw_set_transform_matrix(p_transform); + Rect2i rect = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + p_canvas_item->draw_rect(rect, Color(0.0, 0.0, 0.0, 0.3)); + + // Text + p_canvas_item->draw_set_transform_matrix(Transform2D()); + Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + + Color color = Color(1, 1, 1); + String text; + if (tile_data->get_terrain_set() >= 0) { + text = vformat("%d", tile_data->get_terrain_set()); + } else { + text = "-"; + } + Vector2 string_size = font->get_string_size(text); + p_canvas_item->draw_string(font, p_transform.xform(position) + Vector2i(-string_size.x / 2, string_size.y / 2), text, HALIGN_CENTER, string_size.x, -1, color, 1, Color(0, 0, 0, 1)); + } + } + } + } + + p_canvas_item->draw_set_transform_matrix(Transform2D()); +} + +void TileDataTerrainsEditor::forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { + Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); + for (int i = 0; i < line.size(); i++) { + Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + int terrain_set = drag_painted_value; + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + + // Save the old terrain_set and terrains bits. + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + if (!drag_modified.has(cell)) { + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + } + + // Set the terrain_set. + tile_data->set_terrain_set(terrain_set); + } + } + drag_last_pos = mm->get_position(); + } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { + int terrain_set = Dictionary(drag_painted_value)["terrain_set"]; + int terrain = Dictionary(drag_painted_value)["terrain"]; + Vector<Vector2i> line = Geometry2D::bresenham_line(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_pos), p_tile_atlas_view->get_atlas_tile_coords_at_pos(mm->get_position())); + for (int i = 0; i < line.size(); i++) { + Vector2i coords = p_tile_set_atlas_source->get_tile_at_coords(line[i]); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + if (tile_data->get_terrain_set() == terrain_set) { + // Save the old terrain_set and terrains bits. + if (!drag_modified.has(cell)) { + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + } + + // Set the terrains bits. + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit); + if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { + tile_data->set_peering_bit_terrain(bit, terrain); + } + } + } + } + } + } + drag_last_pos = mm->get_position(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + if (picker_button->is_pressed()) { + Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + int terrain_set = tile_data->get_terrain_set(); + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + dummy_object->set("terrain_set", terrain_set); + dummy_object->set("terrain", -1); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit)); + } + } + } + terrain_set_property_editor->update_property(); + _update_terrain_selector(); + picker_button->set_pressed(false); + } + } else { + Vector2i coords = p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position()); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + TileData *tile_data = nullptr; + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + } + int terrain_set = int(dummy_object->get("terrain_set")); + int terrain = int(dummy_object->get("terrain")); + if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { + if (mb->is_ctrl_pressed()) { + // Paint terrain set with rect. + drag_type = DRAG_TYPE_PAINT_TERRAIN_SET_RECT; + drag_modified.clear(); + drag_painted_value = terrain_set; + drag_start_pos = mb->get_position(); + } else { + // Paint terrain set. + drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; + drag_modified.clear(); + drag_painted_value = terrain_set; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + + // Save the old terrain_set and terrains bits. + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + + // Set the terrain_set. + tile_data->set_terrain_set(terrain_set); + } + drag_last_pos = mb->get_position(); + } + } else if (tile_data && tile_data->get_terrain_set() == terrain_set) { + if (mb->is_ctrl_pressed()) { + // Paint terrain set with rect. + drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS_RECT; + drag_modified.clear(); + Dictionary painted_dict; + painted_dict["terrain_set"] = terrain_set; + painted_dict["terrain"] = terrain; + drag_painted_value = painted_dict; + drag_start_pos = mb->get_position(); + } else { + // Paint terrain bits. + drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; + drag_modified.clear(); + Dictionary painted_dict; + painted_dict["terrain_set"] = terrain_set; + painted_dict["terrain"] = terrain; + drag_painted_value = painted_dict; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + + // Save the old terrain_set and terrains bits. + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + + // Set the terrain bit. + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + tile_data->set_peering_bit_terrain(bit, terrain); + } + } + } + } + drag_last_pos = mb->get_position(); + } + } + } + } else { + if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET_RECT) { + Rect2i rect; + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); + rect = rect.abs(); + + Set<TileMapCell> edited; + for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { + for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + edited.insert(cell); + } + } + } + undo_redo->create_action(TTR("Painting Terrain Set")); + for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { + Vector2i coords = E->get().get_atlas_coords(); + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), tile_data->get_terrain_set()); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrain_set", coords.x, coords.y, E->get().alternative_tile), drag_painted_value); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit)); + } + } + } + undo_redo->commit_action(true); + 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"]); + 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->commit_action(false); + drag_type = DRAG_TYPE_NONE; + } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { + Dictionary painted = Dictionary(drag_painted_value); + 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(); + Array array = dict["terrain_peering_bits"]; + for (int i = 0; i < array.size(); i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); + } + if (tile_set->is_valid_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->commit_action(false); + drag_type = DRAG_TYPE_NONE; + } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS_RECT) { + Dictionary painted = Dictionary(drag_painted_value); + int terrain_set = int(painted["terrain_set"]); + int terrain = int(painted["terrain"]); + + Rect2i rect; + rect.set_position(p_tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_pos)); + rect.set_end(p_tile_atlas_view->get_atlas_tile_coords_at_pos(mb->get_position())); + rect = rect.abs(); + + Set<TileMapCell> edited; + for (int x = rect.get_position().x; x <= rect.get_end().x; x++) { + for (int y = rect.get_position().y; y <= rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + coords = p_tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + if (tile_data->get_terrain_set() == terrain_set) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + edited.insert(cell); + } + } + } + } + + Vector<Point2> mouse_pos_rect_polygon; + mouse_pos_rect_polygon.push_back(drag_start_pos); + mouse_pos_rect_polygon.push_back(Vector2(mb->get_position().x, drag_start_pos.y)); + mouse_pos_rect_polygon.push_back(mb->get_position()); + mouse_pos_rect_polygon.push_back(Vector2(drag_start_pos.x, mb->get_position().y)); + + undo_redo->create_action(TTR("Painting Terrain")); + for (Set<TileMapCell>::Element *E = edited.front(); E; E = E->next()) { + Vector2i coords = E->get().get_atlas_coords(); + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, 0)); + + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Rect2i texture_region = p_tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + for (int j = 0; j < polygon.size(); j++) { + polygon.write[j] += position; + } + if (!Geometry2D::intersect_polygons(polygon, mouse_pos_rect_polygon).is_empty()) { + // Draw bit. + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), terrain); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->get().alternative_tile), tile_data->get_peering_bit_terrain(bit)); + } + } + } + } + undo_redo->commit_action(true); + drag_type = DRAG_TYPE_NONE; + } + } + } + } +} + +void TileDataTerrainsEditor::forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_set_atlas_source, const Ref<InputEvent> &p_event) { + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + if (drag_type == DRAG_TYPE_PAINT_TERRAIN_SET) { + Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position()); + Vector2i coords = Vector2i(tile.x, tile.y); + int alternative_tile = tile.z; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + if (!drag_modified.has(cell)) { + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + } + tile_data->set_terrain_set(drag_painted_value); + } + + drag_last_pos = mm->get_position(); + } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { + Dictionary painted = Dictionary(drag_painted_value); + int terrain_set = int(painted["terrain_set"]); + int terrain = int(painted["terrain"]); + + Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mm->get_position()); + Vector2i coords = Vector2i(tile.x, tile.y); + int alternative_tile = tile.z; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + + // Save the old terrain_set and terrains bits. + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + if (tile_data->get_terrain_set() == terrain_set) { + if (!drag_modified.has(cell)) { + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + } + + // Set the terrains bits. + Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(j); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(tile_data->get_terrain_set(), bit); + if (Geometry2D::is_segment_intersecting_polygon(mm->get_position() - position, drag_last_pos - position, polygon)) { + tile_data->set_peering_bit_terrain(bit, terrain); + } + } + } + } + } + drag_last_pos = mm->get_position(); + } + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + if (picker_button->is_pressed()) { + Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); + Vector2i coords = Vector2i(tile.x, tile.y); + int alternative_tile = tile.z; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + int terrain_set = tile_data->get_terrain_set(); + Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + dummy_object->set("terrain_set", terrain_set); + dummy_object->set("terrain", -1); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + dummy_object->set("terrain", tile_data->get_peering_bit_terrain(bit)); + } + } + } + terrain_set_property_editor->update_property(); + _update_terrain_selector(); + picker_button->set_pressed(false); + } + } else { + int terrain_set = int(dummy_object->get("terrain_set")); + int terrain = int(dummy_object->get("terrain")); + + Vector3i tile = p_tile_atlas_view->get_alternative_tile_at_pos(mb->get_position()); + Vector2i coords = Vector2i(tile.x, tile.y); + int alternative_tile = tile.z; + + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(coords, alternative_tile)); + + if (terrain_set == -1 || !tile_data || tile_data->get_terrain_set() != terrain_set) { + drag_type = DRAG_TYPE_PAINT_TERRAIN_SET; + drag_modified.clear(); + drag_painted_value = int(dummy_object->get("terrain_set")); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + tile_data->set_terrain_set(drag_painted_value); + } + drag_last_pos = mb->get_position(); + } else if (tile_data && tile_data->get_terrain_set() == terrain_set) { + // Paint terrain bits. + drag_type = DRAG_TYPE_PAINT_TERRAIN_BITS; + drag_modified.clear(); + Dictionary painted_dict; + painted_dict["terrain_set"] = terrain_set; + painted_dict["terrain"] = terrain; + drag_painted_value = painted_dict; + + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + TileMapCell cell; + cell.source_id = 0; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + + // Save the old terrain_set and terrains bits. + Dictionary dict; + dict["terrain_set"] = tile_data->get_terrain_set(); + Array array; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + array.push_back(tile_data->is_valid_peering_bit_terrain(bit) ? tile_data->get_peering_bit_terrain(bit) : -1); + } + dict["terrain_peering_bits"] = array; + drag_modified[cell] = dict; + + // Set the terrain bit. + Rect2i texture_region = p_tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + p_tile_set_atlas_source->get_tile_effective_texture_offset(coords, alternative_tile); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + Vector<Vector2> polygon = tile_set->get_terrain_bit_polygon(terrain_set, bit); + if (Geometry2D::is_point_in_polygon(mb->get_position() - position, polygon)) { + tile_data->set_peering_bit_terrain(bit, terrain); + } + } + } + } + drag_last_pos = mb->get_position(); + } + } + } 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); + 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->commit_action(false); + drag_type = DRAG_TYPE_NONE; + } else if (drag_type == DRAG_TYPE_PAINT_TERRAIN_BITS) { + Dictionary painted = Dictionary(drag_painted_value); + 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(); + Array array = dict["terrain_peering_bits"]; + for (int i = 0; i < array.size(); i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(terrain_set, bit)) { + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[i]), coords.x, coords.y, E->key().alternative_tile), terrain); + } + if (tile_set->is_valid_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->commit_action(false); + drag_type = DRAG_TYPE_NONE; + } + } + } + } +} + +void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + tile_set->draw_terrains(p_canvas_item, p_transform, tile_data); +} + +void TileDataTerrainsEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); + break; + default: + break; + } +} + +TileDataTerrainsEditor::TileDataTerrainsEditor() { + label = memnew(Label); + label->set_text("Painting:"); + add_child(label); + + // Toolbar + toolbar->add_child(memnew(VSeparator)); + + picker_button = memnew(Button); + picker_button->set_flat(true); + picker_button->set_toggle_mode(true); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + toolbar->add_child(picker_button); + + // Setup + dummy_object->add_dummy_property("terrain_set"); + dummy_object->set("terrain_set", -1); + dummy_object->add_dummy_property("terrain"); + dummy_object->set("terrain", -1); + + // Get the default value for the type. + terrain_set_property_editor = memnew(EditorPropertyEnum); + terrain_set_property_editor->set_object_and_property(dummy_object, "terrain_set"); + terrain_set_property_editor->set_label("Terrain Set"); + terrain_set_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1)); + add_child(terrain_set_property_editor); + + terrain_property_editor = memnew(EditorPropertyEnum); + terrain_property_editor->set_object_and_property(dummy_object, "terrain"); + terrain_property_editor->set_label("Terrain"); + terrain_property_editor->connect("property_changed", callable_mp(this, &TileDataTerrainsEditor::_property_value_changed).unbind(1)); + add_child(terrain_property_editor); +} + +TileDataTerrainsEditor::~TileDataTerrainsEditor() { + toolbar->queue_delete(); + memdelete(dummy_object); +} + +Variant TileDataNavigationEditor::_get_painted_value() { + Ref<NavigationPolygon> navigation_polygon; + navigation_polygon.instantiate(); + + for (int i = 0; i < polygon_editor->get_polygon_count(); i++) { + Vector<Vector2> polygon = polygon_editor->get_polygon(i); + navigation_polygon->add_outline(polygon); + } + + navigation_polygon->make_polygons_from_outlines(); + return navigation_polygon; +} + +void TileDataNavigationEditor::_set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + + Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); + polygon_editor->clear_polygons(); + if (navigation_polygon.is_valid()) { + for (int i = 0; i < navigation_polygon->get_outline_count(); i++) { + polygon_editor->add_polygon(navigation_polygon->get_outline(i)); + } + } + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +void TileDataNavigationEditor::_set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND(!tile_data); + Ref<NavigationPolygon> navigation_polygon = p_value; + tile_data->set_navigation_polygon(navigation_layer, navigation_polygon); + + polygon_editor->set_background(p_tile_set_atlas_source->get_texture(), p_tile_set_atlas_source->get_tile_texture_region(p_coords), p_tile_set_atlas_source->get_tile_effective_texture_offset(p_coords, p_alternative_tile), tile_data->get_flip_h(), tile_data->get_flip_v(), tile_data->get_transpose(), tile_data->get_modulate()); +} + +Variant TileDataNavigationEditor::_get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) { + TileData *tile_data = Object::cast_to<TileData>(p_tile_set_atlas_source->get_tile_data(p_coords, p_alternative_tile)); + ERR_FAIL_COND_V(!tile_data, Variant()); + return tile_data->get_navigation_polygon(navigation_layer); +} + +void TileDataNavigationEditor::_setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) { + for (Map<TileMapCell, Variant>::Element *E = p_previous_values.front(); E; E = E->next()) { + Vector2i coords = E->key().get_atlas_coords(); + undo_redo->add_undo_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), E->get()); + undo_redo->add_do_property(p_tile_set_atlas_source, vformat("%d:%d/%d/navigation_layer_%d/polygon", coords.x, coords.y, E->key().alternative_tile, navigation_layer), p_new_value); + } +} + +void TileDataNavigationEditor::_tile_set_changed() { + polygon_editor->set_tile_set(tile_set); +} + +void TileDataNavigationEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + polygon_editor->set_polygons_color(get_tree()->get_debug_navigation_color()); + break; + default: + break; + } +} + +TileDataNavigationEditor::TileDataNavigationEditor() { + polygon_editor = memnew(GenericTilePolygonEditor); + polygon_editor->set_multiple_polygon_mode(true); + add_child(polygon_editor); +} + +void TileDataNavigationEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected) { + TileData *tile_data = _get_tile_data(p_cell); + ERR_FAIL_COND(!tile_data); + + // Draw all shapes. + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + + Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); + if (navigation_polygon.is_valid()) { + Vector<Vector2> verts = navigation_polygon->get_vertices(); + if (verts.size() < 3) { + return; + } + + Color color = p_canvas_item->get_tree()->get_debug_navigation_color(); + if (p_selected) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + selection_color.a = 0.7; + color = selection_color; + } + + RandomPCG rand; + for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { + // An array of vertices for this polygon. + Vector<int> polygon = navigation_polygon->get_polygon(i); + Vector<Vector2> vertices; + vertices.resize(polygon.size()); + for (int j = 0; j < polygon.size(); j++) { + ERR_FAIL_INDEX(polygon[j], verts.size()); + vertices.write[j] = verts[polygon[j]]; + } + + // Generate the polygon color, slightly randomly modified from the settings one. + Color random_variation_color; + random_variation_color.set_hsv(color.get_h() + rand.random(-1.0, 1.0) * 0.05, color.get_s(), color.get_v() + rand.random(-1.0, 1.0) * 0.1); + random_variation_color.a = color.a; + Vector<Color> colors; + colors.push_back(random_variation_color); + + RenderingServer::get_singleton()->canvas_item_add_polygon(p_canvas_item->get_canvas_item(), vertices, colors); + } + } + + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); +} diff --git a/editor/plugins/tiles/tile_data_editors.h b/editor/plugins/tiles/tile_data_editors.h new file mode 100644 index 0000000000..781f26cc02 --- /dev/null +++ b/editor/plugins/tiles/tile_data_editors.h @@ -0,0 +1,408 @@ +/*************************************************************************/ +/* tile_data_editors.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_DATA_EDITORS_H +#define TILE_DATA_EDITORS_H + +#include "tile_atlas_view.h" + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/box_container.h" +#include "scene/gui/control.h" +#include "scene/gui/label.h" +#include "scene/resources/tile_set.h" + +class TileDataEditor : public VBoxContainer { + GDCLASS(TileDataEditor, VBoxContainer); + +private: + void _call_tile_set_changed(); + +protected: + Ref<TileSet> tile_set; + TileData *_get_tile_data(TileMapCell p_cell); + virtual void _tile_set_changed(){}; + + static void _bind_methods(); + +public: + void set_tile_set(Ref<TileSet> p_tile_set); + + // Input to handle painting. + virtual Control *get_toolbar() { return nullptr; }; + virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){}; + virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform){}; + virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event){}; + virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event){}; + + // Used to draw the tile data property value over a tile. + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false){}; +}; + +class DummyObject : public Object { + GDCLASS(DummyObject, Object) +private: + Map<String, Variant> properties; + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + +public: + bool has_dummy_property(StringName p_name); + void add_dummy_property(StringName p_name); + void remove_dummy_property(StringName p_name); + void clear_dummy_properties(); +}; + +class GenericTilePolygonEditor : public VBoxContainer { + GDCLASS(GenericTilePolygonEditor, VBoxContainer); + +private: + Ref<TileSet> tile_set; + LocalVector<Vector<Point2>> polygons; + bool multiple_polygon_mode = false; + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + // UI + int hovered_polygon_index = -1; + int hovered_point_index = -1; + int hovered_segment_index = -1; + Vector2 hovered_segment_point; + + enum DragType { + DRAG_TYPE_NONE, + DRAG_TYPE_DRAG_POINT, + DRAG_TYPE_CREATE_POINT, + DRAG_TYPE_PAN, + }; + DragType drag_type; + int drag_polygon_index; + int drag_point_index; + Vector2 drag_last_pos; + PackedVector2Array drag_old_polygon; + + HBoxContainer *toolbar; + Ref<ButtonGroup> tools_button_group; + Button *button_create; + Button *button_edit; + Button *button_delete; + Button *button_pixel_snap; + MenuButton *button_advanced_menu; + + Vector<Point2> in_creation_polygon; + + Panel *panel; + Control *base_control; + EditorZoomWidget *editor_zoom_widget; + Button *button_center_view; + Vector2 panning; + + Ref<Texture2D> background_texture; + Rect2 background_region; + Vector2 background_offset; + bool background_h_flip; + bool background_v_flip; + bool background_transpose; + Color background_modulate; + + Color polygon_color = Color(1.0, 0.0, 0.0); + + enum AdvancedMenuOption { + RESET_TO_DEFAULT_TILE, + CLEAR_TILE, + }; + + void _base_control_draw(); + void _zoom_changed(); + void _advanced_menu_item_pressed(int p_item_pressed); + void _center_view(); + void _base_control_gui_input(Ref<InputEvent> p_event); + + void _snap_to_tile_shape(Point2 &r_point, float &r_current_snapped_dist, float p_snap_dist); + void _snap_to_half_pixel(Point2 &r_point); + void _grab_polygon_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_point_index); + void _grab_polygon_segment_point(Vector2 p_pos, const Transform2D &p_polygon_xform, int &r_polygon_index, int &r_segment_index, Vector2 &r_point); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void set_tile_set(Ref<TileSet> p_tile_set); + void set_background(Ref<Texture2D> p_texture, Rect2 p_region = Rect2(), Vector2 p_offset = Vector2(), bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false, Color p_modulate = Color(1.0, 1.0, 1.0, 0.0)); + + int get_polygon_count(); + int add_polygon(Vector<Point2> p_polygon, int p_index = -1); + void remove_polygon(int p_index); + void clear_polygons(); + void set_polygon(int p_polygon_index, Vector<Point2> p_polygon); + Vector<Point2> get_polygon(int p_polygon_index); + + void set_polygons_color(Color p_color); + void set_multiple_polygon_mode(bool p_multiple_polygon_mode); + + GenericTilePolygonEditor(); +}; + +class TileDataDefaultEditor : public TileDataEditor { + GDCLASS(TileDataDefaultEditor, TileDataEditor); + +private: + // Toolbar + HBoxContainer *toolbar = memnew(HBoxContainer); + Button *picker_button; + + // UI + Ref<Texture2D> tile_bool_checked; + Ref<Texture2D> tile_bool_unchecked; + Label *label; + + EditorProperty *property_editor = nullptr; + + // Painting state. + enum DragType { + DRAG_TYPE_NONE = 0, + DRAG_TYPE_PAINT, + DRAG_TYPE_PAINT_RECT, + }; + DragType drag_type = DRAG_TYPE_NONE; + Vector2 drag_start_pos; + Vector2 drag_last_pos; + Map<TileMapCell, Variant> drag_modified; + Variant drag_painted_value; + + void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + +protected: + DummyObject *dummy_object = memnew(DummyObject); + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + StringName type; + String property; + void _notification(int p_what); + + virtual Variant _get_painted_value(); + virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); + virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value); + virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile); + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value); + +public: + virtual Control *get_toolbar() override { return toolbar; }; + virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; + virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; + virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; + virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + + void setup_property_editor(Variant::Type p_type, String p_property, String p_label = "", Variant p_default_value = Variant()); + + TileDataDefaultEditor(); + ~TileDataDefaultEditor(); +}; + +class TileDataTextureOffsetEditor : public TileDataDefaultEditor { + GDCLASS(TileDataTextureOffsetEditor, TileDataDefaultEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; +}; + +class TileDataPositionEditor : public TileDataDefaultEditor { + GDCLASS(TileDataPositionEditor, TileDataDefaultEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; +}; + +class TileDataYSortEditor : public TileDataDefaultEditor { + GDCLASS(TileDataYSortEditor, TileDataDefaultEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; +}; + +class TileDataOcclusionShapeEditor : public TileDataDefaultEditor { + GDCLASS(TileDataOcclusionShapeEditor, TileDataDefaultEditor); + +private: + int occlusion_layer = -1; + + // UI + GenericTilePolygonEditor *polygon_editor; + + void _polygon_changed(PackedVector2Array p_polygon); + + virtual Variant _get_painted_value() override; + virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; + virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; + virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + +protected: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + virtual void _tile_set_changed() override; + + void _notification(int p_what); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + + void set_occlusion_layer(int p_occlusion_layer) { occlusion_layer = p_occlusion_layer; } + + TileDataOcclusionShapeEditor(); +}; + +class TileDataCollisionEditor : public TileDataDefaultEditor { + GDCLASS(TileDataCollisionEditor, TileDataDefaultEditor); + + int physics_layer = -1; + + // UI + GenericTilePolygonEditor *polygon_editor; + DummyObject *dummy_object = memnew(DummyObject); + Map<StringName, EditorProperty *> property_editors; + + void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + void _polygons_changed(); + + virtual Variant _get_painted_value() override; + virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; + virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; + virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + +protected: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + virtual void _tile_set_changed() override; + + void _notification(int p_what); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + + void set_physics_layer(int p_physics_layer) { physics_layer = p_physics_layer; } + + TileDataCollisionEditor(); + ~TileDataCollisionEditor(); +}; + +class TileDataTerrainsEditor : public TileDataEditor { + GDCLASS(TileDataTerrainsEditor, TileDataEditor); + +private: + // Toolbar + HBoxContainer *toolbar = memnew(HBoxContainer); + Button *picker_button; + + // Painting state. + enum DragType { + DRAG_TYPE_NONE = 0, + DRAG_TYPE_PAINT_TERRAIN_SET, + DRAG_TYPE_PAINT_TERRAIN_SET_RECT, + DRAG_TYPE_PAINT_TERRAIN_BITS, + DRAG_TYPE_PAINT_TERRAIN_BITS_RECT, + }; + DragType drag_type = DRAG_TYPE_NONE; + Vector2 drag_start_pos; + Vector2 drag_last_pos; + Map<TileMapCell, Variant> drag_modified; + Variant drag_painted_value; + + // UI + Label *label; + DummyObject *dummy_object = memnew(DummyObject); + EditorPropertyEnum *terrain_set_property_editor = nullptr; + EditorPropertyEnum *terrain_property_editor = nullptr; + + void _property_value_changed(StringName p_property, Variant p_value, StringName p_field); + + void _update_terrain_selector(); + +protected: + virtual void _tile_set_changed() override; + + void _notification(int p_what); + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + +public: + virtual Control *get_toolbar() override { return toolbar; }; + virtual void forward_draw_over_atlas(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; + virtual void forward_draw_over_alternatives(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, CanvasItem *p_canvas_item, Transform2D p_transform) override; + virtual void forward_painting_atlas_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; + virtual void forward_painting_alternatives_gui_input(TileAtlasView *p_tile_atlas_view, TileSetAtlasSource *p_tile_atlas_source, const Ref<InputEvent> &p_event) override; + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + + TileDataTerrainsEditor(); + ~TileDataTerrainsEditor(); +}; + +class TileDataNavigationEditor : public TileDataDefaultEditor { + GDCLASS(TileDataNavigationEditor, TileDataDefaultEditor); + +private: + int navigation_layer = -1; + PackedVector2Array navigation_polygon; + + // UI + GenericTilePolygonEditor *polygon_editor; + + void _polygon_changed(PackedVector2Array p_polygon); + + virtual Variant _get_painted_value() override; + virtual void _set_painted_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; + virtual void _set_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile, Variant p_value) override; + virtual Variant _get_value(TileSetAtlasSource *p_tile_set_atlas_source, Vector2 p_coords, int p_alternative_tile) override; + virtual void _setup_undo_redo_action(TileSetAtlasSource *p_tile_set_atlas_source, Map<TileMapCell, Variant> p_previous_values, Variant p_new_value) override; + +protected: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + virtual void _tile_set_changed() override; + + void _notification(int p_what); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileMapCell p_cell, bool p_selected = false) override; + + void set_navigation_layer(int p_navigation_layer) { navigation_layer = p_navigation_layer; } + + TileDataNavigationEditor(); +}; + +#endif // TILE_DATA_EDITORS_H diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp new file mode 100644 index 0000000000..86bd115ac2 --- /dev/null +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -0,0 +1,3525 @@ +/*************************************************************************/ +/* tile_map_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_map_editor.h" + +#include "tiles_editor_plugin.h" + +#include "editor/editor_resource_preview.h" +#include "editor/editor_scale.h" +#include "editor/plugins/canvas_item_editor_plugin.h" + +#include "scene/gui/center_container.h" +#include "scene/gui/split_container.h" + +#include "core/input/input.h" +#include "core/math/geometry_2d.h" +#include "core/os/keyboard.h" + +void TileMapEditorTilesPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + select_tool_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); + paint_tool_button->set_icon(get_theme_icon("Edit", "EditorIcons")); + line_tool_button->set_icon(get_theme_icon("CurveLinear", "EditorIcons")); + rect_tool_button->set_icon(get_theme_icon("Rectangle", "EditorIcons")); + bucket_tool_button->set_icon(get_theme_icon("Bucket", "EditorIcons")); + + picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); + erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons")); + + toggle_grid_button->set_icon(get_theme_icon("Grid", "EditorIcons")); + + missing_atlas_texture_icon = get_theme_icon("TileSet", "EditorIcons"); + + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); + break; + case NOTIFICATION_VISIBILITY_CHANGED: + _stop_dragging(); + break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); + break; + } +} + +void TileMapEditorTilesPlugin::tile_set_changed() { + _update_fix_selected_and_hovered(); + _update_tile_set_sources_list(); + _update_bottom_panel(); +} + +void TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled(bool p_pressed) { + scatter_spinbox->set_editable(p_pressed); +} + +void TileMapEditorTilesPlugin::_on_scattering_spinbox_changed(double p_value) { + scattering = p_value; +} + +void TileMapEditorTilesPlugin::_on_grid_toggled(bool p_pressed) { + EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed); +} + +void TileMapEditorTilesPlugin::_update_toolbar() { + // Stop draggig if needed. + _stop_dragging(); + + // Hide all settings. + for (int i = 0; i < tools_settings->get_child_count(); i++) { + Object::cast_to<CanvasItem>(tools_settings->get_child(i))->hide(); + } + + // Show only the correct settings. + if (tool_buttons_group->get_pressed_button() == select_tool_button) { + } else if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + tools_settings_vsep_2->show(); + random_tile_checkbox->show(); + scatter_label->show(); + scatter_spinbox->show(); + } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + tools_settings_vsep_2->show(); + random_tile_checkbox->show(); + scatter_label->show(); + scatter_spinbox->show(); + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + tools_settings_vsep_2->show(); + random_tile_checkbox->show(); + scatter_label->show(); + scatter_spinbox->show(); + } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + tools_settings_vsep_2->show(); + bucket_continuous_checkbox->show(); + random_tile_checkbox->show(); + scatter_label->show(); + scatter_spinbox->show(); + } +} + +Control *TileMapEditorTilesPlugin::get_toolbar() const { + return toolbar; +} + +void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { + // Update the sources. + int old_current = sources_list->get_current(); + sources_list->clear(); + + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + for (int i = 0; i < tile_set->get_source_count(); i++) { + int source_id = tile_set->get_source_id(i); + + TileSetSource *source = *tile_set->get_source(source_id); + + Ref<Texture2D> texture; + String item_text; + + // Atlas source. + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + texture = atlas_source->get_texture(); + if (texture.is_valid()) { + item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + } else { + item_text = vformat("No Texture Atlas Source (id:%d)", source_id); + } + } + + // Scene collection source. + TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scene_collection_source) { + texture = get_theme_icon("PackedScene", "EditorIcons"); + item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id); + } + + // Use default if not valid. + if (item_text.is_empty()) { + item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id); + } + if (!texture.is_valid()) { + texture = missing_atlas_texture_icon; + } + + sources_list->add_item(item_text, texture); + sources_list->set_item_metadata(i, source_id); + } + + if (sources_list->get_item_count() > 0) { + if (old_current > 0) { + // Keep the current selected item if needed. + sources_list->set_current(CLAMP(old_current, 0, sources_list->get_item_count() - 1)); + } else { + sources_list->set_current(0); + } + sources_list->emit_signal("item_selected", sources_list->get_current()); + } + + // Synchronize + TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current()); +} + +void TileMapEditorTilesPlugin::_update_bottom_panel() { + // Update the atlas display. + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_index = sources_list->get_current(); + if (source_index >= 0 && source_index < sources_list->get_item_count()) { + atlas_sources_split_container->show(); + missing_source_label->hide(); + + int source_id = sources_list->get_item_metadata(source_index); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + + if (atlas_source) { + tile_atlas_view->show(); + scene_tiles_list->hide(); + invalid_source_label->hide(); + _update_atlas_view(); + } else if (scenes_collection_source) { + tile_atlas_view->hide(); + scene_tiles_list->show(); + invalid_source_label->hide(); + _update_scenes_collection_view(); + } else { + tile_atlas_view->hide(); + scene_tiles_list->hide(); + invalid_source_label->show(); + } + } else { + atlas_sources_split_container->hide(); + missing_source_label->show(); + + tile_atlas_view->hide(); + scene_tiles_list->hide(); + invalid_source_label->hide(); + } +} + +void TileMapEditorTilesPlugin::_update_atlas_view() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + ERR_FAIL_COND(!atlas_source); + + tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id); + TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view); + tile_atlas_control->update(); +} + +void TileMapEditorTilesPlugin::_update_scenes_collection_view() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + ERR_FAIL_COND(!scenes_collection_source); + + // Clear the list. + scene_tiles_list->clear(); + + // Rebuild the list. + for (int i = 0; i < scenes_collection_source->get_scene_tiles_count(); i++) { + int scene_id = scenes_collection_source->get_scene_tile_id(i); + + Ref<PackedScene> scene = scenes_collection_source->get_scene_tile_scene(scene_id); + + int item_index = 0; + if (scene.is_valid()) { + item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id)); + Variant udata = i; + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata); + } else { + item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons")); + } + scene_tiles_list->set_item_metadata(item_index, scene_id); + + // Check if in selection. + if (tile_set_selection.has(TileMapCell(source_id, Vector2i(), scene_id))) { + scene_tiles_list->select(item_index, false); + } + } + + // Icon size update. + int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE; + scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size)); +} + +void TileMapEditorTilesPlugin::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) { + int index = p_ud; + + if (index >= 0 && index < scene_tiles_list->get_item_count()) { + scene_tiles_list->set_item_icon(index, p_preview); + } +} + +void TileMapEditorTilesPlugin::_scenes_list_multi_selected(int p_index, bool p_selected) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Add or remove the Tile form the selection. + int scene_id = scene_tiles_list->get_item_metadata(p_index); + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + ERR_FAIL_COND(!scenes_collection_source); + + TileMapCell selected = TileMapCell(source_id, Vector2i(), scene_id); + + // Clear the selection if shift is not pressed. + if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + tile_set_selection.clear(); + } + + if (p_selected) { + tile_set_selection.insert(selected); + } else { + if (tile_set_selection.has(selected)) { + tile_set_selection.erase(selected); + } + } + + _update_selection_pattern_from_tileset_selection(); +} + +void TileMapEditorTilesPlugin::_scenes_list_nothing_selected() { + scene_tiles_list->deselect_all(); + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); + _update_selection_pattern_from_tileset_selection(); +} + +bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + if (!is_visible_in_tree()) { + // If the bottom editor is not visible, we ignore inputs. + return false; + } + + if (CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) { + return false; + } + + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return false; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return false; + } + + // Shortcuts + if (ED_IS_SHORTCUT("tiles_editor/cut", p_event) || ED_IS_SHORTCUT("tiles_editor/copy", p_event)) { + // Fill in the clipboard. + if (!tile_map_selection.is_empty()) { + memdelete(tile_map_clipboard); + TypedArray<Vector2i> coords_array; + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + coords_array.push_back(E->get()); + } + tile_map_clipboard = tile_map->get_pattern(coords_array); + } + + if (ED_IS_SHORTCUT("tiles_editor/cut", p_event)) { + // Delete selected tiles. + if (!tile_map_selection.is_empty()) { + undo_redo->create_action(TTR("Delete tiles")); + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get())); + } + undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + tile_map_selection.clear(); + undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + undo_redo->commit_action(); + } + } + + return true; + } + if (ED_IS_SHORTCUT("tiles_editor/paste", p_event)) { + if (drag_type == DRAG_TYPE_NONE) { + drag_type = DRAG_TYPE_CLIPBOARD_PASTE; + } + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + if (ED_IS_SHORTCUT("tiles_editor/cancel", p_event)) { + if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { + drag_type = DRAG_TYPE_NONE; + CanvasItemEditor::get_singleton()->update_viewport(); + return true; + } + } + if (ED_IS_SHORTCUT("tiles_editor/delete", p_event)) { + // Delete selected tiles. + if (!tile_map_selection.is_empty()) { + undo_redo->create_action(TTR("Delete tiles")); + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + undo_redo->add_undo_method(tile_map, "set_cell", E->get(), tile_map->get_cell_source_id(E->get()), tile_map->get_cell_atlas_coords(E->get()), tile_map->get_cell_alternative_tile(E->get())); + } + undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + tile_map_selection.clear(); + undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + undo_redo->commit_action(); + } + return true; + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + has_mouse = true; + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Vector2 mpos = xform.affine_inverse().xform(mm->get_position()); + + switch (drag_type) { + case DRAG_TYPE_PAINT: { + 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 == -1) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + } + tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + } 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 == -1) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + } + tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + } + } + } break; + default: + break; + } + drag_last_mouse_pos = mpos; + CanvasItemEditor::get_singleton()->update_viewport(); + + return true; + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + has_mouse = true; + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Vector2 mpos = xform.affine_inverse().xform(mb->get_position()); + + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + // Pressed + if (tool_buttons_group->get_pressed_button() == select_tool_button) { + drag_start_mouse_pos = mpos; + if (tile_map_selection.has(tile_map->world_to_map(drag_start_mouse_pos)) && !mb->is_shift_pressed()) { + // Move the selection + drag_type = DRAG_TYPE_MOVE; + drag_modified.clear(); + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + Vector2i coords = E->get(); + drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + tile_map->set_cell(coords, -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + } + } else { + // Select tiles + drag_type = DRAG_TYPE_SELECT; + } + } else { + // Check if we are picking a tile. + if (picker_button->is_pressed()) { + drag_type = DRAG_TYPE_PICK; + drag_start_mouse_pos = mpos; + } else { + // Paint otherwise. + if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + 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 == -1) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + } + tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + } else if (tool_buttons_group->get_pressed_button() == line_tool_button) { + drag_type = DRAG_TYPE_LINE; + drag_start_mouse_pos = mpos; + drag_modified.clear(); + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button) { + drag_type = DRAG_TYPE_RECT; + drag_start_mouse_pos = mpos; + drag_modified.clear(); + } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button) { + drag_type = DRAG_TYPE_BUCKET; + drag_start_mouse_pos = mpos; + drag_modified.clear(); + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); + for (int i = 0; i < line.size(); i++) { + if (!drag_modified.has(line[i])) { + Map<Vector2i, TileMapCell> to_draw = _draw_bucket_fill(line[i], bucket_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 == -1) { + continue; + } + Vector2i coords = E->key(); + if (!drag_modified.has(coords)) { + drag_modified.insert(coords, TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords))); + } + tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + } + } + } + } + } + + } else { + // Released + _stop_dragging(); + } + + CanvasItemEditor::get_singleton()->update_viewport(); + + return true; + } + drag_last_mouse_pos = mpos; + } + + return false; +} + +void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + if (!tile_map->is_visible_in_tree()) { + return; + } + + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Vector2i tile_shape_size = tile_set->get_tile_size(); + + // Draw the selection. + if (is_visible_in_tree() && tool_buttons_group->get_pressed_button() == select_tool_button) { + // In select mode, we only draw the current selection if we are modifying it (pressing control or shift). + if (drag_type == DRAG_TYPE_MOVE || (drag_type == DRAG_TYPE_SELECT && !Input::get_singleton()->is_key_pressed(KEY_CTRL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { + // Do nothing + } else { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + tile_map->draw_cells_outline(p_overlay, tile_map_selection, selection_color, xform); + } + } + + // Handle the preview of the tiles to be placed. + if (is_visible_in_tree() && has_mouse) { // Only if the tilemap editor is opened and the viewport is hovered. + Map<Vector2i, TileMapCell> preview; + Rect2i drawn_grid_rect; + + if (drag_type == DRAG_TYPE_PICK) { + // Draw the area being picvked. + Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + rect.size += Vector2i(1, 1); + for (int x = rect.position.x; x < rect.get_end().x; x++) { + for (int y = rect.position.y; y < rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + if (tile_map->get_cell_source_id(coords) != -1) { + Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size)); + tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false); + } + } + } + } else if (drag_type == DRAG_TYPE_SELECT) { + // Draw the area being selected. + Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + rect.size += Vector2i(1, 1); + Set<Vector2i> to_draw; + for (int x = rect.position.x; x < rect.get_end().x; x++) { + for (int y = rect.position.y; y < rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + if (tile_map->get_cell_source_id(coords) != -1) { + to_draw.insert(coords); + } + } + } + tile_map->draw_cells_outline(p_overlay, to_draw, Color(1.0, 1.0, 1.0), xform); + } else if (drag_type == DRAG_TYPE_MOVE) { + // Preview when moving. + Vector2i top_left; + if (!tile_map_selection.is_empty()) { + top_left = tile_map_selection.front()->get(); + } + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + top_left = top_left.min(E->get()); + } + Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left); + offset = tile_map->world_to_map(drag_last_mouse_pos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset); + + TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); + for (int i = 0; i < selection_used_cells.size(); i++) { + Vector2i coords = tile_map->map_pattern(offset + top_left, selection_used_cells[i], selection_pattern); + preview[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i])); + } + } else if (drag_type == DRAG_TYPE_CLIPBOARD_PASTE) { + // Preview when pasting. + Vector2 mouse_offset = (Vector2(tile_map_clipboard->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); + TypedArray<Vector2i> clipboard_used_cells = tile_map_clipboard->get_used_cells(); + for (int i = 0; i < clipboard_used_cells.size(); i++) { + 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()) { + 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) { + 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); + expand_grid = true; + } else if (drag_type == DRAG_TYPE_LINE) { + // Preview for a line pattern. + preview = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, drag_last_mouse_pos); + expand_grid = true; + } + } else if (tool_buttons_group->get_pressed_button() == rect_tool_button && drag_type == DRAG_TYPE_RECT) { + // Preview for a line pattern. + preview = _draw_rect(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(drag_last_mouse_pos)); + expand_grid = true; + } else if (tool_buttons_group->get_pressed_button() == bucket_tool_button && drag_type == DRAG_TYPE_NONE) { + // Preview for a line pattern. + preview = _draw_bucket_fill(tile_map->world_to_map(drag_last_mouse_pos), bucket_continuous_checkbox->is_pressed()); + } + + // 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()); + } + } + } + + if (!preview.is_empty()) { + const int fading = 5; + + // Draw the lines of the grid behind the preview. + bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"); + if (display_grid) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + if (drawn_grid_rect.size.x > 0 && drawn_grid_rect.size.y > 0) { + drawn_grid_rect = drawn_grid_rect.grow(fading); + for (int x = drawn_grid_rect.position.x; x < (drawn_grid_rect.position.x + drawn_grid_rect.size.x); x++) { + for (int y = drawn_grid_rect.position.y; y < (drawn_grid_rect.position.y + drawn_grid_rect.size.y); y++) { + Vector2i pos_in_rect = Vector2i(x, y) - drawn_grid_rect.position; + + // Fade out the border of the grid. + float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.x, (float)(drawn_grid_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f); + float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)drawn_grid_rect.size.y, (float)(drawn_grid_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f); + float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); + + Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size)); + Color color = grid_color; + color.a = color.a * opacity; + tile_set->draw_tile_shape(p_overlay, cell_region, color, false); + } + } + } + } + + // Draw the preview. + for (Map<Vector2i, TileMapCell>::Element *E = preview.front(); E; E = E->next()) { + Vector2i size = tile_set->get_tile_size(); + Vector2 position = tile_map->map_to_world(E->key()) - size / 2; + Rect2 cell_region = xform.xform(Rect2(position, size)); + if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true); + } else { + if (tile_set->has_source(E->get().source_id)) { + TileSetSource *source = *tile_set->get_source(E->get().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)); + + // 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); + + // Compute the destination rectangle in the CanvasItem. + Rect2 dest_rect; + dest_rect.size = source_rect.size; + + 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); + } else { + dest_rect.position = (tile_map->map_to_world(E->key()) - dest_rect.size / 2 - tile_offset); + } + + dest_rect = xform.xform(dest_rect); + + if (tile_data->get_flip_h()) { + dest_rect.size.x = -dest_rect.size.x; + } + + if (tile_data->get_flip_v()) { + dest_rect.size.y = -dest_rect.size.y; + } + + // Get the tile modulation. + Color modulate = tile_data->get_modulate(); + Color self_modulate = tile_map->get_self_modulate(); + modulate = Color(modulate.r * self_modulate.r, modulate.g * self_modulate.g, modulate.b * self_modulate.b, modulate.a * self_modulate.a); + + // Draw the tile. + p_overlay->draw_texture_rect_region(atlas_source->get_texture(), dest_rect, source_rect, modulate * Color(1.0, 1.0, 1.0, 0.5), transpose, tile_set->is_uv_clipping()); + } else { + tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0, 0.5), true); + } + } else { + tile_set->draw_tile_shape(p_overlay, cell_region, Color(0.0, 0.0, 0.0, 0.5), true); + } + } + } + } + } +} + +void TileMapEditorTilesPlugin::_mouse_exited_viewport() { + has_mouse = false; + CanvasItemEditor::get_singleton()->update_viewport(); +} + +TileMapCell TileMapEditorTilesPlugin::_pick_random_tile(const TileMapPattern *p_pattern) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return TileMapCell(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return TileMapCell(); + } + + TypedArray<Vector2i> used_cells = p_pattern->get_used_cells(); + double sum = 0.0; + for (int i = 0; i < used_cells.size(); i++) { + int source_id = p_pattern->get_cell_source_id(used_cells[i]); + Vector2i atlas_coords = p_pattern->get_cell_atlas_coords(used_cells[i]); + int alternative_tile = p_pattern->get_cell_alternative_tile(used_cells[i]); + + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile)); + ERR_FAIL_COND_V(!tile_data, TileMapCell()); + sum += tile_data->get_probability(); + } else { + sum += 1.0; + } + } + + double empty_probability = sum * scattering; + double current = 0.0; + double rand = Math::random(0.0, sum + empty_probability); + for (int i = 0; i < used_cells.size(); i++) { + int source_id = p_pattern->get_cell_source_id(used_cells[i]); + Vector2i atlas_coords = p_pattern->get_cell_atlas_coords(used_cells[i]); + int alternative_tile = p_pattern->get_cell_alternative_tile(used_cells[i]); + + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + current += Object::cast_to<TileData>(atlas_source->get_tile_data(atlas_coords, alternative_tile))->get_probability(); + } else { + current += 1.0; + } + + if (current >= rand) { + return TileMapCell(source_id, atlas_coords, alternative_tile); + } + } + return TileMapCell(); +} + +Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map<Vector2i, TileMapCell>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map<Vector2i, TileMapCell>(); + } + + // Get or create the pattern. + TileMapPattern erase_pattern; + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + TileMapPattern *pattern = erase_button->is_pressed() ? &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()) { + // 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++) { + output.insert(line[i], _pick_random_tile(pattern)); + } + } else { + // Paint the pattern. + // If we paint several tiles, we virtually move the mouse as if it was in the center of the "brush" + Vector2 mouse_offset = (Vector2(pattern->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); + Vector2i last_hovered_cell = tile_map->world_to_map(p_from_mouse_pos - mouse_offset); + Vector2i new_hovered_cell = tile_map->world_to_map(p_to_mouse_pos - mouse_offset); + Vector2i drag_start_cell = tile_map->world_to_map(p_start_drag_mouse_pos - mouse_offset); + + TypedArray<Vector2i> used_cells = pattern->get_used_cells(); + Vector2i offset = Vector2i(Math::posmod(drag_start_cell.x, pattern->get_size().x), Math::posmod(drag_start_cell.y, pattern->get_size().y)); // Note: no posmodv for Vector2i for now. Meh.s + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, (last_hovered_cell - offset) / pattern->get_size(), (new_hovered_cell - offset) / pattern->get_size()); + for (int i = 0; i < line.size(); i++) { + Vector2i top_left = line[i] * pattern->get_size() + offset; + for (int j = 0; j < used_cells.size(); j++) { + Vector2i coords = tile_map->map_pattern(top_left, used_cells[j], pattern); + output.insert(coords, TileMapCell(pattern->get_cell_source_id(used_cells[j]), pattern->get_cell_atlas_coords(used_cells[j]), pattern->get_cell_alternative_tile(used_cells[j]))); + } + } + } + } + return output; +} + +Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map<Vector2i, TileMapCell>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map<Vector2i, TileMapCell>(); + } + + // Create the rect to draw. + Rect2i rect = Rect2i(p_start_cell, p_end_cell - p_start_cell).abs(); + rect.size += Vector2i(1, 1); + + // Get or create the pattern. + TileMapPattern erase_pattern; + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + + // Compute the offset to align things to the bottom or right. + bool aligned_right = p_end_cell.x < p_start_cell.x; + bool valigned_bottom = p_end_cell.y < p_start_cell.y; + Vector2i offset = Vector2i(aligned_right ? -(pattern->get_size().x - (rect.get_size().x % pattern->get_size().x)) : 0, valigned_bottom ? -(pattern->get_size().y - (rect.get_size().y % pattern->get_size().y)) : 0); + + Map<Vector2i, TileMapCell> output; + if (!pattern->is_empty()) { + if (!erase_button->is_pressed() && 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++) { + Vector2i coords = rect.position + Vector2i(x, y); + output.insert(coords, _pick_random_tile(pattern)); + } + } + } else { + // Paint the pattern. + TypedArray<Vector2i> used_cells = pattern->get_used_cells(); + for (int x = 0; x <= rect.size.x / pattern->get_size().x; x++) { + for (int y = 0; y <= rect.size.y / pattern->get_size().y; y++) { + Vector2i pattern_coords = rect.position + Vector2i(x, y) * pattern->get_size() + offset; + for (int j = 0; j < used_cells.size(); j++) { + Vector2i coords = pattern_coords + used_cells[j]; + if (rect.has_point(coords)) { + output.insert(coords, TileMapCell(pattern->get_cell_source_id(used_cells[j]), pattern->get_cell_atlas_coords(used_cells[j]), pattern->get_cell_alternative_tile(used_cells[j]))); + } + } + } + } + } + } + + return output; +} + +Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map<Vector2i, TileMapCell>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map<Vector2i, TileMapCell>(); + } + + // Get or create the pattern. + TileMapPattern erase_pattern; + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + + Map<Vector2i, TileMapCell> output; + if (!pattern->is_empty()) { + TileMapCell source = tile_map->get_cell(p_coords); + + // If we are filling empty tiles, compute the tilemap boundaries. + Rect2i boundaries; + if (source.source_id == -1) { + boundaries = tile_map->get_used_rect(); + } + + if (p_contiguous) { + // Replace continuous tiles like the source. + Set<Vector2i> already_checked; + List<Vector2i> to_check; + to_check.push_back(p_coords); + while (!to_check.is_empty()) { + Vector2i coords = to_check.back()->get(); + to_check.pop_back(); + if (!already_checked.has(coords)) { + if (source.source_id == tile_map->get_cell_source_id(coords) && + source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) && + source.alternative_tile == tile_map->get_cell_alternative_tile(coords) && + (source.source_id != -1 || boundaries.has_point(coords))) { + if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + // Paint a random tile. + output.insert(coords, _pick_random_tile(pattern)); + } else { + // Paint the pattern. + Vector2i pattern_coords = (coords - p_coords) % pattern->get_size(); // Note: it would be good to have posmodv for Vector2i. + pattern_coords.x = pattern_coords.x < 0 ? pattern_coords.x + pattern->get_size().x : pattern_coords.x; + pattern_coords.y = pattern_coords.y < 0 ? pattern_coords.y + pattern->get_size().y : pattern_coords.y; + if (pattern->has_cell(pattern_coords)) { + output.insert(coords, TileMapCell(pattern->get_cell_source_id(pattern_coords), pattern->get_cell_atlas_coords(pattern_coords), pattern->get_cell_alternative_tile(pattern_coords))); + } else { + output.insert(coords, TileMapCell()); + } + } + + // Get surrounding tiles (handles different tile shapes). + TypedArray<Vector2i> around = tile_map->get_surrounding_tiles(coords); + for (int i = 0; i < around.size(); i++) { + to_check.push_back(around[i]); + } + } + already_checked.insert(coords); + } + } + } else { + // Replace all tiles like the source. + TypedArray<Vector2i> to_check; + if (source.source_id == -1) { + Rect2i rect = tile_map->get_used_rect(); + if (rect.size.x <= 0 || rect.size.y <= 0) { + rect = Rect2i(p_coords, Vector2i(1, 1)); + } + for (int x = boundaries.position.x; x < boundaries.get_end().x; x++) { + for (int y = boundaries.position.y; y < boundaries.get_end().y; y++) { + to_check.append(Vector2i(x, y)); + } + } + } else { + to_check = tile_map->get_used_cells(); + } + for (int i = 0; i < to_check.size(); i++) { + Vector2i coords = to_check[i]; + if (source.source_id == tile_map->get_cell_source_id(coords) && + source.get_atlas_coords() == tile_map->get_cell_atlas_coords(coords) && + source.alternative_tile == tile_map->get_cell_alternative_tile(coords) && + (source.source_id != -1 || boundaries.has_point(coords))) { + if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + // Paint a random tile. + output.insert(coords, _pick_random_tile(pattern)); + } else { + // Paint the pattern. + Vector2i pattern_coords = (coords - p_coords) % pattern->get_size(); // Note: it would be good to have posmodv for Vector2i. + pattern_coords.x = pattern_coords.x < 0 ? pattern_coords.x + pattern->get_size().x : pattern_coords.x; + pattern_coords.y = pattern_coords.y < 0 ? pattern_coords.y + pattern->get_size().y : pattern_coords.y; + if (pattern->has_cell(pattern_coords)) { + output.insert(coords, TileMapCell(pattern->get_cell_source_id(pattern_coords), pattern->get_cell_atlas_coords(pattern_coords), pattern->get_cell_alternative_tile(pattern_coords))); + } else { + output.insert(coords, TileMapCell()); + } + } + } + } + } + } + return output; +} + +void TileMapEditorTilesPlugin::_stop_dragging() { + if (drag_type == DRAG_TYPE_NONE) { + return; + } + + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Vector2 mpos = xform.affine_inverse().xform(CanvasItemEditor::get_singleton()->get_viewport_control()->get_local_mouse_position()); + + switch (drag_type) { + case DRAG_TYPE_SELECT: { + undo_redo->create_action(TTR("Change selection")); + undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + + if (!Input::get_singleton()->is_key_pressed(KEY_SHIFT) && !Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + tile_map_selection.clear(); + } + Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + for (int x = rect.position.x; x <= rect.get_end().x; x++) { + for (int y = rect.position.y; y <= rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + if (Input::get_singleton()->is_key_pressed(KEY_CTRL)) { + if (tile_map_selection.has(coords)) { + tile_map_selection.erase(coords); + } + } else { + if (tile_map->get_cell_source_id(coords) != -1) { + tile_map_selection.insert(coords); + } + } + } + } + undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + undo_redo->commit_action(false); + + _update_selection_pattern_from_tilemap_selection(); + _update_tileset_selection_from_selection_pattern(); + } break; + case DRAG_TYPE_MOVE: { + Vector2i top_left; + if (!tile_map_selection.is_empty()) { + top_left = tile_map_selection.front()->get(); + } + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + top_left = top_left.min(E->get()); + } + + Vector2i offset = drag_start_mouse_pos - tile_map->map_to_world(top_left); + offset = tile_map->world_to_map(mpos - offset) - tile_map->world_to_map(drag_start_mouse_pos - offset); + + TypedArray<Vector2i> selection_used_cells = selection_pattern->get_used_cells(); + + Vector2i coords; + Map<Vector2i, TileMapCell> cells_undo; + for (int i = 0; i < selection_used_cells.size(); i++) { + coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern); + cells_undo[coords] = TileMapCell(drag_modified[coords].source_id, drag_modified[coords].get_atlas_coords(), drag_modified[coords].alternative_tile); + coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); + cells_undo[coords] = TileMapCell(tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)); + } + + Map<Vector2i, TileMapCell> cells_do; + for (int i = 0; i < selection_used_cells.size(); i++) { + coords = tile_map->map_pattern(top_left, selection_used_cells[i], selection_pattern); + cells_do[coords] = TileMapCell(); + } + for (int i = 0; i < selection_used_cells.size(); i++) { + coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); + cells_do[coords] = TileMapCell(selection_pattern->get_cell_source_id(selection_used_cells[i]), selection_pattern->get_cell_atlas_coords(selection_used_cells[i]), selection_pattern->get_cell_alternative_tile(selection_used_cells[i])); + } + undo_redo->create_action(TTR("Move tiles")); + // Move the tiles. + for (Map<Vector2i, TileMapCell>::Element *E = cells_do.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + for (Map<Vector2i, TileMapCell>::Element *E = cells_undo.front(); E; E = E->next()) { + undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + + // Update the selection. + undo_redo->add_undo_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + tile_map_selection.clear(); + for (int i = 0; i < selection_used_cells.size(); i++) { + coords = tile_map->map_pattern(top_left + offset, selection_used_cells[i], selection_pattern); + tile_map_selection.insert(coords); + } + undo_redo->add_do_method(this, "_set_tile_map_selection", _get_tile_map_selection()); + undo_redo->commit_action(); + } break; + case DRAG_TYPE_PICK: { + Rect2i rect = Rect2i(tile_map->world_to_map(drag_start_mouse_pos), tile_map->world_to_map(mpos) - tile_map->world_to_map(drag_start_mouse_pos)).abs(); + rect.size += Vector2i(1, 1); + memdelete(selection_pattern); + TypedArray<Vector2i> coords_array; + for (int x = rect.position.x; x < rect.get_end().x; x++) { + for (int y = rect.position.y; y < rect.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + if (tile_map->get_cell_source_id(coords) != -1) { + coords_array.push_back(coords); + } + } + } + selection_pattern = tile_map->get_pattern(coords_array); + if (!selection_pattern->is_empty()) { + _update_tileset_selection_from_selection_pattern(); + } else { + _update_selection_pattern_from_tileset_selection(); + } + picker_button->set_pressed(false); + } break; + case DRAG_TYPE_PAINT: { + undo_redo->create_action(TTR("Paint tiles")); + for (Map<Vector2i, TileMapCell>::Element *E = drag_modified.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key())); + undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().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 == -1) { + continue; + } + undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(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 == -1) { + continue; + } + undo_redo->add_do_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + undo_redo->add_undo_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(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()) { + if (!erase_button->is_pressed() && E->get().source_id == -1) { + continue; + } + undo_redo->add_do_method(tile_map, "set_cell", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key())); + undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + undo_redo->commit_action(false); + } break; + case DRAG_TYPE_CLIPBOARD_PASTE: { + Vector2 mouse_offset = (Vector2(tile_map_clipboard->get_size()) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); + undo_redo->create_action(TTR("Paste tiles")); + TypedArray<Vector2i> used_cells = tile_map_clipboard->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i coords = tile_map->map_pattern(tile_map->world_to_map(mpos - mouse_offset), used_cells[i], tile_map_clipboard); + undo_redo->add_do_method(tile_map, "set_cell", coords, tile_map_clipboard->get_cell_source_id(used_cells[i]), tile_map_clipboard->get_cell_atlas_coords(used_cells[i]), tile_map_clipboard->get_cell_alternative_tile(used_cells[i])); + undo_redo->add_undo_method(tile_map, "set_cell", coords, tile_map->get_cell_source_id(coords), tile_map->get_cell_atlas_coords(coords), tile_map->get_cell_alternative_tile(coords)); + } + undo_redo->commit_action(); + } break; + default: + break; + } + drag_type = DRAG_TYPE_NONE; +} + +void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); + return; + } + + int source_index = sources_list->get_current(); + if (source_index < 0 || source_index >= sources_list->get_item_count()) { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); + return; + } + + int source_id = sources_list->get_item_metadata(source_index); + + // Clear hovered if needed. + if (source_id != hovered_tile.source_id || + !tile_set->has_source(hovered_tile.source_id) || + !tile_set->get_source(hovered_tile.source_id)->has_tile(hovered_tile.get_atlas_coords()) || + !tile_set->get_source(hovered_tile.source_id)->has_alternative_tile(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)) { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + } + + // Selection if needed. + for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { + const TileMapCell *selected = &(E->get()); + if (!tile_set->has_source(selected->source_id) || + !tile_set->get_source(selected->source_id)->has_tile(selected->get_atlas_coords()) || + !tile_set->get_source(selected->source_id)->has_alternative_tile(selected->get_atlas_coords(), selected->alternative_tile)) { + tile_set_selection.erase(E); + } + } + + if (!tile_map_selection.is_empty()) { + _update_selection_pattern_from_tilemap_selection(); + } else { + _update_selection_pattern_from_tileset_selection(); + } +} + +void TileMapEditorTilesPlugin::_update_selection_pattern_from_tilemap_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + memdelete(selection_pattern); + + TypedArray<Vector2i> coords_array; + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + coords_array.push_back(E->get()); + } + selection_pattern = tile_map->get_pattern(coords_array); +} + +void TileMapEditorTilesPlugin::_update_selection_pattern_from_tileset_selection() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Clear the tilemap selection. + tile_map_selection.clear(); + + // Clear the selected pattern. + selection_pattern->clear(); + + // Group per source. + Map<int, List<const TileMapCell *>> per_source; + for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { + per_source[E->get().source_id].push_back(&(E->get())); + } + + int vertical_offset = 0; + for (Map<int, List<const TileMapCell *>>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) { + // 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()); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + // Organize using coordinates. + for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell *current = E_cell->get(); + if (current->alternative_tile == 0) { + organized_pattern[current->get_atlas_coords()] = current; + } else { + unorganized.push_back(current); + } + } + + // Compute the encompassing rect for the organized pattern. + Map<Vector2i, const TileMapCell *>::Element *E_cell = organized_pattern.front(); + if (E_cell) { + encompassing_rect_coords = Rect2i(E_cell->key(), Vector2i(1, 1)); + for (; E_cell; E_cell = E_cell->next()) { + encompassing_rect_coords.expand_to(E_cell->key() + Vector2i(1, 1)); + encompassing_rect_coords.expand_to(E_cell->key()); + } + } + } else { + // Add everything unorganized. + for (List<const TileMapCell *>::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) { + unorganized.push_back(E_cell->get()); + } + } + + // 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); + } + Vector2i organized_size = selection_pattern->get_size(); + int unorganized_index = 0; + for (List<const TileMapCell *>::Element *E_cell = unorganized.front(); E_cell; E_cell = E_cell->next()) { + selection_pattern->set_cell(Vector2(organized_size.x + unorganized_index, vertical_offset), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + unorganized_index++; + } + vertical_offset += MAX(organized_size.y, 1); + } + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern() { + tile_set_selection.clear(); + TypedArray<Vector2i> used_cells = selection_pattern->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i coords = used_cells[i]; + if (selection_pattern->get_cell_source_id(coords) != -1) { + tile_set_selection.insert(TileMapCell(selection_pattern->get_cell_source_id(coords), selection_pattern->get_cell_atlas_coords(coords), selection_pattern->get_cell_alternative_tile(coords))); + } + } + _update_bottom_panel(); +} + +void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_index = sources_list->get_current(); + if (source_index < 0 || source_index >= sources_list->get_item_count()) { + return; + } + + int source_id = sources_list->get_item_metadata(source_index); + if (!tile_set->has_source(source_id)) { + return; + } + + TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Draw the selection. + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { + if (E->get().source_id == source_id && E->get().alternative_tile == 0) { + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get().get_atlas_coords()), selection_color, false); + } + } + + // Draw the hovered tile. + if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0 && !tile_set_dragging_selection) { + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(hovered_tile.get_atlas_coords()), Color(1.0, 1.0, 1.0), false); + } + + // Draw the selection rect. + if (tile_set_dragging_selection) { + Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos); + Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + + Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs(); + region.size += Vector2i(1, 1); + + Set<Vector2i> to_draw; + for (int x = region.position.x; x < region.get_end().x; x++) { + for (int y = region.position.y; y < region.get_end().y; y++) { + Vector2i tile = atlas->get_tile_at_coords(Vector2i(x, y)); + if (tile != TileSetSource::INVALID_ATLAS_COORDS) { + to_draw.insert(tile); + } + } + } + Color selection_rect_color = selection_color.lightened(0.2); + for (Set<Vector2i>::Element *E = to_draw.front(); E; E = E->next()) { + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get()), selection_rect_color, false); + } + } +} + +void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + tile_set_dragging_selection = false; + tile_atlas_control->update(); +} + +void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_index = sources_list->get_current(); + if (source_index < 0 || source_index >= sources_list->get_item_count()) { + return; + } + + int source_id = sources_list->get_item_metadata(source_index); + if (!tile_set->has_source(source_id)) { + return; + } + + TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Update the hovered tile + hovered_tile.source_id = source_id; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + coords = atlas->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + hovered_tile.set_atlas_coords(coords); + hovered_tile.alternative_tile = 0; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + tile_atlas_control->update(); + alternative_tiles_control->update(); + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { // Pressed + tile_set_dragging_selection = true; + tile_set_drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position(); + if (!mb->is_shift_pressed()) { + tile_set_selection.clear(); + } + + if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) { + if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0))) { + tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0)); + } else { + tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), 0)); + } + } + _update_selection_pattern_from_tileset_selection(); + } else { // Released + if (tile_set_dragging_selection) { + if (!mb->is_shift_pressed()) { + tile_set_selection.clear(); + } + // Compute the covered area. + Vector2i start_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_set_drag_start_mouse_pos); + Vector2i end_tile = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + if (start_tile != TileSetSource::INVALID_ATLAS_COORDS && end_tile != TileSetSource::INVALID_ATLAS_COORDS) { + Rect2i region = Rect2i(start_tile, end_tile - start_tile).abs(); + region.size += Vector2i(1, 1); + + // To update the selection, we copy the selected/not selected status of the tiles we drag from. + Vector2i start_coords = atlas->get_tile_at_coords(start_tile); + if (mb->is_shift_pressed() && start_coords != TileSetSource::INVALID_ATLAS_COORDS && !tile_set_selection.has(TileMapCell(source_id, start_coords, 0))) { + // Remove from the selection. + for (int x = region.position.x; x < region.get_end().x; x++) { + for (int y = region.position.y; y < region.get_end().y; y++) { + Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y)); + if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_selection.has(TileMapCell(source_id, tile_coords, 0))) { + tile_set_selection.erase(TileMapCell(source_id, tile_coords, 0)); + } + } + } + } else { + // Insert in the selection. + for (int x = region.position.x; x < region.get_end().x; x++) { + for (int y = region.position.y; y < region.get_end().y; y++) { + Vector2i tile_coords = atlas->get_tile_at_coords(Vector2i(x, y)); + if (tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { + tile_set_selection.insert(TileMapCell(source_id, tile_coords, 0)); + } + } + } + } + } + _update_selection_pattern_from_tileset_selection(); + } + tile_set_dragging_selection = false; + } + tile_atlas_control->update(); + } +} + +void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_index = sources_list->get_current(); + if (source_index < 0 || source_index >= sources_list->get_item_count()) { + return; + } + + int source_id = sources_list->get_item_metadata(source_index); + if (!tile_set->has_source(source_id)) { + return; + } + + TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Draw the selection. + for (Set<TileMapCell>::Element *E = tile_set_selection.front(); E; E = E->next()) { + if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && E->get().alternative_tile > 0) { + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().get_atlas_coords(), E->get().alternative_tile); + if (rect != Rect2i()) { + alternative_tiles_control->draw_rect(rect, Color(0.2, 0.2, 1.0), false); + } + } + } + + // Draw hovered tile. + if (hovered_tile.get_atlas_coords() != TileSetSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile > 0) { + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile); + if (rect != Rect2i()) { + alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); + } + } +} + +void TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited() { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + tile_set_dragging_selection = false; + alternative_tiles_control->update(); +} + +void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + int source_index = sources_list->get_current(); + if (source_index < 0 || source_index >= sources_list->get_item_count()) { + return; + } + + int source_id = sources_list->get_item_metadata(source_index); + if (!tile_set->has_source(source_id)) { + return; + } + + TileSetAtlasSource *atlas = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Update the hovered tile + hovered_tile.source_id = source_id; + hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + Vector3i alternative_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position()); + Vector2i coords = Vector2i(alternative_coords.x, alternative_coords.y); + int alternative = alternative_coords.z; + if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetSource::INVALID_TILE_ALTERNATIVE) { + hovered_tile.set_atlas_coords(coords); + hovered_tile.alternative_tile = alternative; + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + tile_atlas_control->update(); + alternative_tiles_control->update(); + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { // Pressed + // Left click pressed. + if (!mb->is_shift_pressed()) { + tile_set_selection.clear(); + } + + if (coords != TileSetSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + if (mb->is_shift_pressed() && tile_set_selection.has(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile))) { + tile_set_selection.erase(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)); + } else { + tile_set_selection.insert(TileMapCell(source_id, hovered_tile.get_atlas_coords(), hovered_tile.alternative_tile)); + } + } + _update_selection_pattern_from_tileset_selection(); + } + tile_atlas_control->update(); + alternative_tiles_control->update(); + } +} + +void TileMapEditorTilesPlugin::_set_tile_map_selection(const TypedArray<Vector2i> &p_selection) { + tile_map_selection.clear(); + for (int i = 0; i < p_selection.size(); i++) { + tile_map_selection.insert(p_selection[i]); + } + _update_selection_pattern_from_tilemap_selection(); + _update_tileset_selection_from_selection_pattern(); + CanvasItemEditor::get_singleton()->update_viewport(); +} + +TypedArray<Vector2i> TileMapEditorTilesPlugin::_get_tile_map_selection() const { + TypedArray<Vector2i> output; + for (Set<Vector2i>::Element *E = tile_map_selection.front(); E; E = E->next()) { + output.push_back(E->get()); + } + return output; +} + +void TileMapEditorTilesPlugin::edit(ObjectID p_tile_map_id) { + tile_map_id = p_tile_map_id; + + // Clear the selection. + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); +} + +void TileMapEditorTilesPlugin::_bind_methods() { + ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileMapEditorTilesPlugin::_scene_thumbnail_done); + ClassDB::bind_method(D_METHOD("_set_tile_map_selection", "selection"), &TileMapEditorTilesPlugin::_set_tile_map_selection); + ClassDB::bind_method(D_METHOD("_get_tile_map_selection"), &TileMapEditorTilesPlugin::_get_tile_map_selection); +} + +TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { + CanvasItemEditor::get_singleton()->get_viewport_control()->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_mouse_exited_viewport)); + + // --- Shortcuts --- + ED_SHORTCUT("tiles_editor/cut", TTR("Cut"), KEY_MASK_CMD | KEY_X); + ED_SHORTCUT("tiles_editor/copy", TTR("Copy"), KEY_MASK_CMD | KEY_C); + ED_SHORTCUT("tiles_editor/paste", TTR("Paste"), KEY_MASK_CMD | KEY_V); + ED_SHORTCUT("tiles_editor/cancel", TTR("Cancel"), KEY_ESCAPE); + ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE); + + // --- Toolbar --- + toolbar = memnew(HBoxContainer); + toolbar->set_h_size_flags(SIZE_EXPAND_FILL); + + HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer); + + tool_buttons_group.instantiate(); + + select_tool_button = memnew(Button); + select_tool_button->set_flat(true); + select_tool_button->set_toggle_mode(true); + select_tool_button->set_button_group(tool_buttons_group); + select_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/selection_tool", "Selection", KEY_S)); + select_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(select_tool_button); + + paint_tool_button = memnew(Button); + paint_tool_button->set_flat(true); + paint_tool_button->set_toggle_mode(true); + paint_tool_button->set_button_group(tool_buttons_group); + paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E)); + paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(paint_tool_button); + + line_tool_button = memnew(Button); + line_tool_button->set_flat(true); + line_tool_button->set_toggle_mode(true); + line_tool_button->set_button_group(tool_buttons_group); + line_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/line_tool", "Line", KEY_L)); + line_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(line_tool_button); + + rect_tool_button = memnew(Button); + rect_tool_button->set_flat(true); + rect_tool_button->set_toggle_mode(true); + rect_tool_button->set_button_group(tool_buttons_group); + rect_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/rect_tool", "Rect", KEY_R)); + rect_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(rect_tool_button); + + bucket_tool_button = memnew(Button); + bucket_tool_button->set_flat(true); + bucket_tool_button->set_toggle_mode(true); + bucket_tool_button->set_button_group(tool_buttons_group); + bucket_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/bucket_tool", "Bucket", KEY_B)); + bucket_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTilesPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(bucket_tool_button); + toolbar->add_child(tilemap_tiles_tools_buttons); + + // -- TileMap tool settings -- + tools_settings = memnew(HBoxContainer); + toolbar->add_child(tools_settings); + + tools_settings_vsep = memnew(VSeparator); + tools_settings->add_child(tools_settings_vsep); + + // Picker + picker_button = memnew(Button); + picker_button->set_flat(true); + picker_button->set_toggle_mode(true); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); + tools_settings->add_child(picker_button); + + // Erase button. + erase_button = memnew(Button); + erase_button->set_flat(true); + erase_button->set_toggle_mode(true); + erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); + tools_settings->add_child(erase_button); + + // Separator 2. + tools_settings_vsep_2 = memnew(VSeparator); + tools_settings->add_child(tools_settings_vsep_2); + + // Continuous checkbox. + bucket_continuous_checkbox = memnew(CheckBox); + bucket_continuous_checkbox->set_flat(true); + bucket_continuous_checkbox->set_text(TTR("Contiguous")); + tools_settings->add_child(bucket_continuous_checkbox); + + // Random tile checkbox. + random_tile_checkbox = memnew(CheckBox); + random_tile_checkbox->set_flat(true); + random_tile_checkbox->set_text(TTR("Place Random Tile")); + random_tile_checkbox->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_random_tile_checkbox_toggled)); + tools_settings->add_child(random_tile_checkbox); + + // Random tile scattering. + scatter_label = memnew(Label); + scatter_label->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile.")); + scatter_label->set_text(TTR("Scattering:")); + tools_settings->add_child(scatter_label); + + scatter_spinbox = memnew(SpinBox); + scatter_spinbox->set_min(0.0); + scatter_spinbox->set_max(1000); + scatter_spinbox->set_step(0.001); + scatter_spinbox->set_tooltip(TTR("Defines the probability of painting nothing instead of a randomly selected tile.")); + scatter_spinbox->get_line_edit()->add_theme_constant_override("minimum_character_width", 4); + scatter_spinbox->connect("value_changed", callable_mp(this, &TileMapEditorTilesPlugin::_on_scattering_spinbox_changed)); + tools_settings->add_child(scatter_spinbox); + + _on_random_tile_checkbox_toggled(false); + + // Wide empty separation control. + Control *h_empty_space = memnew(Control); + h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL); + toolbar->add_child(h_empty_space); + + // Grid toggle. + toggle_grid_button = memnew(Button); + toggle_grid_button->set_flat(true); + toggle_grid_button->set_toggle_mode(true); + toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditorTilesPlugin::_on_grid_toggled)); + toolbar->add_child(toggle_grid_button); + + // Default tool. + paint_tool_button->set_pressed(true); + _update_toolbar(); + + // --- Bottom panel --- + set_name("Tiles"); + + missing_source_label = memnew(Label); + missing_source_label->set_text(TTR("This TileMap's TileSet has no source configured. Edit the TileSet resource to add one.")); + missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL); + missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); + missing_source_label->set_align(Label::ALIGN_CENTER); + missing_source_label->set_valign(Label::VALIGN_CENTER); + missing_source_label->hide(); + add_child(missing_source_label); + + atlas_sources_split_container = memnew(HSplitContainer); + atlas_sources_split_container->set_h_size_flags(SIZE_EXPAND_FILL); + atlas_sources_split_container->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(atlas_sources_split_container); + + sources_list = memnew(ItemList); + sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + sources_list->set_h_size_flags(SIZE_EXPAND_FILL); + sources_list->set_stretch_ratio(0.25); + sources_list->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1)); + sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_bottom_panel).unbind(1)); + sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list)); + atlas_sources_split_container->add_child(sources_list); + + // Tile atlas source. + tile_atlas_view = memnew(TileAtlasView); + tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view->set_texture_grid_visible(false); + tile_atlas_view->set_tile_shape_grid_visible(false); + tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform)); + atlas_sources_split_container->add_child(tile_atlas_view); + + tile_atlas_control = memnew(Control); + tile_atlas_control->connect("draw", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_draw)); + tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited)); + tile_atlas_control->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_tile_atlas_control_gui_input)); + tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control); + + alternative_tiles_control = memnew(Control); + alternative_tiles_control->connect("draw", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_draw)); + alternative_tiles_control->connect("mouse_exited", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited)); + alternative_tiles_control->connect("gui_input", callable_mp(this, &TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input)); + tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control); + + // Scenes collection source. + scene_tiles_list = memnew(ItemList); + scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_drag_forwarding(this); + scene_tiles_list->set_select_mode(ItemList::SELECT_MULTI); + scene_tiles_list->connect("multi_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_multi_selected)); + scene_tiles_list->connect("nothing_selected", callable_mp(this, &TileMapEditorTilesPlugin::_scenes_list_nothing_selected)); + scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + atlas_sources_split_container->add_child(scene_tiles_list); + + // Invalid source label. + invalid_source_label = memnew(Label); + invalid_source_label->set_text(TTR("Invalid source selected.")); + invalid_source_label->set_h_size_flags(SIZE_EXPAND_FILL); + invalid_source_label->set_v_size_flags(SIZE_EXPAND_FILL); + invalid_source_label->set_align(Label::ALIGN_CENTER); + invalid_source_label->set_valign(Label::VALIGN_CENTER); + invalid_source_label->hide(); + atlas_sources_split_container->add_child(invalid_source_label); + + _update_bottom_panel(); +} + +TileMapEditorTilesPlugin::~TileMapEditorTilesPlugin() { + memdelete(selection_pattern); + memdelete(tile_map_clipboard); +} + +void TileMapEditorTerrainsPlugin::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + paint_tool_button->set_icon(get_theme_icon("Edit", "EditorIcons")); + picker_button->set_icon(get_theme_icon("ColorPick", "EditorIcons")); + erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons")); + break; + } +} + +void TileMapEditorTerrainsPlugin::tile_set_changed() { + _update_terrains_cache(); + _update_terrains_tree(); + _update_tiles_list(); +} + +void TileMapEditorTerrainsPlugin::_update_toolbar() { + // Hide all settings. + for (int i = 0; i < tools_settings->get_child_count(); i++) { + Object::cast_to<CanvasItem>(tools_settings->get_child(i))->hide(); + } + + // Show only the correct settings. + if (tool_buttons_group->get_pressed_button() == paint_tool_button) { + tools_settings_vsep->show(); + picker_button->show(); + erase_button->show(); + } +} + +Control *TileMapEditorTerrainsPlugin::get_toolbar() const { + return toolbar; +} + +Map<Vector2i, TileSet::CellNeighbor> TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const { + Map<Vector2i, TileSet::CellNeighbor> output; + Ref<TileSet> tile_set = tile_map->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), output); + + TileSet::TileShape shape = tile_set->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + switch (bit) { + case 0: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + break; + default: + ERR_FAIL_V(output); + } + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (bit) { + case 0: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_CORNER)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + break; + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + // Half offset shapes. + TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (bit) { + case 0: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_SIDE; + break; + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_CORNER; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } else { + switch (bit) { + case 0: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 1: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE; + break; + case 2: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)] = TileSet::CELL_NEIGHBOR_LEFT_CORNER; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER; + break; + case 3: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_SIDE; + break; + case 4: + output[base_cell_coords] = TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE; + output[tile_map->get_neighbor_cell(base_cell_coords, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)] = TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE; + break; + default: + ERR_FAIL_V(output); + } + } + } + return output; +} + +TileMapEditorTerrainsPlugin::Constraint::Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain) { + // The way we build the constraint make it easy to detect conflicting constraints. + tile_map = p_tile_map; + + Ref<TileSet> tile_set = tile_map->get_tileset(); + ERR_FAIL_COND(!tile_set.is_valid()); + + TileSet::TileShape shape = tile_set->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE || shape == TileSet::TILE_SHAPE_ISOMETRIC) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 0; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 0; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, p_bit); + break; + default: + ERR_FAIL(); + break; + } + } else { + // Half-offset shapes + TileSet::TileOffsetAxis offset_axis = tile_set->get_tile_offset_axis(); + if (offset_axis == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_SIDE: + bit = 0; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_CORNER: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_LEFT_SIDE: + bit = 0; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_CORNER: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } else { + switch (p_bit) { + case TileSet::CELL_NEIGHBOR_RIGHT_CORNER: + bit = 0; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE: + bit = 1; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_SIDE: + bit = 3; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER: + bit = 0; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE: + bit = 4; + base_cell_coords = p_position; + break; + case TileSet::CELL_NEIGHBOR_LEFT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE: + bit = 1; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER: + bit = 0; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_SIDE: + bit = 3; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER: + bit = 2; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_SIDE); + break; + case TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE: + bit = 4; + base_cell_coords = p_tile_map->get_neighbor_cell(p_position, TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + break; + default: + ERR_FAIL(); + break; + } + } + } + terrain = p_terrain; +} + +Set<TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<Constraint> p_constraints) const { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Set<TerrainsTilePattern>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Set<TerrainsTilePattern>(); + } + + // Returns all tiles compatible with the given constraints. + Set<TerrainsTilePattern> compatible_terrain_tile_patterns; + for (Map<TerrainsTilePattern, Set<TileMapCell>>::Element *E = per_terrain_terrains_tile_patterns_tiles[p_terrain_set].front(); E; E = E->next()) { + int valid = true; + int in_pattern_count = 0; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) { + // Check if the bit is compatible with the constraints. + Constraint terrain_bit_constraint = Constraint(tile_map, p_position, bit, E->key()[in_pattern_count]); + + Set<Constraint>::Element *in_set_constraint_element = p_constraints.find(terrain_bit_constraint); + if (in_set_constraint_element && in_set_constraint_element->get().get_terrain() != terrain_bit_constraint.get_terrain()) { + valid = false; + break; + } + in_pattern_count++; + } + } + + if (valid) { + compatible_terrain_tile_patterns.insert(E->key()); + } + } + + return compatible_terrain_tile_patterns; +} + +Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Set<Constraint>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Set<Constraint>(); + } + + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set<Constraint>()); + + // Build a set of dummy constraints get the constrained points. + Set<Constraint> dummy_constraints; + for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) { + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { // Iterates over sides. + TileSet::CellNeighbor bit = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, bit)) { + dummy_constraints.insert(Constraint(tile_map, E->get(), bit, -1)); + } + } + } + + // For each constrained point, we get all overlapping tiles, and select the most adequate terrain for it. + Set<Constraint> constraints; + for (Set<Constraint>::Element *E = dummy_constraints.front(); E; E = E->next()) { + Constraint c = E->get(); + + Map<int, int> terrain_count; + + // Count the number of occurrences per terrain. + Map<Vector2i, TileSet::CellNeighbor> overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); + for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_overlapping = overlapping_terrain_bits.front(); E_overlapping; E_overlapping = E_overlapping->next()) { + if (!p_to_replace.has(E_overlapping->key())) { + TileMapCell neighbor_cell = tile_map->get_cell(E_overlapping->key()); + TileData *neighbor_tile_data = nullptr; + if (terrain_tiles.has(neighbor_cell) && terrain_tiles[neighbor_cell]->get_terrain_set() == p_terrain_set) { + neighbor_tile_data = terrain_tiles[neighbor_cell]; + } + + int terrain = neighbor_tile_data ? neighbor_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(E_overlapping->get())) : -1; + if (terrain_count.has(terrain)) { + terrain_count[terrain] = 0; + } + terrain_count[terrain] += 1; + } + } + + // Get the terrain with the max number of occurrences. + int max = 0; + int max_terrain = -1; + for (Map<int, int>::Element *E_terrain_count = terrain_count.front(); E_terrain_count; E_terrain_count = E_terrain_count->next()) { + if (E_terrain_count->get() > max) { + max = E_terrain_count->get(); + max_terrain = E_terrain_count->key(); + } + } + + // Set the adequate terrain. + if (max > 0) { + c.set_terrain(max_terrain); + constraints.insert(c); + } + } + + return constraints; +} + +Set<TileMapEditorTerrainsPlugin::Constraint> TileMapEditorTerrainsPlugin::_get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Set<TileMapEditorTerrainsPlugin::Constraint>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Set<TileMapEditorTerrainsPlugin::Constraint>(); + } + + // Compute the constraints needed from the surrounding tiles. + Set<TileMapEditorTerrainsPlugin::Constraint> output; + int in_pattern_count = 0; + for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor side = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(p_terrain_set, side)) { + Constraint c = Constraint(tile_map, p_position, side, p_terrains_tile_pattern[in_pattern_count]); + output.insert(c); + in_pattern_count++; + } + } + + return output; +} + +Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern> TileMapEditorTerrainsPlugin::_wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map<Vector2i, TerrainsTilePattern>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map<Vector2i, TileMapEditorTerrainsPlugin::TerrainsTilePattern>(); + } + + // Copy the constraints set. + Set<TileMapEditorTerrainsPlugin::Constraint> constraints = p_constraints; + + // Compute all acceptable tiles for each cell. + Map<Vector2i, Set<TerrainsTilePattern>> per_cell_acceptable_tiles; + for (Set<Vector2i>::Element *E = p_to_replace.front(); E; E = E->next()) { + per_cell_acceptable_tiles[E->get()] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, E->get(), constraints); + } + + // Output map. + Map<Vector2i, TerrainsTilePattern> output; + + // Add all positions to a set. + Set<Vector2i> to_replace = Set<Vector2i>(p_to_replace); + while (!to_replace.is_empty()) { + // Compute the minimum number of tile possibilities for each cell. + int min_nb_possibilities = 100000000; + for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { + min_nb_possibilities = MIN(min_nb_possibilities, E->get().size()); + } + + // Get the set of possible cells to fill. + LocalVector<Vector2i> to_choose_from; + for (Map<Vector2i, Set<TerrainsTilePattern>>::Element *E = per_cell_acceptable_tiles.front(); E; E = E->next()) { + if (E->get().size() == min_nb_possibilities) { + to_choose_from.push_back(E->key()); + } + } + + // Randomly pick a tile out of the most constrained. + Vector2i selected_cell_to_replace = to_choose_from[Math::random(0, to_choose_from.size() - 1)]; + + // Randomly select a tile out of them the put it in the grid. + Set<TerrainsTilePattern> valid_tiles = per_cell_acceptable_tiles[selected_cell_to_replace]; + if (valid_tiles.is_empty()) { + // No possibilities :/ + break; + } + int random_terrain_tile_pattern_index = Math::random(0, valid_tiles.size() - 1); + Set<TerrainsTilePattern>::Element *E = valid_tiles.front(); + for (int i = 0; i < random_terrain_tile_pattern_index; i++) { + E = E->next(); + } + TerrainsTilePattern selected_terrain_tile_pattern = E->get(); + + // Set the selected cell into the output. + output[selected_cell_to_replace] = selected_terrain_tile_pattern; + to_replace.erase(selected_cell_to_replace); + per_cell_acceptable_tiles.erase(selected_cell_to_replace); + + // Add the new constraints from the added tiles. + Set<TileMapEditorTerrainsPlugin::Constraint> new_constraints = _get_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern); + for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E_constraint = new_constraints.front(); E_constraint; E_constraint = E_constraint->next()) { + constraints.insert(E_constraint->get()); + } + + // Compute valid tiles again for neighbors. + for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor side = TileSet::CellNeighbor(i); + if (tile_map->is_existing_neighbor(side)) { + Vector2i neighbor = tile_map->get_neighbor_cell(selected_cell_to_replace, side); + if (to_replace.has(neighbor)) { + per_cell_acceptable_tiles[neighbor] = _get_valid_terrains_tile_patterns_for_constraints(p_terrain_set, neighbor, constraints); + } + } + } + } + return output; +} + +TileMapCell TileMapEditorTerrainsPlugin::_get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return TileMapCell(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return TileMapCell(); + } + + // Count the sum of probabilities. + double sum = 0.0; + Set<TileMapCell> set = per_terrain_terrains_tile_patterns_tiles[p_terrain_set][p_terrain_tile_pattern]; + for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) { + if (E->get().source_id >= 0) { + Ref<TileSetSource> source = tile_set->get_source(E->get().source_id); + + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); + sum += tile_data->get_probability(); + } else { + sum += 1.0; + } + } else { + sum += 1.0; + } + } + + // Generate a random number. + double count = 0.0; + double picked = Math::random(0.0, sum); + + // Pick the tile. + for (Set<TileMapCell>::Element *E = set.front(); E; E = E->next()) { + if (E->get().source_id >= 0) { + Ref<TileSetSource> source = tile_set->get_source(E->get().source_id); + + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); + count += tile_data->get_probability(); + } else { + count += 1.0; + } + } else { + count += 1.0; + } + + if (count >= picked) { + return E->get(); + } + } + + ERR_FAIL_V(TileMapCell()); +} + +Map<Vector2i, TileMapCell> TileMapEditorTerrainsPlugin::_draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map<Vector2i, TileMapCell>(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map<Vector2i, TileMapCell>(); + } + + Map<Vector2i, TileMapCell> output; + + // Add the constraints from the added tiles. + Set<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(); + + Set<TileMapEditorTerrainsPlugin::Constraint> cell_constraints = _get_constraints_from_added_tile(coords, p_terrain_set, terrains_tile_pattern); + for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = cell_constraints.front(); E; E = E->next()) { + added_tiles_constraints_set.insert(E->get()); + } + } + + // Build the list of potential tiles to replace. + Set<Vector2i> potential_to_replace; + for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { + Vector2i coords = E_to_paint->key(); + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (tile_map->is_existing_neighbor(TileSet::CellNeighbor(i))) { + Vector2i neighbor = tile_map->get_neighbor_cell(coords, TileSet::CellNeighbor(i)); + if (!p_to_paint.has(neighbor)) { + potential_to_replace.insert(neighbor); + } + } + } + } + + // Set of tiles to replace + Set<Vector2i> to_replace; + + // Add the central tiles to the one to replace. TODO: maybe change that. + for (Map<Vector2i, TerrainsTilePattern>::Element *E_to_paint = p_to_paint.front(); E_to_paint; E_to_paint = E_to_paint->next()) { + to_replace.insert(E_to_paint->key()); + } + + // Add the constraints from the surroundings of the modified areas. + Set<TileMapEditorTerrainsPlugin::Constraint> removed_cells_constraints_set; + bool to_replace_modified = true; + while (to_replace_modified) { + // Get the constraints from the removed cells. + removed_cells_constraints_set = _get_constraints_from_removed_cells_list(to_replace, p_terrain_set); + + // Filter the sources to make sure they are in the potential_to_replace. + Map<Constraint, Set<Vector2i>> source_tiles_of_constraint; + for (Set<Constraint>::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) { + Map<Vector2i, TileSet::CellNeighbor> sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits(); + for (Map<Vector2i, TileSet::CellNeighbor>::Element *E_source_tile_of_constraint = sources_of_constraint.front(); E_source_tile_of_constraint; E_source_tile_of_constraint = E_source_tile_of_constraint->next()) { + if (potential_to_replace.has(E_source_tile_of_constraint->key())) { + source_tiles_of_constraint[E->get()].insert(E_source_tile_of_constraint->key()); + } + } + } + + to_replace_modified = false; + for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { + Constraint c = E->get(); + // Check if we have a conflict in constraints. + if (removed_cells_constraints_set.has(c) && removed_cells_constraints_set.find(c)->get().get_terrain() != c.get_terrain()) { + // If we do, we search for a neighbor to remove. + if (source_tiles_of_constraint.has(c) && !source_tiles_of_constraint[c].is_empty()) { + // Remove it. + Vector2i to_add_to_remove = source_tiles_of_constraint[c].front()->get(); + potential_to_replace.erase(to_add_to_remove); + to_replace.insert(to_add_to_remove); + to_replace_modified = true; + for (Map<Constraint, Set<Vector2i>>::Element *E_source_tiles_of_constraint = source_tiles_of_constraint.front(); E_source_tiles_of_constraint; E_source_tiles_of_constraint = E_source_tiles_of_constraint->next()) { + E_source_tiles_of_constraint->get().erase(to_add_to_remove); + } + break; + } + } + } + } + + // Combine all constraints together. + Set<TileMapEditorTerrainsPlugin::Constraint> constraints = removed_cells_constraints_set; + for (Set<TileMapEditorTerrainsPlugin::Constraint>::Element *E = added_tiles_constraints_set.front(); E; E = E->next()) { + constraints.insert(E->get()); + } + + // Run WFC to fill the holes with the constraints. + Map<Vector2i, TerrainsTilePattern> wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints); + + // Use the WFC run for the output. + for (Map<Vector2i, TerrainsTilePattern>::Element *E = wfc_output.front(); E; E = E->next()) { + output[E->key()] = _get_random_tile_from_pattern(p_terrain_set, E->get()); + } + + // 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()); + } + + return output; +} + +bool TileMapEditorTerrainsPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + if (!is_visible_in_tree()) { + // If the bottom editor is not visible, we ignore inputs. + return false; + } + + if (CanvasItemEditor::get_singleton()->get_current_tool() != CanvasItemEditor::TOOL_SELECT) { + return false; + } + + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return false; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return false; + } + + // Get the selected terrain. + TerrainsTilePattern selected_terrains_tile_pattern; + int selected_terrain_set = -1; + + TreeItem *selected_tree_item = terrains_tree->get_selected(); + if (selected_tree_item && selected_tree_item->get_metadata(0)) { + Dictionary metadata_dict = selected_tree_item->get_metadata(0); + // Selected terrain + selected_terrain_set = metadata_dict["terrain_set"]; + + // Selected tile + if (erase_button->is_pressed()) { + selected_terrains_tile_pattern.clear(); + for (uint32_t i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + TileSet::CellNeighbor side = TileSet::CellNeighbor(i); + if (tile_set->is_valid_peering_bit_terrain(selected_terrain_set, side)) { + selected_terrains_tile_pattern.push_back(-1); + } + } + } else if (terrains_tile_list->is_anything_selected()) { + metadata_dict = terrains_tile_list->get_item_metadata(terrains_tile_list->get_selected_items()[0]); + selected_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"]; + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Vector2 mpos = xform.affine_inverse().xform(mm->get_position()); + + switch (drag_type) { + case DRAG_TYPE_PAINT: { + if (selected_terrain_set >= 0) { + Vector<Vector2i> line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); + Map<Vector2i, TerrainsTilePattern> to_draw; + for (int i = 0; i < line.size(); i++) { + to_draw[line[i]] = selected_terrains_tile_pattern; + } + Map<Vector2i, TileMapCell> modified = _draw_terrains(to_draw, selected_terrain_set); + for (Map<Vector2i, TileMapCell>::Element *E = modified.front(); E; E = E->next()) { + if (!drag_modified.has(E->key())) { + drag_modified[E->key()] = tile_map->get_cell(E->key()); + } + tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + } + } break; + default: + break; + } + drag_last_mouse_pos = mpos; + CanvasItemEditor::get_singleton()->update_viewport(); + + return true; + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + 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->is_pressed()) { + // Pressed + if (picker_button->is_pressed()) { + drag_type = DRAG_TYPE_PICK; + } else { + // Paint otherwise. + if (selected_terrain_set >= 0 && !selected_terrains_tile_pattern.is_empty() && tool_buttons_group->get_pressed_button() == paint_tool_button) { + drag_type = DRAG_TYPE_PAINT; + drag_start_mouse_pos = mpos; + + drag_modified.clear(); + + Map<Vector2i, TerrainsTilePattern> terrains_to_draw; + terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern; + + Map<Vector2i, TileMapCell> to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set); + for (Map<Vector2i, TileMapCell>::Element *E = to_draw.front(); E; E = E->next()) { + drag_modified[E->key()] = tile_map->get_cell(E->key()); + tile_map->set_cell(E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + } + } + } else { + // Released + switch (drag_type) { + case DRAG_TYPE_PICK: { + Vector2i coords = tile_map->world_to_map(mpos); + TileMapCell tile = tile_map->get_cell(coords); + + if (terrain_tiles.has(tile)) { + Array terrains_tile_pattern = _build_terrains_tile_pattern(terrain_tiles[tile]); + + // Find the tree item for the right terrain set. + bool need_tree_item_switch = true; + TreeItem *tree_item = terrains_tree->get_selected(); + if (tree_item) { + Dictionary metadata_dict = tree_item->get_metadata(0); + if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) { + int terrain_set = metadata_dict["terrain_set"]; + int terrain_id = metadata_dict["terrain_id"]; + if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) { + need_tree_item_switch = false; + } + } + } + + if (need_tree_item_switch) { + for (tree_item = terrains_tree->get_root()->get_first_child(); tree_item; tree_item = tree_item->get_next_visible()) { + Dictionary metadata_dict = tree_item->get_metadata(0); + if (metadata_dict.has("terrain_set") && metadata_dict.has("terrain_id")) { + int terrain_set = metadata_dict["terrain_set"]; + int terrain_id = metadata_dict["terrain_id"]; + if (per_terrain_terrains_tile_patterns[terrain_set][terrain_id].has(terrains_tile_pattern)) { + // Found + tree_item->select(0); + _update_tiles_list(); + break; + } + } + } + } + + // Find the list item for the given tile. + if (tree_item) { + for (int i = 0; i < terrains_tile_list->get_item_count(); i++) { + Dictionary metadata_dict = terrains_tile_list->get_item_metadata(i); + TerrainsTilePattern in_meta_terrains_tile_pattern = metadata_dict["terrains_tile_pattern"]; + bool equals = true; + for (int j = 0; j < terrains_tile_pattern.size(); j++) { + if (terrains_tile_pattern[j] != in_meta_terrains_tile_pattern[j]) { + equals = false; + break; + } + } + if (equals) { + terrains_tile_list->select(i); + break; + } + } + } else { + ERR_PRINT("Terrain tile not found."); + } + } + picker_button->set_pressed(false); + } 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", E->key(), tile_map->get_cell_source_id(E->key()), tile_map->get_cell_atlas_coords(E->key()), tile_map->get_cell_alternative_tile(E->key())); + undo_redo->add_undo_method(tile_map, "set_cell", E->key(), E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); + } + undo_redo->commit_action(false); + } break; + default: + break; + } + drag_type = DRAG_TYPE_NONE; + } + + CanvasItemEditor::get_singleton()->update_viewport(); + + return true; + } + drag_last_mouse_pos = mpos; + } + + return false; +} + +TileMapEditorTerrainsPlugin::TerrainsTilePattern TileMapEditorTerrainsPlugin::_build_terrains_tile_pattern(TileData *p_tile_data) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return TerrainsTilePattern(); + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return TerrainsTilePattern(); + } + + TerrainsTilePattern output; + for (int i = 0; i < TileSet::CELL_NEIGHBOR_MAX; i++) { + if (tile_set->is_valid_peering_bit_terrain(p_tile_data->get_terrain_set(), TileSet::CellNeighbor(i))) { + output.push_back(p_tile_data->get_peering_bit_terrain(TileSet::CellNeighbor(i))); + } + } + return output; +} + +void TileMapEditorTerrainsPlugin::_update_terrains_cache() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Compute the tile sides. + tile_sides.clear(); + TileSet::TileShape shape = tile_set->get_tile_shape(); + if (shape == TileSet::TILE_SHAPE_SQUARE) { + tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE); + } else if (shape == TileSet::TILE_SHAPE_ISOMETRIC) { + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + } else { + if (tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_HORIZONTAL) { + tile_sides.push_back(TileSet::CELL_NEIGHBOR_RIGHT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + } else { + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_SIDE); + tile_sides.push_back(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE); + } + } + + // Organizes tiles into structures. + per_terrain_terrains_tile_patterns_tiles.resize(tile_set->get_terrain_sets_count()); + per_terrain_terrains_tile_patterns.resize(tile_set->get_terrain_sets_count()); + for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) { + per_terrain_terrains_tile_patterns_tiles[i].clear(); + per_terrain_terrains_tile_patterns[i].resize(tile_set->get_terrains_count(i)); + for (int j = 0; j < (int)per_terrain_terrains_tile_patterns[i].size(); j++) { + per_terrain_terrains_tile_patterns[i][j].clear(); + } + } + + for (int source_index = 0; source_index < tile_set->get_source_count(); source_index++) { + int source_id = tile_set->get_source_id(source_index); + Ref<TileSetSource> source = tile_set->get_source(source_id); + + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + for (int tile_index = 0; tile_index < source->get_tiles_count(); tile_index++) { + Vector2i tile_id = source->get_tile_id(tile_index); + for (int alternative_index = 0; alternative_index < source->get_alternative_tiles_count(tile_id); alternative_index++) { + int alternative_id = source->get_alternative_tile_id(tile_id, alternative_index); + + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + int terrain_set = tile_data->get_terrain_set(); + if (terrain_set >= 0) { + ERR_FAIL_INDEX(terrain_set, (int)per_terrain_terrains_tile_patterns.size()); + + TileMapCell cell; + cell.source_id = source_id; + cell.set_atlas_coords(tile_id); + cell.alternative_tile = alternative_id; + + TerrainsTilePattern terrains_tile_pattern = _build_terrains_tile_pattern(tile_data); + + // Terrain bits. + for (int i = 0; i < terrains_tile_pattern.size(); i++) { + int terrain = terrains_tile_pattern[i]; + if (terrain >= 0 && terrain < (int)per_terrain_terrains_tile_patterns[terrain_set].size()) { + per_terrain_terrains_tile_patterns[terrain_set][terrain].insert(terrains_tile_pattern); + terrain_tiles[cell] = tile_data; + per_terrain_terrains_tile_patterns_tiles[terrain_set][terrains_tile_pattern].insert(cell); + } + } + } + } + } + } + } + + // Add the empty cell in the possible patterns and cells. + for (int i = 0; i < tile_set->get_terrain_sets_count(); i++) { + TerrainsTilePattern empty_pattern; + for (int j = 0; j < TileSet::CELL_NEIGHBOR_MAX; j++) { + if (tile_set->is_valid_peering_bit_terrain(i, TileSet::CellNeighbor(j))) { + empty_pattern.push_back(-1); + } + } + + TileMapCell empty_cell; + empty_cell.source_id = -1; + empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell); + } +} + +void TileMapEditorTerrainsPlugin::_update_terrains_tree() { + terrains_tree->clear(); + terrains_tree->create_item(); + + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Fill in the terrain list. + Vector<Vector<Ref<Texture2D>>> icons = tile_set->generate_terrains_icons(Size2(16, 16) * EDSCALE); + for (int terrain_set_index = 0; terrain_set_index < tile_set->get_terrain_sets_count(); terrain_set_index++) { + // Add an item for the terrain set. + TreeItem *terrain_set_tree_item = terrains_tree->create_item(); + String matches; + if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS_AND_SIDES) { + terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchCornersAndSides", "EditorIcons")); + matches = String(TTR("Matches Corners and Sides")); + } else if (tile_set->get_terrain_set_mode(terrain_set_index) == TileSet::TERRAIN_MODE_MATCH_CORNERS) { + terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchCorners", "EditorIcons")); + matches = String(TTR("Matches Corners Only")); + } else { + terrain_set_tree_item->set_icon(0, get_theme_icon("TerrainMatchSides", "EditorIcons")); + matches = String(TTR("Matches Sides Only")); + } + terrain_set_tree_item->set_text(0, vformat("Terrain Set %d (%s)", terrain_set_index, matches)); + terrain_set_tree_item->set_selectable(0, false); + + for (int terrain_index = 0; terrain_index < tile_set->get_terrains_count(terrain_set_index); terrain_index++) { + // Add the item to the terrain list. + TreeItem *terrain_tree_item = terrains_tree->create_item(terrain_set_tree_item); + terrain_tree_item->set_text(0, tile_set->get_terrain_name(terrain_set_index, terrain_index)); + terrain_tree_item->set_icon_max_width(0, 32 * EDSCALE); + terrain_tree_item->set_icon(0, icons[terrain_set_index][terrain_index]); + + Dictionary metadata_dict; + metadata_dict["terrain_set"] = terrain_set_index; + metadata_dict["terrain_id"] = terrain_index; + terrain_tree_item->set_metadata(0, metadata_dict); + } + } +} + +void TileMapEditorTerrainsPlugin::_update_tiles_list() { + terrains_tile_list->clear(); + + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + TreeItem *selected_tree_item = terrains_tree->get_selected(); + if (selected_tree_item && selected_tree_item->get_metadata(0)) { + Dictionary metadata_dict = selected_tree_item->get_metadata(0); + int selected_terrain_set = metadata_dict["terrain_set"]; + int selected_terrain_id = metadata_dict["terrain_id"]; + ERR_FAIL_INDEX(selected_terrain_set, (int)per_terrain_terrains_tile_patterns.size()); + ERR_FAIL_INDEX(selected_terrain_id, (int)per_terrain_terrains_tile_patterns[selected_terrain_set].size()); + + // Sort the items in a map by the number of corresponding terrains. + Map<int, Set<TerrainsTilePattern>> sorted; + for (Set<TerrainsTilePattern>::Element *E = per_terrain_terrains_tile_patterns[selected_terrain_set][selected_terrain_id].front(); E; E = E->next()) { + // Count the number of matching sides/terrains. + int count = 0; + + for (int i = 0; i < E->get().size(); i++) { + if (int(E->get()[i]) == selected_terrain_id) { + count++; + } + } + sorted[count].insert(E->get()); + } + + for (Map<int, Set<TerrainsTilePattern>>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) { + for (Set<TerrainsTilePattern>::Element *E = E_set->get().front(); E; E = E->next()) { + TerrainsTilePattern terrains_tile_pattern = E->get(); + + // Get the icon. + Ref<Texture2D> icon; + Rect2 region; + bool transpose = false; + + double max_probability = -1.0; + for (Set<TileMapCell>::Element *E_tile_map_cell = per_terrain_terrains_tile_patterns_tiles[selected_terrain_set][terrains_tile_pattern].front(); E_tile_map_cell; E_tile_map_cell = E_tile_map_cell->next()) { + Ref<TileSetSource> source = tile_set->get_source(E_tile_map_cell->get().source_id); + + Ref<TileSetAtlasSource> atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(E_tile_map_cell->get().get_atlas_coords(), E_tile_map_cell->get().alternative_tile)); + if (tile_data->get_probability() > max_probability) { + icon = atlas_source->get_texture(); + region = atlas_source->get_tile_texture_region(E_tile_map_cell->get().get_atlas_coords()); + if (tile_data->get_flip_h()) { + region.position.x += region.size.x; + region.size.x = -region.size.x; + } + if (tile_data->get_flip_v()) { + region.position.y += region.size.y; + region.size.y = -region.size.y; + } + transpose = tile_data->get_transpose(); + max_probability = tile_data->get_probability(); + } + } + } + + // Create the ItemList's item. + int item_index = terrains_tile_list->add_item(""); + terrains_tile_list->set_item_icon(item_index, icon); + terrains_tile_list->set_item_icon_region(item_index, region); + terrains_tile_list->set_item_icon_transposed(item_index, transpose); + Dictionary list_metadata_dict; + list_metadata_dict["terrains_tile_pattern"] = terrains_tile_pattern; + terrains_tile_list->set_item_metadata(item_index, list_metadata_dict); + } + } + if (terrains_tile_list->get_item_count() > 0) { + terrains_tile_list->select(0); + } + } +} + +void TileMapEditorTerrainsPlugin::edit(ObjectID p_tile_map_id) { + tile_map_id = p_tile_map_id; + _update_terrains_cache(); + _update_terrains_tree(); + _update_tiles_list(); +} + +TileMapEditorTerrainsPlugin::TileMapEditorTerrainsPlugin() { + set_name("Terrains"); + + HSplitContainer *tilemap_tab_terrains = memnew(HSplitContainer); + tilemap_tab_terrains->set_h_size_flags(SIZE_EXPAND_FILL); + tilemap_tab_terrains->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(tilemap_tab_terrains); + + terrains_tree = memnew(Tree); + terrains_tree->set_h_size_flags(SIZE_EXPAND_FILL); + terrains_tree->set_stretch_ratio(0.25); + terrains_tree->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + terrains_tree->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + terrains_tree->set_hide_root(true); + terrains_tree->connect("item_selected", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_tiles_list)); + tilemap_tab_terrains->add_child(terrains_tree); + + terrains_tile_list = memnew(ItemList); + terrains_tile_list->set_h_size_flags(SIZE_EXPAND_FILL); + terrains_tile_list->set_max_columns(0); + terrains_tile_list->set_same_column_width(true); + terrains_tile_list->set_fixed_icon_size(Size2(30, 30) * EDSCALE); + terrains_tile_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + tilemap_tab_terrains->add_child(terrains_tile_list); + + // --- Toolbar --- + toolbar = memnew(HBoxContainer); + + HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer); + + tool_buttons_group.instantiate(); + + paint_tool_button = memnew(Button); + paint_tool_button->set_flat(true); + paint_tool_button->set_toggle_mode(true); + paint_tool_button->set_button_group(tool_buttons_group); + paint_tool_button->set_pressed(true); + paint_tool_button->set_shortcut(ED_SHORTCUT("tiles_editor/paint_tool", "Paint", KEY_E)); + paint_tool_button->connect("pressed", callable_mp(this, &TileMapEditorTerrainsPlugin::_update_toolbar)); + tilemap_tiles_tools_buttons->add_child(paint_tool_button); + + toolbar->add_child(tilemap_tiles_tools_buttons); + + // -- TileMap tool settings -- + tools_settings = memnew(HBoxContainer); + toolbar->add_child(tools_settings); + + tools_settings_vsep = memnew(VSeparator); + tools_settings->add_child(tools_settings_vsep); + + // Picker + picker_button = memnew(Button); + picker_button->set_flat(true); + picker_button->set_toggle_mode(true); + picker_button->set_shortcut(ED_SHORTCUT("tiles_editor/picker", "Picker", KEY_P)); + picker_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); + tools_settings->add_child(picker_button); + + // Erase button. + erase_button = memnew(Button); + erase_button->set_flat(true); + erase_button->set_toggle_mode(true); + erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + erase_button->connect("pressed", callable_mp(CanvasItemEditor::get_singleton(), &CanvasItemEditor::update_viewport)); + tools_settings->add_child(erase_button); +} + +TileMapEditorTerrainsPlugin::~TileMapEditorTerrainsPlugin() { +} + +void TileMapEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + missing_tile_texture = get_theme_icon("StatusWarning", "EditorIcons"); + warning_pattern_texture = get_theme_icon("WarningPattern", "EditorIcons"); + break; + case NOTIFICATION_INTERNAL_PROCESS: + if (is_visible_in_tree() && tileset_changed_needs_update) { + _update_bottom_panel(); + tile_map_editor_plugins[tabs->get_current_tab()]->tile_set_changed(); + CanvasItemEditor::get_singleton()->update_viewport(); + tileset_changed_needs_update = false; + } + break; + } +} + +void TileMapEditor::_update_bottom_panel() { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + Ref<TileSet> tile_set = tile_map->get_tileset(); + + // Update the visibility of controls. + missing_tileset_label->set_visible(!tile_set.is_valid()); + if (!tile_set.is_valid()) { + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + tile_map_editor_plugins[i]->hide(); + } + } else { + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab()); + } + } +} + +Vector<Vector2i> TileMapEditor::get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell) { + ERR_FAIL_COND_V(!p_tile_map, Vector<Vector2i>()); + + Ref<TileSet> tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector<Vector2i>()); + + if (tile_set->get_tile_shape() == TileSet::TILE_SHAPE_SQUARE) { + return Geometry2D::bresenham_line(p_from_cell, p_to_cell); + } else { + // Adapt the bresenham line algorithm to half-offset shapes. + // See this blog post: http://zvold.blogspot.com/2010/01/bresenhams-line-drawing-algorithm-on_26.html + Vector<Point2i> points; + + bool transposed = tile_set->get_tile_offset_axis() == TileSet::TILE_OFFSET_AXIS_VERTICAL; + p_from_cell = TileMap::transform_coords_layout(p_from_cell, tile_set->get_tile_offset_axis(), tile_set->get_tile_layout(), TileSet::TILE_LAYOUT_STACKED); + p_to_cell = TileMap::transform_coords_layout(p_to_cell, tile_set->get_tile_offset_axis(), tile_set->get_tile_layout(), TileSet::TILE_LAYOUT_STACKED); + if (transposed) { + SWAP(p_from_cell.x, p_from_cell.y); + SWAP(p_to_cell.x, p_to_cell.y); + } + + Vector2i delta = p_to_cell - p_from_cell; + delta = Vector2i(2 * delta.x + ABS(p_to_cell.y % 2) - ABS(p_from_cell.y % 2), delta.y); + Vector2i sign = delta.sign(); + + Vector2i current = p_from_cell; + points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout())); + + int err = 0; + if (ABS(delta.y) < ABS(delta.x)) { + Vector2i err_step = 3 * delta.abs(); + while (current != p_to_cell) { + err += err_step.y; + if (err > ABS(delta.x)) { + if (sign.x == 0) { + current += Vector2(sign.y, 0); + } else { + current += Vector2(bool(current.y % 2) ^ (sign.x < 0) ? sign.x : 0, sign.y); + } + err -= err_step.x; + } else { + current += Vector2i(sign.x, 0); + err += err_step.y; + } + points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout())); + } + } else { + Vector2i err_step = delta.abs(); + while (current != p_to_cell) { + err += err_step.x; + if (err > 0) { + if (sign.x == 0) { + current += Vector2(0, sign.y); + } else { + current += Vector2(bool(current.y % 2) ^ (sign.x < 0) ? sign.x : 0, sign.y); + } + err -= err_step.y; + } else { + if (sign.x == 0) { + current += Vector2(0, sign.y); + } else { + current += Vector2(bool(current.y % 2) ^ (sign.x > 0) ? -sign.x : 0, sign.y); + } + err += err_step.y; + } + points.push_back(TileMap::transform_coords_layout(transposed ? Vector2i(current.y, current.x) : current, tile_set->get_tile_offset_axis(), TileSet::TILE_LAYOUT_STACKED, tile_set->get_tile_layout())); + } + } + + return points; + } +} + +void TileMapEditor::_tile_map_changed() { + tileset_changed_needs_update = true; +} + +void TileMapEditor::_tab_changed(int p_tab_id) { + // Make the plugin edit the correct tilemap. + tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id); + + // Update toolbar. + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + tile_map_editor_plugins[i]->get_toolbar()->set_visible(i == p_tab_id); + } + + // Update visible panel. + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map || !tile_map->get_tileset().is_valid()) { + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + tile_map_editor_plugins[i]->hide(); + } + } else { + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + tile_map_editor_plugins[i]->set_visible(i == tabs->get_current_tab()); + } + } + + // Graphical update. + tile_map_editor_plugins[tabs->get_current_tab()]->update(); + CanvasItemEditor::get_singleton()->update_viewport(); +} + +bool TileMapEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + return tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_gui_input(p_event); +} + +void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref<TileSet> tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + if (!tile_map->is_visible_in_tree()) { + return; + } + + Transform2D xform = CanvasItemEditor::get_singleton()->get_canvas_transform() * tile_map->get_global_transform(); + Transform2D xform_inv = xform.affine_inverse(); + Vector2i tile_shape_size = tile_set->get_tile_size(); + + // Draw tiles with invalid IDs in the grid. + float icon_ratio = MIN(missing_tile_texture->get_size().x / tile_set->get_tile_size().x, missing_tile_texture->get_size().y / tile_set->get_tile_size().y) / 3; + TypedArray<Vector2i> used_cells = tile_map->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i coords = used_cells[i]; + int tile_source_id = tile_map->get_cell_source_id(coords); + if (tile_source_id >= 0) { + Vector2i tile_atlas_coords = tile_map->get_cell_atlas_coords(coords); + int tile_alternative_tile = tile_map->get_cell_alternative_tile(coords); + + TileSetSource *source = nullptr; + if (tile_set->has_source(tile_source_id)) { + source = *tile_set->get_source(tile_source_id); + } + + if (!source || !source->has_tile(tile_atlas_coords) || !source->has_alternative_tile(tile_atlas_coords, tile_alternative_tile)) { + // Generate a random color from the hashed values of the tiles. + Array to_hash; + to_hash.push_back(tile_source_id); + to_hash.push_back(tile_atlas_coords); + to_hash.push_back(tile_alternative_tile); + uint32_t hash = RandomPCG(to_hash.hash()).rand(); + + Color color; + color = color.from_hsv( + (float)((hash >> 24) & 0xFF) / 256.0, + Math::lerp(0.5, 1.0, (float)((hash >> 16) & 0xFF) / 256.0), + Math::lerp(0.5, 1.0, (float)((hash >> 8) & 0xFF) / 256.0), + 0.8); + + // Draw the scaled tile. + Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - Vector2(tile_shape_size) / 2, Vector2(tile_shape_size))); + tile_set->draw_tile_shape(p_overlay, cell_region, color, true, warning_pattern_texture); + + // Draw the warning icon. + Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_ratio * missing_tile_texture->get_size() * xform.get_scale() / 2), icon_ratio * missing_tile_texture->get_size() * xform.get_scale()); + p_overlay->draw_texture_rect(missing_tile_texture, rect); + } + } + } + + // Fading on the border. + const int fading = 5; + + // Determine the drawn area. + Size2 screen_size = p_overlay->get_size(); + Rect2i screen_rect; + screen_rect.position = tile_map->world_to_map(xform_inv.xform(Vector2())); + screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(0, screen_size.height)))); + screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(Vector2(screen_size.width, 0)))); + screen_rect.expand_to(tile_map->world_to_map(xform_inv.xform(screen_size))); + screen_rect = screen_rect.grow(1); + + Rect2i tilemap_used_rect = tile_map->get_used_rect(); + + Rect2i displayed_rect = tilemap_used_rect.intersection(screen_rect); + displayed_rect = displayed_rect.grow(fading); + + // Reduce the drawn area to avoid crashes if needed. + int max_size = 100; + if (displayed_rect.size.x > max_size) { + displayed_rect = displayed_rect.grow_individual(-(displayed_rect.size.x - max_size) / 2, 0, -(displayed_rect.size.x - max_size) / 2, 0); + } + if (displayed_rect.size.y > max_size) { + displayed_rect = displayed_rect.grow_individual(0, -(displayed_rect.size.y - max_size) / 2, 0, -(displayed_rect.size.y - max_size) / 2); + } + + // Draw the grid. + bool display_grid = EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid"); + if (display_grid) { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) { + for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) { + Vector2i pos_in_rect = Vector2i(x, y) - displayed_rect.position; + + // Fade out the border of the grid. + float left_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.x), 0.0f, 1.0f); + float right_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.x, (float)(displayed_rect.size.x - fading), (float)pos_in_rect.x), 0.0f, 1.0f); + float top_opacity = CLAMP(Math::inverse_lerp(0.0f, (float)fading, (float)pos_in_rect.y), 0.0f, 1.0f); + float bottom_opacity = CLAMP(Math::inverse_lerp((float)displayed_rect.size.y, (float)(displayed_rect.size.y - fading), (float)pos_in_rect.y), 0.0f, 1.0f); + float opacity = CLAMP(MIN(left_opacity, MIN(right_opacity, MIN(top_opacity, bottom_opacity))) + 0.1, 0.0f, 1.0f); + + Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(Vector2(x, y)) - tile_shape_size / 2, tile_shape_size)); + Color color = grid_color; + color.a = color.a * opacity; + tile_set->draw_tile_shape(p_overlay, cell_region, color, false); + } + } + } + + // Draw the IDs for debug. + /*Ref<Font> font = get_theme_font("font", "Label"); + for (int x = displayed_rect.position.x; x < (displayed_rect.position.x + displayed_rect.size.x); x++) { + for (int y = displayed_rect.position.y; y < (displayed_rect.position.y + displayed_rect.size.y); y++) { + p_overlay->draw_string(font, xform.xform(tile_map->map_to_world(Vector2(x, y))) + Vector2i(-tile_shape_size.x / 2, 0), vformat("%s", Vector2(x, y))); + } + }*/ + + // Draw the plugins. + tile_map_editor_plugins[tabs->get_current_tab()]->forward_canvas_draw_over_viewport(p_overlay); +} + +void TileMapEditor::edit(TileMap *p_tile_map) { + if (p_tile_map && p_tile_map->get_instance_id() == tile_map_id) { + return; + } + + // Disconnect to changes. + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map) { + tile_map->disconnect("changed", callable_mp(this, &TileMapEditor::_tile_map_changed)); + } + + // Change the edited object. + if (p_tile_map) { + tile_map_id = p_tile_map->get_instance_id(); + tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + // Connect to changes. + if (!tile_map->is_connected("changed", callable_mp(this, &TileMapEditor::_tile_map_changed))) { + tile_map->connect("changed", callable_mp(this, &TileMapEditor::_tile_map_changed)); + } + } else { + tile_map_id = ObjectID(); + } + + // Call the plugins. + tile_map_editor_plugins[tabs->get_current_tab()]->edit(tile_map_id); + + _tile_map_changed(); +} + +TileMapEditor::TileMapEditor() { + set_process_internal(true); + + // TileMap editor plugins + tile_map_editor_plugins.push_back(memnew(TileMapEditorTilesPlugin)); + tile_map_editor_plugins.push_back(memnew(TileMapEditorTerrainsPlugin)); + + // Tabs. + tabs = memnew(Tabs); + tabs->set_clip_tabs(false); + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + tabs->add_tab(tile_map_editor_plugins[i]->get_name()); + } + tabs->connect("tab_changed", callable_mp(this, &TileMapEditor::_tab_changed)); + + // --- TileMap toolbar --- + tilemap_toolbar = memnew(HBoxContainer); + tilemap_toolbar->set_h_size_flags(SIZE_EXPAND_FILL); + tilemap_toolbar->add_child(tabs); + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + tile_map_editor_plugins[i]->get_toolbar()->hide(); + tilemap_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar()); + } + + missing_tileset_label = memnew(Label); + missing_tileset_label->set_text(TTR("The edited TileMap node has no TileSet resource.")); + missing_tileset_label->set_h_size_flags(SIZE_EXPAND_FILL); + missing_tileset_label->set_v_size_flags(SIZE_EXPAND_FILL); + missing_tileset_label->set_align(Label::ALIGN_CENTER); + missing_tileset_label->set_valign(Label::VALIGN_CENTER); + missing_tileset_label->hide(); + add_child(missing_tileset_label); + + for (int i = 0; i < tile_map_editor_plugins.size(); i++) { + add_child(tile_map_editor_plugins[i]); + tile_map_editor_plugins[i]->set_h_size_flags(SIZE_EXPAND_FILL); + tile_map_editor_plugins[i]->set_v_size_flags(SIZE_EXPAND_FILL); + tile_map_editor_plugins[i]->set_visible(i == 0); + } + + _tab_changed(0); +} + +TileMapEditor::~TileMapEditor() { +} diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h new file mode 100644 index 0000000000..a6f4ec3021 --- /dev/null +++ b/editor/plugins/tiles/tile_map_editor.h @@ -0,0 +1,343 @@ +/*************************************************************************/ +/* tile_map_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_MAP_EDITOR_H +#define TILE_MAP_EDITOR_H + +#include "tile_atlas_view.h" + +#include "core/typedefs.h" +#include "editor/editor_node.h" +#include "scene/2d/tile_map.h" +#include "scene/gui/box_container.h" +#include "scene/gui/tabs.h" + +class TileMapEditorPlugin : public VBoxContainer { +public: + virtual Control *get_toolbar() const { + return memnew(Control); + }; + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return false; }; + virtual void forward_canvas_draw_over_viewport(Control *p_overlay){}; + virtual void tile_set_changed(){}; + virtual void edit(ObjectID p_tile_map_id){}; +}; + +class TileMapEditorTilesPlugin : public TileMapEditorPlugin { + GDCLASS(TileMapEditorTilesPlugin, TileMapEditorPlugin); + +private: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + ObjectID tile_map_id; + virtual void edit(ObjectID p_tile_map_id) override; + + ///// Toolbar ///// + HBoxContainer *toolbar; + + Ref<ButtonGroup> tool_buttons_group; + Button *select_tool_button; + Button *paint_tool_button; + Button *line_tool_button; + Button *rect_tool_button; + Button *bucket_tool_button; + Button *picker_button; + + HBoxContainer *tools_settings; + VSeparator *tools_settings_vsep; + Button *erase_button; + CheckBox *bucket_continuous_checkbox; + + VSeparator *tools_settings_vsep_2; + CheckBox *random_tile_checkbox; + float scattering = 0.0; + Label *scatter_label; + SpinBox *scatter_spinbox; + void _on_random_tile_checkbox_toggled(bool p_pressed); + void _on_scattering_spinbox_changed(double p_value); + + Button *toggle_grid_button; + void _on_grid_toggled(bool p_pressed); + + void _update_toolbar(); + + ///// Tilemap editing. ///// + bool has_mouse = false; + void _mouse_exited_viewport(); + + enum DragType { + DRAG_TYPE_NONE = 0, + DRAG_TYPE_SELECT, + DRAG_TYPE_MOVE, + DRAG_TYPE_PAINT, + DRAG_TYPE_LINE, + DRAG_TYPE_RECT, + DRAG_TYPE_BUCKET, + DRAG_TYPE_PICK, + DRAG_TYPE_CLIPBOARD_PASTE, + }; + DragType drag_type = DRAG_TYPE_NONE; + Vector2 drag_start_mouse_pos; + Vector2 drag_last_mouse_pos; + Map<Vector2i, TileMapCell> drag_modified; + + TileMapCell _pick_random_tile(const TileMapPattern *p_pattern); + Map<Vector2i, TileMapCell> _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2 p_to_mouse_pos); + Map<Vector2i, TileMapCell> _draw_rect(Vector2i p_start_cell, Vector2i p_end_cell); + Map<Vector2i, TileMapCell> _draw_bucket_fill(Vector2i p_coords, bool p_contiguous); + void _stop_dragging(); + + ///// Selection system. ///// + Set<Vector2i> tile_map_selection; + TileMapPattern *tile_map_clipboard = memnew(TileMapPattern); + TileMapPattern *selection_pattern = memnew(TileMapPattern); + void _set_tile_map_selection(const TypedArray<Vector2i> &p_selection); + TypedArray<Vector2i> _get_tile_map_selection() const; + + Set<TileMapCell> tile_set_selection; + + void _update_selection_pattern_from_tilemap_selection(); + void _update_selection_pattern_from_tileset_selection(); + void _update_tileset_selection_from_selection_pattern(); + void _update_fix_selected_and_hovered(); + + ///// Bottom panel. ////. + Label *missing_source_label; + Label *invalid_source_label; + + ItemList *sources_list; + + Ref<Texture2D> missing_atlas_texture_icon; + void _update_tile_set_sources_list(); + + void _update_bottom_panel(); + + // Atlas sources. + TileMapCell hovered_tile; + TileAtlasView *tile_atlas_view; + HSplitContainer *atlas_sources_split_container; + + bool tile_set_dragging_selection = false; + Vector2i tile_set_drag_start_mouse_pos; + + Control *tile_atlas_control; + void _tile_atlas_control_mouse_exited(); + void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event); + void _tile_atlas_control_draw(); + + Control *alternative_tiles_control; + void _tile_alternatives_control_draw(); + void _tile_alternatives_control_mouse_exited(); + void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event); + + void _update_atlas_view(); + + // Scenes collection sources. + ItemList *scene_tiles_list; + + void _update_scenes_collection_view(); + void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud); + void _scenes_list_multi_selected(int p_index, bool p_selected); + void _scenes_list_nothing_selected(); + + // Update callback + virtual void tile_set_changed() override; + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + virtual Control *get_toolbar() const override; + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override; + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override; + + TileMapEditorTilesPlugin(); + ~TileMapEditorTilesPlugin(); +}; + +class TileMapEditorTerrainsPlugin : public TileMapEditorPlugin { + GDCLASS(TileMapEditorTerrainsPlugin, TileMapEditorPlugin); + +private: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + ObjectID tile_map_id; + virtual void edit(ObjectID p_tile_map_id) override; + + // Toolbar. + HBoxContainer *toolbar; + + Ref<ButtonGroup> tool_buttons_group; + Button *paint_tool_button; + + HBoxContainer *tools_settings; + VSeparator *tools_settings_vsep; + Button *picker_button; + Button *erase_button; + + void _update_toolbar(); + + // TileMap editing. + enum DragType { + DRAG_TYPE_NONE = 0, + DRAG_TYPE_PAINT, + DRAG_TYPE_PICK, + }; + DragType drag_type = DRAG_TYPE_NONE; + Vector2 drag_start_mouse_pos; + Vector2 drag_last_mouse_pos; + Map<Vector2i, TileMapCell> drag_modified; + + // Painting + class Constraint { + private: + const TileMap *tile_map; + Vector2i base_cell_coords = Vector2i(); + int bit = -1; + int terrain = -1; + + public: + // TODO implement difference operator. + bool operator<(const Constraint &p_other) const { + if (base_cell_coords == p_other.base_cell_coords) { + return bit < p_other.bit; + } + return base_cell_coords < p_other.base_cell_coords; + } + + String to_string() const { + return vformat("Constraint {pos:%s, bit:%d, terrain:%d}", base_cell_coords, bit, terrain); + } + + Vector2i get_base_cell_coords() const { + return base_cell_coords; + } + + Map<Vector2i, TileSet::CellNeighbor> get_overlapping_coords_and_peering_bits() const; + + void set_terrain(int p_terrain) { + terrain = p_terrain; + } + + int get_terrain() const { + return terrain; + } + + Constraint(const TileMap *p_tile_map, const Vector2i &p_position, const TileSet::CellNeighbor &p_bit, int p_terrain); + Constraint() {} + }; + + typedef Array TerrainsTilePattern; + + Set<TerrainsTilePattern> _get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const; + Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_removed_cells_list(const Set<Vector2i> &p_to_replace, int p_terrain_set) const; + Set<TileMapEditorTerrainsPlugin::Constraint> _get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const; + Map<Vector2i, TerrainsTilePattern> _wave_function_collapse(const Set<Vector2i> &p_to_replace, int p_terrain_set, const Set<TileMapEditorTerrainsPlugin::Constraint> p_constraints) const; + TileMapCell _get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const; + Map<Vector2i, TileMapCell> _draw_terrains(const Map<Vector2i, TerrainsTilePattern> &p_to_paint, int p_terrain_set) const; + + // Cached data. + + TerrainsTilePattern _build_terrains_tile_pattern(TileData *p_tile_data); + LocalVector<Map<TerrainsTilePattern, Set<TileMapCell>>> per_terrain_terrains_tile_patterns_tiles; + LocalVector<LocalVector<Set<TerrainsTilePattern>>> per_terrain_terrains_tile_patterns; + + Map<TileMapCell, TileData *> terrain_tiles; + LocalVector<TileSet::CellNeighbor> tile_sides; + + // Bottom panel. + Tree *terrains_tree; + ItemList *terrains_tile_list; + + // Update functions. + void _update_terrains_cache(); + void _update_terrains_tree(); + void _update_tiles_list(); + + // Update callback + virtual void tile_set_changed() override; + +protected: + void _notification(int p_what); + // static void _bind_methods(); + +public: + virtual Control *get_toolbar() const override; + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override; + //virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override; + + TileMapEditorTerrainsPlugin(); + ~TileMapEditorTerrainsPlugin(); +}; + +class TileMapEditor : public VBoxContainer { + GDCLASS(TileMapEditor, VBoxContainer); + +private: + bool tileset_changed_needs_update = false; + ObjectID tile_map_id; + + // Vector to keep plugins. + Vector<TileMapEditorPlugin *> tile_map_editor_plugins; + + // Toolbar. + HBoxContainer *tilemap_toolbar; + + // Bottom panel + Label *missing_tileset_label; + Tabs *tabs; + void _update_bottom_panel(); + + // TileMap + Ref<Texture2D> missing_tile_texture; + Ref<Texture2D> warning_pattern_texture; + + // CallBack + void _tile_map_changed(); + void _tab_changed(int p_tab_changed); + +protected: + void _notification(int p_what); + void _draw_shape(Control *p_control, Rect2 p_region, TileSet::TileShape p_shape, TileSet::TileOffsetAxis p_offset_axis, Color p_color); + +public: + bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); + void forward_canvas_draw_over_viewport(Control *p_overlay); + + void edit(TileMap *p_tile_map); + Control *get_toolbar() { return tilemap_toolbar; }; + + TileMapEditor(); + ~TileMapEditor(); + + // Static functions. + static Vector<Vector2i> get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell); +}; + +#endif // TILE_MAP_EDITOR_PLUGIN_H diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp new file mode 100644 index 0000000000..9d849a0df5 --- /dev/null +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -0,0 +1,2322 @@ +/*************************************************************************/ +/* tile_set_atlas_source_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_set_atlas_source_editor.h" + +#include "tiles_editor_plugin.h" + +#include "editor/editor_inspector.h" +#include "editor/editor_scale.h" +#include "editor/progress_dialog.h" + +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/control.h" +#include "scene/gui/item_list.h" +#include "scene/gui/separator.h" +#include "scene/gui/split_container.h" +#include "scene/gui/tab_container.h" + +#include "core/core_string_names.h" +#include "core/math/geometry_2d.h" +#include "core/os/keyboard.h" + +void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id(int p_id) { + ERR_FAIL_COND(p_id < 0); + if (source_id == p_id) { + return; + } + ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Atlas Source ID. Another source exists with id %d.", p_id)); + + int previous_source = source_id; + source_id = p_id; // source_id must be updated before, because it's used by the source list update. + tile_set->set_source_id(previous_source, p_id); + emit_signal("changed", "id"); +} + +int TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id() { + return source_id; +} + +bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_set(const StringName &p_name, const Variant &p_value) { + bool valid = false; + tile_set_atlas_source->set(p_name, p_value, &valid); + if (valid) { + emit_signal("changed", String(p_name).utf8().get_data()); + } + return valid; +} + +bool TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get(const StringName &p_name, Variant &r_ret) const { + if (!tile_set_atlas_source) { + return false; + } + bool valid = false; + r_ret = tile_set_atlas_source->get(p_name, &valid); + return valid; +} + +void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { + 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, "")); +} + +void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::_bind_methods() { + // -- Shape and layout -- + ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::set_id); + ClassDB::bind_method(D_METHOD("get_id"), &TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::get_id); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id"); + + ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); +} + +void TileSetAtlasSourceEditor::TileSetAtlasSourceProxyObject::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set_atlas_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); + + // Disconnect to changes. + if (tile_set_atlas_source) { + tile_set_atlas_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + + tile_set = p_tile_set; + tile_set_atlas_source = p_tile_set_atlas_source; + source_id = p_source_id; + + // Connect to changes. + if (tile_set_atlas_source) { + if (!tile_set_atlas_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { + tile_set_atlas_source->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + } + + notify_property_list_changed(); +} + +// -- Proxy object used by the tile inspector -- +bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_set(const StringName &p_name, const Variant &p_value) { + if (!tile_set_atlas_source) { + return false; + } + + if (tiles.size() == 1) { + const Vector2i &coords = tiles.front()->get().tile; + const int &alternative = tiles.front()->get().alternative; + + if (alternative == 0 && p_name == "atlas_coords") { + Vector2i as_vector2i = Vector2i(p_value); + ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, as_vector2i), false); + + if (tiles_set_atlas_source_editor->selection.front()->get().tile == coords) { + tiles_set_atlas_source_editor->selection.clear(); + tiles_set_atlas_source_editor->selection.insert({ as_vector2i, 0 }); + tiles_set_atlas_source_editor->_update_tile_id_label(); + } + + tile_set_atlas_source->move_tile_in_atlas(coords, as_vector2i); + tiles.clear(); + tiles.insert({ as_vector2i, 0 }); + emit_signal("changed", "atlas_coords"); + return true; + } else if (alternative == 0 && p_name == "size_in_atlas") { + Vector2i as_vector2i = Vector2i(p_value); + ERR_FAIL_COND_V(!tile_set_atlas_source->can_move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i), false); + + tile_set_atlas_source->move_tile_in_atlas(coords, TileSetSource::INVALID_ATLAS_COORDS, as_vector2i); + emit_signal("changed", "size_in_atlas"); + return true; + } else if (alternative > 0 && p_name == "alternative_id") { + int as_int = int(p_value); + ERR_FAIL_COND_V(as_int < 0, false); + ERR_FAIL_COND_V_MSG(tile_set_atlas_source->has_alternative_tile(coords, as_int), false, vformat("Cannot change alternative tile ID. Another alternative exists with id %d for tile at coords %s.", as_int, coords)); + + if (tiles_set_atlas_source_editor->selection.front()->get().alternative == alternative) { + tiles_set_atlas_source_editor->selection.clear(); + tiles_set_atlas_source_editor->selection.insert({ coords, as_int }); + } + + int previous_alternative_tile = alternative; + tiles.clear(); + tiles.insert({ coords, as_int }); // tiles must be updated before. + tile_set_atlas_source->set_alternative_tile_id(coords, previous_alternative_tile, as_int); + + emit_signal("changed", "alternative_id"); + return true; + } + } + + bool any_valid = false; + for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { + const Vector2i &coords = E->get().tile; + const int &alternative = E->get().alternative; + + bool valid = false; + TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + ERR_FAIL_COND_V(!tile_data, false); + tile_data->set(p_name, p_value, &valid); + + any_valid |= valid; + } + + if (any_valid) { + emit_signal("changed", String(p_name).utf8().get_data()); + } + + return any_valid; +} + +bool TileSetAtlasSourceEditor::AtlasTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const { + if (!tile_set_atlas_source) { + return false; + } + + if (tiles.size() == 1) { + const Vector2i &coords = tiles.front()->get().tile; + const int &alternative = tiles.front()->get().alternative; + + if (alternative == 0 && p_name == "atlas_coords") { + r_ret = coords; + return true; + } else if (alternative == 0 && p_name == "size_in_atlas") { + r_ret = tile_set_atlas_source->get_tile_size_in_atlas(coords); + return true; + } else if (alternative > 0 && p_name == "alternative_id") { + r_ret = alternative; + 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. + const Vector2i &coords = E->get().tile; + const int &alternative = E->get().alternative; + + TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + ERR_FAIL_COND_V(!tile_data, false); + + bool valid = false; + r_ret = tile_data->get(p_name, &valid); + if (valid) { + return true; + } + } + + return false; +} + +void TileSetAtlasSourceEditor::AtlasTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { + if (!tile_set_atlas_source) { + return; + } + + 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, "")); + } else { + p_list->push_back(PropertyInfo(Variant::INT, "alternative_id", 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; + String property; + bool operator<(const PropertyId &p_other) const { + return occurence_id == p_other.occurence_id ? property < p_other.property : occurence_id < p_other.occurence_id; + } + }; + struct PLData { + int uses = 0; + PropertyInfo property_info; + }; + Map<PropertyId, PLData> usage; + + List<PLData *> data_list; + for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { + const Vector2i &coords = E->get().tile; + const int &alternative = E->get().alternative; + + TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + ERR_FAIL_COND(!tile_data); + + List<PropertyInfo> list; + tile_data->get_property_list(&list); + + Map<String, int> counts; // Counts the number of time a property appears (useful for groups that may appear more than once) + for (List<PropertyInfo>::Element *E_property = list.front(); E_property; E_property = E_property->next()) { + const String &property_string = E_property->get().name; + if (!tile_data->is_allowing_transform() && (property_string == "flip_h" || property_string == "flip_v" || property_string == "transpose")) { + continue; + } + + if (!counts.has(property_string)) { + counts[property_string] = 1; + } else { + counts[property_string] += 1; + } + + PropertyInfo stored_property_info = E_property->get(); + stored_property_info.usage |= PROPERTY_USAGE_STORAGE; // Ignore the storage flag in comparing properties. + + PropertyId id = { counts[property_string], property_string }; + if (!usage.has(id)) { + usage[id] = { 1, stored_property_info }; + data_list.push_back(&usage[id]); + } else if (usage[id].property_info == stored_property_info) { + usage[id].uses += 1; + } + } + } + + // Add only properties that are common to all tiles. + for (List<PLData *>::Element *E = data_list.front(); E; E = E->next()) { + if (E->get()->uses == tiles.size()) { + p_list->push_back(E->get()->property_info); + } + } +} + +void TileSetAtlasSourceEditor::AtlasTileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles) { + ERR_FAIL_COND(!p_tile_set_atlas_source); + ERR_FAIL_COND(p_tiles.is_empty()); + for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) { + ERR_FAIL_COND(E->get().tile == TileSetSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(E->get().alternative < 0); + } + + // Disconnect to changes. + for (Set<TileSelection>::Element *E = tiles.front(); E; E = E->next()) { + const Vector2i &coords = E->get().tile; + const int &alternative = E->get().alternative; + + if (tile_set_atlas_source && tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) { + TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + if (tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { + tile_data->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + } + } + + tile_set_atlas_source = p_tile_set_atlas_source; + tiles = Set<TileSelection>(p_tiles); + + // Connect to changes. + for (Set<TileSelection>::Element *E = p_tiles.front(); E; E = E->next()) { + const Vector2i &coords = E->get().tile; + const int &alternative = E->get().alternative; + + if (tile_set_atlas_source->has_tile(coords) && tile_set_atlas_source->has_alternative_tile(coords, alternative)) { + TileData *tile_data = Object::cast_to<TileData>(tile_set_atlas_source->get_tile_data(coords, alternative)); + if (!tile_data->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { + tile_data->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + } + } + + notify_property_list_changed(); +} + +void TileSetAtlasSourceEditor::AtlasTileProxyObject::_bind_methods() { + ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); +} + +void TileSetAtlasSourceEditor::_inspector_property_selected(String p_property) { + selected_property = p_property; + _update_atlas_view(); + _update_current_tile_data_editor(); +} + +void TileSetAtlasSourceEditor::_update_tile_id_label() { + if (selection.size() == 1) { + TileSelection selected = selection.front()->get(); + tool_tile_id_label->set_text(vformat("%d, %s, %d", tile_set_atlas_source_id, selected.tile, selected.alternative)); + tool_tile_id_label->set_tooltip(vformat(TTR("Selected tile:\nSource: %d\nAtlas coordinates: %s\nAlternative: %d"), tile_set_atlas_source_id, selected.tile, selected.alternative)); + tool_tile_id_label->show(); + } else { + tool_tile_id_label->hide(); + } +} + +void TileSetAtlasSourceEditor::_update_source_inspector() { + // Update the proxy object. + atlas_source_proxy_object->edit(tile_set, tile_set_atlas_source, tile_set_atlas_source_id); + + // Update the "clear outside texture" button. + tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, !tile_set_atlas_source->has_tiles_outside_texture()); +} + +void TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles() { + // Fix selected. + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + TileSelection selected = E->get(); + if (!tile_set_atlas_source->has_tile(selected.tile) || !tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) { + selection.erase(E); + } + } + + // Fix hovered. + if (!tile_set_atlas_source->has_tile(hovered_base_tile_coords)) { + hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; + } + Vector2i coords = Vector2i(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y); + int alternative = hovered_alternative_tile_coords.z; + if (!tile_set_atlas_source->has_tile(coords) || !tile_set_atlas_source->has_alternative_tile(coords, alternative)) { + hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); + } +} + +void TileSetAtlasSourceEditor::_update_atlas_source_inspector() { + // Update visibility. + bool visible = tools_button_group->get_pressed_button() == tool_setup_atlas_source_button; + atlas_source_inspector_label->set_visible(visible); + atlas_source_inspector->set_visible(visible); +} + +void TileSetAtlasSourceEditor::_update_tile_inspector() { + // Update visibility. + if (tools_button_group->get_pressed_button() == tool_select_button) { + if (!selection.is_empty()) { + tile_proxy_object->edit(tile_set_atlas_source, selection); + } + tile_inspector_label->show(); + tile_inspector->set_visible(!selection.is_empty()); + tile_inspector_no_tile_selected_label->set_visible(selection.is_empty()); + } else { + tile_inspector_label->hide(); + tile_inspector->hide(); + tile_inspector_no_tile_selected_label->hide(); + } +} + +void TileSetAtlasSourceEditor::_update_tile_data_editors() { + String previously_selected; + if (tile_data_editors_tree && tile_data_editors_tree->get_selected()) { + previously_selected = tile_data_editors_tree->get_selected()->get_metadata(0); + } + + tile_data_editors_tree->clear(); + + TreeItem *root = tile_data_editors_tree->create_item(); + + TreeItem *group; +#define ADD_TILE_DATA_EDITOR_GROUP(text) \ + group = tile_data_editors_tree->create_item(root); \ + group->set_custom_bg_color(0, group_color); \ + group->set_selectable(0, false); \ + group->set_disable_folding(true); \ + group->set_text(0, text); + + TreeItem *item; +#define ADD_TILE_DATA_EDITOR(parent, text, property) \ + item = tile_data_editors_tree->create_item(parent); \ + item->set_text(0, text); \ + item->set_metadata(0, property); \ + if (property == previously_selected) { \ + item->select(0); \ + } + + // Theming. + tile_data_editors_tree->add_theme_constant_override("vseparation", 1); + tile_data_editors_tree->add_theme_constant_override("hseparation", 3); + + Color group_color = get_theme_color("prop_category", "Editor"); + + // List of editors. + // --- Rendering --- + ADD_TILE_DATA_EDITOR_GROUP("Rendering"); + + ADD_TILE_DATA_EDITOR(group, "Texture Offset", "texture_offset"); + if (!tile_data_editors.has("texture_offset")) { + TileDataTextureOffsetEditor *tile_data_texture_offset_editor = memnew(TileDataTextureOffsetEditor); + tile_data_texture_offset_editor->hide(); + tile_data_texture_offset_editor->setup_property_editor(Variant::VECTOR2, "texture_offset"); + tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_texture_offset_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors["texture_offset"] = tile_data_texture_offset_editor; + } + + ADD_TILE_DATA_EDITOR(group, "Modulate", "modulate"); + if (!tile_data_editors.has("modulate")) { + TileDataDefaultEditor *tile_data_modulate_editor = memnew(TileDataDefaultEditor()); + tile_data_modulate_editor->hide(); + tile_data_modulate_editor->setup_property_editor(Variant::COLOR, "modulate", "", Color(1.0, 1.0, 1.0, 1.0)); + tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_modulate_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors["modulate"] = tile_data_modulate_editor; + } + + ADD_TILE_DATA_EDITOR(group, "Z Index", "z_index"); + if (!tile_data_editors.has("z_index")) { + TileDataDefaultEditor *tile_data_z_index_editor = memnew(TileDataDefaultEditor()); + tile_data_z_index_editor->hide(); + tile_data_z_index_editor->setup_property_editor(Variant::INT, "z_index"); + tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_z_index_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors["z_index"] = tile_data_z_index_editor; + } + + ADD_TILE_DATA_EDITOR(group, "Y Sort Origin", "y_sort_origin"); + if (!tile_data_editors.has("y_sort_origin")) { + TileDataYSortEditor *tile_data_y_sort_editor = memnew(TileDataYSortEditor); + tile_data_y_sort_editor->hide(); + tile_data_y_sort_editor->setup_property_editor(Variant::INT, "y_sort_origin"); + tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_y_sort_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors["y_sort_origin"] = tile_data_y_sort_editor; + } + + for (int i = 0; i < tile_set->get_occlusion_layers_count(); i++) { + ADD_TILE_DATA_EDITOR(group, vformat("Occlusion Layer %d", i), vformat("occlusion_layer_%d", i)); + if (!tile_data_editors.has(vformat("occlusion_layer_%d", i))) { + TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor()); + tile_data_occlusion_shape_editor->hide(); + tile_data_occlusion_shape_editor->set_occlusion_layer(i); + tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_occlusion_shape_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors[vformat("occlusion_layer_%d", i)] = tile_data_occlusion_shape_editor; + } + } + for (int i = tile_set->get_occlusion_layers_count(); tile_data_editors.has(vformat("occlusion_layer_%d", i)); i++) { + tile_data_editors[vformat("occlusion_layer_%d", i)]->queue_delete(); + tile_data_editors.erase(vformat("occlusion_layer_%d", i)); + } + + // --- Rendering --- + ADD_TILE_DATA_EDITOR(root, "Terrains", "terrain_set"); + if (!tile_data_editors.has("terrain_set")) { + TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor); + tile_data_terrains_editor->hide(); + tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_terrains_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors["terrain_set"] = tile_data_terrains_editor; + } + + // --- Miscellaneous --- + ADD_TILE_DATA_EDITOR(root, "Probability", "probability"); + if (!tile_data_editors.has("probability")) { + TileDataDefaultEditor *tile_data_probability_editor = memnew(TileDataDefaultEditor()); + tile_data_probability_editor->hide(); + tile_data_probability_editor->setup_property_editor(Variant::FLOAT, "probability", "", 1.0); + tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_probability_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors["probability"] = tile_data_probability_editor; + } + + // --- Physics --- + ADD_TILE_DATA_EDITOR_GROUP("Physics"); + for (int i = 0; i < tile_set->get_physics_layers_count(); i++) { + ADD_TILE_DATA_EDITOR(group, vformat("Physics Layer %d", i), vformat("physics_layer_%d", i)); + if (!tile_data_editors.has(vformat("physics_layer_%d", i))) { + TileDataCollisionEditor *tile_data_collision_editor = memnew(TileDataCollisionEditor()); + tile_data_collision_editor->hide(); + tile_data_collision_editor->set_physics_layer(i); + tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_collision_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors[vformat("physics_layer_%d", i)] = tile_data_collision_editor; + } + } + for (int i = tile_set->get_physics_layers_count(); tile_data_editors.has(vformat("physics_layer_%d", i)); i++) { + tile_data_editors[vformat("physics_layer_%d", i)]->queue_delete(); + tile_data_editors.erase(vformat("physics_layer_%d", i)); + } + + // --- Navigation --- + ADD_TILE_DATA_EDITOR_GROUP("Navigation"); + for (int i = 0; i < tile_set->get_navigation_layers_count(); i++) { + ADD_TILE_DATA_EDITOR(group, vformat("Navigation Layer %d", i), vformat("navigation_layer_%d", i)); + if (!tile_data_editors.has(vformat("navigation_layer_%d", i))) { + TileDataNavigationEditor *tile_data_navigation_editor = memnew(TileDataNavigationEditor()); + tile_data_navigation_editor->hide(); + tile_data_navigation_editor->set_navigation_layer(i); + tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_navigation_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors[vformat("navigation_layer_%d", i)] = tile_data_navigation_editor; + } + } + for (int i = tile_set->get_navigation_layers_count(); tile_data_editors.has(vformat("navigation_layer_%d", i)); i++) { + tile_data_editors[vformat("navigation_layer_%d", i)]->queue_delete(); + tile_data_editors.erase(vformat("navigation_layer_%d", i)); + } + + // --- Custom Data --- + ADD_TILE_DATA_EDITOR_GROUP("Custom Data"); + for (int i = 0; i < tile_set->get_custom_data_layers_count(); i++) { + if (tile_set->get_custom_data_name(i).is_empty()) { + ADD_TILE_DATA_EDITOR(group, vformat("Custom Data %d", i), vformat("custom_data_%d", i)); + } else { + ADD_TILE_DATA_EDITOR(group, tile_set->get_custom_data_name(i), vformat("custom_data_%d", i)); + } + if (!tile_data_editors.has(vformat("custom_data_%d", i))) { + TileDataDefaultEditor *tile_data_custom_data_editor = memnew(TileDataDefaultEditor()); + tile_data_custom_data_editor->hide(); + tile_data_custom_data_editor->setup_property_editor(tile_set->get_custom_data_type(i), vformat("custom_data_%d", i), tile_set->get_custom_data_name(i)); + tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)tile_atlas_control_unscaled, &Control::update)); + tile_data_custom_data_editor->connect("needs_redraw", callable_mp((CanvasItem *)alternative_tiles_control_unscaled, &Control::update)); + tile_data_editors[vformat("custom_data_%d", i)] = tile_data_custom_data_editor; + } + } + for (int i = tile_set->get_custom_data_layers_count(); tile_data_editors.has(vformat("custom_data_%d", i)); i++) { + tile_data_editors[vformat("custom_data_%d", i)]->queue_delete(); + tile_data_editors.erase(vformat("custom_data_%d", i)); + } + +#undef ADD_TILE_DATA_EDITOR_GROUP +#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()) { + // Tile Data Editor. + TileDataEditor *tile_data_editor = E->get(); + if (!tile_data_editor->is_inside_tree()) { + tile_data_painting_editor_container->add_child(tile_data_editor); + } + tile_data_editor->set_tile_set(tile_set); + + // Toolbar. + Control *toolbar = tile_data_editor->get_toolbar(); + if (!toolbar->is_inside_tree()) { + tool_settings_tile_data_toolbar_container->add_child(toolbar); + } + toolbar->hide(); + } + + // 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")); + tile_data_editors_label->set_visible(is_visible); +} + +void TileSetAtlasSourceEditor::_update_current_tile_data_editor() { + // Find the property to use. + String property; + if (tools_button_group->get_pressed_button() == tool_select_button && tile_inspector->is_visible() && !tile_inspector->get_selected_path().is_empty()) { + Vector<String> components = tile_inspector->get_selected_path().split("/"); + if (components.size() >= 1) { + property = components[0]; + + // Workaround for terrains as they don't have a common first component. + if (property.begins_with("terrains_")) { + property = "terrain_set"; + } + } + } else if (tools_button_group->get_pressed_button() == tool_paint_button && tile_data_editors_tree->get_selected()) { + property = tile_data_editors_tree->get_selected()->get_metadata(0); + tile_data_editor_dropdown_button->set_text(tile_data_editors_tree->get_selected()->get_text(0)); + } + + // 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(); + } + if (tile_data_editors.has(property)) { + current_tile_data_editor = tile_data_editors[property]; + } else { + current_tile_data_editor = nullptr; + } + + // Get the correct editor for the TileData's property. + if (current_tile_data_editor) { + current_tile_data_editor_toolbar = current_tile_data_editor->get_toolbar(); + current_property = property; + current_tile_data_editor->set_visible(tools_button_group->get_pressed_button() == tool_paint_button); + current_tile_data_editor_toolbar->set_visible(tools_button_group->get_pressed_button() == tool_paint_button); + } +} + +void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw() { + if (!has_theme_icon("arrow", "OptionButton")) { + return; + } + + RID ci = tile_data_editor_dropdown_button->get_canvas_item(); + Ref<Texture2D> arrow = Control::get_theme_icon("arrow", "OptionButton"); + Color clr = Color(1, 1, 1); + if (get_theme_constant("modulate_arrow")) { + switch (tile_data_editor_dropdown_button->get_draw_mode()) { + case BaseButton::DRAW_PRESSED: + clr = get_theme_color("font_pressed_color"); + break; + case BaseButton::DRAW_HOVER: + clr = get_theme_color("font_hover_color"); + break; + case BaseButton::DRAW_DISABLED: + clr = get_theme_color("font_disabled_color"); + break; + default: + clr = get_theme_color("font_color"); + } + } + + Size2 size = tile_data_editor_dropdown_button->get_size(); + + Point2 ofs; + if (is_layout_rtl()) { + ofs = Point2(get_theme_constant("arrow_margin", "OptionButton"), int(Math::abs((size.height - arrow->get_height()) / 2))); + } else { + ofs = Point2(size.width - arrow->get_width() - get_theme_constant("arrow_margin", "OptionButton"), int(Math::abs((size.height - arrow->get_height()) / 2))); + } + arrow->draw(ci, ofs, clr); +} + +void TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed() { + Size2 size = tile_data_editor_dropdown_button->get_size(); + tile_data_editors_popup->set_position(tile_data_editor_dropdown_button->get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y)); + tile_data_editors_popup->set_size(Size2(size.width, 0)); + tile_data_editors_popup->popup(); +} + +void TileSetAtlasSourceEditor::_tile_data_editors_tree_selected() { + tile_data_editors_popup->call_deferred("hide"); + _update_current_tile_data_editor(); + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); +} + +void TileSetAtlasSourceEditor::_update_atlas_view() { + // Update the atlas display. + tile_atlas_view->set_atlas_source(*tile_set, tile_set_atlas_source, tile_set_atlas_source_id); + + // Create a bunch of buttons to add alternative tiles. + for (int i = 0; i < alternative_tiles_control->get_child_count(); i++) { + alternative_tiles_control->get_child(i)->queue_delete(); + } + + Vector2i pos; + Vector2 texture_region_base_size = tile_set_atlas_source->get_texture_region_size(); + int texture_region_base_size_min = MIN(texture_region_base_size.x, texture_region_base_size.y); + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i tile_id = tile_set_atlas_source->get_tile_id(i); + int alternative_count = tile_set_atlas_source->get_alternative_tiles_count(tile_id); + if (alternative_count > 1) { + // Compute the right extremity of alternative. + int y_increment = 0; + pos.x = 0; + for (int j = 1; j < alternative_count; j++) { + int alternative_id = tile_set_atlas_source->get_alternative_tile_id(tile_id, j); + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(tile_id, alternative_id); + pos.x = MAX(pos.x, rect.get_end().x); + y_increment = MAX(y_increment, rect.size.y); + } + + // Create and position the button. + Button *button = memnew(Button); + alternative_tiles_control->add_child(button); + button->set_flat(true); + button->set_icon(get_theme_icon("Add", "EditorIcons")); + button->add_theme_style_override("normal", memnew(StyleBoxEmpty)); + button->add_theme_style_override("hover", memnew(StyleBoxEmpty)); + button->add_theme_style_override("focus", memnew(StyleBoxEmpty)); + button->add_theme_style_override("pressed", memnew(StyleBoxEmpty)); + button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, -1)); + button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min))); + button->set_expand_icon(true); + + pos.y += y_increment; + } + } + tile_atlas_view->set_padding(Side::SIDE_RIGHT, texture_region_base_size_min); + + // Redraw everything. + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + tile_atlas_view->update(); + + // Synchronize atlas view. + TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view); +} + +void TileSetAtlasSourceEditor::_update_toolbar() { + // Show the tools and settings. + if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) { + if (current_tile_data_editor_toolbar) { + current_tile_data_editor_toolbar->hide(); + } + tool_settings_vsep->show(); + tools_settings_erase_button->show(); + tool_advanced_menu_buttom->show(); + } else if (tools_button_group->get_pressed_button() == tool_select_button) { + if (current_tile_data_editor_toolbar) { + current_tile_data_editor_toolbar->hide(); + } + tool_settings_vsep->hide(); + tools_settings_erase_button->hide(); + tool_advanced_menu_buttom->hide(); + } else if (tools_button_group->get_pressed_button() == tool_paint_button) { + if (current_tile_data_editor_toolbar) { + current_tile_data_editor_toolbar->show(); + } + tool_settings_vsep->hide(); + tools_settings_erase_button->hide(); + tool_advanced_menu_buttom->hide(); + } +} + +void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() { + hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + tile_atlas_view->update(); +} + +void TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed() { + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); +} + +void TileSetAtlasSourceEditor::_tile_atlas_control_gui_input(const Ref<InputEvent> &p_event) { + // Update the hovered coords. + hovered_base_tile_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + + // Forward the event to the current tile data editor if we are in the painting mode. + if (tools_button_group->get_pressed_button() == tool_paint_button) { + if (current_tile_data_editor) { + 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_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + tile_atlas_view->update(); + return; + } else { + // Handle the event. + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + Vector2i last_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_last_mouse_pos); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + + Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + + if (drag_type == DRAG_TYPE_NONE) { + if (selection.size() == 1) { + // Change the cursor depending on the hovered thing. + TileSelection selected = selection.front()->get(); + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { + Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); + Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); + Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); + Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); + Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); + const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; + const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; + CursorShape cursor_shape = CURSOR_ARROW; + bool can_grow[4]; + for (int i = 0; i < 4; i++) { + can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); + can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; + } + for (int i = 0; i < 4; i++) { + Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + if (can_grow[i] && can_grow[(i + 3) % 4] && 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]; + 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; + } + } + tile_atlas_control->set_default_cursor_shape(cursor_shape); + } + } + } else if (drag_type == DRAG_TYPE_CREATE_BIG_TILE) { + // Create big tile. + new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + + Rect2i new_rect = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); + new_rect.size += Vector2i(1, 1); + // Check if the new tile can fit in the new rect. + if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { + // Move and resize the tile. + tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); + drag_current_tile = new_rect.position; + } + } else if (drag_type == DRAG_TYPE_CREATE_TILES) { + // Create tiles. + last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + + Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); + for (int i = 0; i < line.size(); i++) { + if (tile_set_atlas_source->get_tile_at_coords(line[i]) == TileSetSource::INVALID_ATLAS_COORDS) { + tile_set_atlas_source->create_tile(line[i]); + drag_modified_tiles.insert(line[i]); + } + } + + drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position(); + + } else if (drag_type == DRAG_TYPE_REMOVE_TILES) { + // Remove tiles. + last_base_tiles_coords = last_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + + Vector<Point2i> line = Geometry2D::bresenham_line(last_base_tiles_coords, new_base_tiles_coords); + for (int i = 0; i < line.size(); i++) { + Vector2i base_tile_coords = tile_set_atlas_source->get_tile_at_coords(line[i]); + if (base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { + drag_modified_tiles.insert(base_tile_coords); + } + } + + drag_last_mouse_pos = tile_atlas_control->get_local_mouse_position(); + } else if (drag_type == DRAG_TYPE_MOVE_TILE) { + // Move tile. + Vector2 mouse_offset = (Vector2(tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)) / 2.0 - Vector2(0.5, 0.5)) * tile_set->get_tile_size(); + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position() - mouse_offset); + coords = coords.max(Vector2i(0, 0)).min(grid_size - Vector2i(1, 1)); + if (drag_current_tile != coords && tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, coords)) { + tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, coords); + selection.clear(); + selection.insert({ coords, 0 }); + drag_current_tile = coords; + + // Update only what's needed. + tile_set_atlas_source_changed_needs_update = false; + _update_tile_inspector(); + _update_atlas_view(); + _update_tile_id_label(); + _update_current_tile_data_editor(); + } + } else if (drag_type == DRAG_TYPE_MAY_POPUP_MENU) { + if (Vector2(drag_start_mouse_pos).distance_to(tile_atlas_control->get_local_mouse_position()) > 5.0 * EDSCALE) { + drag_type = DRAG_TYPE_NONE; + } + } else if (drag_type >= DRAG_TYPE_RESIZE_TOP_LEFT && drag_type <= DRAG_TYPE_RESIZE_LEFT) { + // Resizing a tile. + new_base_tiles_coords = new_base_tiles_coords.max(Vector2i(-1, -1)).min(grid_size); + + Rect2i old_rect = Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)); + Rect2i new_rect = old_rect; + + if (drag_type == DRAG_TYPE_RESIZE_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT) { + new_rect.position.x = MIN(new_base_tiles_coords.x + 1, old_rect.get_end().x - 1); + new_rect.size.x = old_rect.get_end().x - new_rect.position.x; + } + if (drag_type == DRAG_TYPE_RESIZE_TOP || drag_type == DRAG_TYPE_RESIZE_TOP_LEFT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT) { + new_rect.position.y = MIN(new_base_tiles_coords.y + 1, old_rect.get_end().y - 1); + new_rect.size.y = old_rect.get_end().y - new_rect.position.y; + } + + if (drag_type == DRAG_TYPE_RESIZE_RIGHT || drag_type == DRAG_TYPE_RESIZE_TOP_RIGHT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) { + new_rect.set_end(Vector2i(MAX(new_base_tiles_coords.x, old_rect.position.x + 1), new_rect.get_end().y)); + } + if (drag_type == DRAG_TYPE_RESIZE_BOTTOM || drag_type == DRAG_TYPE_RESIZE_BOTTOM_LEFT || drag_type == DRAG_TYPE_RESIZE_BOTTOM_RIGHT) { + new_rect.set_end(Vector2i(new_rect.get_end().x, MAX(new_base_tiles_coords.y, old_rect.position.y + 1))); + } + + if (tile_set_atlas_source->can_move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size)) { + tile_set_atlas_source->move_tile_in_atlas(drag_current_tile, new_rect.position, new_rect.size); + selection.clear(); + selection.insert({ new_rect.position, 0 }); + drag_current_tile = new_rect.position; + + // Update only what's needed. + tile_set_atlas_source_changed_needs_update = false; + _update_tile_inspector(); + _update_atlas_view(); + _update_tile_id_label(); + _update_current_tile_data_editor(); + } + } + + // Redraw for the hovered tile. + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + tile_atlas_view->update(); + return; + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + // Left click pressed. + if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) { + if (tools_settings_erase_button->is_pressed()) { + // Erasing + if (mb->is_ctrl_pressed() || mb->is_shift_pressed()) { + // Remove tiles using rect. + + // Setup the dragging info. + drag_type = DRAG_TYPE_REMOVE_TILES_USING_RECT; + drag_start_mouse_pos = mouse_local_pos; + drag_last_mouse_pos = drag_start_mouse_pos; + } else { + // Remove tiles. + + // Setup the dragging info. + drag_type = DRAG_TYPE_REMOVE_TILES; + drag_start_mouse_pos = mouse_local_pos; + drag_last_mouse_pos = drag_start_mouse_pos; + + // Remove a first tile. + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + coords = tile_set_atlas_source->get_tile_at_coords(coords); + } + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + drag_modified_tiles.insert(coords); + } + } + } else { + // Creating + if (mb->is_shift_pressed()) { + // Create a big tile. + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); + if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { + // Setup the dragging info, only if we start on an empty tile. + drag_type = DRAG_TYPE_CREATE_BIG_TILE; + drag_start_mouse_pos = mouse_local_pos; + drag_last_mouse_pos = drag_start_mouse_pos; + drag_current_tile = coords; + + // Create a tile. + tile_set_atlas_source->create_tile(coords); + } + } else if (mb->is_ctrl_pressed()) { + // Create tiles using rect. + drag_type = DRAG_TYPE_CREATE_TILES_USING_RECT; + drag_start_mouse_pos = mouse_local_pos; + drag_last_mouse_pos = drag_start_mouse_pos; + } else { + // Create tiles. + + // Setup the dragging info. + drag_type = DRAG_TYPE_CREATE_TILES; + drag_start_mouse_pos = mouse_local_pos; + drag_last_mouse_pos = drag_start_mouse_pos; + + // Create a first tile if needed. + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + if (coords != TileSetSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { + tile_set_atlas_source->create_tile(coords); + drag_modified_tiles.insert(coords); + } + } + } + } else if (tools_button_group->get_pressed_button() == tool_select_button) { + // Dragging a handle. + drag_type = DRAG_TYPE_NONE; + if (selection.size() == 1) { + TileSelection selected = selection.front()->get(); + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selected.alternative == 0) { + Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); + Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); + Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); + Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); + const Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; + const Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; + CursorShape cursor_shape = CURSOR_ARROW; + bool can_grow[4]; + for (int i = 0; i < 4; i++) { + can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); + can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; + } + for (int i = 0; i < 4; i++) { + Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + if (can_grow[i] && can_grow[(i + 3) % 4] && 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; + drag_last_mouse_pos = drag_start_mouse_pos; + drag_current_tile = selected.tile; + 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]; + 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; + drag_last_mouse_pos = drag_start_mouse_pos; + drag_current_tile = selected.tile; + drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); + cursor_shape = (i % 2) ? CURSOR_HSIZE : CURSOR_VSIZE; + } + } + tile_atlas_control->set_default_cursor_shape(cursor_shape); + } + } + + // Selecting then dragging a tile. + if (drag_type == DRAG_TYPE_NONE) { + TileSelection selected = { TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE }; + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + coords = tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + selected = { coords, 0 }; + } + } + + bool shift = mb->is_shift_pressed(); + if (!shift && selection.size() == 1 && selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { + // Start move dragging. + drag_type = DRAG_TYPE_MOVE_TILE; + drag_start_mouse_pos = mouse_local_pos; + drag_last_mouse_pos = drag_start_mouse_pos; + drag_current_tile = selected.tile; + drag_start_tile_shape = Rect2i(selected.tile, tile_set_atlas_source->get_tile_size_in_atlas(selected.tile)); + tile_atlas_control->set_default_cursor_shape(CURSOR_MOVE); + } else { + // Start selection dragging. + drag_type = DRAG_TYPE_RECT_SELECT; + drag_start_mouse_pos = mouse_local_pos; + drag_last_mouse_pos = drag_start_mouse_pos; + } + } + } + } else { + // Left click released. + _end_dragging(); + } + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + tile_atlas_view->update(); + return; + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + // Right click pressed. + if (mb->is_pressed()) { + drag_type = DRAG_TYPE_MAY_POPUP_MENU; + drag_start_mouse_pos = tile_atlas_control->get_local_mouse_position(); + } else { + // Right click released. + _end_dragging(); + } + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + tile_atlas_view->update(); + return; + } + } + } +} + +void TileSetAtlasSourceEditor::_end_dragging() { + switch (drag_type) { + case DRAG_TYPE_CREATE_TILES: + undo_redo->create_action(TTR("Create tiles")); + for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_set_atlas_source, "create_tile", E->get()); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", E->get()); + } + undo_redo->commit_action(false); + break; + case DRAG_TYPE_CREATE_BIG_TILE: + undo_redo->create_action(TTR("Create a tile")); + undo_redo->add_do_method(tile_set_atlas_source, "create_tile", drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", drag_current_tile); + undo_redo->commit_action(false); + break; + case DRAG_TYPE_REMOVE_TILES: { + List<PropertyInfo> list; + tile_set_atlas_source->get_property_list(&list); + Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + undo_redo->create_action(TTR("Remove tiles")); + for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) { + Vector2i coords = E->get(); + undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords); + undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords); + if (per_tile.has(coords)) { + for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) { + String property = E_property->get()->name; + Variant value = tile_set_atlas_source->get(property); + if (value.get_type() != Variant::NIL) { + undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value); + } + } + } + } + undo_redo->commit_action(); + } break; + case DRAG_TYPE_CREATE_TILES_USING_RECT: { + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); + area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); + undo_redo->create_action(TTR("Create tiles")); + for (int x = area.get_position().x; x < area.get_end().x; x++) { + for (int y = area.get_position().y; y < area.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { + undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords); + } + } + } + undo_redo->commit_action(); + } break; + case DRAG_TYPE_REMOVE_TILES_USING_RECT: { + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); + area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); + List<PropertyInfo> list; + tile_set_atlas_source->get_property_list(&list); + Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + + Set<Vector2i> to_delete; + for (int x = area.get_position().x; x < area.get_end().x; x++) { + for (int y = area.get_position().y; y < area.get_end().y; y++) { + Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + to_delete.insert(coords); + } + } + } + + undo_redo->create_action(TTR("Remove tiles")); + undo_redo->add_do_method(this, "_set_selection_from_array", Array()); + for (Set<Vector2i>::Element *E = to_delete.front(); E; E = E->next()) { + Vector2i coords = E->get(); + undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords); + undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords); + if (per_tile.has(coords)) { + for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) { + String property = E_property->get()->name; + Variant value = tile_set_atlas_source->get(property); + if (value.get_type() != Variant::NIL) { + undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value); + } + } + } + } + undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); + undo_redo->commit_action(); + } break; + case DRAG_TYPE_MOVE_TILE: + if (drag_current_tile != drag_start_tile_shape.position) { + undo_redo->create_action(TTR("Move a tile")); + undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)); + undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array()); + undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size); + Array array; + array.push_back(drag_start_tile_shape.position); + array.push_back(0); + undo_redo->add_undo_method(this, "_set_selection_from_array", array); + undo_redo->commit_action(false); + } + break; + case DRAG_TYPE_RECT_SELECT: { + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + ERR_FAIL_COND(start_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(new_base_tiles_coords == TileSetSource::INVALID_ATLAS_COORDS); + + Rect2i region = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); + region.size += Vector2i(1, 1); + + undo_redo->create_action(TTR("Select tiles")); + undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); + + // Determine if we clear, then add or remove to the selection. + bool add_to_selection = true; + if (Input::get_singleton()->is_key_pressed(KEY_SHIFT)) { + Vector2i coords = tile_set_atlas_source->get_tile_at_coords(start_base_tiles_coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + if (selection.has({ coords, 0 })) { + add_to_selection = false; + } + } + } else { + selection.clear(); + } + + // Modify the selection. + for (int x = region.position.x; x < region.get_end().x; x++) { + for (int y = region.position.y; y < region.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + coords = tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + if (add_to_selection && !selection.has({ coords, 0 })) { + selection.insert({ coords, 0 }); + } else if (!add_to_selection && selection.has({ coords, 0 })) { + selection.erase({ coords, 0 }); + } + } + } + } + _update_tile_inspector(); + _update_tile_id_label(); + _update_current_tile_data_editor(); + undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array()); + undo_redo->commit_action(false); + } break; + case DRAG_TYPE_MAY_POPUP_MENU: { + Vector2 mouse_local_pos = tile_atlas_control->get_local_mouse_position(); + TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 }; + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { + selected.tile = tile_set_atlas_source->get_tile_at_coords(selected.tile); + } + + // Set the selection if needed. + if (selection.size() <= 1) { + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { + undo_redo->create_action(TTR("Select tiles")); + undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); + selection.clear(); + selection.insert(selected); + undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array()); + undo_redo->commit_action(false); + _update_tile_inspector(); + _update_tile_id_label(); + _update_current_tile_data_editor(); + } + } + + // Pops up the correct menu, depending on whether we have a tile or not. + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS && selection.has(selected)) { + // We have a tile. + menu_option_coords = selected.tile; + menu_option_alternative = 0; + base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); + } else if (hovered_base_tile_coords != TileSetSource::INVALID_ATLAS_COORDS) { + // We don't have a tile, but can create one. + menu_option_coords = hovered_base_tile_coords; + menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; + empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); + } + } break; + case DRAG_TYPE_RESIZE_TOP_LEFT: + case DRAG_TYPE_RESIZE_TOP: + case DRAG_TYPE_RESIZE_TOP_RIGHT: + case DRAG_TYPE_RESIZE_RIGHT: + case DRAG_TYPE_RESIZE_BOTTOM_RIGHT: + case DRAG_TYPE_RESIZE_BOTTOM: + case DRAG_TYPE_RESIZE_BOTTOM_LEFT: + case DRAG_TYPE_RESIZE_LEFT: + if (drag_start_tile_shape != Rect2i(drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile))) { + undo_redo->create_action(TTR("Resize a tile")); + undo_redo->add_do_method(tile_set_atlas_source, "move_tile_in_atlas", drag_start_tile_shape.position, drag_current_tile, tile_set_atlas_source->get_tile_size_in_atlas(drag_current_tile)); + undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array()); + undo_redo->add_undo_method(tile_set_atlas_source, "move_tile_in_atlas", drag_current_tile, drag_start_tile_shape.position, drag_start_tile_shape.size); + Array array; + array.push_back(drag_start_tile_shape.position); + array.push_back(0); + undo_redo->add_undo_method(this, "_set_selection_from_array", array); + undo_redo->commit_action(false); + } + break; + default: + break; + } + + drag_modified_tiles.clear(); + drag_type = DRAG_TYPE_NONE; + tile_atlas_control->set_default_cursor_shape(CURSOR_ARROW); +} + +Map<Vector2i, List<const PropertyInfo *>> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas) { + // Group properties per tile. + Map<Vector2i, List<const PropertyInfo *>> per_tile; + for (const List<PropertyInfo>::Element *E_property = r_list.front(); E_property; E_property = E_property->next()) { + Vector<String> components = String(E_property->get().name).split("/", true, 1); + if (components.size() >= 1) { + Vector<String> coord_arr = components[0].split(":"); + if (coord_arr.size() == 2 && coord_arr[0].is_valid_int() && coord_arr[1].is_valid_int()) { + Vector2i coords = Vector2i(coord_arr[0].to_int(), coord_arr[1].to_int()); + per_tile[coords].push_back(&(E_property->get())); + } + } + } + return per_tile; +} + +void TileSetAtlasSourceEditor::_menu_option(int p_option) { + switch (p_option) { + case TILE_DELETE: { + List<PropertyInfo> list; + tile_set_atlas_source->get_property_list(&list); + Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + undo_redo->create_action(TTR("Remove tile")); + + // Remove tiles + Set<Vector2i> removed; + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + TileSelection selected = E->get(); + if (selected.alternative == 0) { + // Remove a tile. + undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", selected.tile); + undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", selected.tile); + removed.insert(selected.tile); + if (per_tile.has(selected.tile)) { + for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) { + String property = E_property->get()->name; + Variant value = tile_set_atlas_source->get(property); + if (value.get_type() != Variant::NIL) { + undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value); + } + } + } + } + } + + // Remove alternatives + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + TileSelection selected = E->get(); + if (selected.alternative > 0 && !removed.has(selected.tile)) { + // Remove an alternative tile. + undo_redo->add_do_method(tile_set_atlas_source, "remove_alternative_tile", selected.tile, selected.alternative); + undo_redo->add_undo_method(tile_set_atlas_source, "create_alternative_tile", selected.tile, selected.alternative); + if (per_tile.has(selected.tile)) { + for (List<const PropertyInfo *>::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) { + Vector<String> components = E_property->get()->name.split("/", true, 2); + if (components.size() >= 2 && components[1].is_valid_int() && components[1].to_int() == selected.alternative) { + String property = E_property->get()->name; + Variant value = tile_set_atlas_source->get(property); + if (value.get_type() != Variant::NIL) { + undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value); + } + } + } + } + } + } + undo_redo->commit_action(); + _update_fix_selected_and_hovered_tiles(); + _update_tile_id_label(); + } break; + case TILE_CREATE: { + undo_redo->create_action(TTR("Create a tile")); + undo_redo->add_do_method(tile_set_atlas_source, "create_tile", menu_option_coords); + Array array; + array.push_back(menu_option_coords); + array.push_back(0); + undo_redo->add_do_method(this, "_set_selection_from_array", array); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", menu_option_coords); + undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); + undo_redo->commit_action(); + _update_tile_id_label(); + } break; + case TILE_CREATE_ALTERNATIVE: { + undo_redo->create_action(TTR("Create tile alternatives")); + Array array; + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + if (E->get().alternative == 0) { + int next_id = tile_set_atlas_source->get_next_alternative_tile_id(E->get().tile); + undo_redo->add_do_method(tile_set_atlas_source, "create_alternative_tile", E->get().tile, next_id); + array.push_back(E->get().tile); + array.push_back(next_id); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_alternative_tile", E->get().tile, next_id); + } + } + undo_redo->add_do_method(this, "_set_selection_from_array", array); + undo_redo->add_undo_method(this, "_set_selection_from_array", _get_selection_as_array()); + 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; + case ADVANCED_AUTO_REMOVE_TILES: { + _auto_remove_tiles(); + } break; + } +} + +void TileSetAtlasSourceEditor::_unhandled_key_input(const Ref<InputEvent> &p_event) { + // Check for shortcuts. + if (ED_IS_SHORTCUT("tiles_editor/delete_tile", p_event)) { + if (tools_button_group->get_pressed_button() == tool_select_button && !selection.is_empty()) { + _menu_option(TILE_DELETE); + accept_event(); + } + } +} + +void TileSetAtlasSourceEditor::_set_selection_from_array(Array p_selection) { + ERR_FAIL_COND((p_selection.size() % 2) != 0); + selection.clear(); + for (int i = 0; i < p_selection.size() / 2; i++) { + TileSelection selected = { p_selection[i * 2], p_selection[i * 2 + 1] }; + if (tile_set_atlas_source->has_tile(selected.tile) && tile_set_atlas_source->has_alternative_tile(selected.tile, selected.alternative)) { + selection.insert(selected); + } + } + _update_tile_inspector(); + _update_tile_id_label(); + _update_atlas_view(); + _update_current_tile_data_editor(); +} + +Array TileSetAtlasSourceEditor::_get_selection_as_array() { + Array output; + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + output.push_back(E->get().tile); + output.push_back(E->get().alternative); + } + return output; +} + +void TileSetAtlasSourceEditor::_tile_atlas_control_draw() { + // Colors. + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + + // Draw the selected tile. + if (tools_button_group->get_pressed_button() == tool_select_button) { + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + TileSelection selected = E->get(); + if (selected.alternative == 0) { + // Draw the rect. + Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); + tile_atlas_control->draw_rect(region, selection_color, false); + } + } + + if (selection.size() == 1) { + // Draw the resize handles (only when it's possible to expand). + TileSelection selected = selection.front()->get(); + Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(selected.tile); + Size2 zoomed_size = resize_handle->get_size() / tile_atlas_view->get_zoom(); + Rect2 region = tile_set_atlas_source->get_tile_texture_region(selected.tile); + Rect2 rect = region.grow_individual(zoomed_size.x, zoomed_size.y, 0, 0); + Vector2i coords[] = { Vector2i(0, 0), Vector2i(1, 0), Vector2i(1, 1), Vector2i(0, 1) }; + Vector2i directions[] = { Vector2i(0, -1), Vector2i(1, 0), Vector2i(0, 1), Vector2i(-1, 0) }; + bool can_grow[4]; + for (int i = 0; i < 4; i++) { + can_grow[i] = tile_set_atlas_source->can_move_tile_in_atlas(selected.tile, selected.tile + directions[i]); + can_grow[i] |= (i % 2 == 0) ? size_in_atlas.y > 1 : size_in_atlas.x > 1; + } + for (int i = 0; i < 4; i++) { + Vector2 pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[i]; + if (can_grow[i] && can_grow[(i + 3) % 4]) { + tile_atlas_control->draw_texture_rect(resize_handle, Rect2(pos, zoomed_size), false); + } else { + tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2(pos, zoomed_size), false); + } + Vector2 next_pos = rect.position + Vector2(rect.size.x, rect.size.y) * coords[(i + 1) % 4]; + if (can_grow[i]) { + tile_atlas_control->draw_texture_rect(resize_handle, Rect2((pos + next_pos) / 2.0, zoomed_size), false); + } else { + tile_atlas_control->draw_texture_rect(resize_handle_disabled, Rect2((pos + next_pos) / 2.0, zoomed_size), false); + } + } + } + } + + if (drag_type == DRAG_TYPE_REMOVE_TILES) { + // Draw the tiles to be removed. + for (Set<Vector2i>::Element *E = drag_modified_tiles.front(); E; E = E->next()) { + tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(E->get()), Color(0.0, 0.0, 0.0), false); + } + } else if (drag_type == DRAG_TYPE_RECT_SELECT || drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT) { + // Draw tiles to be removed. + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); + area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); + + Color color = Color(0.0, 0.0, 0.0); + if (drag_type == DRAG_TYPE_RECT_SELECT) { + color = selection_color.lightened(0.2); + } + + Set<Vector2i> to_paint; + for (int x = area.get_position().x; x < area.get_end().x; x++) { + for (int y = area.get_position().y; y < area.get_end().y; y++) { + Vector2i coords = tile_set_atlas_source->get_tile_at_coords(Vector2i(x, y)); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + to_paint.insert(coords); + } + } + } + + for (Set<Vector2i>::Element *E = to_paint.front(); E; E = E->next()) { + Vector2i coords = E->get(); + tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(coords), color, false); + } + } else if (drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) { + // Draw tiles to be created. + Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); + Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); + + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); + area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); + for (int x = area.get_position().x; x < area.get_end().x; x++) { + for (int y = area.get_position().y; y < area.get_end().y; y++) { + Vector2i coords = Vector2i(x, y); + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { + Vector2i origin = margins + (coords * (tile_size + separation)); + tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false); + } + } + } + } + + // Draw the hovered tile. + if (drag_type == DRAG_TYPE_REMOVE_TILES_USING_RECT || drag_type == DRAG_TYPE_CREATE_TILES_USING_RECT) { + // Draw the rect. + Vector2i start_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(drag_start_mouse_pos); + Vector2i new_base_tiles_coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + Rect2i area = Rect2i(start_base_tiles_coords, new_base_tiles_coords - start_base_tiles_coords).abs(); + area.set_end((area.get_end() + Vector2i(1, 1)).min(tile_set_atlas_source->get_atlas_grid_size())); + Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); + Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); + Vector2i origin = margins + (area.position * (tile_size + separation)); + tile_atlas_control->draw_rect(Rect2i(origin, area.size * tile_size), Color(1.0, 1.0, 1.0), false); + } else { + Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + if (hovered_base_tile_coords.x >= 0 && hovered_base_tile_coords.y >= 0 && hovered_base_tile_coords.x < grid_size.x && hovered_base_tile_coords.y < grid_size.y) { + Vector2i hovered_tile = tile_set_atlas_source->get_tile_at_coords(hovered_base_tile_coords); + if (hovered_tile != TileSetSource::INVALID_ATLAS_COORDS) { + // Draw existing hovered tile. + tile_atlas_control->draw_rect(tile_set_atlas_source->get_tile_texture_region(hovered_tile), Color(1.0, 1.0, 1.0), false); + } else { + // Draw empty tile, only in add/remove tiles mode. + if (tools_button_group->get_pressed_button() == tool_setup_atlas_source_button) { + Vector2i margins = tile_set_atlas_source->get_margins(); + Vector2i separation = tile_set_atlas_source->get_separation(); + Vector2i tile_size = tile_set_atlas_source->get_texture_region_size(); + Vector2i origin = margins + (hovered_base_tile_coords * (tile_size + separation)); + tile_atlas_control->draw_rect(Rect2i(origin, tile_size), Color(1.0, 1.0, 1.0), false); + } + } + } + } +} + +void TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw() { + if (current_tile_data_editor) { + // Draw the preview of the selected property. + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i coords = tile_set_atlas_source->get_tile_id(i); + Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(coords); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(coords, 0); + + Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); + xform.translate(position); + + if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, 0 })) { + continue; + } + + TileMapCell cell; + cell.source_id = tile_set_atlas_source_id; + cell.set_atlas_coords(coords); + cell.alternative_tile = 0; + current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell); + } + + // Draw the selection on top of other. + if (tools_button_group->get_pressed_button() == tool_select_button) { + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + if (E->get().alternative != 0) { + continue; + } + Rect2i texture_region = tile_set_atlas_source->get_tile_texture_region(E->get().tile); + Vector2i position = (texture_region.position + texture_region.get_end()) / 2 + tile_set_atlas_source->get_tile_effective_texture_offset(E->get().tile, 0); + + Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); + xform.translate(position); + + TileMapCell cell; + cell.source_id = tile_set_atlas_source_id; + cell.set_atlas_coords(E->get().tile); + cell.alternative_tile = 0; + current_tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, cell, true); + } + } + + // Call the TileData's editor custom draw function. + if (tools_button_group->get_pressed_button() == tool_paint_button) { + Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); + current_tile_data_editor->forward_draw_over_atlas(tile_atlas_view, tile_set_atlas_source, tile_atlas_control_unscaled, xform); + } + } +} + +void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event) { + // Update the hovered alternative tile. + hovered_alternative_tile_coords = tile_atlas_view->get_alternative_tile_at_pos(alternative_tiles_control->get_local_mouse_position()); + + // Forward the event to the current tile data editor if we are in the painting mode. + if (tools_button_group->get_pressed_button() == tool_paint_button) { + if (current_tile_data_editor) { + current_tile_data_editor->forward_painting_alternatives_gui_input(tile_atlas_view, tile_set_atlas_source, p_event); + } + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + tile_atlas_view->update(); + return; + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + } + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid()) { + drag_type = DRAG_TYPE_NONE; + + Vector2 mouse_local_pos = alternative_tiles_control->get_local_mouse_position(); + if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { + // Left click pressed. + if (tools_button_group->get_pressed_button() == tool_select_button) { + Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos); + + selection.clear(); + TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) }; + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { + selection.insert(selected); + } + + _update_tile_inspector(); + _update_tile_id_label(); + } + } + } else if (mb->get_button_index() == MOUSE_BUTTON_RIGHT) { + if (mb->is_pressed()) { + // Right click pressed + Vector3 tile = tile_atlas_view->get_alternative_tile_at_pos(mouse_local_pos); + + selection.clear(); + TileSelection selected = { Vector2i(tile.x, tile.y), int(tile.z) }; + if (selected.tile != TileSetSource::INVALID_ATLAS_COORDS) { + selection.insert(selected); + } + + _update_tile_inspector(); + _update_tile_id_label(); + + if (selection.size() == 1) { + selected = selection.front()->get(); + menu_option_coords = selected.tile; + menu_option_alternative = selected.alternative; + alternative_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); + } + } + } + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); + } +} + +void TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited() { + hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); + tile_atlas_control->update(); + tile_atlas_control_unscaled->update(); + alternative_tiles_control->update(); + alternative_tiles_control_unscaled->update(); +} + +void TileSetAtlasSourceEditor::_tile_alternatives_control_draw() { + Color grid_color = EditorSettings::get_singleton()->get("editors/tiles_editor/grid_color"); + Color selection_color = Color().from_hsv(Math::fposmod(grid_color.get_h() + 0.5, 1.0), grid_color.get_s(), grid_color.get_v(), 1.0); + + // Update the hovered alternative tile. + if (tools_button_group->get_pressed_button() == tool_select_button) { + // Draw hovered tile. + Vector2i coords = Vector2(hovered_alternative_tile_coords.x, hovered_alternative_tile_coords.y); + if (coords != TileSetSource::INVALID_ATLAS_COORDS) { + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, hovered_alternative_tile_coords.z); + if (rect != Rect2i()) { + alternative_tiles_control->draw_rect(rect, Color(1.0, 1.0, 1.0), false); + } + } + + // Draw selected tile. + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + TileSelection selected = E->get(); + if (selected.alternative >= 1) { + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(selected.tile, selected.alternative); + if (rect != Rect2i()) { + alternative_tiles_control->draw_rect(rect, selection_color, false); + } + } + } + } +} + +void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { + // Draw the preview of the selected property. + if (current_tile_data_editor) { + // Draw the preview of the currently selected property. + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i coords = tile_set_atlas_source->get_tile_id(i); + for (int j = 0; j < tile_set_atlas_source->get_alternative_tiles_count(coords); j++) { + int alternative_tile = tile_set_atlas_source->get_alternative_tile_id(coords, j); + if (alternative_tile == 0) { + continue; + } + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(coords, alternative_tile); + Vector2 position = (rect.get_position() + rect.get_end()) / 2; + + Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform(); + xform.translate(position); + + if (tools_button_group->get_pressed_button() == tool_select_button && selection.has({ coords, alternative_tile })) { + continue; + } + + TileMapCell cell; + cell.source_id = tile_set_atlas_source_id; + cell.set_atlas_coords(coords); + cell.alternative_tile = alternative_tile; + current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell); + } + } + + // Draw the selection on top of other. + if (tools_button_group->get_pressed_button() == tool_select_button) { + for (Set<TileSelection>::Element *E = selection.front(); E; E = E->next()) { + if (E->get().alternative == 0) { + continue; + } + Rect2i rect = tile_atlas_view->get_alternative_tile_rect(E->get().tile, E->get().alternative); + Vector2 position = (rect.get_position() + rect.get_end()) / 2; + + Transform2D xform = alternative_tiles_control->get_parent_control()->get_transform(); + xform.translate(position); + + TileMapCell cell; + cell.source_id = tile_set_atlas_source_id; + cell.set_atlas_coords(E->get().tile); + cell.alternative_tile = E->get().alternative; + current_tile_data_editor->draw_over_tile(alternative_tiles_control_unscaled, xform, cell, true); + } + } + + // Call the TileData's editor custom draw function. + if (tools_button_group->get_pressed_button() == tool_paint_button) { + Transform2D xform = tile_atlas_control->get_parent_control()->get_transform(); + current_tile_data_editor->forward_draw_over_alternatives(tile_atlas_view, tile_set_atlas_source, alternative_tiles_control_unscaled, xform); + } + } +} + +void TileSetAtlasSourceEditor::_tile_set_atlas_source_changed() { + tile_set_atlas_source_changed_needs_update = true; +} + +void TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed(String p_what) { + if (p_what == "texture" && !atlas_source_proxy_object->get("texture").is_null()) { + confirm_auto_create_tiles->popup_centered(); + } else if (p_what == "id") { + emit_signal("source_id_changed", atlas_source_proxy_object->get_id()); + } +} + +void TileSetAtlasSourceEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { + UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); + + AtlasTileProxyObject *tile_data = Object::cast_to<AtlasTileProxyObject>(p_edited); + if (tile_data) { + 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)); + 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)); + } + } + } else if (p_property == "terrain_set") { + int current_terrain_set = tile_data->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])); + } + } + } + } +#undef ADD_UNDO +} + +void TileSetAtlasSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set_atlas_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_atlas_source); + + if (p_tile_set == tile_set && p_tile_set_atlas_source == tile_set_atlas_source && p_source_id == tile_set_atlas_source_id) { + return; + } + + // 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)); + } + + // Clear the selection. + selection.clear(); + + // Change the edited object. + tile_set = p_tile_set; + tile_set_atlas_source = p_tile_set_atlas_source; + 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)); + } + + // Update everything. + _update_source_inspector(); + + // Update the selected tile. + _update_fix_selected_and_hovered_tiles(); + _update_tile_id_label(); + _update_atlas_view(); + _update_atlas_source_inspector(); + _update_tile_inspector(); + _update_tile_data_editors(); + _update_current_tile_data_editor(); +} + +void TileSetAtlasSourceEditor::init_source() { + confirm_auto_create_tiles->popup_centered(); +} + +void TileSetAtlasSourceEditor::_auto_create_tiles() { + if (!tile_set_atlas_source) { + return; + } + + 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(); + undo_redo->create_action(TTR("Create tiles in non-transparent texture regions")); + for (int y = 0; y < grid_size.y; y++) { + for (int x = 0; x < grid_size.x; x++) { + // Check if we have a tile at the coord + Vector2i coords = Vector2i(x, y); + if (tile_set_atlas_source->get_tile_at_coords(coords) == TileSetSource::INVALID_ATLAS_COORDS) { + // Check if the texture is empty at the given coords. + Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size); + bool is_opaque = false; + for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) { + for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) { + if (texture->is_pixel_opaque(region_x, region_y)) { + is_opaque = true; + break; + } + } + if (is_opaque) { + break; + } + } + + // If we do have opaque pixels, create a tile. + if (is_opaque) { + undo_redo->add_do_method(tile_set_atlas_source, "create_tile", coords); + undo_redo->add_undo_method(tile_set_atlas_source, "remove_tile", coords); + } + } + } + } + undo_redo->commit_action(); + } +} + +void TileSetAtlasSourceEditor::_auto_remove_tiles() { + if (!tile_set_atlas_source) { + return; + } + + 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(); + Vector2i grid_size = tile_set_atlas_source->get_atlas_grid_size(); + + undo_redo->create_action(TTR("Remove tiles in fully transparent texture regions")); + + List<PropertyInfo> list; + tile_set_atlas_source->get_property_list(&list); + Map<Vector2i, List<const PropertyInfo *>> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + + for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) { + Vector2i coords = tile_set_atlas_source->get_tile_id(i); + Vector2i size_in_atlas = tile_set_atlas_source->get_tile_size_in_atlas(coords); + + // Skip tiles outside texture. + if ((coords.x + size_in_atlas.x) > grid_size.x || (coords.y + size_in_atlas.y) > grid_size.y) { + continue; + } + + // Check if the texture is empty at the given coords. + Rect2i region = Rect2i(margins + (coords * (texture_region_size + separation)), texture_region_size * size_in_atlas); + bool is_opaque = false; + for (int region_x = region.get_position().x; region_x < region.get_end().x; region_x++) { + for (int region_y = region.get_position().y; region_y < region.get_end().y; region_y++) { + if (texture->is_pixel_opaque(region_x, region_y)) { + is_opaque = true; + break; + } + } + if (is_opaque) { + break; + } + } + + // If we do have opaque pixels, create a tile. + if (!is_opaque) { + undo_redo->add_do_method(tile_set_atlas_source, "remove_tile", coords); + undo_redo->add_undo_method(tile_set_atlas_source, "create_tile", coords); + if (per_tile.has(coords)) { + for (List<const PropertyInfo *>::Element *E_property = per_tile[coords].front(); E_property; E_property = E_property->next()) { + String property = E_property->get()->name; + Variant value = tile_set_atlas_source->get(property); + if (value.get_type() != Variant::NIL) { + undo_redo->add_undo_method(tile_set_atlas_source, "set", E_property->get()->name, value); + } + } + } + } + } + undo_redo->commit_action(); + } +} + +void TileSetAtlasSourceEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + tool_setup_atlas_source_button->set_icon(get_theme_icon("Tools", "EditorIcons")); + tool_select_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); + tool_paint_button->set_icon(get_theme_icon("CanvasItem", "EditorIcons")); + + tools_settings_erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons")); + + tool_advanced_menu_buttom->set_icon(get_theme_icon("GuiTabMenu", "EditorIcons")); + + resize_handle = get_theme_icon("EditorHandle", "EditorIcons"); + resize_handle_disabled = get_theme_icon("EditorHandleDisabled", "EditorIcons"); + break; + case NOTIFICATION_INTERNAL_PROCESS: + if (tile_set_atlas_source_changed_needs_update) { + // Update everything. + _update_source_inspector(); + + // Update the selected tile. + _update_fix_selected_and_hovered_tiles(); + _update_tile_id_label(); + _update_atlas_view(); + _update_atlas_source_inspector(); + _update_tile_inspector(); + _update_tile_data_editors(); + _update_current_tile_data_editor(); + + tile_set_atlas_source_changed_needs_update = false; + } + break; + default: + break; + } +} + +void TileSetAtlasSourceEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileSetAtlasSourceEditor::_unhandled_key_input); + ClassDB::bind_method(D_METHOD("_set_selection_from_array"), &TileSetAtlasSourceEditor::_set_selection_from_array); + + ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id"))); +} + +TileSetAtlasSourceEditor::TileSetAtlasSourceEditor() { + set_process_unhandled_key_input(true); + set_process_internal(true); + + // -- Right side -- + HSplitContainer *split_container_right_side = memnew(HSplitContainer); + split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(split_container_right_side); + + // Middle panel. + ScrollContainer *middle_panel = memnew(ScrollContainer); + middle_panel->set_enable_h_scroll(false); + middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE); + split_container_right_side->add_child(middle_panel); + + VBoxContainer *middle_vbox_container = memnew(VBoxContainer); + middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL); + middle_panel->add_child(middle_vbox_container); + + // Tile inspector. + tile_inspector_label = memnew(Label); + tile_inspector_label->set_text(TTR("Tile Properties:")); + 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_inspector = memnew(EditorInspector); + tile_inspector->set_undo_redo(undo_redo); + tile_inspector->set_enable_v_scroll(false); + tile_inspector->edit(tile_proxy_object); + tile_inspector->set_use_folding(true); + tile_inspector->connect("property_selected", callable_mp(this, &TileSetAtlasSourceEditor::_inspector_property_selected)); + middle_vbox_container->add_child(tile_inspector); + + tile_inspector_no_tile_selected_label = memnew(Label); + tile_inspector_no_tile_selected_label->set_align(Label::ALIGN_CENTER); + tile_inspector_no_tile_selected_label->set_text(TTR("No tile selected.")); + middle_vbox_container->add_child(tile_inspector_no_tile_selected_label); + + // Property values palette. + tile_data_editors_popup = memnew(Popup); + + tile_data_editors_label = memnew(Label); + tile_data_editors_label->set_text(TTR("Paint Properties:")); + middle_vbox_container->add_child(tile_data_editors_label); + + tile_data_editor_dropdown_button = memnew(Button); + tile_data_editor_dropdown_button->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_draw)); + tile_data_editor_dropdown_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editor_dropdown_button_pressed)); + middle_vbox_container->add_child(tile_data_editor_dropdown_button); + tile_data_editor_dropdown_button->add_child(tile_data_editors_popup); + + tile_data_editors_tree = memnew(Tree); + tile_data_editors_tree->set_hide_root(true); + tile_data_editors_tree->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + tile_data_editors_tree->set_h_scroll_enabled(false); + tile_data_editors_tree->set_v_scroll_enabled(false); + tile_data_editors_tree->connect("item_selected", callable_mp(this, &TileSetAtlasSourceEditor::_tile_data_editors_tree_selected)); + tile_data_editors_popup->add_child(tile_data_editors_tree); + + tile_data_painting_editor_container = memnew(VBoxContainer); + tile_data_painting_editor_container->set_h_size_flags(SIZE_EXPAND_FILL); + middle_vbox_container->add_child(tile_data_painting_editor_container); + + // Atlas source inspector. + atlas_source_inspector_label = memnew(Label); + atlas_source_inspector_label->set_text(TTR("Atlas Properties:")); + middle_vbox_container->add_child(atlas_source_inspector_label); + + atlas_source_proxy_object = memnew(TileSetAtlasSourceProxyObject()); + atlas_source_proxy_object->connect("changed", callable_mp(this, &TileSetAtlasSourceEditor::_atlas_source_proxy_object_changed)); + + atlas_source_inspector = memnew(EditorInspector); + atlas_source_inspector->set_undo_redo(undo_redo); + atlas_source_inspector->set_enable_v_scroll(false); + atlas_source_inspector->edit(atlas_source_proxy_object); + middle_vbox_container->add_child(atlas_source_inspector); + + // Right panel. + VBoxContainer *right_panel = memnew(VBoxContainer); + right_panel->set_h_size_flags(SIZE_EXPAND_FILL); + right_panel->set_v_size_flags(SIZE_EXPAND_FILL); + split_container_right_side->add_child(right_panel); + + // -- Dialogs -- + confirm_auto_create_tiles = memnew(AcceptDialog); + confirm_auto_create_tiles->set_title(TTR("Create tiles automatically in non-transparent texture regions?")); + confirm_auto_create_tiles->set_text(TTR("The atlas's texture was modified.\nWould you like to automatically create tiles in the atlas?")); + confirm_auto_create_tiles->get_ok_button()->set_text(TTR("Yes")); + confirm_auto_create_tiles->add_cancel_button()->set_text(TTR("No")); + confirm_auto_create_tiles->connect("confirmed", callable_mp(this, &TileSetAtlasSourceEditor::_auto_create_tiles)); + add_child(confirm_auto_create_tiles); + + // -- Toolbox -- + tools_button_group.instantiate(); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_source_inspector).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_data_editors).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_current_tile_data_editor).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view).unbind(1)); + tools_button_group->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar).unbind(1)); + + toolbox = memnew(HBoxContainer); + right_panel->add_child(toolbox); + + tool_setup_atlas_source_button = memnew(Button); + tool_setup_atlas_source_button->set_flat(true); + tool_setup_atlas_source_button->set_toggle_mode(true); + tool_setup_atlas_source_button->set_pressed(true); + tool_setup_atlas_source_button->set_button_group(tools_button_group); + tool_setup_atlas_source_button->set_tooltip(TTR("Atlas Setup. Add/Remove tiles tool (use the shift key to create big tiles, control for rectangle editing).")); + toolbox->add_child(tool_setup_atlas_source_button); + + tool_select_button = memnew(Button); + tool_select_button->set_flat(true); + tool_select_button->set_toggle_mode(true); + tool_select_button->set_pressed(false); + tool_select_button->set_button_group(tools_button_group); + tool_select_button->set_tooltip(TTR("Select tiles.")); + toolbox->add_child(tool_select_button); + + tool_paint_button = memnew(Button); + tool_paint_button->set_flat(true); + tool_paint_button->set_toggle_mode(true); + tool_paint_button->set_button_group(tools_button_group); + tool_paint_button->set_tooltip(TTR("Paint properties.")); + toolbox->add_child(tool_paint_button); + + // Tool settings. + tool_settings = memnew(HBoxContainer); + toolbox->add_child(tool_settings); + + tool_settings_vsep = memnew(VSeparator); + tool_settings->add_child(tool_settings_vsep); + + tool_settings_tile_data_toolbar_container = memnew(HBoxContainer); + tool_settings->add_child(tool_settings_tile_data_toolbar_container); + + tools_settings_erase_button = memnew(Button); + tools_settings_erase_button->set_flat(true); + tools_settings_erase_button->set_toggle_mode(true); + tools_settings_erase_button->set_shortcut(ED_SHORTCUT("tiles_editor/eraser", "Eraser", KEY_E)); + tools_settings_erase_button->set_shortcut_context(this); + tool_settings->add_child(tools_settings_erase_button); + + tool_advanced_menu_buttom = memnew(MenuButton); + tool_advanced_menu_buttom->set_flat(true); + tool_advanced_menu_buttom->get_popup()->add_item(TTR("Cleanup Tiles Outside Texture"), ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE); + tool_advanced_menu_buttom->get_popup()->set_item_disabled(0, true); + tool_advanced_menu_buttom->get_popup()->add_item(TTR("Create Tiles in Non-Transparent Texture Regions"), ADVANCED_AUTO_CREATE_TILES); + tool_advanced_menu_buttom->get_popup()->add_item(TTR("Remove Tiles in Fully Transparent Texture Regions"), ADVANCED_AUTO_REMOVE_TILES); + tool_advanced_menu_buttom->get_popup()->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); + toolbox->add_child(tool_advanced_menu_buttom); + + _update_toolbar(); + + // Right side of toolbar. + Control *middle_space = memnew(Control); + middle_space->set_h_size_flags(SIZE_EXPAND_FILL); + toolbox->add_child(middle_space); + + tool_tile_id_label = memnew(Label); + tool_tile_id_label->set_mouse_filter(Control::MOUSE_FILTER_STOP); + toolbox->add_child(tool_tile_id_label); + _update_tile_id_label(); + + // Tile atlas view. + tile_atlas_view = memnew(TileAtlasView); + tile_atlas_view->set_h_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view->set_v_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view->connect("transform_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_view_transform)); + tile_atlas_view->connect("transform_changed", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_view_transform_changed).unbind(2)); + right_panel->add_child(tile_atlas_view); + + base_tile_popup_menu = memnew(PopupMenu); + base_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete", TTR("Delete"), KEY_DELETE), TILE_DELETE); + base_tile_popup_menu->add_item(TTR("Create an Alternative Tile"), TILE_CREATE_ALTERNATIVE); + base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); + tile_atlas_view->add_child(base_tile_popup_menu); + + empty_base_tile_popup_menu = memnew(PopupMenu); + empty_base_tile_popup_menu->add_item(TTR("Create a Tile"), TILE_CREATE); + empty_base_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); + tile_atlas_view->add_child(empty_base_tile_popup_menu); + + tile_atlas_control = memnew(Control); + tile_atlas_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_draw)); + tile_atlas_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited)); + tile_atlas_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_gui_input)); + tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control); + + tile_atlas_control_unscaled = memnew(Control); + tile_atlas_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + tile_atlas_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_atlas_control_unscaled_draw)); + tile_atlas_view->add_control_over_atlas_tiles(tile_atlas_control_unscaled, false); + tile_atlas_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + + alternative_tile_popup_menu = memnew(PopupMenu); + alternative_tile_popup_menu->add_shortcut(ED_SHORTCUT("tiles_editor/delete_tile", TTR("Delete"), KEY_DELETE), TILE_DELETE); + alternative_tile_popup_menu->connect("id_pressed", callable_mp(this, &TileSetAtlasSourceEditor::_menu_option)); + tile_atlas_view->add_child(alternative_tile_popup_menu); + + alternative_tiles_control = memnew(Control); + alternative_tiles_control->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_draw)); + alternative_tiles_control->connect("mouse_exited", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_mouse_exited)); + alternative_tiles_control->connect("gui_input", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input)); + tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control); + + alternative_tiles_control_unscaled = memnew(Control); + alternative_tiles_control_unscaled->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + alternative_tiles_control_unscaled->connect("draw", callable_mp(this, &TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw)); + tile_atlas_view->add_control_over_alternative_tiles(alternative_tiles_control_unscaled, false); + alternative_tiles_control_unscaled->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + + tile_atlas_view_missing_source_label = memnew(Label); + tile_atlas_view_missing_source_label->set_text(TTR("Add or select an atlas texture to the left panel.")); + tile_atlas_view_missing_source_label->set_align(Label::ALIGN_CENTER); + tile_atlas_view_missing_source_label->set_valign(Label::VALIGN_CENTER); + tile_atlas_view_missing_source_label->set_h_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view_missing_source_label->set_v_size_flags(SIZE_EXPAND_FILL); + tile_atlas_view_missing_source_label->hide(); + right_panel->add_child(tile_atlas_view_missing_source_label); +} + +TileSetAtlasSourceEditor::~TileSetAtlasSourceEditor() { + memdelete(tile_proxy_object); + memdelete(atlas_source_proxy_object); +} diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h new file mode 100644 index 0000000000..dbb0756a16 --- /dev/null +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -0,0 +1,283 @@ +/*************************************************************************/ +/* tile_set_atlas_source_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_SET_ATLAS_SOURCE_EDITOR_H +#define TILE_SET_ATLAS_SOURCE_EDITOR_H + +#include "tile_atlas_view.h" +#include "tile_data_editors.h" + +#include "editor/editor_node.h" +#include "scene/gui/split_container.h" +#include "scene/resources/tile_set.h" + +class TileSet; + +class TileSetAtlasSourceEditor : public HBoxContainer { + GDCLASS(TileSetAtlasSourceEditor, HBoxContainer); + +private: + // A class to store which tiles are selected. + struct TileSelection { + Vector2i tile = TileSetSource::INVALID_ATLAS_COORDS; + int alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; + + bool operator<(const TileSelection &p_other) const { + if (tile == p_other.tile) { + return alternative < p_other.alternative; + } else { + return tile < p_other.tile; + } + } + }; + + // -- Proxy object for an atlas source, needed by the inspector -- + class TileSetAtlasSourceProxyObject : public Object { + GDCLASS(TileSetAtlasSourceProxyObject, Object); + + private: + Ref<TileSet> tile_set; + TileSetAtlasSource *tile_set_atlas_source = nullptr; + int source_id = -1; + + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + static void _bind_methods(); + + public: + void set_id(int p_id); + int get_id(); + + void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + }; + + // -- Proxy object for a tile, needed by the inspector -- + class AtlasTileProxyObject : public Object { + GDCLASS(AtlasTileProxyObject, Object); + + private: + TileSetAtlasSourceEditor *tiles_set_atlas_source_editor; + + TileSetAtlasSource *tile_set_atlas_source = nullptr; + Set<TileSelection> tiles = Set<TileSelection>(); + + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + + public: + // Update the proxyed object. + void edit(TileSetAtlasSource *p_tile_set_atlas_source, Set<TileSelection> p_tiles = Set<TileSelection>()); + + AtlasTileProxyObject(TileSetAtlasSourceEditor *p_tiles_set_atlas_source_editor) { + tiles_set_atlas_source_editor = p_tiles_set_atlas_source_editor; + } + }; + + Ref<TileSet> tile_set; + TileSetAtlasSource *tile_set_atlas_source = nullptr; + int tile_set_atlas_source_id = -1; + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + bool tile_set_atlas_source_changed_needs_update = false; + + // -- Properties painting -- + VBoxContainer *tile_data_painting_editor_container; + Label *tile_data_editors_label; + Button *tile_data_editor_dropdown_button; + Popup *tile_data_editors_popup; + Tree *tile_data_editors_tree; + void _tile_data_editor_dropdown_button_draw(); + void _tile_data_editor_dropdown_button_pressed(); + + // -- Tile data editors -- + String current_property; + Control *current_tile_data_editor_toolbar = nullptr; + Map<String, TileDataEditor *> tile_data_editors; + TileDataEditor *current_tile_data_editor = nullptr; + void _tile_data_editors_tree_selected(); + + // -- Inspector -- + AtlasTileProxyObject *tile_proxy_object; + Label *tile_inspector_label; + EditorInspector *tile_inspector; + Label *tile_inspector_no_tile_selected_label; + String selected_property; + void _inspector_property_selected(String p_property); + + TileSetAtlasSourceProxyObject *atlas_source_proxy_object; + Label *atlas_source_inspector_label; + EditorInspector *atlas_source_inspector; + + // -- Atlas view -- + HBoxContainer *toolbox; + Label *tile_atlas_view_missing_source_label; + TileAtlasView *tile_atlas_view; + + // Dragging + enum DragType { + DRAG_TYPE_NONE = 0, + DRAG_TYPE_CREATE_TILES, + DRAG_TYPE_CREATE_TILES_USING_RECT, + DRAG_TYPE_CREATE_BIG_TILE, + DRAG_TYPE_REMOVE_TILES, + DRAG_TYPE_REMOVE_TILES_USING_RECT, + + DRAG_TYPE_MOVE_TILE, + + DRAG_TYPE_RECT_SELECT, + + DRAG_TYPE_MAY_POPUP_MENU, + + // Warning: keep in this order. + DRAG_TYPE_RESIZE_TOP_LEFT, + DRAG_TYPE_RESIZE_TOP, + DRAG_TYPE_RESIZE_TOP_RIGHT, + DRAG_TYPE_RESIZE_RIGHT, + DRAG_TYPE_RESIZE_BOTTOM_RIGHT, + DRAG_TYPE_RESIZE_BOTTOM, + DRAG_TYPE_RESIZE_BOTTOM_LEFT, + DRAG_TYPE_RESIZE_LEFT, + }; + DragType drag_type = DRAG_TYPE_NONE; + Vector2i drag_start_mouse_pos; + Vector2i drag_last_mouse_pos; + Vector2i drag_current_tile; + + Rect2i drag_start_tile_shape; + Set<Vector2i> drag_modified_tiles; + void _end_dragging(); + + Map<Vector2i, List<const PropertyInfo *>> _group_properties_per_tiles(const List<PropertyInfo> &r_list, const TileSetAtlasSource *p_atlas); + + // Popup functions. + enum MenuOptions { + TILE_CREATE, + TILE_CREATE_ALTERNATIVE, + TILE_DELETE, + + ADVANCED_CLEANUP_TILES_OUTSIDE_TEXTURE, + ADVANCED_AUTO_CREATE_TILES, + ADVANCED_AUTO_REMOVE_TILES, + }; + Vector2i menu_option_coords; + int menu_option_alternative = TileSetSource::INVALID_TILE_ALTERNATIVE; + void _menu_option(int p_option); + + // Tool buttons. + Ref<ButtonGroup> tools_button_group; + Button *tool_setup_atlas_source_button; + Button *tool_select_button; + Button *tool_paint_button; + Label *tool_tile_id_label; + + // Tool settings. + HBoxContainer *tool_settings; + VSeparator *tool_settings_vsep; + HBoxContainer *tool_settings_tile_data_toolbar_container; + Button *tools_settings_erase_button; + MenuButton *tool_advanced_menu_buttom; + + // Selection. + Set<TileSelection> selection; + + void _set_selection_from_array(Array p_selection); + Array _get_selection_as_array(); + + // A control on the tile atlas to draw and handle input events. + Vector2i hovered_base_tile_coords = TileSetSource::INVALID_ATLAS_COORDS; + + PopupMenu *base_tile_popup_menu; + PopupMenu *empty_base_tile_popup_menu; + Ref<Texture2D> resize_handle; + Ref<Texture2D> resize_handle_disabled; + Control *tile_atlas_control; + Control *tile_atlas_control_unscaled; + void _tile_atlas_control_draw(); + void _tile_atlas_control_unscaled_draw(); + void _tile_atlas_control_mouse_exited(); + void _tile_atlas_control_gui_input(const Ref<InputEvent> &p_event); + void _tile_atlas_view_transform_changed(); + + // A control over the alternative tiles. + Vector3i hovered_alternative_tile_coords = Vector3i(TileSetSource::INVALID_ATLAS_COORDS.x, TileSetSource::INVALID_ATLAS_COORDS.y, TileSetSource::INVALID_TILE_ALTERNATIVE); + + PopupMenu *alternative_tile_popup_menu; + Control *alternative_tiles_control; + Control *alternative_tiles_control_unscaled; + void _tile_alternatives_control_draw(); + void _tile_alternatives_control_unscaled_draw(); + void _tile_alternatives_control_mouse_exited(); + void _tile_alternatives_control_gui_input(const Ref<InputEvent> &p_event); + + // -- Update functions -- + void _update_tile_id_label(); + void _update_source_inspector(); + void _update_fix_selected_and_hovered_tiles(); + void _update_atlas_source_inspector(); + void _update_tile_inspector(); + void _update_tile_data_editors(); + void _update_current_tile_data_editor(); + void _update_manage_tile_properties_button(); + void _update_atlas_view(); + void _update_toolbar(); + + // -- input events -- + void _unhandled_key_input(const Ref<InputEvent> &p_event); + + // -- Misc -- + void _auto_create_tiles(); + void _auto_remove_tiles(); + AcceptDialog *confirm_auto_create_tiles; + + void _tile_set_atlas_source_changed(); + 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); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void edit(Ref<TileSet> p_tile_set, TileSetAtlasSource *p_tile_set_source, int p_source_id); + void init_source(); + + TileSetAtlasSourceEditor(); + ~TileSetAtlasSourceEditor(); +}; + +#endif // TILE_SET_ATLAS_SOURCE_EDITOR_H diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp new file mode 100644 index 0000000000..2c2ebd107f --- /dev/null +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -0,0 +1,512 @@ +/*************************************************************************/ +/* tile_set_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_set_editor.h" + +#include "tile_data_editors.h" +#include "tiles_editor_plugin.h" + +#include "editor/editor_scale.h" + +#include "scene/gui/box_container.h" +#include "scene/gui/control.h" +#include "scene/gui/tab_container.h" + +TileSetEditor *TileSetEditor::singleton = nullptr; + +void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + ERR_FAIL_COND(!tile_set.is_valid()); + + if (!can_drop_data_fw(p_point, p_data, p_from)) { + return; + } + + if (p_from == sources_list) { + // Handle dropping a texture in the list of atlas resources. + int source_id = -1; + int added = 0; + Dictionary d = p_data; + Vector<String> files = d["files"]; + for (int i = 0; i < files.size(); i++) { + Ref<Texture2D> resource = ResourceLoader::load(files[i]); + if (resource.is_valid()) { + // Retrieve the id for the next created source. + source_id = tile_set->get_next_source_id(); + + // Actually create the new source. + Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource); + atlas_source->set_texture(resource); + undo_redo->create_action(TTR("Add a new atlas source")); + undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id); + undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size()); + undo_redo->add_undo_method(*tile_set, "remove_source", source_id); + undo_redo->commit_action(); + added += 1; + } + } + + if (added == 1) { + tile_set_atlas_source_editor->init_source(); + } + + // Update the selected source (thus triggering an update). + _update_atlas_sources_list(source_id); + } +} + +bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + ERR_FAIL_COND_V(!tile_set.is_valid(), false); + + if (p_from == sources_list) { + Dictionary d = p_data; + + if (!d.has("type")) { + return false; + } + + // Check if we have a Texture2D. + if (String(d["type"]) == "files") { + Vector<String> files = d["files"]; + + if (files.size() == 0) { + return false; + } + + for (int i = 0; i < files.size(); i++) { + String file = files[i]; + String ftype = EditorFileSystem::get_singleton()->get_file_type(file); + + if (!ClassDB::is_parent_class(ftype, "Texture2D")) { + return false; + } + } + + return true; + } + } + return false; +} + +void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Get the previously selected id. + int old_selected = -1; + if (sources_list->get_current() >= 0) { + int source_id = sources_list->get_item_metadata(sources_list->get_current()); + if (tile_set->has_source(source_id)) { + old_selected = source_id; + } + } + + int to_select = -1; + if (force_selected_id >= 0) { + to_select = force_selected_id; + } else if (old_selected >= 0) { + to_select = old_selected; + } + + // Clear the list. + sources_list->clear(); + + // Update the atlas sources. + for (int i = 0; i < tile_set->get_source_count(); i++) { + int source_id = tile_set->get_source_id(i); + + TileSetSource *source = *tile_set->get_source(source_id); + + Ref<Texture2D> texture; + String item_text; + + // Atlas source. + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(source); + if (atlas_source) { + texture = atlas_source->get_texture(); + if (texture.is_valid()) { + item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + } else { + item_text = vformat(TTR("No Texture Atlas Source (id:%d)"), source_id); + } + } + + // Scene collection source. + TileSetScenesCollectionSource *scene_collection_source = Object::cast_to<TileSetScenesCollectionSource>(source); + if (scene_collection_source) { + texture = get_theme_icon("PackedScene", "EditorIcons"); + item_text = vformat(TTR("Scene Collection Source (id:%d)"), source_id); + } + + // Use default if not valid. + if (item_text.is_empty()) { + item_text = vformat(TTR("Unknown Type Source (id:%d)"), source_id); + } + if (!texture.is_valid()) { + texture = missing_texture_texture; + } + + sources_list->add_item(item_text, texture); + sources_list->set_item_metadata(i, source_id); + } + + // Set again the current selected item if needed. + if (to_select >= 0) { + for (int i = 0; i < sources_list->get_item_count(); i++) { + if ((int)sources_list->get_item_metadata(i) == to_select) { + sources_list->set_current(i); + if (old_selected != to_select) { + sources_list->emit_signal("item_selected", sources_list->get_current()); + } + break; + } + } + } + + // If nothing is selected, select the first entry. + if (sources_list->get_current() < 0 && sources_list->get_item_count() > 0) { + sources_list->set_current(0); + if (old_selected != int(sources_list->get_item_metadata(0))) { + sources_list->emit_signal("item_selected", sources_list->get_current()); + } + } + + // If there is no source left, hide all editors and show the label. + _source_selected(sources_list->get_current()); + + // Synchronize the lists. + TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current()); +} + +void TileSetEditor::_source_selected(int p_source_index) { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Update the selected source. + sources_delete_button->set_disabled(p_source_index < 0); + + if (p_source_index >= 0) { + int source_id = sources_list->get_item_metadata(p_source_index); + TileSetAtlasSource *atlas_source = Object::cast_to<TileSetAtlasSource>(*tile_set->get_source(source_id)); + TileSetScenesCollectionSource *scenes_collection_source = Object::cast_to<TileSetScenesCollectionSource>(*tile_set->get_source(source_id)); + if (atlas_source) { + no_source_selected_label->hide(); + tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id); + tile_set_atlas_source_editor->show(); + tile_set_scenes_collection_source_editor->hide(); + } else if (scenes_collection_source) { + no_source_selected_label->hide(); + tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->edit(*tile_set, scenes_collection_source, source_id); + tile_set_scenes_collection_source_editor->show(); + } else { + no_source_selected_label->show(); + tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->hide(); + } + } else { + no_source_selected_label->show(); + tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->hide(); + } +} + +void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { + ERR_FAIL_COND(!tile_set.is_valid()); + + switch (p_id_pressed) { + case 0: { + int source_id = tile_set->get_next_source_id(); + + Ref<TileSetAtlasSource> atlas_source = memnew(TileSetAtlasSource); + + // Add a new source. + undo_redo->create_action(TTR("Add atlas source")); + undo_redo->add_do_method(*tile_set, "add_source", atlas_source, source_id); + undo_redo->add_do_method(*atlas_source, "set_texture_region_size", tile_set->get_tile_size()); + undo_redo->add_undo_method(*tile_set, "remove_source", source_id); + undo_redo->commit_action(); + + _update_atlas_sources_list(source_id); + } break; + case 1: { + int source_id = tile_set->get_next_source_id(); + + Ref<TileSetScenesCollectionSource> scene_collection_source = memnew(TileSetScenesCollectionSource); + + // Add a new source. + undo_redo->create_action(TTR("Add atlas source")); + undo_redo->add_do_method(*tile_set, "add_source", scene_collection_source, source_id); + undo_redo->add_undo_method(*tile_set, "remove_source", source_id); + undo_redo->commit_action(); + + _update_atlas_sources_list(source_id); + } break; + default: + ERR_FAIL(); + } +} + +void TileSetEditor::_source_delete_pressed() { + ERR_FAIL_COND(!tile_set.is_valid()); + + // Update the selected source. + int to_delete = sources_list->get_item_metadata(sources_list->get_current()); + + Ref<TileSetSource> source = tile_set->get_source(to_delete); + + // Remove the source. + undo_redo->create_action(TTR("Remove source")); + undo_redo->add_do_method(*tile_set, "remove_source", to_delete); + undo_redo->add_undo_method(*tile_set, "add_source", source, to_delete); + undo_redo->commit_action(); + + _update_atlas_sources_list(); +} + +void TileSetEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + sources_delete_button->set_icon(get_theme_icon("Remove", "EditorIcons")); + sources_add_button->set_icon(get_theme_icon("Add", "EditorIcons")); + missing_texture_texture = get_theme_icon("TileSet", "EditorIcons"); + break; + case NOTIFICATION_INTERNAL_PROCESS: + if (tile_set_changed_needs_update) { + if (tile_set.is_valid()) { + tile_set->set_edited(true); + } + _update_atlas_sources_list(); + tile_set_changed_needs_update = false; + } + break; + default: + break; + } +} + +void TileSetEditor::_tile_set_changed() { + tile_set_changed_needs_update = true; +} + +void TileSetEditor::_undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value) { + UndoRedo *undo_redo = Object::cast_to<UndoRedo>(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); + TileSet *tile_set = Object::cast_to<TileSet>(p_edited); + if (tile_set) { + Vector<String> components = p_property.split("/", true, 3); + for (int i = 0; i < tile_set->get_source_count(); i++) { + int source_id = tile_set->get_source_id(i); + + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + if (tas.is_valid()) { + for (int j = 0; j < tas->get_tiles_count(); j++) { + Vector2i tile_id = tas->get_tile_id(j); + for (int k = 0; k < tas->get_alternative_tiles_count(tile_id); k++) { + int alternative_id = tas->get_alternative_tile_id(tile_id, k); + TileData *tile_data = Object::cast_to<TileData>(tas->get_tile_data(tile_id, alternative_id)); + ERR_FAIL_COND(!tile_data); + + if (p_property == "occlusion_layers_count") { + int new_layer_count = p_new_value; + int old_layer_count = tile_set->get_occlusion_layers_count(); + if (new_layer_count < old_layer_count) { + for (int occclusion_layer_index = new_layer_count - 1; occclusion_layer_index < old_layer_count; occclusion_layer_index++) { + ADD_UNDO(tile_data, vformat("occlusion_layer_%d/polygon", occclusion_layer_index)); + } + } + } else if (p_property == "physics_layers_count") { + int new_layer_count = p_new_value; + int old_layer_count = tile_set->get_physics_layers_count(); + if (new_layer_count < old_layer_count) { + for (int physics_layer_index = new_layer_count - 1; physics_layer_index < old_layer_count; physics_layer_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygons_count", physics_layer_index)); + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(physics_layer_index); polygon_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/points", physics_layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way", physics_layer_index, polygon_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/polygon_%d/one_way_margin", physics_layer_index, polygon_index)); + } + } + } + } else if ((p_property == "terrains_sets_count" && tile_data->get_terrain_set() >= (int)p_new_value) || + (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "mode") || + (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_int() && components[1] == "terrains_count" && tile_data->get_terrain_set() == components[0].trim_prefix("terrain_set_").to_int() && (int)p_new_value < tile_set->get_terrains_count(tile_data->get_terrain_set()))) { + ADD_UNDO(tile_data, "terrain_set"); + for (int l = 0; l < TileSet::CELL_NEIGHBOR_MAX; l++) { + TileSet::CellNeighbor bit = TileSet::CellNeighbor(l); + if (tile_data->is_valid_peering_bit_terrain(bit)) { + ADD_UNDO(tile_data, "terrains_peering_bit/" + String(TileSet::CELL_NEIGHBOR_ENUM_TO_TEXT[l])); + } + } + } else if (p_property == "navigation_layers_count") { + int new_layer_count = p_new_value; + int old_layer_count = tile_set->get_navigation_layers_count(); + if (new_layer_count < old_layer_count) { + for (int navigation_layer_index = new_layer_count - 1; navigation_layer_index < old_layer_count; navigation_layer_index++) { + ADD_UNDO(tile_data, vformat("navigation_layer_%d/polygon", navigation_layer_index)); + } + } + } else if (p_property == "custom_data_layers_count") { + int new_layer_count = p_new_value; + int old_layer_count = tile_set->get_custom_data_layers_count(); + if (new_layer_count < old_layer_count) { + for (int custom_data_layer_index = new_layer_count - 1; custom_data_layer_index < old_layer_count; custom_data_layer_index++) { + ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer_index)); + } + } + } else if (components.size() == 2 && components[0].begins_with("custom_data_layer_") && components[0].trim_prefix("custom_data_layer_").is_valid_int() && components[1] == "type") { + int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_int(); + ADD_UNDO(tile_data, vformat("custom_data_%d", custom_data_layer)); + } + } + } + } + } + } +#undef ADD_UNDO +} + +void TileSetEditor::_bind_methods() { + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &TileSetEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &TileSetEditor::drop_data_fw); +} + +void TileSetEditor::edit(Ref<TileSet> p_tile_set) { + if (p_tile_set == tile_set) { + return; + } + + // Remove listener. + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); + } + + // Change the edited object. + tile_set = p_tile_set; + + // Add the listener again. + if (tile_set.is_valid()) { + tile_set->connect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); + _update_atlas_sources_list(); + } + + tile_set_atlas_source_editor->hide(); + tile_set_scenes_collection_source_editor->hide(); + no_source_selected_label->show(); +} + +TileSetEditor::TileSetEditor() { + singleton = this; + + set_process_internal(true); + + // Split container. + HSplitContainer *split_container = memnew(HSplitContainer); + split_container->set_name(TTR("Tiles")); + split_container->set_h_size_flags(SIZE_EXPAND_FILL); + split_container->set_v_size_flags(SIZE_EXPAND_FILL); + add_child(split_container); + + // Sources list. + VBoxContainer *split_container_left_side = memnew(VBoxContainer); + split_container_left_side->set_h_size_flags(SIZE_EXPAND_FILL); + split_container_left_side->set_v_size_flags(SIZE_EXPAND_FILL); + split_container_left_side->set_stretch_ratio(0.25); + split_container_left_side->set_custom_minimum_size(Size2i(70, 0) * EDSCALE); + split_container->add_child(split_container_left_side); + + sources_list = memnew(ItemList); + sources_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + sources_list->set_h_size_flags(SIZE_EXPAND_FILL); + sources_list->set_v_size_flags(SIZE_EXPAND_FILL); + sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected)); + sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list)); + sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + sources_list->set_drag_forwarding(this); + split_container_left_side->add_child(sources_list); + + HBoxContainer *sources_bottom_actions = memnew(HBoxContainer); + sources_bottom_actions->set_alignment(HBoxContainer::ALIGN_END); + split_container_left_side->add_child(sources_bottom_actions); + + sources_delete_button = memnew(Button); + sources_delete_button->set_flat(true); + sources_delete_button->set_disabled(true); + sources_delete_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_delete_pressed)); + sources_bottom_actions->add_child(sources_delete_button); + + sources_add_button = memnew(MenuButton); + sources_add_button->set_flat(true); + sources_add_button->get_popup()->add_item(TTR("Atlas")); + sources_add_button->get_popup()->add_item(TTR("Scenes Collection")); + sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed)); + sources_bottom_actions->add_child(sources_add_button); + + // Right side container. + VBoxContainer *split_container_right_side = memnew(VBoxContainer); + split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); + split_container_right_side->set_v_size_flags(SIZE_EXPAND_FILL); + split_container->add_child(split_container_right_side); + + // No source selected. + no_source_selected_label = memnew(Label); + no_source_selected_label->set_text(TTR("No TileSet source selected. Select or create a TileSet source.")); + no_source_selected_label->set_h_size_flags(SIZE_EXPAND_FILL); + no_source_selected_label->set_v_size_flags(SIZE_EXPAND_FILL); + no_source_selected_label->set_align(Label::ALIGN_CENTER); + no_source_selected_label->set_valign(Label::VALIGN_CENTER); + split_container_right_side->add_child(no_source_selected_label); + + // Atlases editor. + tile_set_atlas_source_editor = memnew(TileSetAtlasSourceEditor); + tile_set_atlas_source_editor->set_h_size_flags(SIZE_EXPAND_FILL); + tile_set_atlas_source_editor->set_v_size_flags(SIZE_EXPAND_FILL); + tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list)); + split_container_right_side->add_child(tile_set_atlas_source_editor); + tile_set_atlas_source_editor->hide(); + + // Scenes collection editor. + tile_set_scenes_collection_source_editor = memnew(TileSetScenesCollectionSourceEditor); + tile_set_scenes_collection_source_editor->set_h_size_flags(SIZE_EXPAND_FILL); + tile_set_scenes_collection_source_editor->set_v_size_flags(SIZE_EXPAND_FILL); + tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_atlas_sources_list)); + split_container_right_side->add_child(tile_set_scenes_collection_source_editor); + tile_set_scenes_collection_source_editor->hide(); + + // Registers UndoRedo inspector callback. + EditorNode::get_singleton()->get_editor_data().add_undo_redo_inspector_hook_callback(callable_mp(this, &TileSetEditor::_undo_redo_inspector_callback)); +} + +TileSetEditor::~TileSetEditor() { + if (tile_set.is_valid()) { + tile_set->disconnect("changed", callable_mp(this, &TileSetEditor::_tile_set_changed)); + } +} diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h new file mode 100644 index 0000000000..9e50aca62f --- /dev/null +++ b/editor/plugins/tiles/tile_set_editor.h @@ -0,0 +1,84 @@ +/*************************************************************************/ +/* tile_set_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_SET_EDITOR_H +#define TILE_SET_EDITOR_H + +#include "scene/gui/box_container.h" +#include "scene/resources/tile_set.h" +#include "tile_set_atlas_source_editor.h" +#include "tile_set_scenes_collection_source_editor.h" + +class TileSetEditor : public VBoxContainer { + GDCLASS(TileSetEditor, VBoxContainer); + + static TileSetEditor *singleton; + +private: + Ref<TileSet> tile_set; + bool tile_set_changed_needs_update = false; + + Label *no_source_selected_label; + TileSetAtlasSourceEditor *tile_set_atlas_source_editor; + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + void _update_atlas_sources_list(int force_selected_id = -1); + + // -- Sources management -- + Button *sources_delete_button; + MenuButton *sources_add_button; + ItemList *sources_list; + Ref<Texture2D> missing_texture_texture; + void _source_selected(int p_source_index); + void _source_add_id_pressed(int p_id_pressed); + void _source_delete_pressed(); + + void _tile_set_changed(); + + void _undo_redo_inspector_callback(Object *p_undo_redo, Object *p_edited, String p_property, Variant p_new_value); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + _FORCE_INLINE_ static TileSetEditor *get_singleton() { return singleton; } + + void edit(Ref<TileSet> p_tile_set); + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + + TileSetEditor(); + ~TileSetEditor(); +}; + +#endif // TILE_SET_EDITOR_PLUGIN_H diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp new file mode 100644 index 0000000000..568d4ca8d7 --- /dev/null +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.cpp @@ -0,0 +1,511 @@ +/*************************************************************************/ +/* tile_set_scenes_collection_source_editor.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tile_set_scenes_collection_source_editor.h" + +#include "editor/editor_resource_preview.h" +#include "editor/editor_scale.h" +#include "editor/editor_settings.h" + +#include "scene/gui/item_list.h" + +#include "core/core_string_names.h" + +void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id(int p_id) { + ERR_FAIL_COND(p_id < 0); + if (source_id == p_id) { + return; + } + ERR_FAIL_COND_MSG(tile_set->has_source(p_id), vformat("Cannot change TileSet Scenes Collection source ID. Another TileSet source exists with id %d.", p_id)); + + int previous_source = source_id; + source_id = p_id; // source_id must be updated before, because it's used by the source list update. + tile_set->set_source_id(previous_source, p_id); + emit_signal("changed", "id"); +} + +int TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id() { + return source_id; +} + +bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_set(const StringName &p_name, const Variant &p_value) { + bool valid = false; + tile_set_scenes_collection_source->set(p_name, p_value, &valid); + if (valid) { + emit_signal("changed", String(p_name).utf8().get_data()); + } + return valid; +} + +bool TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_get(const StringName &p_name, Variant &r_ret) const { + if (!tile_set_scenes_collection_source) { + return false; + } + bool valid = false; + r_ret = tile_set_scenes_collection_source->get(p_name, &valid); + return valid; +} + +void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::_bind_methods() { + // -- Shape and layout -- + ClassDB::bind_method(D_METHOD("set_id", "id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::set_id); + ClassDB::bind_method(D_METHOD("get_id"), &TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::get_id); + + ADD_PROPERTY(PropertyInfo(Variant::INT, "id"), "set_id", "get_id"); + + ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); +} + +void TileSetScenesCollectionSourceEditor::TileSetScenesCollectionProxyObject::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set_scenes_collection_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source); + + // Disconnect to changes. + if (tile_set_scenes_collection_source) { + tile_set_scenes_collection_source->disconnect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + + tile_set = p_tile_set; + tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; + source_id = p_source_id; + + // Connect to changes. + if (tile_set_scenes_collection_source) { + if (!tile_set_scenes_collection_source->is_connected(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed))) { + tile_set_scenes_collection_source->connect(CoreStringNames::get_singleton()->property_list_changed, callable_mp((Object *)this, &Object::notify_property_list_changed)); + } + } + + notify_property_list_changed(); +} + +// -- Proxy object used by the tile inspector -- +bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_set(const StringName &p_name, const Variant &p_value) { + if (!tile_set_scenes_collection_source) { + return false; + } + + if (p_name == "id") { + int as_int = int(p_value); + ERR_FAIL_COND_V(as_int < 0, false); + ERR_FAIL_COND_V(tile_set_scenes_collection_source->has_scene_tile_id(as_int), false); + tile_set_scenes_collection_source->set_scene_tile_id(scene_id, as_int); + scene_id = as_int; + emit_signal("changed", "id"); + for (int i = 0; i < tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_count(); i++) { + if (int(tile_set_scenes_collection_source_editor->scene_tiles_list->get_item_metadata(i)) == scene_id) { + tile_set_scenes_collection_source_editor->scene_tiles_list->select(i); + break; + } + } + return true; + } else if (p_name == "scene") { + tile_set_scenes_collection_source->set_scene_tile_scene(scene_id, p_value); + emit_signal("changed", "scene"); + return true; + } else if (p_name == "display_placeholder") { + tile_set_scenes_collection_source->set_scene_tile_display_placeholder(scene_id, p_value); + emit_signal("changed", "display_placeholder"); + return true; + } + + return false; +} + +bool TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get(const StringName &p_name, Variant &r_ret) const { + if (!tile_set_scenes_collection_source) { + return false; + } + + if (p_name == "id") { + r_ret = scene_id; + return true; + } else if (p_name == "scene") { + r_ret = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id); + return true; + } else if (p_name == "display_placeholder") { + r_ret = tile_set_scenes_collection_source->get_scene_tile_display_placeholder(scene_id); + return true; + } + + return false; +} + +void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_get_property_list(List<PropertyInfo> *p_list) const { + if (!tile_set_scenes_collection_source) { + return; + } + + p_list->push_back(PropertyInfo(Variant::INT, "id", PROPERTY_HINT_NONE, "")); + p_list->push_back(PropertyInfo(Variant::OBJECT, "scene", PROPERTY_HINT_RESOURCE_TYPE, "PackedScene")); + p_list->push_back(PropertyInfo(Variant::BOOL, "display_placeholder", PROPERTY_HINT_NONE, "")); +} + +void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::edit(TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_scene_id) { + ERR_FAIL_COND(!p_tile_set_scenes_collection_source); + ERR_FAIL_COND(!p_tile_set_scenes_collection_source->has_scene_tile_id(p_scene_id)); + + tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; + scene_id = p_scene_id; + + notify_property_list_changed(); +} + +void TileSetScenesCollectionSourceEditor::SceneTileProxyObject::_bind_methods() { + ADD_SIGNAL(MethodInfo("changed", PropertyInfo(Variant::STRING, "what"))); +} + +void TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed(String p_what) { + if (p_what == "id") { + emit_signal("source_id_changed", scenes_collection_source_proxy_object->get_id()); + } +} + +void TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed() { + tile_set_scenes_collection_source_changed_needs_update = true; +} + +void TileSetScenesCollectionSourceEditor::_scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud) { + int index = p_ud; + + if (index >= 0 && index < scene_tiles_list->get_item_count()) { + scene_tiles_list->set_item_icon(index, p_preview); + } +} + +void TileSetScenesCollectionSourceEditor::_scenes_list_item_activated(int p_index) { + Ref<PackedScene> packed_scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_tiles_list->get_item_metadata(p_index)); + if (packed_scene.is_valid()) { + EditorNode::get_singleton()->open_request(packed_scene->get_path()); + } +} + +void TileSetScenesCollectionSourceEditor::_source_add_pressed() { + int scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + undo_redo->create_action(TTR("Add a Scene Tile")); + undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", Ref<PackedScene>(), scene_id); + undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); + undo_redo->commit_action(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); +} + +void TileSetScenesCollectionSourceEditor::_source_delete_pressed() { + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + ERR_FAIL_COND(selected_indices.size() <= 0); + int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]); + + undo_redo->create_action(TTR("Remove a Scene Tile")); + undo_redo->add_do_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); + undo_redo->add_undo_method(tile_set_scenes_collection_source, "create_scene_tile", tile_set_scenes_collection_source->get_scene_tile_scene(scene_id), scene_id); + undo_redo->commit_action(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); +} + +void TileSetScenesCollectionSourceEditor::_update_source_inspector() { + // Update the proxy object. + scenes_collection_source_proxy_object->edit(tile_set, tile_set_scenes_collection_source, tile_set_source_id); +} + +void TileSetScenesCollectionSourceEditor::_update_tile_inspector() { + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + bool has_atlas_tile_selected = (selected_indices.size() > 0); + + // Update the proxy object. + if (has_atlas_tile_selected) { + int scene_id = scene_tiles_list->get_item_metadata(selected_indices[0]); + tile_proxy_object->edit(tile_set_scenes_collection_source, scene_id); + } + + // Update visibility. + tile_inspector_label->set_visible(has_atlas_tile_selected); + tile_inspector->set_visible(has_atlas_tile_selected); +} + +void TileSetScenesCollectionSourceEditor::_update_action_buttons() { + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + scene_tile_delete_button->set_disabled(selected_indices.size() <= 0); +} + +void TileSetScenesCollectionSourceEditor::_update_scenes_list() { + if (!tile_set_scenes_collection_source) { + return; + } + + // Get the previously selected id. + Vector<int> selected_indices = scene_tiles_list->get_selected_items(); + int old_selected_scene_id = (selected_indices.size() > 0) ? int(scene_tiles_list->get_item_metadata(selected_indices[0])) : -1; + + // Clear the list. + scene_tiles_list->clear(); + + // Rebuild the list. + int to_reselect = -1; + for (int i = 0; i < tile_set_scenes_collection_source->get_scene_tiles_count(); i++) { + int scene_id = tile_set_scenes_collection_source->get_scene_tile_id(i); + + Ref<PackedScene> scene = tile_set_scenes_collection_source->get_scene_tile_scene(scene_id); + + int item_index = 0; + if (scene.is_valid()) { + item_index = scene_tiles_list->add_item(vformat("%s (path:%s id:%d)", scene->get_path().get_file().get_basename(), scene->get_path(), scene_id)); + Variant udata = i; + EditorResourcePreview::get_singleton()->queue_edited_resource_preview(scene, this, "_scene_thumbnail_done", udata); + } else { + item_index = scene_tiles_list->add_item(TTR("Tile with Invalid Scene"), get_theme_icon("PackedScene", "EditorIcons")); + } + scene_tiles_list->set_item_metadata(item_index, scene_id); + + if (old_selected_scene_id >= 0 && scene_id == old_selected_scene_id) { + to_reselect = i; + } + } + + // Reselect if needed. + if (to_reselect >= 0) { + scene_tiles_list->select(to_reselect); + } + + // Icon size update. + int int_size = int(EditorSettings::get_singleton()->get("filesystem/file_dialog/thumbnail_size")) * EDSCALE; + scene_tiles_list->set_fixed_icon_size(Vector2(int_size, int_size)); +} + +void TileSetScenesCollectionSourceEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: + scene_tile_add_button->set_icon(get_theme_icon("Add", "EditorIcons")); + scene_tile_delete_button->set_icon(get_theme_icon("Remove", "EditorIcons")); + _update_scenes_list(); + break; + case NOTIFICATION_INTERNAL_PROCESS: + if (tile_set_scenes_collection_source_changed_needs_update) { + // Update everything. + _update_source_inspector(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); + tile_set_scenes_collection_source_changed_needs_update = false; + } + break; + case NOTIFICATION_VISIBILITY_CHANGED: + // Update things just in case. + _update_scenes_list(); + _update_action_buttons(); + break; + default: + break; + } +} + +void TileSetScenesCollectionSourceEditor::edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + ERR_FAIL_COND(!p_tile_set_scenes_collection_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tile_set->get_source(p_source_id) != p_tile_set_scenes_collection_source); + + if (p_tile_set == tile_set && p_tile_set_scenes_collection_source == tile_set_scenes_collection_source && p_source_id == tile_set_source_id) { + return; + } + + // Remove listener for old objects. + if (tile_set_scenes_collection_source) { + tile_set_scenes_collection_source->disconnect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); + } + + // Change the edited object. + tile_set = p_tile_set; + tile_set_scenes_collection_source = p_tile_set_scenes_collection_source; + tile_set_source_id = p_source_id; + + // Add the listener again. + if (tile_set_scenes_collection_source) { + tile_set_scenes_collection_source->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_tile_set_scenes_collection_source_changed)); + } + + // Update everything. + _update_source_inspector(); + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); +} + +void TileSetScenesCollectionSourceEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { + if (!can_drop_data_fw(p_point, p_data, p_from)) { + return; + } + + if (p_from == scene_tiles_list) { + // Handle dropping a texture in the list of atlas resources. + int scene_id = -1; + Dictionary d = p_data; + Vector<String> files = d["files"]; + for (int i = 0; i < files.size(); i++) { + Ref<PackedScene> resource = ResourceLoader::load(files[i]); + if (resource.is_valid()) { + scene_id = tile_set_scenes_collection_source->get_next_scene_tile_id(); + undo_redo->create_action(TTR("Add a Scene Tile")); + undo_redo->add_do_method(tile_set_scenes_collection_source, "create_scene_tile", resource, scene_id); + undo_redo->add_undo_method(tile_set_scenes_collection_source, "remove_scene_tile", scene_id); + undo_redo->commit_action(); + } + } + + _update_scenes_list(); + _update_action_buttons(); + _update_tile_inspector(); + } +} + +bool TileSetScenesCollectionSourceEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { + if (p_from == scene_tiles_list) { + Dictionary d = p_data; + + if (!d.has("type")) { + return false; + } + + // Check if we have a Texture2D. + if (String(d["type"]) == "files") { + Vector<String> files = d["files"]; + + if (files.size() == 0) { + return false; + } + + for (int i = 0; i < files.size(); i++) { + String file = files[i]; + String ftype = EditorFileSystem::get_singleton()->get_file_type(file); + + if (!ClassDB::is_parent_class(ftype, "PackedScene")) { + return false; + } + } + + return true; + } + } + return false; +} + +void TileSetScenesCollectionSourceEditor::_bind_methods() { + ADD_SIGNAL(MethodInfo("source_id_changed", PropertyInfo(Variant::INT, "source_id"))); + + ClassDB::bind_method(D_METHOD("_scene_thumbnail_done"), &TileSetScenesCollectionSourceEditor::_scene_thumbnail_done); + ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &TileSetScenesCollectionSourceEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("drop_data_fw"), &TileSetScenesCollectionSourceEditor::drop_data_fw); +} + +TileSetScenesCollectionSourceEditor::TileSetScenesCollectionSourceEditor() { + // -- Right side -- + HSplitContainer *split_container_right_side = memnew(HSplitContainer); + split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(split_container_right_side); + + // Middle panel. + ScrollContainer *middle_panel = memnew(ScrollContainer); + middle_panel->set_enable_h_scroll(false); + middle_panel->set_custom_minimum_size(Size2i(200, 0) * EDSCALE); + split_container_right_side->add_child(middle_panel); + + VBoxContainer *middle_vbox_container = memnew(VBoxContainer); + middle_vbox_container->set_h_size_flags(SIZE_EXPAND_FILL); + middle_panel->add_child(middle_vbox_container); + + // Scenes collection source inspector. + scenes_collection_source_inspector_label = memnew(Label); + scenes_collection_source_inspector_label->set_text(TTR("Scenes collection properties:")); + middle_vbox_container->add_child(scenes_collection_source_inspector_label); + + scenes_collection_source_proxy_object = memnew(TileSetScenesCollectionProxyObject()); + scenes_collection_source_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_collection_source_proxy_object_changed)); + + scenes_collection_source_inspector = memnew(EditorInspector); + scenes_collection_source_inspector->set_undo_redo(undo_redo); + scenes_collection_source_inspector->set_enable_v_scroll(false); + scenes_collection_source_inspector->edit(scenes_collection_source_proxy_object); + middle_vbox_container->add_child(scenes_collection_source_inspector); + + // Tile inspector. + tile_inspector_label = memnew(Label); + tile_inspector_label->set_text(TTR("Tile properties:")); + tile_inspector_label->hide(); + middle_vbox_container->add_child(tile_inspector_label); + + tile_proxy_object = memnew(SceneTileProxyObject(this)); + tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_scenes_list).unbind(1)); + tile_proxy_object->connect("changed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1)); + + tile_inspector = memnew(EditorInspector); + tile_inspector->set_undo_redo(undo_redo); + tile_inspector->set_enable_v_scroll(false); + tile_inspector->edit(tile_proxy_object); + tile_inspector->set_use_folding(true); + middle_vbox_container->add_child(tile_inspector); + + // Scenes list. + VBoxContainer *right_vbox_container = memnew(VBoxContainer); + split_container_right_side->add_child(right_vbox_container); + + scene_tiles_list = memnew(ItemList); + scene_tiles_list->set_h_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_v_size_flags(SIZE_EXPAND_FILL); + scene_tiles_list->set_drag_forwarding(this); + scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_tile_inspector).unbind(1)); + scene_tiles_list->connect("item_selected", callable_mp(this, &TileSetScenesCollectionSourceEditor::_update_action_buttons).unbind(1)); + scene_tiles_list->connect("item_activated", callable_mp(this, &TileSetScenesCollectionSourceEditor::_scenes_list_item_activated)); + scene_tiles_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + right_vbox_container->add_child(scene_tiles_list); + + HBoxContainer *scenes_bottom_actions = memnew(HBoxContainer); + right_vbox_container->add_child(scenes_bottom_actions); + + scene_tile_add_button = memnew(Button); + scene_tile_add_button->set_flat(true); + scene_tile_add_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_add_pressed)); + scenes_bottom_actions->add_child(scene_tile_add_button); + + scene_tile_delete_button = memnew(Button); + scene_tile_delete_button->set_flat(true); + scene_tile_delete_button->set_disabled(true); + scene_tile_delete_button->connect("pressed", callable_mp(this, &TileSetScenesCollectionSourceEditor::_source_delete_pressed)); + scenes_bottom_actions->add_child(scene_tile_delete_button); +} + +TileSetScenesCollectionSourceEditor::~TileSetScenesCollectionSourceEditor() { + memdelete(scenes_collection_source_proxy_object); + memdelete(tile_proxy_object); +} diff --git a/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h new file mode 100644 index 0000000000..195aa79bc4 --- /dev/null +++ b/editor/plugins/tiles/tile_set_scenes_collection_source_editor.h @@ -0,0 +1,139 @@ +/*************************************************************************/ +/* tile_set_scenes_collection_source_editor.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H +#define TILE_SET_SCENES_COLLECTION_SOURCE_EDITOR_H + +#include "editor/editor_node.h" +#include "scene/gui/box_container.h" +#include "scene/resources/tile_set.h" + +class TileSetScenesCollectionSourceEditor : public HBoxContainer { + GDCLASS(TileSetScenesCollectionSourceEditor, HBoxContainer); + +private: + // -- Proxy object for an atlas source, needed by the inspector -- + class TileSetScenesCollectionProxyObject : public Object { + GDCLASS(TileSetScenesCollectionProxyObject, Object); + + private: + Ref<TileSet> tile_set; + TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; + int source_id = -1; + + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + static void _bind_methods(); + + public: + void set_id(int p_id); + int get_id(); + + void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id); + }; + + // -- Proxy object for a tile, needed by the inspector -- + class SceneTileProxyObject : public Object { + GDCLASS(SceneTileProxyObject, Object); + + private: + TileSetScenesCollectionSourceEditor *tile_set_scenes_collection_source_editor; + + TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; + int source_id; + int scene_id; + + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _get_property_list(List<PropertyInfo> *p_list) const; + + static void _bind_methods(); + + public: + // Update the proxyed object. + void edit(TileSetScenesCollectionSource *p_tile_set_atlas_source, int p_scene_id); + + SceneTileProxyObject(TileSetScenesCollectionSourceEditor *p_tiles_set_scenes_collection_source_editor) { + tile_set_scenes_collection_source_editor = p_tiles_set_scenes_collection_source_editor; + } + }; + +private: + Ref<TileSet> tile_set; + TileSetScenesCollectionSource *tile_set_scenes_collection_source = nullptr; + int tile_set_source_id = -1; + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + bool tile_set_scenes_collection_source_changed_needs_update = false; + + // Source inspector. + TileSetScenesCollectionProxyObject *scenes_collection_source_proxy_object; + Label *scenes_collection_source_inspector_label; + EditorInspector *scenes_collection_source_inspector; + + // Tile inspector. + SceneTileProxyObject *tile_proxy_object; + Label *tile_inspector_label; + EditorInspector *tile_inspector; + + ItemList *scene_tiles_list; + Button *scene_tile_add_button; + Button *scene_tile_delete_button; + + void _tile_set_scenes_collection_source_changed(); + void _scenes_collection_source_proxy_object_changed(String p_what); + void _scene_thumbnail_done(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, Variant p_ud); + void _scenes_list_item_activated(int p_index); + + void _source_add_pressed(); + void _source_delete_pressed(); + + // Update methods. + void _update_source_inspector(); + void _update_tile_inspector(); + void _update_scenes_list(); + void _update_action_buttons(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + void edit(Ref<TileSet> p_tile_set, TileSetScenesCollectionSource *p_tile_set_scenes_collection_source, int p_source_id); + void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from); + bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const; + TileSetScenesCollectionSourceEditor(); + ~TileSetScenesCollectionSourceEditor(); +}; + +#endif diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp new file mode 100644 index 0000000000..fb111efc17 --- /dev/null +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -0,0 +1,277 @@ +/*************************************************************************/ +/* tiles_editor_plugin.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "tiles_editor_plugin.h" + +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "editor/plugins/canvas_item_editor_plugin.h" + +#include "scene/2d/tile_map.h" +#include "scene/resources/tile_set.h" + +#include "scene/gui/box_container.h" +#include "scene/gui/button.h" +#include "scene/gui/control.h" +#include "scene/gui/separator.h" + +#include "tile_set_editor.h" + +TilesEditor *TilesEditor::singleton = nullptr; + +void TilesEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: + case NOTIFICATION_THEME_CHANGED: { + tileset_tilemap_switch_button->set_icon(get_theme_icon("TileSet", "EditorIcons")); + } break; + case NOTIFICATION_INTERNAL_PROCESS: { + if (tile_map_changed_needs_update) { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map) { + tile_set = tile_map->get_tileset(); + } + _update_switch_button(); + _update_editors(); + } + } break; + } +} + +void TilesEditor::_tile_map_changed() { + tile_map_changed_needs_update = true; +} + +void TilesEditor::_update_switch_button() { + // Force the buttons status if needed. + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map && !tile_set.is_valid()) { + tileset_tilemap_switch_button->set_pressed(false); + } else if (!tile_map && tile_set.is_valid()) { + tileset_tilemap_switch_button->set_pressed(true); + } +} + +void TilesEditor::_update_editors() { + // Set editors visibility. + tilemap_toolbar->set_visible(!tileset_tilemap_switch_button->is_pressed()); + tilemap_editor->set_visible(!tileset_tilemap_switch_button->is_pressed()); + tileset_editor->set_visible(tileset_tilemap_switch_button->is_pressed()); + + // Enable/disable the switch button. + if (!tileset_tilemap_switch_button->is_pressed()) { + if (!tile_set.is_valid()) { + tileset_tilemap_switch_button->set_disabled(true); + tileset_tilemap_switch_button->set_tooltip(TTR("This TileMap has no assigned TileSet, assign a TileSet to this TileMap to edit it.")); + } else { + tileset_tilemap_switch_button->set_disabled(false); + tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor.")); + } + } else { + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + tileset_tilemap_switch_button->set_disabled(true); + tileset_tilemap_switch_button->set_tooltip(TTR("You are editing a TileSet resource. Select a TileMap node to paint.")); + } else { + tileset_tilemap_switch_button->set_disabled(false); + tileset_tilemap_switch_button->set_tooltip(TTR("Switch between TileSet/TileMap editor.")); + } + } + + // If tile_map is not edited, we change the edited only if we are not editing a tile_set. + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map) { + tilemap_editor->edit(tile_map); + } else { + tilemap_editor->edit(nullptr); + } + tileset_editor->edit(tile_set); + + // Update the viewport + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void TilesEditor::set_atlas_sources_lists_current(int p_current) { + atlas_sources_lists_current = p_current; +} + +void TilesEditor::synchronize_atlas_sources_list(Object *p_current) { + ItemList *item_list = Object::cast_to<ItemList>(p_current); + ERR_FAIL_COND(!item_list); + + if (item_list->is_visible_in_tree()) { + if (atlas_sources_lists_current < 0 || atlas_sources_lists_current >= item_list->get_item_count()) { + item_list->deselect_all(); + } else { + item_list->set_current(atlas_sources_lists_current); + item_list->emit_signal("item_selected", atlas_sources_lists_current); + } + } +} + +void TilesEditor::set_atlas_view_transform(float p_zoom, Vector2 p_scroll) { + atlas_view_zoom = p_zoom; + atlas_view_scroll = p_scroll; +} + +void TilesEditor::synchronize_atlas_view(Object *p_current) { + TileAtlasView *tile_atlas_view = Object::cast_to<TileAtlasView>(p_current); + ERR_FAIL_COND(!tile_atlas_view); + + if (tile_atlas_view->is_visible_in_tree()) { + tile_atlas_view->set_transform(atlas_view_zoom, Vector2(atlas_view_scroll.x, atlas_view_scroll.y)); + } +} + +void TilesEditor::edit(Object *p_object) { + // Disconnect to changes. + TileMap *tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + if (tile_map) { + tile_map->disconnect("changed", callable_mp(this, &TilesEditor::_tile_map_changed)); + } + + // Update edited objects. + tile_set = Ref<TileSet>(); + if (p_object) { + if (p_object->is_class("TileMap")) { + tile_map_id = p_object->get_instance_id(); + tile_map = Object::cast_to<TileMap>(ObjectDB::get_instance(tile_map_id)); + tile_set = tile_map->get_tileset(); + } else if (p_object->is_class("TileSet")) { + tile_set = Ref<TileSet>(p_object); + if (tile_map) { + if (tile_map->get_tileset() != tile_set) { + tile_map = nullptr; + } + } + } + + // Update pressed status button. + if (p_object->is_class("TileMap")) { + tileset_tilemap_switch_button->set_pressed(false); + } else if (p_object->is_class("TileSet")) { + tileset_tilemap_switch_button->set_pressed(true); + } + } + + // Update the editors. + _update_switch_button(); + _update_editors(); + + // Add change listener. + if (tile_map) { + tile_map->connect("changed", callable_mp(this, &TilesEditor::_tile_map_changed)); + } +} + +void TilesEditor::_bind_methods() { +} + +TilesEditor::TilesEditor(EditorNode *p_editor) { + set_process_internal(true); + + // Update the singleton. + singleton = this; + + // Toolbar. + HBoxContainer *toolbar = memnew(HBoxContainer); + toolbar->set_h_size_flags(SIZE_EXPAND_FILL); + add_child(toolbar); + + // Switch button. + tileset_tilemap_switch_button = memnew(Button); + tileset_tilemap_switch_button->set_flat(true); + tileset_tilemap_switch_button->set_toggle_mode(true); + tileset_tilemap_switch_button->connect("toggled", callable_mp(this, &TilesEditor::_update_editors).unbind(1)); + toolbar->add_child(tileset_tilemap_switch_button); + + // Tilemap editor. + tilemap_editor = memnew(TileMapEditor); + tilemap_editor->set_h_size_flags(SIZE_EXPAND_FILL); + tilemap_editor->set_v_size_flags(SIZE_EXPAND_FILL); + tilemap_editor->hide(); + add_child(tilemap_editor); + + tilemap_toolbar = tilemap_editor->get_toolbar(); + toolbar->add_child(tilemap_toolbar); + + // Tileset editor. + tileset_editor = memnew(TileSetEditor); + tileset_editor->set_h_size_flags(SIZE_EXPAND_FILL); + tileset_editor->set_v_size_flags(SIZE_EXPAND_FILL); + tileset_editor->hide(); + add_child(tileset_editor); + + // Initialization. + _update_switch_button(); + _update_editors(); +} + +TilesEditor::~TilesEditor() { +} + +/////////////////////////////////////////////////////////////// + +void TilesEditorPlugin::_notification(int p_what) { +} + +void TilesEditorPlugin::make_visible(bool p_visible) { + if (p_visible) { + tiles_editor_button->show(); + editor_node->make_bottom_panel_item_visible(tiles_editor); + //get_tree()->connect_compat("idle_frame", tileset_editor, "_on_workspace_process"); + } else { + editor_node->hide_bottom_panel(); + tiles_editor_button->hide(); + //get_tree()->disconnect_compat("idle_frame", tileset_editor, "_on_workspace_process"); + } +} + +void TilesEditorPlugin::edit(Object *p_object) { + tiles_editor->edit(p_object); +} + +bool TilesEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("TileMap") || p_object->is_class("TileSet"); +} + +TilesEditorPlugin::TilesEditorPlugin(EditorNode *p_node) { + editor_node = p_node; + + tiles_editor = memnew(TilesEditor(p_node)); + tiles_editor->set_custom_minimum_size(Size2(0, 200) * EDSCALE); + tiles_editor->hide(); + + tiles_editor_button = p_node->add_bottom_panel_item(TTR("Tiles"), tiles_editor); + tiles_editor_button->hide(); +} + +TilesEditorPlugin::~TilesEditorPlugin() { +} diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h new file mode 100644 index 0000000000..6cc6f51598 --- /dev/null +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -0,0 +1,114 @@ +/*************************************************************************/ +/* tiles_editor_plugin.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TILES_EDITOR_PLUGIN_H +#define TILES_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "scene/gui/box_container.h" + +#include "tile_atlas_view.h" +#include "tile_map_editor.h" +#include "tile_set_editor.h" + +class TilesEditor : public VBoxContainer { + GDCLASS(TilesEditor, VBoxContainer); + + static TilesEditor *singleton; + +private: + bool tile_map_changed_needs_update = false; + ObjectID tile_map_id; + Ref<TileSet> tile_set; + + Button *tileset_tilemap_switch_button; + + Control *tilemap_toolbar; + TileMapEditor *tilemap_editor; + + TileSetEditor *tileset_editor; + + void _update_switch_button(); + void _update_editors(); + + // For synchronization. + int atlas_sources_lists_current = 0; + float atlas_view_zoom = 1.0; + Vector2 atlas_view_scroll = Vector2(); + + void _tile_map_changed(); + +protected: + void _notification(int p_what); + static void _bind_methods(); + +public: + _FORCE_INLINE_ static TilesEditor *get_singleton() { return singleton; } + + bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) { return tilemap_editor->forward_canvas_gui_input(p_event); } + void forward_canvas_draw_over_viewport(Control *p_overlay) { tilemap_editor->forward_canvas_draw_over_viewport(p_overlay); } + + // To synchronize the atlas sources lists. + void set_atlas_sources_lists_current(int p_current); + void synchronize_atlas_sources_list(Object *p_current); + + void set_atlas_view_transform(float p_zoom, Vector2 p_scroll); + void synchronize_atlas_view(Object *p_current); + + void edit(Object *p_object); + + TilesEditor(EditorNode *p_editor); + ~TilesEditor(); +}; + +class TilesEditorPlugin : public EditorPlugin { + GDCLASS(TilesEditorPlugin, EditorPlugin); + +private: + EditorNode *editor_node; + TilesEditor *tiles_editor; + Button *tiles_editor_button; + +protected: + void _notification(int p_what); + +public: + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return tiles_editor->forward_canvas_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { tiles_editor->forward_canvas_draw_over_viewport(p_overlay); } + + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + TilesEditorPlugin(EditorNode *p_node); + ~TilesEditorPlugin(); +}; + +#endif // TILES_EDITOR_PLUGIN_H diff --git a/editor/plugins/version_control_editor_plugin.cpp b/editor/plugins/version_control_editor_plugin.cpp index 0af3b936cb..10679ad6f2 100644 --- a/editor/plugins/version_control_editor_plugin.cpp +++ b/editor/plugins/version_control_editor_plugin.cpp @@ -180,7 +180,7 @@ void VersionControlEditorPlugin::_stage_selected() { staged_files_count = 0; TreeItem *root = stage_files->get_root(); if (root) { - TreeItem *file_entry = root->get_children(); + TreeItem *file_entry = root->get_first_child(); while (file_entry) { if (file_entry->is_checked(0)) { EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0)); @@ -207,7 +207,7 @@ void VersionControlEditorPlugin::_stage_all() { staged_files_count = 0; TreeItem *root = stage_files->get_root(); if (root) { - TreeItem *file_entry = root->get_children(); + TreeItem *file_entry = root->get_first_child(); while (file_entry) { EditorVCSInterface::get_singleton()->stage_file(file_entry->get_metadata(0)); file_entry->set_icon_modulate(0, EditorNode::get_singleton()->get_gui_base()->get_theme_color("success_color", "Editor")); @@ -476,6 +476,7 @@ VersionControlEditorPlugin::VersionControlEditorPlugin() { version_control_dock = memnew(PanelContainer); version_control_dock->set_v_size_flags(Control::SIZE_EXPAND_FILL); + version_control_dock->set_custom_minimum_size(Size2(0, 300) * EDSCALE); version_control_dock->hide(); diff_vbc = memnew(VBoxContainer); diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp index acc77bd098..fb91cbb866 100644 --- a/editor/plugins/visual_shader_editor_plugin.cpp +++ b/editor/plugins/visual_shader_editor_plugin.cpp @@ -44,6 +44,7 @@ #include "scene/gui/panel.h" #include "scene/main/window.h" #include "scene/resources/visual_shader_nodes.h" +#include "scene/resources/visual_shader_particle_nodes.h" #include "scene/resources/visual_shader_sdf_nodes.h" #include "servers/display_server.h" #include "servers/rendering/shader_types.h" @@ -71,13 +72,13 @@ const int MAX_FLOAT_CONST_DEFS = sizeof(float_constant_defs) / sizeof(FloatConst Control *VisualShaderNodePlugin::create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node) { if (get_script_instance()) { - return get_script_instance()->call("create_editor", p_parent_resource, p_node); + return get_script_instance()->call("_create_editor", p_parent_resource, p_node); } return nullptr; } void VisualShaderNodePlugin::_bind_methods() { - BIND_VMETHOD(MethodInfo(Variant::OBJECT, "create_editor", PropertyInfo(Variant::OBJECT, "parent_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::OBJECT, "for_node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode"))); + BIND_VMETHOD(MethodInfo(Variant::OBJECT, "_create_editor", PropertyInfo(Variant::OBJECT, "parent_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::OBJECT, "for_node", PROPERTY_HINT_RESOURCE_TYPE, "VisualShaderNode"))); } /////////////////// @@ -109,6 +110,7 @@ void VisualShaderGraphPlugin::_bind_methods() { ClassDB::bind_method("set_uniform_name", &VisualShaderGraphPlugin::set_uniform_name); ClassDB::bind_method("set_expression", &VisualShaderGraphPlugin::set_expression); ClassDB::bind_method("update_curve", &VisualShaderGraphPlugin::update_curve); + ClassDB::bind_method("update_curve3", &VisualShaderGraphPlugin::update_curve3); ClassDB::bind_method("update_constant", &VisualShaderGraphPlugin::update_constant); } @@ -210,9 +212,19 @@ void VisualShaderGraphPlugin::set_uniform_name(VisualShader::Type p_type, int p_ } void VisualShaderGraphPlugin::update_curve(int p_node_id) { - if (links.has(p_node_id) && links[p_node_id].curve_editor) { + if (links.has(p_node_id) && links[p_node_id].curve_editors[0]) { if (((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture().is_valid()) { - links[p_node_id].curve_editor->set_curve(((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture()->get_curve()); + links[p_node_id].curve_editors[0]->set_curve(((VisualShaderNodeCurveTexture *)links[p_node_id].visual_node)->get_texture()->get_curve()); + } + } +} + +void VisualShaderGraphPlugin::update_curve3(int p_node_id) { + if (links.has(p_node_id) && links[p_node_id].curve_editors[0] && links[p_node_id].curve_editors[1] && links[p_node_id].curve_editors[2]) { + if (((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture().is_valid()) { + links[p_node_id].curve_editors[0]->set_curve(((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture()->get_curve_x()); + links[p_node_id].curve_editors[1]->set_curve(((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture()->get_curve_y()); + links[p_node_id].curve_editors[2]->set_curve(((VisualShaderNodeCurve3Texture *)links[p_node_id].visual_node)->get_texture()->get_curve_z()); } } } @@ -264,8 +276,8 @@ void VisualShaderGraphPlugin::register_expression_edit(int p_node_id, CodeEdit * links[p_node_id].expression_edit = p_expression_edit; } -void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, CurveEditor *p_curve_editor) { - links[p_node_id].curve_editor = p_curve_editor; +void VisualShaderGraphPlugin::register_curve_editor(int p_node_id, int p_index, CurveEditor *p_curve_editor) { + links[p_node_id].curve_editors[p_index] = p_curve_editor; } void VisualShaderGraphPlugin::update_uniform_refs() { @@ -311,7 +323,7 @@ void VisualShaderGraphPlugin::make_dirty(bool p_enabled) { } void VisualShaderGraphPlugin::register_link(VisualShader::Type p_type, int p_id, VisualShaderNode *p_visual_node, GraphNode *p_graph_node) { - links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, Map<int, InputPort>(), Map<int, Port>(), nullptr, nullptr, nullptr, nullptr, nullptr }); + links.insert(p_id, { p_type, p_visual_node, p_graph_node, p_visual_node->get_output_port_for_preview() != -1, -1, Map<int, InputPort>(), Map<int, Port>(), nullptr, nullptr, nullptr, nullptr, { nullptr, nullptr, nullptr } }); } void VisualShaderGraphPlugin::register_output_port(int p_node_id, int p_port, TextureButton *p_button) { @@ -322,6 +334,12 @@ void VisualShaderGraphPlugin::register_uniform_name(int p_node_id, LineEdit *p_u links[p_node_id].uniform_name = p_uniform_name; } +void VisualShaderGraphPlugin::update_theme() { + vector_expanded_color[0] = VisualShaderEditor::get_singleton()->get_theme_color("axis_x_color", "Editor"); // red + vector_expanded_color[1] = VisualShaderEditor::get_singleton()->get_theme_color("axis_y_color", "Editor"); // green + vector_expanded_color[2] = VisualShaderEditor::get_singleton()->get_theme_color("axis_z_color", "Editor"); // blue +} + void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (p_type != visual_shader->get_shader_type()) { return; @@ -340,6 +358,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { Color(1.0, 1.0, 0.0), // sampler }; + static const String vector_expanded_name[3] = { + "red", + "green", + "blue" + }; + Ref<VisualShaderNode> vsnode = visual_shader->get_node(p_type, p_id); Ref<VisualShaderNodeResizableBase> resizable_node = Object::cast_to<VisualShaderNodeResizableBase>(vsnode.ptr()); @@ -349,6 +373,8 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { Ref<VisualShaderNodeGroupBase> group_node = Object::cast_to<VisualShaderNodeGroupBase>(vsnode.ptr()); bool is_group = !group_node.is_null(); + bool is_comment = false; + Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(group_node.ptr()); bool is_expression = !expression_node.is_null(); String expression = ""; @@ -392,17 +418,22 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (is_resizable) { Ref<VisualShaderNodeComment> comment_node = Object::cast_to<VisualShaderNodeComment>(vsnode.ptr()); if (comment_node.is_valid()) { + is_comment = true; node->set_comment(true); Label *comment_label = memnew(Label); node->add_child(comment_label); comment_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); comment_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); - comment_label->set_mouse_filter(Control::MouseFilter::MOUSE_FILTER_STOP); comment_label->set_text(comment_node->get_description()); } } + Ref<VisualShaderNodeParticleEmit> emit = vsnode; + if (emit.is_valid()) { + node->set_custom_minimum_size(Size2(200 * EDSCALE, 0)); + } + Ref<VisualShaderNodeUniform> uniform = vsnode; if (uniform.is_valid()) { VisualShaderEditor::get_singleton()->graph->add_child(node); @@ -412,7 +443,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { register_uniform_name(p_id, uniform_name); uniform_name->set_text(uniform->get_uniform_name()); node->add_child(uniform_name); - uniform_name->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_changed), varray(p_id)); + uniform_name->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_changed), varray(p_id)); uniform_name->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_uniform_line_edit_focus_out), varray(uniform_name, p_id)); if (vsnode->get_input_port_count() == 0 && vsnode->get_output_port_count() == 1 && vsnode->get_output_port_name(0) == "") { @@ -452,6 +483,18 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { custom_editor = hbox; } + Ref<VisualShaderNodeCurve3Texture> curve3 = vsnode; + if (curve3.is_valid()) { + if (curve3->get_texture().is_valid() && !curve3->get_texture()->is_connected("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve3))) { + curve3->get_texture()->connect("changed", callable_mp(VisualShaderEditor::get_singleton()->get_graph_plugin(), &VisualShaderGraphPlugin::update_curve3), varray(p_id)); + } + + HBoxContainer *hbox = memnew(HBoxContainer); + custom_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hbox->add_child(custom_editor); + custom_editor = hbox; + } + Ref<VisualShaderNodeFloatConstant> float_const = vsnode; if (float_const.is_valid()) { HBoxContainer *hbox = memnew(HBoxContainer); @@ -475,19 +518,12 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { port_offset++; node->add_child(custom_editor); - if (curve.is_valid()) { + bool is_curve = curve.is_valid() || curve3.is_valid(); + + if (is_curve) { VisualShaderEditor::get_singleton()->graph->add_child(node); VisualShaderEditor::get_singleton()->_update_created_node(node); - CurveEditor *curve_editor = memnew(CurveEditor); - node->add_child(curve_editor); - register_curve_editor(p_id, curve_editor); - curve_editor->set_custom_minimum_size(Size2(300, 0)); - curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); - if (curve->get_texture().is_valid()) { - curve_editor->set_curve(curve->get_texture()->get_curve()); - } - TextureButton *preview = memnew(TextureButton); preview->set_toggle_mode(true); preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityHidden", "EditorIcons")); @@ -499,12 +535,59 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, 0), CONNECT_DEFERRED); custom_editor->add_child(preview); + if (vsnode->get_output_port_for_preview() >= 0) { + show_port_preview(p_type, p_id, vsnode->get_output_port_for_preview()); + } + } + + if (curve.is_valid()) { + CurveEditor *curve_editor = memnew(CurveEditor); + node->add_child(curve_editor); + register_curve_editor(p_id, 0, curve_editor); + curve_editor->set_custom_minimum_size(Size2(300, 0)); + curve_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve->get_texture().is_valid()) { + curve_editor->set_curve(curve->get_texture()->get_curve()); + } + } + + if (curve3.is_valid()) { + CurveEditor *curve_editor_x = memnew(CurveEditor); + node->add_child(curve_editor_x); + register_curve_editor(p_id, 0, curve_editor_x); + curve_editor_x->set_custom_minimum_size(Size2(300, 0)); + curve_editor_x->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve3->get_texture().is_valid()) { + curve_editor_x->set_curve(curve3->get_texture()->get_curve_x()); + } + + CurveEditor *curve_editor_y = memnew(CurveEditor); + node->add_child(curve_editor_y); + register_curve_editor(p_id, 1, curve_editor_y); + curve_editor_y->set_custom_minimum_size(Size2(300, 0)); + curve_editor_y->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve3->get_texture().is_valid()) { + curve_editor_y->set_curve(curve3->get_texture()->get_curve_y()); + } + + CurveEditor *curve_editor_z = memnew(CurveEditor); + node->add_child(curve_editor_z); + register_curve_editor(p_id, 2, curve_editor_z); + curve_editor_z->set_custom_minimum_size(Size2(300, 0)); + curve_editor_z->set_h_size_flags(Control::SIZE_EXPAND_FILL); + if (curve3->get_texture().is_valid()) { + curve_editor_z->set_curve(curve3->get_texture()->get_curve_z()); + } + } + + if (is_curve) { VisualShaderNode::PortType port_left = vsnode->get_input_port_type(0); VisualShaderNode::PortType port_right = vsnode->get_output_port_type(0); node->set_slot(0, true, port_left, type_color[port_left], true, port_right, type_color[port_right]); VisualShaderEditor::get_singleton()->call_deferred("_set_node_size", (int)p_type, p_id, size); } + if (vsnode->is_use_prop_slots()) { return; } @@ -551,13 +634,32 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { } } - for (int i = 0; i < MAX(vsnode->get_input_port_count(), vsnode->get_output_port_count()); i++) { + int output_port_count = 0; + for (int i = 0; i < vsnode->get_output_port_count(); i++) { + if (vsnode->_is_output_port_expanded(i)) { + if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { + output_port_count += 3; + } + } + output_port_count++; + } + int max_ports = MAX(vsnode->get_input_port_count(), output_port_count); + VisualShaderNode::PortType expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + int expanded_port_counter = 0; + + for (int i = 0, j = 0; i < max_ports; i++, j++) { + if (expanded_type == VisualShaderNode::PORT_TYPE_VECTOR && expanded_port_counter >= 3) { + expanded_type = VisualShaderNode::PORT_TYPE_SCALAR; + expanded_port_counter = 0; + i -= 3; + } + if (vsnode->is_port_separator(i)) { node->add_child(memnew(HSeparator)); port_offset++; } - bool valid_left = i < vsnode->get_input_port_count(); + bool valid_left = j < vsnode->get_input_port_count(); VisualShaderNode::PortType port_left = VisualShaderNode::PORT_TYPE_SCALAR; bool port_left_used = false; String name_left; @@ -565,18 +667,24 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_left = vsnode->get_input_port_name(i); port_left = vsnode->get_input_port_type(i); for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { - if (E->get().to_node == p_id && E->get().to_port == i) { + if (E->get().to_node == p_id && E->get().to_port == j) { port_left_used = true; } } } - bool valid_right = i < vsnode->get_output_port_count(); + bool valid_right = true; VisualShaderNode::PortType port_right = VisualShaderNode::PORT_TYPE_SCALAR; String name_right; - if (valid_right) { - name_right = vsnode->get_output_port_name(i); - port_right = vsnode->get_output_port_type(i); + + if (expanded_type == VisualShaderNode::PORT_TYPE_SCALAR) { + valid_right = i < vsnode->get_output_port_count(); + if (valid_right) { + name_right = vsnode->get_output_port_name(i); + port_right = vsnode->get_output_port_type(i); + } + } else { + name_right = vector_expanded_name[expanded_port_counter++]; } HBoxContainer *hb = memnew(HBoxContainer); @@ -621,7 +729,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_left); - name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_input_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, false), CONNECT_DEFERRED); Button *remove_btn = memnew(Button); @@ -662,7 +770,7 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { name_box->set_custom_minimum_size(Size2(65 * EDSCALE, 0)); name_box->set_h_size_flags(Control::SIZE_EXPAND_FILL); name_box->set_text(name_right); - name_box->connect("text_entered", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); + name_box->connect("text_submitted", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_change_output_port_name), varray(name_box, p_id, i), CONNECT_DEFERRED); name_box->connect("focus_exited", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_port_name_focus_out), varray(name_box, p_id, i, true), CONNECT_DEFERRED); OptionButton *type_box = memnew(OptionButton); @@ -684,17 +792,29 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { } } - if (valid_right && visual_shader->get_shader_type() == VisualShader::TYPE_FRAGMENT && 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("GuiVisibilityHidden", "EditorIcons")); - preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityVisible", "EditorIcons")); - preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + if (valid_right) { + if (vsnode->is_output_port_expandable(i)) { + TextureButton *expand = memnew(TextureButton); + expand->set_toggle_mode(true); + expand->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiTreeArrowDown", "EditorIcons")); + expand->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiTreeArrowRight", "EditorIcons")); + expand->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + expand->set_pressed(vsnode->_is_output_port_expanded(i)); + expand->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_expand_output_port), varray(p_id, i, !vsnode->_is_output_port_expanded(i)), CONNECT_DEFERRED); + 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) { + TextureButton *preview = memnew(TextureButton); + preview->set_toggle_mode(true); + preview->set_normal_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityHidden", "EditorIcons")); + preview->set_pressed_texture(VisualShaderEditor::get_singleton()->get_theme_icon("GuiVisibilityVisible", "EditorIcons")); + preview->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - register_output_port(p_id, i, preview); + register_output_port(p_id, j, preview); - preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, i), CONNECT_DEFERRED); - hb->add_child(preview); + preview->connect("pressed", callable_mp(VisualShaderEditor::get_singleton(), &VisualShaderEditor::_preview_select_port), varray(p_id, j), CONNECT_DEFERRED); + hb->add_child(preview); + } } if (is_group) { @@ -706,7 +826,40 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { node->add_child(hb); + if (expanded_type != VisualShaderNode::PORT_TYPE_SCALAR) { + continue; + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], valid_right, port_right, type_color[port_right]); + + if (vsnode->_is_output_port_expanded(i)) { + if (vsnode->get_output_port_type(i) == VisualShaderNode::PORT_TYPE_VECTOR) { + port_offset++; + valid_left = (i + 1) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 1); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[0]); + port_offset++; + + valid_left = (i + 2) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 2); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[1]); + port_offset++; + + valid_left = (i + 3) < vsnode->get_input_port_count(); + port_left = VisualShaderNode::PORT_TYPE_SCALAR; + if (valid_left) { + port_left = vsnode->get_input_port_type(i + 3); + } + node->set_slot(i + port_offset, valid_left, port_left, type_color[port_left], true, VisualShaderNode::PORT_TYPE_SCALAR, vector_expanded_color[2]); + expanded_type = VisualShaderNode::PORT_TYPE_VECTOR; + } + } } if (vsnode->get_output_port_for_preview() >= 0) { @@ -728,14 +881,15 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (is_expression) { CodeEdit *expression_box = memnew(CodeEdit); Ref<CodeHighlighter> expression_syntax_highlighter; - expression_syntax_highlighter.instance(); - expression_node->set_control(expression_box, 0); + expression_syntax_highlighter.instantiate(); + expression_node->set_ctrl_pressed(expression_box, 0); node->add_child(expression_box); register_expression_edit(p_id, expression_box); Color background_color = EDITOR_GET("text_editor/highlighting/background_color"); Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color"); Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); Color function_color = EDITOR_GET("text_editor/highlighting/function_color"); @@ -746,7 +900,11 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expression_box->add_theme_color_override("background_color", background_color); for (List<String>::Element *E = VisualShaderEditor::get_singleton()->keyword_list.front(); E; E = E->next()) { - expression_syntax_highlighter->add_keyword_color(E->get(), keyword_color); + if (ShaderLanguage::is_control_flow_keyword(E->get())) { + expression_syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color); + } else { + expression_syntax_highlighter->add_keyword_color(E->get(), keyword_color); + } } expression_box->add_theme_font_override("font", VisualShaderEditor::get_singleton()->get_theme_font("expression", "EditorFonts")); @@ -759,6 +917,10 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { expression_syntax_highlighter->add_color_region("/*", "*/", comment_color, false); expression_syntax_highlighter->add_color_region("//", "", comment_color, true); + expression_box->clear_comment_delimiters(); + expression_box->add_comment_delimiter("/*", "*/", false); + expression_box->add_comment_delimiter("//", "", true); + expression_box->set_text(expression); expression_box->set_context_menu_enabled(false); expression_box->set_draw_line_numbers(true); @@ -768,6 +930,9 @@ void VisualShaderGraphPlugin::add_node(VisualShader::Type p_type, int p_id) { if (!uniform.is_valid()) { VisualShaderEditor::get_singleton()->graph->add_child(node); + if (is_comment) { + VisualShaderEditor::get_singleton()->graph->move_child(node, 0); // to prevents a bug where comment node overlaps its content + } VisualShaderEditor::get_singleton()->_update_created_node(node); if (is_resizable) { VisualShaderEditor::get_singleton()->call_deferred("_set_node_size", (int)p_type, p_id, size); @@ -786,6 +951,7 @@ void VisualShaderGraphPlugin::remove_node(VisualShader::Type p_type, int p_id) { void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { if (visual_shader->get_shader_type() == p_type) { VisualShaderEditor::get_singleton()->graph->connect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + connections.push_back({ p_from_node, p_from_port, p_to_node, p_to_port }); if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr) { links[p_to_node].input_ports[p_to_port].default_input_button->hide(); } @@ -795,6 +961,12 @@ void VisualShaderGraphPlugin::connect_nodes(VisualShader::Type p_type, int p_fro void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { if (visual_shader->get_shader_type() == p_type) { VisualShaderEditor::get_singleton()->graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port); + for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) { + if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) { + connections.erase(E); + break; + } + } if (links[p_to_node].input_ports.has(p_to_port) && links[p_to_node].input_ports[p_to_port].default_input_button != nullptr && links[p_to_node].visual_node->get_input_port_default_value(p_to_port).get_type() != Variant::NIL) { links[p_to_node].input_ports[p_to_port].default_input_button->show(); set_input_port_default_value(p_type, p_to_node, p_to_port, links[p_to_node].visual_node->get_input_port_default_value(p_to_port)); @@ -917,13 +1089,13 @@ bool VisualShaderEditor::_is_available(int p_mode) { if (p_mode != -1) { switch (current_mode) { - case 0: // Vertex or Emit + case 0: // Vertex / Emit current_mode = 1; break; - case 1: // Fragment or Process + case 1: // Fragment / Process current_mode = 2; break; - case 2: // Light or End + case 2: // Light / Collide current_mode = 4; break; default: @@ -951,7 +1123,7 @@ void VisualShaderEditor::update_custom_nodes() { Ref<Script> script = Ref<Script>(res); Ref<VisualShaderNodeCustom> ref; - ref.instance(); + ref.instantiate(); ref->set_script(script); String name; @@ -1125,7 +1297,7 @@ void VisualShaderEditor::_update_options_menu() { item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("bool", "EditorIcons")); break; case VisualShaderNode::PORT_TYPE_TRANSFORM: - item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform", "EditorIcons")); + item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform3D", "EditorIcons")); break; case VisualShaderNode::PORT_TYPE_SAMPLER: item->set_icon(0, EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons")); @@ -1139,22 +1311,29 @@ void VisualShaderEditor::_update_options_menu() { void VisualShaderEditor::_set_mode(int p_which) { if (p_which == VisualShader::MODE_SKY) { - edit_type_standart->set_visible(false); + edit_type_standard->set_visible(false); edit_type_particles->set_visible(false); edit_type_sky->set_visible(true); edit_type = edit_type_sky; + custom_mode_box->set_visible(false); mode = MODE_FLAGS_SKY; } else if (p_which == VisualShader::MODE_PARTICLES) { - edit_type_standart->set_visible(false); + edit_type_standard->set_visible(false); edit_type_particles->set_visible(true); edit_type_sky->set_visible(false); edit_type = edit_type_particles; + if ((edit_type->get_selected() + 3) > VisualShader::TYPE_PROCESS) { + custom_mode_box->set_visible(false); + } else { + custom_mode_box->set_visible(true); + } mode = MODE_FLAGS_PARTICLES; } else { edit_type_particles->set_visible(false); - edit_type_standart->set_visible(true); + edit_type_standard->set_visible(true); edit_type_sky->set_visible(false); - edit_type = edit_type_standart; + edit_type = edit_type_standard; + custom_mode_box->set_visible(false); mode = MODE_FLAGS_SPATIAL_CANVASITEM; } visual_shader->set_shader_type(get_current_shader_type()); @@ -1175,26 +1354,15 @@ void VisualShaderEditor::_draw_color_over_button(Object *obj, Color p_color) { } void VisualShaderEditor::_update_created_node(GraphNode *node) { - if (EditorSettings::get_singleton()->get("interface/theme/use_graph_node_headers")) { - Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode"); - Color c = sb->get_border_color(); - Color ic; - Color mono_color; - if (((c.r + c.g + c.b) / 3) < 0.7) { - mono_color = Color(1.0, 1.0, 1.0); - ic = Color(0.0, 0.0, 0.0, 0.7); - } else { - mono_color = Color(0.0, 0.0, 0.0); - ic = Color(1.0, 1.0, 1.0, 0.7); - } - mono_color.a = 0.85; - c = mono_color; + const Ref<StyleBoxFlat> sb = node->get_theme_stylebox("frame", "GraphNode"); + Color c = sb->get_border_color(); + const Color mono_color = ((c.r + c.g + c.b) / 3) < 0.7 ? Color(1.0, 1.0, 1.0, 0.85) : Color(0.0, 0.0, 0.0, 0.85); + c = mono_color; - node->add_theme_color_override("title_color", c); - c.a = 0.7; - node->add_theme_color_override("close_color", c); - node->add_theme_color_override("resizer_color", ic); - } + node->add_theme_color_override("title_color", c); + c.a = 0.7; + node->add_theme_color_override("close_color", c); + node->add_theme_color_override("resizer_color", c); } void VisualShaderEditor::_update_uniforms(bool p_update_refs) { @@ -1294,6 +1462,7 @@ void VisualShaderEditor::_update_graph() { graph_plugin->clear_links(); graph_plugin->make_dirty(true); + graph_plugin->update_theme(); for (int n_i = 0; n_i < nodes.size(); n_i++) { graph_plugin->add_node(type, nodes[n_i]); @@ -1317,9 +1486,9 @@ void VisualShaderEditor::_update_graph() { VisualShader::Type VisualShaderEditor::get_current_shader_type() const { VisualShader::Type type; if (mode & MODE_FLAGS_PARTICLES) { - type = VisualShader::Type(edit_type->get_selected() + 3); + type = VisualShader::Type(edit_type->get_selected() + 3 + (custom_mode_enabled ? 3 : 0)); } else if (mode & MODE_FLAGS_SKY) { - type = VisualShader::Type(edit_type->get_selected() + 6); + type = VisualShader::Type(edit_type->get_selected() + 8); } else { type = VisualShader::Type(edit_type->get_selected()); } @@ -1442,6 +1611,92 @@ void VisualShaderEditor::_change_output_port_name(const String &p_text, Object * undo_redo->commit_action(); } +void VisualShaderEditor::_expand_output_port(int p_node, int p_port, bool p_expand) { + VisualShader::Type type = get_current_shader_type(); + + Ref<VisualShaderNode> node = visual_shader->get_node(type, p_node); + ERR_FAIL_COND(!node.is_valid()); + + if (p_expand) { + undo_redo->create_action(TTR("Expand Output Port")); + } else { + undo_redo->create_action(TTR("Shrink Output Port")); + } + + undo_redo->add_do_method(node.ptr(), "_set_output_port_expanded", p_port, p_expand); + undo_redo->add_undo_method(node.ptr(), "_set_output_port_expanded", p_port, !p_expand); + + int type_size = 0; + if (node->get_output_port_type(p_port) == VisualShaderNode::PORT_TYPE_VECTOR) { + type_size = 3; + } + + List<VisualShader::Connection> conns; + visual_shader->get_node_connections(type, &conns); + + for (List<VisualShader::Connection>::Element *E = conns.front(); E; E = E->next()) { + int from_node = E->get().from_node; + int from_port = E->get().from_port; + int to_node = E->get().to_node; + int to_port = E->get().to_port; + + if (from_node == p_node) { + if (p_expand) { + if (from_port > p_port) { // reconnect ports after expanded ports + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); + + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port + type_size, to_node, to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port + type_size, to_node, to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port + type_size, to_node, to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port + type_size, to_node, to_port); + } + } else { + if (from_port > p_port + type_size) { // reconnect ports after expanded ports + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); + + undo_redo->add_do_method(visual_shader.ptr(), "connect_nodes", type, from_node, from_port - type_size, to_node, to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port - type_size, to_node, to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port - type_size, to_node, to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port - type_size, to_node, to_port); + } else if (from_port > p_port) { // disconnect component ports + undo_redo->add_do_method(visual_shader.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(visual_shader.ptr(), "connect_nodes_forced", type, from_node, from_port, to_node, to_port); + + undo_redo->add_do_method(graph_plugin.ptr(), "disconnect_nodes", type, from_node, from_port, to_node, to_port); + undo_redo->add_undo_method(graph_plugin.ptr(), "connect_nodes", type, from_node, from_port, to_node, to_port); + } + } + } + } + + int preview_port = node->get_output_port_for_preview(); + if (p_expand) { + if (preview_port > p_port) { + undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", preview_port + type_size); + undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", preview_port); + } + } else { + if (preview_port > p_port + type_size) { + undo_redo->add_do_method(node.ptr(), "set_output_port_for_preview", preview_port - type_size); + undo_redo->add_undo_method(node.ptr(), "set_output_port_for_preview", preview_port); + } + } + + undo_redo->add_do_method(graph_plugin.ptr(), "update_node", type, p_node); + undo_redo->add_undo_method(graph_plugin.ptr(), "update_node", type, p_node); + undo_redo->commit_action(); +} + void VisualShaderEditor::_remove_input_port(int p_node, int p_port) { VisualShader::Type type = get_current_shader_type(); Ref<VisualShaderNodeGroupBase> node = visual_shader->get_node(type, p_node); @@ -1590,7 +1845,7 @@ void VisualShaderEditor::_set_node_size(int p_type, int p_node, const Vector2 &p Ref<VisualShaderNodeExpression> expression_node = Object::cast_to<VisualShaderNodeExpression>(node.ptr()); Control *text_box = nullptr; if (!expression_node.is_null()) { - text_box = expression_node->get_control(0); + text_box = expression_node->is_ctrl_pressed(0); if (text_box) { text_box->set_custom_minimum_size(Size2(0, 0)); } @@ -1670,7 +1925,7 @@ void VisualShaderEditor::_comment_title_text_changed(const String &p_new_text) { comment_title_change_popup->set_size(Size2(-1, -1)); } -void VisualShaderEditor::_comment_title_text_entered(const String &p_new_text) { +void VisualShaderEditor::_comment_title_text_submitted(const String &p_new_text) { comment_title_change_popup->hide(); } @@ -1818,47 +2073,6 @@ void VisualShaderEditor::_edit_port_default_input(Object *p_button, int p_node, editing_port = p_port; } -void VisualShaderEditor::_add_custom_node(const String &p_path) { - int idx = -1; - - for (int i = custom_node_option_idx; i < add_options.size(); i++) { - if (add_options[i].script.is_valid()) { - if (add_options[i].script->get_path() == p_path) { - idx = i; - break; - } - } - } - if (idx != -1) { - _add_node(idx); - } -} - -void VisualShaderEditor::_add_cubemap_node(const String &p_path) { - VisualShaderNodeCubemap *cubemap = (VisualShaderNodeCubemap *)_add_node(cubemap_node_option_idx, -1); - cubemap->set_cube_map(ResourceLoader::load(p_path)); -} - -void VisualShaderEditor::_add_texture2d_node(const String &p_path) { - VisualShaderNodeTexture *texture2d = (VisualShaderNodeTexture *)_add_node(texture2d_node_option_idx, -1); - texture2d->set_texture(ResourceLoader::load(p_path)); -} - -void VisualShaderEditor::_add_texture2d_array_node(const String &p_path) { - VisualShaderNodeTexture2DArray *texture2d_array = (VisualShaderNodeTexture2DArray *)_add_node(texture2d_array_node_option_idx, -1); - texture2d_array->set_texture_array(ResourceLoader::load(p_path)); -} - -void VisualShaderEditor::_add_texture3d_node(const String &p_path) { - VisualShaderNodeTexture3D *texture3d = (VisualShaderNodeTexture3D *)_add_node(texture3d_node_option_idx, -1); - texture3d->set_texture(ResourceLoader::load(p_path)); -} - -void VisualShaderEditor::_add_curve_node(const String &p_path) { - VisualShaderNodeCurveTexture *curve = (VisualShaderNodeCurveTexture *)_add_node(curve_node_option_idx, -1); - curve->set_texture(ResourceLoader::load(p_path)); -} - void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { // FLOAT_OP { @@ -1950,6 +2164,16 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { } } + //UV_FUNC + { + VisualShaderNodeUVFunc *uvFunc = Object::cast_to<VisualShaderNodeUVFunc>(p_node); + + if (uvFunc) { + uvFunc->set_function((VisualShaderNodeUVFunc::Function)p_op_idx); + return; + } + } + // IS { VisualShaderNodeIs *is = Object::cast_to<VisualShaderNodeIs>(p_node); @@ -2047,16 +2271,18 @@ void VisualShaderEditor::_setup_node(VisualShaderNode *p_node, int p_op_idx) { } } -VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) { - ERR_FAIL_INDEX_V(p_idx, add_options.size(), nullptr); +void VisualShaderEditor::_add_node(int p_idx, int p_op_idx, String p_resource_path, int p_node_idx) { + ERR_FAIL_INDEX(p_idx, add_options.size()); + + VisualShader::Type type = get_current_shader_type(); Ref<VisualShaderNode> vsnode; bool is_custom = add_options[p_idx].is_custom; if (!is_custom && add_options[p_idx].type != String()) { - VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(add_options[p_idx].type)); - ERR_FAIL_COND_V(!vsn, nullptr); + VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(add_options[p_idx].type)); + ERR_FAIL_COND(!vsn); VisualShaderNodeFloatConstant *constant = Object::cast_to<VisualShaderNodeFloatConstant>(vsn); @@ -2076,12 +2302,35 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) { } } + VisualShaderNodeUniformRef *uniform_ref = Object::cast_to<VisualShaderNodeUniformRef>(vsn); + + if (uniform_ref && to_node != -1 && to_slot != -1) { + VisualShaderNode::PortType input_port_type = visual_shader->get_node(type, to_node)->get_input_port_type(to_slot); + bool success = false; + + for (int i = 0; i < uniform_ref->get_uniforms_count(); i++) { + if (uniform_ref->get_port_type_by_index(i) == input_port_type) { + uniform_ref->set_uniform_name(uniform_ref->get_uniform_name_by_index(i)); + success = true; + break; + } + } + if (!success) { + for (int i = 0; i < uniform_ref->get_uniforms_count(); i++) { + if (visual_shader->is_port_types_compatible(uniform_ref->get_port_type_by_index(i), input_port_type)) { + uniform_ref->set_uniform_name(uniform_ref->get_uniform_name_by_index(i)); + break; + } + } + } + } + vsnode = Ref<VisualShaderNode>(vsn); } else { - ERR_FAIL_COND_V(add_options[p_idx].script.is_null(), nullptr); + ERR_FAIL_COND(add_options[p_idx].script.is_null()); String base_type = add_options[p_idx].script->get_instance_base_type(); - VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instance(base_type)); - ERR_FAIL_COND_V(!vsn, nullptr); + VisualShaderNode *vsn = Object::cast_to<VisualShaderNode>(ClassDB::instantiate(base_type)); + ERR_FAIL_COND(!vsn); vsnode = Ref<VisualShaderNode>(vsn); vsnode->set_script(add_options[p_idx].script); } @@ -2096,11 +2345,13 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) { } saved_node_pos_dirty = false; - VisualShader::Type type = get_current_shader_type(); - int id_to_use = visual_shader->get_valid_node_id(type); - undo_redo->create_action(TTR("Add Node to Visual Shader")); + if (p_resource_path.is_empty()) { + undo_redo->create_action(TTR("Add Node to Visual Shader")); + } else { + id_to_use += p_node_idx; + } undo_redo->add_do_method(visual_shader.ptr(), "add_node", type, vsnode, position, id_to_use); undo_redo->add_undo_method(visual_shader.ptr(), "remove_node", type, id_to_use); undo_redo->add_do_method(graph_plugin.ptr(), "add_node", type, id_to_use); @@ -2157,6 +2408,13 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) { undo_redo->add_do_method(graph_plugin.ptr(), "connect_nodes", type, _from_node, _from_slot, to_node, to_slot); undo_redo->add_undo_method(graph_plugin.ptr(), "disconnect_nodes", type, _from_node, _from_slot, to_node, to_slot); } else { + // Need to setting up Input node properly before committing since `is_port_types_compatible` (calling below) is using `mode` and `shader_type`. + VisualShaderNodeInput *input = Object::cast_to<VisualShaderNodeInput>(vsnode.ptr()); + if (input) { + input->set_shader_mode(visual_shader->get_mode()); + input->set_shader_type(visual_shader->get_shader_type()); + } + // Attempting to connect to the first correct port. for (int i = 0; i < vsnode->get_output_port_count(); i++) { if (visual_shader->is_port_types_compatible(vsnode->get_output_port_type(i), input_port_type)) { @@ -2215,8 +2473,35 @@ VisualShaderNode *VisualShaderEditor::_add_node(int p_idx, int p_op_idx) { graph_plugin->call_deferred("update_curve", id_to_use); } - undo_redo->commit_action(); - return vsnode.ptr(); + VisualShaderNodeCurve3Texture *curve3 = Object::cast_to<VisualShaderNodeCurve3Texture>(vsnode.ptr()); + if (curve3) { + graph_plugin->call_deferred("update_curve3", id_to_use); + } + + if (p_resource_path.is_empty()) { + undo_redo->commit_action(); + } else { + //post-initialization + + VisualShaderNodeTexture *texture2d = Object::cast_to<VisualShaderNodeTexture>(vsnode.ptr()); + VisualShaderNodeTexture3D *texture3d = Object::cast_to<VisualShaderNodeTexture3D>(vsnode.ptr()); + + if (texture2d || texture3d || curve || curve3) { + undo_redo->add_do_method(vsnode.ptr(), "set_texture", ResourceLoader::load(p_resource_path)); + return; + } + + VisualShaderNodeCubemap *cubemap = Object::cast_to<VisualShaderNodeCubemap>(vsnode.ptr()); + if (cubemap) { + undo_redo->add_do_method(vsnode.ptr(), "set_cube_map", ResourceLoader::load(p_resource_path)); + return; + } + + VisualShaderNodeTexture2DArray *texture2d_array = Object::cast_to<VisualShaderNodeTexture2DArray>(vsnode.ptr()); + if (texture2d_array) { + undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path)); + } + } } void VisualShaderEditor::_node_dragged(const Vector2 &p_from, const Vector2 &p_to, int p_node) { @@ -2433,7 +2718,7 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { for (Set<int>::Element *E = current_set.front(); E; E = E->next()) { int node_id = E->get(); Ref<VisualShaderNode> node = visual_shader->get_node(type_id, node_id); - bool catched = false; + bool caught = false; Variant var; // float @@ -2442,112 +2727,112 @@ void VisualShaderEditor::_convert_constants_to_uniforms(bool p_vice_versa) { if (float_const.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeFloatConstant", "VisualShaderNodeFloatUniform"); var = float_const->get_constant(); - catched = true; + caught = true; } } else { Ref<VisualShaderNodeFloatUniform> float_uniform = Object::cast_to<VisualShaderNodeFloatUniform>(node.ptr()); if (float_uniform.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeFloatUniform", "VisualShaderNodeFloatConstant"); var = float_uniform->get_default_value(); - catched = true; + caught = true; } } // int - if (!catched) { + if (!caught) { if (!p_vice_versa) { Ref<VisualShaderNodeIntConstant> int_const = Object::cast_to<VisualShaderNodeIntConstant>(node.ptr()); if (int_const.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeIntConstant", "VisualShaderNodeIntUniform"); var = int_const->get_constant(); - catched = true; + caught = true; } } else { Ref<VisualShaderNodeIntUniform> int_uniform = Object::cast_to<VisualShaderNodeIntUniform>(node.ptr()); if (int_uniform.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeIntUniform", "VisualShaderNodeIntConstant"); var = int_uniform->get_default_value(); - catched = true; + caught = true; } } } // boolean - if (!catched) { + if (!caught) { if (!p_vice_versa) { Ref<VisualShaderNodeBooleanConstant> boolean_const = Object::cast_to<VisualShaderNodeBooleanConstant>(node.ptr()); if (boolean_const.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeBooleanConstant", "VisualShaderNodeBooleanUniform"); var = boolean_const->get_constant(); - catched = true; + caught = true; } } else { Ref<VisualShaderNodeBooleanUniform> boolean_uniform = Object::cast_to<VisualShaderNodeBooleanUniform>(node.ptr()); if (boolean_uniform.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeBooleanUniform", "VisualShaderNodeBooleanConstant"); var = boolean_uniform->get_default_value(); - catched = true; + caught = true; } } } // vec3 - if (!catched) { + if (!caught) { if (!p_vice_versa) { Ref<VisualShaderNodeVec3Constant> vec3_const = Object::cast_to<VisualShaderNodeVec3Constant>(node.ptr()); if (vec3_const.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeVec3Constant", "VisualShaderNodeVec3Uniform"); var = vec3_const->get_constant(); - catched = true; + caught = true; } } else { Ref<VisualShaderNodeVec3Uniform> vec3_uniform = Object::cast_to<VisualShaderNodeVec3Uniform>(node.ptr()); if (vec3_uniform.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeVec3Uniform", "VisualShaderNodeVec3Constant"); var = vec3_uniform->get_default_value(); - catched = true; + caught = true; } } } // color - if (!catched) { + if (!caught) { if (!p_vice_versa) { Ref<VisualShaderNodeColorConstant> color_const = Object::cast_to<VisualShaderNodeColorConstant>(node.ptr()); if (color_const.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeColorConstant", "VisualShaderNodeColorUniform"); var = color_const->get_constant(); - catched = true; + caught = true; } } else { Ref<VisualShaderNodeColorUniform> color_uniform = Object::cast_to<VisualShaderNodeColorUniform>(node.ptr()); if (color_uniform.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeColorUniform", "VisualShaderNodeColorConstant"); var = color_uniform->get_default_value(); - catched = true; + caught = true; } } } // transform - if (!catched) { + if (!caught) { if (!p_vice_versa) { Ref<VisualShaderNodeTransformConstant> transform_const = Object::cast_to<VisualShaderNodeTransformConstant>(node.ptr()); if (transform_const.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeTransformConstant", "VisualShaderNodeTransformUniform"); var = transform_const->get_constant(); - catched = true; + caught = true; } } else { Ref<VisualShaderNodeTransformUniform> transform_uniform = Object::cast_to<VisualShaderNodeTransformUniform>(node.ptr()); if (transform_uniform.is_valid()) { _replace_node(type_id, node_id, "VisualShaderNodeTransformUniform", "VisualShaderNodeTransformConstant"); var = transform_uniform->get_default_value(); - catched = true; + caught = true; } } } - ERR_CONTINUE(!catched); + ERR_CONTINUE(!caught); int preview_port = node->get_output_port_for_preview(); if (!p_vice_versa) { @@ -2768,10 +3053,10 @@ void VisualShaderEditor::_notification(int p_what) { // collapse tree by default - TreeItem *category = members->get_root()->get_children(); + TreeItem *category = members->get_root()->get_first_child(); while (category) { category->set_collapsed(true); - TreeItem *sub_category = category->get_children(); + TreeItem *sub_category = category->get_first_child(); while (sub_category) { sub_category->set_collapsed(true); sub_category = sub_category->get_next(); @@ -2792,9 +3077,6 @@ void VisualShaderEditor::_notification(int p_what) { if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { highend_label->set_modulate(get_theme_color("vulkan_color", "Editor")); - error_panel->add_theme_style_override("panel", get_theme_stylebox("bg", "Tree")); - error_label->add_theme_color_override("font_color", get_theme_color("error_color", "Editor")); - node_filter->set_right_icon(Control::get_theme_icon("Search", "EditorIcons")); preview_shader->set_icon(Control::get_theme_icon("Shader", "EditorIcons")); @@ -2803,6 +3085,7 @@ void VisualShaderEditor::_notification(int p_what) { Color background_color = EDITOR_GET("text_editor/highlighting/background_color"); Color text_color = EDITOR_GET("text_editor/highlighting/text_color"); Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color"); + Color control_flow_keyword_color = EDITOR_GET("text_editor/highlighting/control_flow_keyword_color"); Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color"); Color symbol_color = EDITOR_GET("text_editor/highlighting/symbol_color"); Color function_color = EDITOR_GET("text_editor/highlighting/function_color"); @@ -2812,7 +3095,11 @@ void VisualShaderEditor::_notification(int p_what) { preview_text->add_theme_color_override("background_color", background_color); for (List<String>::Element *E = keyword_list.front(); E; E = E->next()) { - syntax_highlighter->add_keyword_color(E->get(), keyword_color); + if (ShaderLanguage::is_control_flow_keyword(E->get())) { + syntax_highlighter->add_keyword_color(E->get(), control_flow_keyword_color); + } else { + syntax_highlighter->add_keyword_color(E->get(), keyword_color); + } } preview_text->add_theme_font_override("font", get_theme_font("expression", "EditorFonts")); @@ -2826,9 +3113,14 @@ void VisualShaderEditor::_notification(int p_what) { syntax_highlighter->add_color_region("/*", "*/", comment_color, false); syntax_highlighter->add_color_region("//", "", comment_color, true); - error_text->add_theme_font_override("font", get_theme_font("status_source", "EditorFonts")); - error_text->add_theme_font_size_override("font_size", get_theme_font_size("status_source_size", "EditorFonts")); - error_text->add_theme_color_override("font_color", get_theme_color("error_color", "Editor")); + preview_text->clear_comment_delimiters(); + preview_text->add_comment_delimiter("/*", "*/", false); + preview_text->add_comment_delimiter("//", "", true); + + error_panel->add_theme_style_override("panel", get_theme_stylebox("panel", "Panel")); + error_label->add_theme_font_override("font", get_theme_font("status_source", "EditorFonts")); + error_label->add_theme_font_size_override("font_size", get_theme_font_size("status_source_size", "EditorFonts")); + error_label->add_theme_color_override("font_color", get_theme_color("error_color", "Editor")); } tools->set_icon(EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Tools", "EditorIcons")); @@ -3050,8 +3342,18 @@ void VisualShaderEditor::_mode_selected(int p_id) { int offset = 0; if (mode & MODE_FLAGS_PARTICLES) { offset = 3; + if (p_id + offset > VisualShader::TYPE_PROCESS) { + custom_mode_box->set_visible(false); + custom_mode_enabled = false; + } else { + custom_mode_box->set_visible(true); + if (custom_mode_box->is_pressed()) { + custom_mode_enabled = true; + offset += 3; + } + } } else if (mode & MODE_FLAGS_SKY) { - offset = 6; + offset = 8; } visual_shader->set_shader_type(VisualShader::Type(p_id + offset)); @@ -3059,6 +3361,21 @@ void VisualShaderEditor::_mode_selected(int p_id) { _update_graph(); } +void VisualShaderEditor::_custom_mode_toggled(bool p_enabled) { + if (!(mode & MODE_FLAGS_PARTICLES)) { + return; + } + custom_mode_enabled = p_enabled; + int id = edit_type->get_selected() + 3; + if (p_enabled) { + visual_shader->set_shader_type(VisualShader::Type(id + 3)); + } else { + visual_shader->set_shader_type(VisualShader::Type(id)); + } + _update_options_menu(); + _update_graph(); +} + void VisualShaderEditor::_input_select_item(Ref<VisualShaderNodeInput> p_input, String p_name) { String prev_name = p_input->get_input_name(); @@ -3211,14 +3528,14 @@ void VisualShaderEditor::_member_cancel() { } void VisualShaderEditor::_tools_menu_option(int p_idx) { - TreeItem *category = members->get_root()->get_children(); + TreeItem *category = members->get_root()->get_first_child(); switch (p_idx) { case EXPAND_ALL: while (category) { category->set_collapsed(false); - TreeItem *sub_category = category->get_children(); + TreeItem *sub_category = category->get_first_child(); while (sub_category) { sub_category->set_collapsed(false); sub_category = sub_category->get_next(); @@ -3232,7 +3549,7 @@ void VisualShaderEditor::_tools_menu_option(int p_idx) { while (category) { category->set_collapsed(true); - TreeItem *sub_category = category->get_children(); + TreeItem *sub_category = category->get_first_child(); while (sub_category) { sub_category->set_collapsed(true); sub_category = sub_category->get_next(); @@ -3334,47 +3651,60 @@ void VisualShaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_da saved_node_pos_dirty = true; _add_node(idx, add_options[idx].sub_func); } else if (d.has("files")) { + undo_redo->create_action(TTR("Add Node(s) to Visual Shader")); + if (d["files"].get_type() == Variant::PACKED_STRING_ARRAY) { - int j = 0; PackedStringArray arr = d["files"]; for (int i = 0; i < arr.size(); i++) { String type = ResourceLoader::get_resource_type(arr[i]); if (type == "GDScript") { Ref<Script> script = ResourceLoader::load(arr[i]); if (script->get_instance_base_type() == "VisualShaderNodeCustom") { - saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_custom_node(arr[i]); - j++; + + int idx = -1; + + for (int j = custom_node_option_idx; j < add_options.size(); j++) { + if (add_options[j].script.is_valid()) { + if (add_options[j].script->get_path() == arr[i]) { + idx = j; + break; + } + } + } + if (idx != -1) { + _add_node(idx, -1, arr[i], i); + } } } else if (type == "CurveTexture") { - saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_curve_node(arr[i]); - j++; + _add_node(curve_node_option_idx, -1, arr[i], i); + } else if (type == "Curve3Texture") { + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); + saved_node_pos_dirty = true; + _add_node(curve3_node_option_idx, -1, arr[i], i); } else if (ClassDB::get_parent_class(type) == "Texture2D") { - saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_texture2d_node(arr[i]); - j++; + _add_node(texture2d_node_option_idx, -1, arr[i], i); } else if (type == "Texture2DArray") { - saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_texture2d_array_node(arr[i]); - j++; + _add_node(texture2d_array_node_option_idx, -1, arr[i], i); } else if (ClassDB::get_parent_class(type) == "Texture3D") { - saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_texture3d_node(arr[i]); - j++; + _add_node(texture3d_node_option_idx, -1, arr[i], i); } else if (type == "Cubemap") { - saved_node_pos = p_point + Vector2(0, j * 210 * EDSCALE); + saved_node_pos = p_point + Vector2(0, i * 250 * EDSCALE); saved_node_pos_dirty = true; - _add_cubemap_node(arr[i]); - j++; + _add_node(cubemap_node_option_idx, -1, arr[i], i); } } } + undo_redo->commit_action(); } } } @@ -3430,17 +3760,18 @@ void VisualShaderEditor::_update_preview() { Error err = sl.compile(code, ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(visual_shader->get_mode())), ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(visual_shader->get_mode())), ShaderLanguage::VaryingFunctionNames(), ShaderTypes::get_singleton()->get_types(), _get_global_variable_type); for (int i = 0; i < preview_text->get_line_count(); i++) { - preview_text->set_line_as_marked(i, false); + preview_text->set_line_background_color(i, Color(0, 0, 0, 0)); } if (err != OK) { - preview_text->set_line_as_marked(sl.get_error_line() - 1, true); - error_text->set_visible(true); + Color error_line_color = EDITOR_GET("text_editor/highlighting/mark_color"); + preview_text->set_line_background_color(sl.get_error_line() - 1, error_line_color); + error_panel->show(); String text = "error(" + itos(sl.get_error_line()) + "): " + sl.get_error_text(); - error_text->set_text(text); + error_label->set_text(text); shader_error = true; } else { - error_text->set_visible(false); + error_panel->hide(); shader_error = false; } } @@ -3470,10 +3801,11 @@ void VisualShaderEditor::_bind_methods() { ClassDB::bind_method("_float_constant_selected", &VisualShaderEditor::_float_constant_selected); ClassDB::bind_method("_update_constant", &VisualShaderEditor::_update_constant); ClassDB::bind_method("_update_uniform", &VisualShaderEditor::_update_uniform); + ClassDB::bind_method("_expand_output_port", &VisualShaderEditor::_expand_output_port); - ClassDB::bind_method(D_METHOD("get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw); - ClassDB::bind_method(D_METHOD("can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw); - ClassDB::bind_method(D_METHOD("drop_data_fw"), &VisualShaderEditor::drop_data_fw); + ClassDB::bind_method(D_METHOD("_get_drag_data_fw"), &VisualShaderEditor::get_drag_data_fw); + ClassDB::bind_method(D_METHOD("_can_drop_data_fw"), &VisualShaderEditor::can_drop_data_fw); + ClassDB::bind_method(D_METHOD("_drop_data_fw"), &VisualShaderEditor::drop_data_fw); ClassDB::bind_method("_is_available", &VisualShaderEditor::_is_available); } @@ -3546,17 +3878,23 @@ VisualShaderEditor::VisualShaderEditor() { graph->get_zoom_hbox()->add_child(vs); graph->get_zoom_hbox()->move_child(vs, 0); - edit_type_standart = memnew(OptionButton); - edit_type_standart->add_item(TTR("Vertex")); - edit_type_standart->add_item(TTR("Fragment")); - edit_type_standart->add_item(TTR("Light")); - edit_type_standart->select(1); - edit_type_standart->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected)); + custom_mode_box = memnew(CheckBox); + custom_mode_box->set_text(TTR("Custom")); + custom_mode_box->set_pressed(false); + custom_mode_box->set_visible(false); + custom_mode_box->connect("toggled", callable_mp(this, &VisualShaderEditor::_custom_mode_toggled)); + + edit_type_standard = memnew(OptionButton); + edit_type_standard->add_item(TTR("Vertex")); + edit_type_standard->add_item(TTR("Fragment")); + edit_type_standard->add_item(TTR("Light")); + edit_type_standard->select(1); + edit_type_standard->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected)); edit_type_particles = memnew(OptionButton); - edit_type_particles->add_item(TTR("Emit")); + edit_type_particles->add_item(TTR("Start")); edit_type_particles->add_item(TTR("Process")); - edit_type_particles->add_item(TTR("End")); + edit_type_particles->add_item(TTR("Collide")); edit_type_particles->select(0); edit_type_particles->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected)); @@ -3565,14 +3903,16 @@ VisualShaderEditor::VisualShaderEditor() { edit_type_sky->select(0); edit_type_sky->connect("item_selected", callable_mp(this, &VisualShaderEditor::_mode_selected)); - edit_type = edit_type_standart; + edit_type = edit_type_standard; + graph->get_zoom_hbox()->add_child(custom_mode_box); + graph->get_zoom_hbox()->move_child(custom_mode_box, 0); + graph->get_zoom_hbox()->add_child(edit_type_standard); + graph->get_zoom_hbox()->move_child(edit_type_standard, 0); graph->get_zoom_hbox()->add_child(edit_type_particles); graph->get_zoom_hbox()->move_child(edit_type_particles, 0); graph->get_zoom_hbox()->add_child(edit_type_sky); graph->get_zoom_hbox()->move_child(edit_type_sky, 0); - graph->get_zoom_hbox()->add_child(edit_type_standart); - graph->get_zoom_hbox()->move_child(edit_type_standart, 0); add_node = memnew(Button); add_node->set_flat(true); @@ -3601,19 +3941,23 @@ VisualShaderEditor::VisualShaderEditor() { preview_vbox = memnew(VBoxContainer); preview_window->add_child(preview_vbox); + preview_vbox->add_theme_constant_override("separation", 0); preview_text = memnew(CodeEdit); - syntax_highlighter.instance(); + syntax_highlighter.instantiate(); preview_vbox->add_child(preview_text); preview_text->set_v_size_flags(Control::SIZE_EXPAND_FILL); preview_text->set_syntax_highlighter(syntax_highlighter); preview_text->set_draw_line_numbers(true); preview_text->set_readonly(true); - error_text = memnew(Label); - preview_vbox->add_child(error_text); - error_text->set_autowrap(true); - error_text->set_visible(false); + error_panel = memnew(PanelContainer); + preview_vbox->add_child(error_panel); + error_panel->set_visible(false); + + error_label = memnew(Label); + error_panel->add_child(error_label); + error_label->set_autowrap(true); /////////////////////////////////////// // POPUP MENU @@ -3709,7 +4053,7 @@ VisualShaderEditor::VisualShaderEditor() { comment_title_change_edit = memnew(LineEdit); comment_title_change_edit->set_expand_to_text_length_enabled(true); comment_title_change_edit->connect("text_changed", callable_mp(this, &VisualShaderEditor::_comment_title_text_changed)); - comment_title_change_edit->connect("text_entered", callable_mp(this, &VisualShaderEditor::_comment_title_text_entered)); + comment_title_change_edit->connect("text_submitted", callable_mp(this, &VisualShaderEditor::_comment_title_text_submitted)); comment_title_change_popup->add_child(comment_title_change_edit); comment_title_change_edit->set_size(Size2(-1, -1)); comment_title_change_popup->set_size(Size2(-1, -1)); @@ -3788,9 +4132,10 @@ VisualShaderEditor::VisualShaderEditor() { // INPUT + const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes."); + // SPATIAL-FOR-ALL - const String input_param_shader_modes = TTR("'%s' input parameter for all shader modes."); add_options.push_back(AddOption("Camera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "camera"), "camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("InvCamera", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_camera"), "inv_camera", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("InvProjection", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "inv_projection"), "inv_projection", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_SPATIAL)); @@ -3809,6 +4154,23 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("UV", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "uv"), "uv", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_CANVAS_ITEM)); + // PARTICLES-FOR-ALL + + add_options.push_back(AddOption("Active", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Alpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("AttractorForce", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "attractor_force"), "attractor_force", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Color", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Custom", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("CustomAlpha", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Delta", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("EmissionTransform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Index", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("LifeTime", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Restart", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_BOOLEAN, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Time", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Transform", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("Velocity", "Input", "All", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, -1, Shader::MODE_PARTICLES)); + ///////////////// add_options.push_back(AddOption("Input", "Input", "Common", "VisualShaderNodeInput", TTR("Input parameter."))); @@ -3821,11 +4183,12 @@ VisualShaderEditor::VisualShaderEditor() { const String input_param_for_sky_shader_mode = TTR("'%s' input parameter for sky shader mode."); const String input_param_for_light_shader_mode = TTR("'%s' input parameter for light shader mode."); const String input_param_for_vertex_shader_mode = TTR("'%s' input parameter for vertex shader mode."); - const String input_param_for_emit_shader_mode = TTR("'%s' input parameter for emit shader mode."); + const String input_param_for_start_shader_mode = TTR("'%s' input parameter for start shader mode."); const String input_param_for_process_shader_mode = TTR("'%s' input parameter for process shader mode."); - const String input_param_for_end_shader_mode = TTR("'%s' input parameter for end shader mode."); - const String input_param_for_emit_and_process_shader_mode = TTR("'%s' input parameter for emit and process shader mode."); - const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader mode."); + const String input_param_for_collide_shader_mode = TTR("'%s' input parameter for collide shader mode."); + const String input_param_for_start_and_process_shader_mode = TTR("'%s' input parameter for start and process shader modes."); + const String input_param_for_process_and_collide_shader_mode = TTR("'%s' input parameter for process and collide shader modes."); + const String input_param_for_vertex_and_fragment_shader_mode = TTR("'%s' input parameter for vertex and fragment shader modes."); add_options.push_back(AddOption("Alpha", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Binormal", "Input", "Fragment", "VisualShaderNodeInput", vformat(input_param_for_vertex_and_fragment_shader_modes, "binormal"), "binormal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_FRAGMENT, Shader::MODE_SPATIAL)); @@ -3902,50 +4265,6 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("Vertex", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "vertex"), "vertex", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); add_options.push_back(AddOption("World", "Input", "Vertex", "VisualShaderNodeInput", vformat(input_param_for_vertex_shader_mode, "world"), "world", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_CANVAS_ITEM)); - // PARTICLES INPUTS - - add_options.push_back(AddOption("Active", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Alpha", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Color", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Custom", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("CustomAlpha", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Delta", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("EmissionTransform", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Index", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("LifeTime", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Restart", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Time", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Transform", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Velocity", "Input", "Emit", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); - - add_options.push_back(AddOption("Active", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Alpha", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Color", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Custom", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("CustomAlpha", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Delta", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("EmissionTransform", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Index", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("LifeTime", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Restart", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Time", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Transform", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Velocity", "Input", "Process", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); - - add_options.push_back(AddOption("Active", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "active"), "active", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Alpha", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "alpha"), "alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Color", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "color"), "color", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Custom", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom"), "custom", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("CustomAlpha", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "custom_alpha"), "custom_alpha", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Delta", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "delta"), "delta", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("EmissionTransform", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "emission_transform"), "emission_transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Index", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "index"), "index", VisualShaderNode::PORT_TYPE_SCALAR_INT, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("LifeTime", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "lifetime"), "lifetime", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Restart", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "restart"), "restart", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Time", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Transform", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "transform"), "transform", VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - add_options.push_back(AddOption("Velocity", "Input", "End", "VisualShaderNodeInput", vformat(input_param_shader_modes, "velocity"), "velocity", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_END, Shader::MODE_PARTICLES)); - // SKY INPUTS add_options.push_back(AddOption("AtCubeMapPass", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "at_cubemap_pass"), "at_cubemap_pass", VisualShaderNode::PORT_TYPE_BOOLEAN, TYPE_FLAGS_SKY, Shader::MODE_SKY)); @@ -3978,6 +4297,22 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("SkyCoords", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "sky_coords"), "sky_coords", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); add_options.push_back(AddOption("Time", "Input", "Sky", "VisualShaderNodeInput", vformat(input_param_for_sky_shader_mode, "time"), "time", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_SKY, Shader::MODE_SKY)); + // PARTICLES + + add_options.push_back(AddOption("CollisionDepth", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_depth"), "collision_depth", VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("CollisionNormal", "Input", "Collide", "VisualShaderNodeInput", vformat(input_param_for_collide_shader_mode, "collision_normal"), "collision_normal", VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + + add_options.push_back(AddOption("EmitParticle", "Particles", "", "VisualShaderNodeParticleEmit", "", -1, -1, -1, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ParticleAccelerator", "Particles", "", "VisualShaderNodeParticleAccelerator", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_PROCESS, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("ParticleRandomness", "Particles", "", "VisualShaderNodeParticleRandomness", "", -1, VisualShaderNode::PORT_TYPE_SCALAR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("MultiplyByAxisAngle", "Particles", "Transform", "VisualShaderNodeParticleMultiplyByAxisAngle", "A node for help to multiply a position input vector by rotation using specific axis. Intended to work with emitters.", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT | TYPE_FLAGS_PROCESS | TYPE_FLAGS_COLLIDE, Shader::MODE_PARTICLES)); + + add_options.push_back(AddOption("BoxEmitter", "Particles", "Emitters", "VisualShaderNodeParticleBoxEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("RingEmitter", "Particles", "Emitters", "VisualShaderNodeParticleRingEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + add_options.push_back(AddOption("SphereEmitter", "Particles", "Emitters", "VisualShaderNodeParticleSphereEmitter", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + + add_options.push_back(AddOption("ConeVelocity", "Particles", "Velocity", "VisualShaderNodeParticleConeVelocity", "", -1, VisualShaderNode::PORT_TYPE_VECTOR, TYPE_FLAGS_EMIT, Shader::MODE_PARTICLES)); + // SCALAR add_options.push_back(AddOption("FloatFunc", "Scalar", "Common", "VisualShaderNodeFloatFunc", TTR("Float function."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); @@ -4065,16 +4400,22 @@ VisualShaderEditor::VisualShaderEditor() { // TEXTURES + add_options.push_back(AddOption("UVFunc", "Textures", "Common", "VisualShaderNodeUVFunc", TTR("Function to be applied on texture coordinates."), -1, VisualShaderNode::PORT_TYPE_VECTOR)); + cubemap_node_option_idx = add_options.size(); add_options.push_back(AddOption("CubeMap", "Textures", "Functions", "VisualShaderNodeCubemap", TTR("Perform the cubic texture lookup."), -1, -1)); curve_node_option_idx = add_options.size(); add_options.push_back(AddOption("CurveTexture", "Textures", "Functions", "VisualShaderNodeCurveTexture", TTR("Perform the curve texture lookup."), -1, -1)); + curve3_node_option_idx = add_options.size(); + add_options.push_back(AddOption("CurveTexture3", "Textures", "Functions", "VisualShaderNodeCurve3Texture", TTR("Perform the ternary curve texture lookup."), -1, -1)); texture2d_node_option_idx = add_options.size(); add_options.push_back(AddOption("Texture2D", "Textures", "Functions", "VisualShaderNodeTexture", TTR("Perform the 2D texture lookup."), -1, -1)); texture2d_array_node_option_idx = add_options.size(); add_options.push_back(AddOption("Texture2DArray", "Textures", "Functions", "VisualShaderNodeTexture2DArray", TTR("Perform the 2D-array texture lookup."), -1, -1, -1, -1, -1)); texture3d_node_option_idx = add_options.size(); add_options.push_back(AddOption("Texture3D", "Textures", "Functions", "VisualShaderNodeTexture3D", TTR("Perform the 3D texture lookup."), -1, -1)); + add_options.push_back(AddOption("UVPanning", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply panning function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_PANNING, VisualShaderNode::PORT_TYPE_VECTOR)); + add_options.push_back(AddOption("UVScaling", "Textures", "Functions", "VisualShaderNodeUVFunc", TTR("Apply scaling function on texture coordinates."), VisualShaderNodeUVFunc::FUNC_SCALING, VisualShaderNode::PORT_TYPE_VECTOR)); add_options.push_back(AddOption("CubeMapUniform", "Textures", "Variables", "VisualShaderNodeCubemapUniform", TTR("Cubic texture uniform lookup."), -1, -1)); add_options.push_back(AddOption("TextureUniform", "Textures", "Variables", "VisualShaderNodeTextureUniform", TTR("2D texture uniform lookup."), -1, -1)); @@ -4091,6 +4432,7 @@ VisualShaderEditor::VisualShaderEditor() { add_options.push_back(AddOption("TransformDecompose", "Transform", "Composition", "VisualShaderNodeTransformDecompose", TTR("Decomposes transform to four vectors."))); add_options.push_back(AddOption("Determinant", "Transform", "Functions", "VisualShaderNodeDeterminant", TTR("Calculates the determinant of a transform."), -1, VisualShaderNode::PORT_TYPE_SCALAR)); + add_options.push_back(AddOption("GetBillboardMatrix", "Transform", "Functions", "VisualShaderNodeBillboard", TTR("Calculates how the object should face the camera to be applied on Model View Matrix output port for 3D objects."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL)); add_options.push_back(AddOption("Inverse", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the inverse of a transform."), VisualShaderNodeTransformFunc::FUNC_INVERSE, VisualShaderNode::PORT_TYPE_TRANSFORM)); add_options.push_back(AddOption("Transpose", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the transpose of a transform."), VisualShaderNodeTransformFunc::FUNC_TRANSPOSE, VisualShaderNode::PORT_TYPE_TRANSFORM)); @@ -4193,20 +4535,13 @@ VisualShaderEditor::VisualShaderEditor() { _update_options_menu(); - error_panel = memnew(PanelContainer); - add_child(error_panel); - error_label = memnew(Label); - error_panel->add_child(error_label); - error_label->set_text("eh"); - error_panel->hide(); - undo_redo = EditorNode::get_singleton()->get_undo_redo(); Ref<VisualShaderNodePluginDefault> default_plugin; - default_plugin.instance(); + default_plugin.instantiate(); add_plugin(default_plugin); - graph_plugin.instance(); + graph_plugin.instantiate(); property_editor = memnew(CustomPropertyEditor); add_child(property_editor); @@ -4280,7 +4615,7 @@ public: EditorNode::get_singleton()->get_gui_base()->get_theme_icon("int", "EditorIcons"), EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Vector3", "EditorIcons"), EditorNode::get_singleton()->get_gui_base()->get_theme_icon("bool", "EditorIcons"), - EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform3D", "EditorIcons"), EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons"), }; @@ -4325,7 +4660,7 @@ public: EditorNode::get_singleton()->get_gui_base()->get_theme_icon("int", "EditorIcons"), EditorNode::get_singleton()->get_gui_base()->get_theme_icon("bool", "EditorIcons"), EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Vector3", "EditorIcons"), - EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform", "EditorIcons"), + EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Transform3D", "EditorIcons"), EditorNode::get_singleton()->get_gui_base()->get_theme_icon("Color", "EditorIcons"), EditorNode::get_singleton()->get_gui_base()->get_theme_icon("ImageTexture", "EditorIcons"), }; @@ -4516,7 +4851,7 @@ Control *VisualShaderNodePluginDefault::create_editor(const Ref<Resource> &p_par if (Object::cast_to<EditorPropertyResource>(prop)) { Object::cast_to<EditorPropertyResource>(prop)->set_use_sub_inspector(false); prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); - } else if (Object::cast_to<EditorPropertyTransform>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) { + } else if (Object::cast_to<EditorPropertyTransform3D>(prop) || Object::cast_to<EditorPropertyVector3>(prop)) { prop->set_custom_minimum_size(Size2(250 * EDSCALE, 0)); } else if (Object::cast_to<EditorPropertyFloat>(prop)) { prop->set_custom_minimum_size(Size2(100 * EDSCALE, 0)); @@ -4633,7 +4968,7 @@ void EditorInspectorShaderModePlugin::parse_begin(Object *p_object) { //do none } -bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide) { +bool EditorInspectorShaderModePlugin::parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) { if (p_path == "mode" && p_object->is_class("VisualShader") && p_type == Variant::INT) { EditorPropertyShaderMode *editor = memnew(EditorPropertyShaderMode); Vector<String> options = p_hint_text.split(","); @@ -4661,14 +4996,14 @@ void VisualShaderNodePortPreview::_shader_changed() { String shader_code = shader->generate_preview_shader(type, node, port, default_textures); Ref<Shader> preview_shader; - preview_shader.instance(); + preview_shader.instantiate(); preview_shader->set_code(shader_code); for (int i = 0; i < default_textures.size(); i++) { preview_shader->set_default_texture_param(default_textures[i].name, default_textures[i].param); } Ref<ShaderMaterial> material; - material.instance(); + material.instantiate(); material->set_shader(preview_shader); //find if a material is also being edited and copy parameters to this one @@ -4753,7 +5088,7 @@ Ref<Resource> VisualShaderConversionPlugin::convert(const Ref<Resource> &p_resou ERR_FAIL_COND_V(!vshader.is_valid(), Ref<Resource>()); Ref<Shader> shader; - shader.instance(); + shader.instantiate(); String code = vshader->get_code(); shader->set_code(code); diff --git a/editor/plugins/visual_shader_editor_plugin.h b/editor/plugins/visual_shader_editor_plugin.h index 6d57d38cab..2b354db7b3 100644 --- a/editor/plugins/visual_shader_editor_plugin.h +++ b/editor/plugins/visual_shader_editor_plugin.h @@ -41,8 +41,8 @@ #include "scene/gui/tree.h" #include "scene/resources/visual_shader.h" -class VisualShaderNodePlugin : public Reference { - GDCLASS(VisualShaderNodePlugin, Reference); +class VisualShaderNodePlugin : public RefCounted { + GDCLASS(VisualShaderNodePlugin, RefCounted); protected: static void _bind_methods(); @@ -51,8 +51,8 @@ public: virtual Control *create_editor(const Ref<Resource> &p_parent_resource, const Ref<VisualShaderNode> &p_node); }; -class VisualShaderGraphPlugin : public Reference { - GDCLASS(VisualShaderGraphPlugin, Reference); +class VisualShaderGraphPlugin : public RefCounted { + GDCLASS(VisualShaderGraphPlugin, RefCounted); private: struct InputPort { @@ -75,7 +75,7 @@ private: LineEdit *uniform_name = nullptr; OptionButton *const_op = nullptr; CodeEdit *expression_edit = nullptr; - CurveEditor *curve_editor = nullptr; + CurveEditor *curve_editors[3] = { nullptr, nullptr, nullptr }; }; Ref<VisualShader> visual_shader; @@ -83,6 +83,8 @@ private: List<VisualShader::Connection> connections; bool dirty = false; + Color vector_expanded_color[3]; + protected: static void _bind_methods(); @@ -95,7 +97,7 @@ public: void register_default_input_button(int p_node_id, int p_port_id, Button *p_button); void register_constant_option_btn(int p_node_id, OptionButton *p_button); void register_expression_edit(int p_node_id, CodeEdit *p_expression_edit); - void register_curve_editor(int p_node_id, CurveEditor *p_curve_editor); + void register_curve_editor(int p_node_id, int p_index, CurveEditor *p_curve_editor); void clear_links(); void set_shader_type(VisualShader::Type p_type); bool is_preview_visible(int p_id) const; @@ -115,10 +117,12 @@ public: void update_uniform_refs(); void set_uniform_name(VisualShader::Type p_type, int p_node_id, const String &p_name); void update_curve(int p_node_id); + void update_curve3(int p_node_id); void update_constant(VisualShader::Type p_type, int p_node_id); void set_expression(VisualShader::Type p_type, int p_node_id, const String &p_expression); int get_constant_index(float p_constant) const; void update_node_size(int p_node_id); + void update_theme(); VisualShader::Type get_shader_type() const; VisualShaderGraphPlugin(); @@ -139,12 +143,11 @@ class VisualShaderEditor : public VBoxContainer { Button *preview_shader; OptionButton *edit_type = nullptr; - OptionButton *edit_type_standart; + OptionButton *edit_type_standard; OptionButton *edit_type_particles; OptionButton *edit_type_sky; - - PanelContainer *error_panel; - Label *error_label; + CheckBox *custom_mode_box; + bool custom_mode_enabled = false; bool pending_update_preview; bool shader_error; @@ -152,7 +155,8 @@ class VisualShaderEditor : public VBoxContainer { VBoxContainer *preview_vbox; CodeEdit *preview_text; Ref<CodeHighlighter> syntax_highlighter; - Label *error_text; + PanelContainer *error_panel; + Label *error_label; UndoRedo *undo_redo; Point2 saved_node_pos; @@ -188,7 +192,9 @@ class VisualShaderEditor : public VBoxContainer { enum ParticlesTypeFlags { TYPE_FLAGS_EMIT = 1, TYPE_FLAGS_PROCESS = 2, - TYPE_FLAGS_END = 4 + TYPE_FLAGS_COLLIDE = 4, + TYPE_FLAGS_EMIT_CUSTOM = 8, + TYPE_FLAGS_PROCESS_CUSTOM = 16, }; enum SkyTypeFlags { @@ -284,21 +290,15 @@ class VisualShaderEditor : public VBoxContainer { int texture3d_node_option_idx; int custom_node_option_idx; int curve_node_option_idx; + int curve3_node_option_idx; List<String> keyword_list; List<VisualShaderNodeUniformRef> uniform_refs; void _draw_color_over_button(Object *obj, Color p_color); - void _add_custom_node(const String &p_path); - void _add_cubemap_node(const String &p_path); - void _add_texture2d_node(const String &p_path); - void _add_texture2d_array_node(const String &p_path); - void _add_texture3d_node(const String &p_path); - void _add_curve_node(const String &p_path); - void _setup_node(VisualShaderNode *p_node, int p_op_idx); - VisualShaderNode *_add_node(int p_idx, int p_op_idx = -1); + void _add_node(int p_idx, int p_op_idx = -1, String p_resource_path = "", int p_node_idx = -1); void _update_options_menu(); void _set_mode(int p_which); @@ -360,7 +360,7 @@ class VisualShaderEditor : public VBoxContainer { void _comment_title_popup_hide(); void _comment_title_popup_focus_out(); void _comment_title_text_changed(const String &p_new_text); - void _comment_title_text_entered(const String &p_new_text); + void _comment_title_text_submitted(const String &p_new_text); void _comment_desc_popup_show(const Point2 &p_position, int p_node_id); void _comment_desc_popup_hide(); @@ -391,6 +391,7 @@ class VisualShaderEditor : public VBoxContainer { Ref<VisualShaderGraphPlugin> graph_plugin; void _mode_selected(int p_id); + void _custom_mode_toggled(bool p_enabled); void _input_select_item(Ref<VisualShaderNodeInput> input, String name); void _uniform_select_item(Ref<VisualShaderNodeUniformRef> p_uniform, String p_name); @@ -408,6 +409,7 @@ class VisualShaderEditor : public VBoxContainer { void _remove_output_port(int p_node, int p_port); void _change_output_port_type(int p_type, int p_node, int p_port); void _change_output_port_name(const String &p_text, Object *p_line_edit, int p_node, int p_port); + void _expand_output_port(int p_node, int p_port, bool p_expand); void _expression_focus_out(Object *code_edit, int p_node); @@ -505,7 +507,7 @@ class EditorInspectorShaderModePlugin : public EditorInspectorPlugin { public: virtual bool can_handle(Object *p_object) override; virtual void parse_begin(Object *p_object) override; - virtual bool parse_property(Object *p_object, Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, int p_usage, bool p_wide = false) override; + virtual bool parse_property(Object *p_object, const Variant::Type p_type, const String &p_path, const PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide = false) override; virtual void parse_end() override; }; diff --git a/editor/plugins/gi_probe_editor_plugin.cpp b/editor/plugins/voxel_gi_editor_plugin.cpp index f309c5da01..162379a49d 100644 --- a/editor/plugins/gi_probe_editor_plugin.cpp +++ b/editor/plugins/voxel_gi_editor_plugin.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gi_probe_editor_plugin.cpp */ +/* voxel_gi_editor_plugin.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,51 +28,48 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "gi_probe_editor_plugin.h" +#include "voxel_gi_editor_plugin.h" -void GIProbeEditorPlugin::_bake() { - if (gi_probe) { - if (gi_probe->get_probe_data().is_null()) { +void VoxelGIEditorPlugin::_bake() { + if (voxel_gi) { + if (voxel_gi->get_probe_data().is_null()) { String path = get_tree()->get_edited_scene_root()->get_filename(); if (path == String()) { - path = "res://" + gi_probe->get_name() + "_data.res"; + path = "res://" + voxel_gi->get_name() + "_data.res"; } else { String ext = path.get_extension(); - path = path.get_basename() + "." + gi_probe->get_name() + "_data.res"; + path = path.get_basename() + "." + voxel_gi->get_name() + "_data.res"; } probe_file->set_current_path(path); probe_file->popup_file_dialog(); return; } - gi_probe->bake(); + voxel_gi->bake(); } } -void GIProbeEditorPlugin::edit(Object *p_object) { - GIProbe *s = Object::cast_to<GIProbe>(p_object); +void VoxelGIEditorPlugin::edit(Object *p_object) { + VoxelGI *s = Object::cast_to<VoxelGI>(p_object); if (!s) { return; } - gi_probe = s; + voxel_gi = s; } -bool GIProbeEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("GIProbe"); +bool VoxelGIEditorPlugin::handles(Object *p_object) const { + return p_object->is_class("VoxelGI"); } -void GIProbeEditorPlugin::_notification(int p_what) { +void VoxelGIEditorPlugin::_notification(int p_what) { if (p_what == NOTIFICATION_PROCESS) { - if (!gi_probe) { + if (!voxel_gi) { return; } - const Vector3i size = gi_probe->get_estimated_cell_size(); + const Vector3i size = voxel_gi->get_estimated_cell_size(); String text = vformat(String::utf8("%d × %d × %d"), size.x, size.y, size.z); - int data_size = 4; - if (GLOBAL_GET("rendering/quality/gi_probes/anisotropic")) { - data_size += 4; - } + const int data_size = 4; const double size_mb = size.x * size.y * size.z * data_size / (1024.0 * 1024.0); text += " - " + vformat(TTR("VRAM Size: %s MB"), String::num(size_mb, 2)); @@ -98,7 +95,7 @@ void GIProbeEditorPlugin::_notification(int p_what) { } } -void GIProbeEditorPlugin::make_visible(bool p_visible) { +void VoxelGIEditorPlugin::make_visible(bool p_visible) { if (p_visible) { bake_hb->show(); set_process(true); @@ -108,38 +105,38 @@ void GIProbeEditorPlugin::make_visible(bool p_visible) { } } -EditorProgress *GIProbeEditorPlugin::tmp_progress = nullptr; +EditorProgress *VoxelGIEditorPlugin::tmp_progress = nullptr; -void GIProbeEditorPlugin::bake_func_begin(int p_steps) { +void VoxelGIEditorPlugin::bake_func_begin(int p_steps) { ERR_FAIL_COND(tmp_progress != nullptr); tmp_progress = memnew(EditorProgress("bake_gi", TTR("Bake GI Probe"), p_steps)); } -void GIProbeEditorPlugin::bake_func_step(int p_step, const String &p_description) { +void VoxelGIEditorPlugin::bake_func_step(int p_step, const String &p_description) { ERR_FAIL_COND(tmp_progress == nullptr); tmp_progress->step(p_description, p_step, false); } -void GIProbeEditorPlugin::bake_func_end() { +void VoxelGIEditorPlugin::bake_func_end() { ERR_FAIL_COND(tmp_progress == nullptr); memdelete(tmp_progress); tmp_progress = nullptr; } -void GIProbeEditorPlugin::_giprobe_save_path_and_bake(const String &p_path) { +void VoxelGIEditorPlugin::_voxel_gi_save_path_and_bake(const String &p_path) { probe_file->hide(); - if (gi_probe) { - gi_probe->bake(); - ERR_FAIL_COND(gi_probe->get_probe_data().is_null()); - ResourceSaver::save(p_path, gi_probe->get_probe_data(), ResourceSaver::FLAG_CHANGE_PATH); + if (voxel_gi) { + voxel_gi->bake(); + ERR_FAIL_COND(voxel_gi->get_probe_data().is_null()); + ResourceSaver::save(p_path, voxel_gi->get_probe_data(), ResourceSaver::FLAG_CHANGE_PATH); } } -void GIProbeEditorPlugin::_bind_methods() { +void VoxelGIEditorPlugin::_bind_methods() { } -GIProbeEditorPlugin::GIProbeEditorPlugin(EditorNode *p_node) { +VoxelGIEditorPlugin::VoxelGIEditorPlugin(EditorNode *p_node) { editor = p_node; bake_hb = memnew(HBoxContainer); bake_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -148,7 +145,7 @@ GIProbeEditorPlugin::GIProbeEditorPlugin(EditorNode *p_node) { bake->set_flat(true); bake->set_icon(editor->get_gui_base()->get_theme_icon("Bake", "EditorIcons")); bake->set_text(TTR("Bake GI Probe")); - bake->connect("pressed", callable_mp(this, &GIProbeEditorPlugin::_bake)); + bake->connect("pressed", callable_mp(this, &VoxelGIEditorPlugin::_bake)); bake_hb->add_child(bake); bake_info = memnew(Label); bake_info->set_h_size_flags(Control::SIZE_EXPAND_FILL); @@ -156,18 +153,18 @@ GIProbeEditorPlugin::GIProbeEditorPlugin(EditorNode *p_node) { bake_hb->add_child(bake_info); add_control_to_container(CONTAINER_SPATIAL_EDITOR_MENU, bake_hb); - gi_probe = nullptr; + voxel_gi = nullptr; probe_file = memnew(EditorFileDialog); probe_file->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); probe_file->add_filter("*.res"); - probe_file->connect("file_selected", callable_mp(this, &GIProbeEditorPlugin::_giprobe_save_path_and_bake)); + probe_file->connect("file_selected", callable_mp(this, &VoxelGIEditorPlugin::_voxel_gi_save_path_and_bake)); get_editor_interface()->get_base_control()->add_child(probe_file); - probe_file->set_title(TTR("Select path for GIProbe Data File")); + probe_file->set_title(TTR("Select path for VoxelGI Data File")); - GIProbe::bake_begin_function = bake_func_begin; - GIProbe::bake_step_function = bake_func_step; - GIProbe::bake_end_function = bake_func_end; + VoxelGI::bake_begin_function = bake_func_begin; + VoxelGI::bake_step_function = bake_func_step; + VoxelGI::bake_end_function = bake_func_end; } -GIProbeEditorPlugin::~GIProbeEditorPlugin() { +VoxelGIEditorPlugin::~VoxelGIEditorPlugin() { } diff --git a/editor/plugins/gi_probe_editor_plugin.h b/editor/plugins/voxel_gi_editor_plugin.h index fdf0623561..4d3cfe90f6 100644 --- a/editor/plugins/gi_probe_editor_plugin.h +++ b/editor/plugins/voxel_gi_editor_plugin.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* gi_probe_editor_plugin.h */ +/* voxel_gi_editor_plugin.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,18 +28,18 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef GIPROBEEDITORPLUGIN_H -#define GIPROBEEDITORPLUGIN_H +#ifndef VOXEL_GIEDITORPLUGIN_H +#define VOXEL_GIEDITORPLUGIN_H #include "editor/editor_node.h" #include "editor/editor_plugin.h" -#include "scene/3d/gi_probe.h" +#include "scene/3d/voxel_gi.h" #include "scene/resources/material.h" -class GIProbeEditorPlugin : public EditorPlugin { - GDCLASS(GIProbeEditorPlugin, EditorPlugin); +class VoxelGIEditorPlugin : public EditorPlugin { + GDCLASS(VoxelGIEditorPlugin, EditorPlugin); - GIProbe *gi_probe; + VoxelGI *voxel_gi; HBoxContainer *bake_hb; Label *bake_info; @@ -54,21 +54,21 @@ class GIProbeEditorPlugin : public EditorPlugin { static void bake_func_end(); void _bake(); - void _giprobe_save_path_and_bake(const String &p_path); + void _voxel_gi_save_path_and_bake(const String &p_path); protected: static void _bind_methods(); void _notification(int p_what); public: - virtual String get_name() const override { return "GIProbe"; } + virtual String get_name() const override { return "VoxelGI"; } bool has_main_screen() const override { return false; } virtual void edit(Object *p_object) override; virtual bool handles(Object *p_object) const override; virtual void make_visible(bool p_visible) override; - GIProbeEditorPlugin(EditorNode *p_node); - ~GIProbeEditorPlugin(); + VoxelGIEditorPlugin(EditorNode *p_node); + ~VoxelGIEditorPlugin(); }; -#endif // GIPROBEEDITORPLUGIN_H +#endif // VOXEL_GIEDITORPLUGIN_H |