diff options
author | Gilles Roudière <gilles.roudiere@gmail.com> | 2021-07-06 14:43:03 +0200 |
---|---|---|
committer | Gilles Roudière <gilles.roudiere@gmail.com> | 2021-07-21 12:36:37 +0200 |
commit | 5d34a81e52877034295fc79e4f4b01d0cfa7dfb1 (patch) | |
tree | fd6ae48af033bd91e1ed74e9abcd68ea8db39844 /editor/plugins/tiles | |
parent | b2187797dfe5fd35b50506abf5502449e3f990ee (diff) |
Implement atlas merging and tile proxies
Diffstat (limited to 'editor/plugins/tiles')
-rw-r--r-- | editor/plugins/tiles/atlas_merging_dialog.cpp | 320 | ||||
-rw-r--r-- | editor/plugins/tiles/atlas_merging_dialog.h | 86 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_atlas_view.h | 2 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_map_editor.cpp | 202 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_map_editor.h | 10 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_proxies_manager_dialog.cpp | 476 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_proxies_manager_dialog.h | 90 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_set_atlas_source_editor.cpp | 2 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_set_atlas_source_editor.h | 4 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_set_editor.cpp | 83 | ||||
-rw-r--r-- | editor/plugins/tiles/tile_set_editor.h | 13 | ||||
-rw-r--r-- | editor/plugins/tiles/tiles_editor_plugin.cpp | 4 | ||||
-rw-r--r-- | editor/plugins/tiles/tiles_editor_plugin.h | 4 |
13 files changed, 1176 insertions, 120 deletions
diff --git a/editor/plugins/tiles/atlas_merging_dialog.cpp b/editor/plugins/tiles/atlas_merging_dialog.cpp new file mode 100644 index 0000000000..bbafc7802b --- /dev/null +++ b/editor/plugins/tiles/atlas_merging_dialog.cpp @@ -0,0 +1,320 @@ +/*************************************************************************/ +/* atlas_merging_dialog.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 "atlas_merging_dialog.h" + +#include "editor/editor_scale.h" + +#include "scene/gui/control.h" +#include "scene/gui/split_container.h" + +void AtlasMergingDialog::_property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing) { + _set(p_property, p_value); +} + +void AtlasMergingDialog::_generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns) { + merged.instantiate(); + merged_mapping.clear(); + + if (p_atlas_sources.size() >= 2) { + Ref<Image> output_image; + output_image.instantiate(); + output_image->create(1, 1, false, Image::FORMAT_RGBA8); + + // Compute the new texture region size. + Vector2i new_texture_region_size; + for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { + Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + new_texture_region_size = new_texture_region_size.max(atlas_source->get_texture_region_size()); + } + + // Generate the merged TileSetAtlasSource. + Vector2i atlas_offset; + int line_height = 0; + for (int source_index = 0; source_index < p_atlas_sources.size(); source_index++) { + Ref<TileSetAtlasSource> atlas_source = p_atlas_sources[source_index]; + merged_mapping.push_back(Map<Vector2i, Vector2i>()); + + // Layout the tiles. + Vector2i atlas_size; + + for (int tile_index = 0; tile_index < atlas_source->get_tiles_count(); tile_index++) { + Vector2i tile_id = atlas_source->get_tile_id(tile_index); + atlas_size = atlas_size.max(tile_id + atlas_source->get_tile_size_in_atlas(tile_id)); + + Rect2i new_tile_rect_in_altas = Rect2i(atlas_offset + tile_id, atlas_source->get_tile_size_in_atlas(tile_id)); + + // Create tiles and alternatives, then copy their properties. + for (int alternative_index = 0; alternative_index < atlas_source->get_alternative_tiles_count(tile_id); alternative_index++) { + int alternative_id = atlas_source->get_alternative_tile_id(tile_id, alternative_index); + if (alternative_id == 0) { + merged->create_tile(new_tile_rect_in_altas.position, new_tile_rect_in_altas.size); + } else { + merged->create_alternative_tile(new_tile_rect_in_altas.position, alternative_index); + } + + // Copy the properties. + TileData *original_tile_data = Object::cast_to<TileData>(atlas_source->get_tile_data(tile_id, alternative_id)); + List<PropertyInfo> properties; + original_tile_data->get_property_list(&properties); + for (List<PropertyInfo>::Element *E = properties.front(); E; E = E->next()) { + const StringName &property_name = E->get().name; + merged->set(property_name, original_tile_data->get(property_name)); + } + + // Add to the mapping. + merged_mapping[source_index][tile_id] = new_tile_rect_in_altas.position; + } + + // Copy the texture. + Rect2i src_rect = atlas_source->get_tile_texture_region(tile_id); + Rect2 dst_rect_wide = Rect2i(new_tile_rect_in_altas.position * new_texture_region_size, new_tile_rect_in_altas.size * new_texture_region_size); + if (dst_rect_wide.get_end().x > output_image->get_width() || dst_rect_wide.get_end().y > output_image->get_height()) { + output_image->crop(MAX(dst_rect_wide.get_end().x, output_image->get_width()), MAX(dst_rect_wide.get_end().y, output_image->get_height())); + } + output_image->blit_rect(atlas_source->get_texture()->get_image(), src_rect, (dst_rect_wide.get_position() + dst_rect_wide.get_end()) / 2 - src_rect.size / 2); + } + + // Compute the atlas offset. + line_height = MAX(atlas_size.y, line_height); + atlas_offset.x += atlas_size.x; + if (atlas_offset.x >= p_max_columns) { + atlas_offset.x = 0; + atlas_offset.y += line_height; + line_height = 0; + } + } + + Ref<ImageTexture> output_image_texture; + output_image_texture.instantiate(); + output_image_texture->create_from_image(output_image); + + merged->set_texture(output_image_texture); + merged->set_texture_region_size(new_texture_region_size); + } +} + +void AtlasMergingDialog::_update_texture() { + Vector<int> selected = atlas_merging_atlases_list->get_selected_items(); + if (selected.size() >= 2) { + Vector<Ref<TileSetAtlasSource>> to_merge; + for (int i = 0; i < selected.size(); i++) { + int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]); + to_merge.push_back(tile_set->get_source(source_id)); + } + _generate_merged(to_merge, next_line_after_column); + preview->set_texture(merged->get_texture()); + preview->show(); + select_2_atlases_label->hide(); + get_ok_button()->set_disabled(false); + merge_button->set_disabled(false); + } else { + _generate_merged(Vector<Ref<TileSetAtlasSource>>(), next_line_after_column); + preview->set_texture(Ref<Texture2D>()); + preview->hide(); + select_2_atlases_label->show(); + get_ok_button()->set_disabled(true); + merge_button->set_disabled(true); + } +} + +void AtlasMergingDialog::_merge_confirmed(String p_path) { + ERR_FAIL_COND(!merged.is_valid()); + + Ref<ImageTexture> output_image_texture = merged->get_texture(); + output_image_texture->get_image()->save_png(p_path); + + Ref<Texture2D> new_texture_resource = ResourceLoader::load(p_path, "Texture2D"); + merged->set_texture(new_texture_resource); + + undo_redo->create_action("Merge TileSetAtlasSource"); + int next_id = tile_set->get_next_source_id(); + undo_redo->add_do_method(*tile_set, "add_source", merged, next_id); + undo_redo->add_undo_method(*tile_set, "remove_source", next_id); + + if (delete_original_atlases) { + // Delete originals if needed. + Vector<int> selected = atlas_merging_atlases_list->get_selected_items(); + for (int i = 0; i < selected.size(); i++) { + int source_id = atlas_merging_atlases_list->get_item_metadata(selected[i]); + Ref<TileSetAtlasSource> tas = tile_set->get_source(source_id); + undo_redo->add_do_method(*tile_set, "remove_source", source_id); + undo_redo->add_undo_method(*tile_set, "add_source", tas, source_id); + + // Add the tile proxies. + for (int tile_index = 0; tile_index < tas->get_tiles_count(); tile_index++) { + Vector2i tile_id = tas->get_tile_id(tile_index); + undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", source_id, tile_id, next_id, merged_mapping[i][tile_id]); + if (tile_set->has_coords_level_tile_proxy(source_id, tile_id)) { + Array a = tile_set->get_coords_level_tile_proxy(source_id, tile_id); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", a[0], a[1]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", source_id, tile_id); + } + } + } + } + undo_redo->commit_action(); + commited_actions_count++; + + hide(); +} + +void AtlasMergingDialog::ok_pressed() { + delete_original_atlases = false; + editor_file_dialog->popup_file_dialog(); +} + +void AtlasMergingDialog::cancel_pressed() { + for (int i = 0; i < commited_actions_count; i++) { + undo_redo->undo(); + } + commited_actions_count = 0; +} + +void AtlasMergingDialog::custom_action(const String &p_action) { + if (p_action == "merge") { + delete_original_atlases = true; + editor_file_dialog->popup_file_dialog(); + } +} + +bool AtlasMergingDialog::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "next_line_after_column" && p_value.get_type() == Variant::INT) { + next_line_after_column = p_value; + _update_texture(); + return true; + } + return false; +} + +bool AtlasMergingDialog::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "next_line_after_column") { + r_ret = next_line_after_column; + return true; + } + return false; +} + +void AtlasMergingDialog::update_tile_set(Ref<TileSet> p_tile_set) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; + + atlas_merging_atlases_list->clear(); + for (int i = 0; i < p_tile_set->get_source_count(); i++) { + int source_id = p_tile_set->get_source_id(i); + Ref<TileSetAtlasSource> atlas_source = p_tile_set->get_source(source_id); + if (atlas_source.is_valid()) { + Ref<Texture2D> texture = atlas_source->get_texture(); + if (texture.is_valid()) { + String item_text = vformat("%s (id:%d)", texture->get_path().get_file(), source_id); + atlas_merging_atlases_list->add_item(item_text, texture); + atlas_merging_atlases_list->set_item_metadata(atlas_merging_atlases_list->get_item_count() - 1, source_id); + } + } + } + + get_ok_button()->set_disabled(true); + merge_button->set_disabled(true); + + commited_actions_count = 0; +} + +AtlasMergingDialog::AtlasMergingDialog() { + // Atlas merging window. + set_title(TTR("Atlas Merging")); + set_hide_on_ok(false); + + // Ok buttons + get_ok_button()->set_text(TTR("Merge (Keep original Atlases)")); + get_ok_button()->set_disabled(true); + merge_button = add_button(TTR("Merge"), true, "merge"); + merge_button->set_disabled(true); + + HSplitContainer *atlas_merging_h_split_container = memnew(HSplitContainer); + atlas_merging_h_split_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_h_split_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + add_child(atlas_merging_h_split_container); + + // Atlas sources item list. + atlas_merging_atlases_list = memnew(ItemList); + atlas_merging_atlases_list->set_fixed_icon_size(Size2i(60, 60) * EDSCALE); + atlas_merging_atlases_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_atlases_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_atlases_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); + atlas_merging_atlases_list->set_custom_minimum_size(Size2(100, 200)); + atlas_merging_atlases_list->set_select_mode(ItemList::SELECT_MULTI); + atlas_merging_atlases_list->connect("multi_selected", callable_mp(this, &AtlasMergingDialog::_update_texture).unbind(2)); + atlas_merging_h_split_container->add_child(atlas_merging_atlases_list); + + VBoxContainer *atlas_merging_right_panel = memnew(VBoxContainer); + atlas_merging_right_panel->set_h_size_flags(Control::SIZE_EXPAND_FILL); + atlas_merging_h_split_container->add_child(atlas_merging_right_panel); + + // Settings. + Label *settings_label = memnew(Label); + settings_label->set_text(TTR("Settings:")); + atlas_merging_right_panel->add_child(settings_label); + + columns_editor_property = memnew(EditorPropertyInteger); + columns_editor_property->set_label(TTR("Next Line After Column")); + columns_editor_property->set_object_and_property(this, "next_line_after_column"); + columns_editor_property->update_property(); + columns_editor_property->connect("property_changed", callable_mp(this, &AtlasMergingDialog::_property_changed)); + atlas_merging_right_panel->add_child(columns_editor_property); + + // Preview. + Label *preview_label = memnew(Label); + preview_label->set_text(TTR("Preview:")); + atlas_merging_right_panel->add_child(preview_label); + + preview = memnew(TextureRect); + preview->set_h_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_v_size_flags(Control::SIZE_EXPAND_FILL); + preview->set_expand(true); + preview->hide(); + preview->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED); + atlas_merging_right_panel->add_child(preview); + + select_2_atlases_label = memnew(Label); + select_2_atlases_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); + select_2_atlases_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); + select_2_atlases_label->set_align(Label::ALIGN_CENTER); + select_2_atlases_label->set_valign(Label::VALIGN_CENTER); + select_2_atlases_label->set_text(TTR("Please select two atlases or more.")); + atlas_merging_right_panel->add_child(select_2_atlases_label); + + // The file dialog to choose the texture path. + editor_file_dialog = memnew(EditorFileDialog); + editor_file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE); + editor_file_dialog->add_filter("*.png"); + editor_file_dialog->connect("file_selected", callable_mp(this, &AtlasMergingDialog::_merge_confirmed)); + add_child(editor_file_dialog); +} diff --git a/editor/plugins/tiles/atlas_merging_dialog.h b/editor/plugins/tiles/atlas_merging_dialog.h new file mode 100644 index 0000000000..7cb54bc17e --- /dev/null +++ b/editor/plugins/tiles/atlas_merging_dialog.h @@ -0,0 +1,86 @@ +/*************************************************************************/ +/* atlas_merging_dialog.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 ATLAS_MERGING_DIALOG_H +#define ATLAS_MERGING_DIALOG_H + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/gui/texture_rect.h" +#include "scene/resources/tile_set.h" + +class AtlasMergingDialog : public ConfirmationDialog { + GDCLASS(AtlasMergingDialog, ConfirmationDialog); + +private: + int commited_actions_count = 0; + bool delete_original_atlases = true; + Ref<TileSetAtlasSource> merged; + LocalVector<Map<Vector2i, Vector2i>> merged_mapping; + Ref<TileSet> tile_set; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + // Settings. + int next_line_after_column = 30; + + // GUI. + ItemList *atlas_merging_atlases_list; + EditorPropertyVector2i *texture_region_size_editor_property; + EditorPropertyInteger *columns_editor_property; + TextureRect *preview; + Label *select_2_atlases_label; + EditorFileDialog *editor_file_dialog; + Button *merge_button; + + void _property_changed(const StringName &p_property, const Variant &p_value, const String &p_field, bool p_changing); + + void _generate_merged(Vector<Ref<TileSetAtlasSource>> p_atlas_sources, int p_max_columns); + void _update_texture(); + void _merge_confirmed(String p_path); + +protected: + virtual void ok_pressed() override; + virtual void cancel_pressed() override; + virtual void custom_action(const String &) override; + + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + +public: + void update_tile_set(Ref<TileSet> p_tile_set); + + AtlasMergingDialog(); +}; + +#endif // ATLAS_MERGING_DIALOG_H diff --git a/editor/plugins/tiles/tile_atlas_view.h b/editor/plugins/tiles/tile_atlas_view.h index bafc2b3985..b2046f4322 100644 --- a/editor/plugins/tiles/tile_atlas_view.h +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -47,7 +47,7 @@ class TileAtlasView : public Control { private: TileSet *tile_set; TileSetAtlasSource *tile_set_atlas_source; - int source_id = -1; + int source_id = TileSet::INVALID_SOURCE; enum DragType { DRAG_TYPE_NONE, diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp index 6e3c55d801..44822bec3a 100644 --- a/editor/plugins/tiles/tile_map_editor.cpp +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -56,18 +56,11 @@ void TileMapEditorTilesPlugin::_notification(int p_what) { picker_button->set_icon(get_theme_icon(SNAME("ColorPick"), SNAME("EditorIcons"))); erase_button->set_icon(get_theme_icon(SNAME("Eraser"), SNAME("EditorIcons"))); - toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); - missing_atlas_texture_icon = get_theme_icon(SNAME("TileSet"), SNAME("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; } } @@ -85,10 +78,6 @@ 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(); @@ -204,7 +193,7 @@ void TileMapEditorTilesPlugin::_update_tile_set_sources_list() { } // Synchronize - TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current()); + TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current()); } void TileMapEditorTilesPlugin::_update_bottom_panel() { @@ -410,7 +399,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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_do_method(tile_map, "set_cell", E->get(), TileSet::INVALID_SOURCE, 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()); @@ -441,7 +430,7 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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_do_method(tile_map, "set_cell", E->get(), TileSet::INVALID_SOURCE, 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()); @@ -462,12 +451,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { 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))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -478,12 +467,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { 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))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -516,8 +505,8 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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); + drag_modified.insert(coords, tile_map->get_cell(coords)); + tile_map->set_cell(coords, TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); } } else { // Select tiles @@ -536,12 +525,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { 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))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -562,12 +551,12 @@ bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref<InputEvent> &p 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) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { 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))); + drag_modified.insert(coords, tile_map->get_cell(coords)); } tile_map->set_cell(coords, E->get().source_id, E->get().get_atlas_coords(), E->get().alternative_tile); } @@ -634,7 +623,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over 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) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { Rect2 cell_region = xform.xform(Rect2(tile_map->map_to_world(coords) - tile_shape_size / 2, tile_shape_size)); tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 1.0, 1.0), false); } @@ -648,7 +637,7 @@ void TileMapEditorTilesPlugin::forward_canvas_draw_over_viewport(Control *p_over 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) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { to_draw.insert(coords); } } @@ -871,7 +860,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_ // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; @@ -923,7 +912,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; // Compute the offset to align things to the bottom or right. @@ -974,7 +963,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // Get or create the pattern. TileMapPattern erase_pattern; - erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); + erase_pattern.set_cell(Vector2i(0, 0), TileSet::INVALID_SOURCE, TileSetSource::INVALID_ATLAS_COORDS, TileSetSource::INVALID_TILE_ALTERNATIVE); TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; Map<Vector2i, TileMapCell> output; @@ -983,7 +972,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i // If we are filling empty tiles, compute the tilemap boundaries. Rect2i boundaries; - if (source.source_id == -1) { + if (source.source_id == TileSet::INVALID_SOURCE) { boundaries = tile_map->get_used_rect(); } @@ -999,7 +988,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i 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))) { + (source.source_id != TileSet::INVALID_SOURCE || 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)); @@ -1027,7 +1016,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i } else { // Replace all tiles like the source. TypedArray<Vector2i> to_check; - if (source.source_id == -1) { + if (source.source_id == TileSet::INVALID_SOURCE) { Rect2i rect = tile_map->get_used_rect(); if (rect.size.x <= 0 || rect.size.y <= 0) { rect = Rect2i(p_coords, Vector2i(1, 1)); @@ -1045,7 +1034,7 @@ Map<Vector2i, TileMapCell> TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i 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))) { + (source.source_id != TileSet::INVALID_SOURCE || 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)); @@ -1102,7 +1091,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { tile_map_selection.erase(coords); } } else { - if (tile_map->get_cell_source_id(coords) != -1) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { tile_map_selection.insert(coords); } } @@ -1173,7 +1162,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { 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) { + if (tile_map->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { coords_array.push_back(coords); } } @@ -1198,7 +1187,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { 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) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { 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); @@ -1210,7 +1199,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { 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) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { 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); @@ -1221,7 +1210,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { 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) { + if (!erase_button->is_pressed() && E->get().source_id == TileSet::INVALID_SOURCE) { 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())); @@ -1249,7 +1238,7 @@ void TileMapEditorTilesPlugin::_stop_dragging() { 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.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); @@ -1260,7 +1249,7 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { Ref<TileSet> tile_set = tile_map->get_tileset(); if (!tile_set.is_valid()) { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); @@ -1271,7 +1260,7 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { 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.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_selection.clear(); @@ -1287,7 +1276,7 @@ void TileMapEditorTilesPlugin::_update_fix_selected_and_hovered() { !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.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; } @@ -1403,7 +1392,7 @@ void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern( 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) { + if (selection_pattern->get_cell_source_id(coords) != TileSet::INVALID_SOURCE) { 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))); } } @@ -1475,7 +1464,7 @@ void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { } void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_dragging_selection = false; @@ -1634,7 +1623,7 @@ void TileMapEditorTilesPlugin::_tile_alternatives_control_draw() { } void TileMapEditorTilesPlugin::_tile_alternatives_control_mouse_exited() { - hovered_tile.source_id = -1; + hovered_tile.source_id = TileSet::INVALID_SOURCE; hovered_tile.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); hovered_tile.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; tile_set_dragging_selection = false; @@ -1856,19 +1845,6 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { _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->set_tooltip(TTR("Toggle grid visibility.")); - 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(); @@ -1898,8 +1874,8 @@ TileMapEditorTilesPlugin::TileMapEditorTilesPlugin() { 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)); + sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list)); atlas_sources_split_container->add_child(sources_list); // Tile atlas source. @@ -2968,7 +2944,7 @@ void TileMapEditorTerrainsPlugin::_update_terrains_cache() { } TileMapCell empty_cell; - empty_cell.source_id = -1; + empty_cell.source_id = TileSet::INVALID_SOURCE; empty_cell.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); empty_cell.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; per_terrain_terrains_tile_patterns_tiles[i][empty_pattern].insert(empty_cell); @@ -3190,6 +3166,9 @@ void TileMapEditor::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: missing_tile_texture = get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")); warning_pattern_texture = get_theme_icon(SNAME("WarningPattern"), SNAME("EditorIcons")); + advanced_menu_button->set_icon(get_theme_icon(SNAME("Tools"), SNAME("EditorIcons"))); + toggle_grid_button->set_icon(get_theme_icon(SNAME("Grid"), SNAME("EditorIcons"))); + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); break; case NOTIFICATION_INTERNAL_PROCESS: if (is_visible_in_tree() && tileset_changed_needs_update) { @@ -3199,6 +3178,44 @@ void TileMapEditor::_notification(int p_what) { tileset_changed_needs_update = false; } break; + case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: + toggle_grid_button->set_pressed(EditorSettings::get_singleton()->get("editors/tiles_editor/display_grid")); + break; + } +} + +void TileMapEditor::_on_grid_toggled(bool p_pressed) { + EditorSettings::get_singleton()->set("editors/tiles_editor/display_grid", p_pressed); +} + +void TileMapEditor::_advanced_menu_button_id_pressed(int p_id) { + 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 (p_id == 0) { // Replace Tile Proxies + undo_redo->create_action(TTR("Replace Tiles with Proxies")); + TypedArray<Vector2i> used_cells = tile_map->get_used_cells(); + for (int i = 0; i < used_cells.size(); i++) { + Vector2i cell_coords = used_cells[i]; + TileMapCell from = tile_map->get_cell(cell_coords); + Array to_array = tile_set->map_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile); + TileMapCell to; + to.source_id = to_array[0]; + to.set_atlas_coords(to_array[1]); + to.alternative_tile = to_array[2]; + if (from != to) { + undo_redo->add_do_method(tile_map, "set_cell", cell_coords, to.source_id, to.get_atlas_coords(), to.alternative_tile); + undo_redo->add_undo_method(tile_map, "set_cell", cell_coords, from.source_id, from.get_atlas_coords(), from.alternative_tile); + } + } + undo_redo->commit_action(); } } @@ -3349,7 +3366,6 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { 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]; @@ -3365,25 +3381,33 @@ void TileMapEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { 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); + Array a = tile_set->map_tile_proxy(tile_source_id, tile_atlas_coords, tile_alternative_tile); + if (int(a[0]) == tile_source_id && Vector2i(a[1]) == tile_atlas_coords && int(a[2]) == tile_alternative_tile) { + // Only display the pattern if we have no proxy tile. + 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()); + int min_axis = missing_tile_texture->get_size().min_axis(); + Vector2 icon_size; + icon_size[min_axis] = tile_set->get_tile_size()[min_axis] / 3; + icon_size[(min_axis + 1) % 2] = (icon_size[min_axis] * missing_tile_texture->get_size()[(min_axis + 1) % 2] / missing_tile_texture->get_size()[min_axis]); + Rect2 rect = Rect2(xform.xform(tile_map->map_to_world(coords)) - (icon_size * xform.get_scale() / 2), icon_size * xform.get_scale()); p_overlay->draw_texture_rect(missing_tile_texture, rect); } } @@ -3503,6 +3527,26 @@ TileMapEditor::TileMapEditor() { tilemap_toolbar->add_child(tile_map_editor_plugins[i]->get_toolbar()); } + // Wide empty separation control. + Control *h_empty_space = memnew(Control); + h_empty_space->set_h_size_flags(SIZE_EXPAND_FILL); + tilemap_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->set_tooltip(TTR("Toggle grid visibility.")); + toggle_grid_button->connect("toggled", callable_mp(this, &TileMapEditor::_on_grid_toggled)); + tilemap_toolbar->add_child(toggle_grid_button); + + // Advanced settings menu button. + advanced_menu_button = memnew(MenuButton); + advanced_menu_button->set_flat(true); + advanced_menu_button->get_popup()->add_item(TTR("Automatically Replace Tiles with Proxies")); + advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileMapEditor::_advanced_menu_button_id_pressed)); + tilemap_toolbar->add_child(advanced_menu_button); + 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); diff --git a/editor/plugins/tiles/tile_map_editor.h b/editor/plugins/tiles/tile_map_editor.h index a6f4ec3021..236774a06b 100644 --- a/editor/plugins/tiles/tile_map_editor.h +++ b/editor/plugins/tiles/tile_map_editor.h @@ -82,9 +82,6 @@ private: 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. ///// @@ -300,6 +297,7 @@ class TileMapEditor : public VBoxContainer { GDCLASS(TileMapEditor, VBoxContainer); private: + UndoRedo *undo_redo = EditorNode::get_undo_redo(); bool tileset_changed_needs_update = false; ObjectID tile_map_id; @@ -309,6 +307,12 @@ private: // Toolbar. HBoxContainer *tilemap_toolbar; + Button *toggle_grid_button; + void _on_grid_toggled(bool p_pressed); + + MenuButton *advanced_menu_button; + void _advanced_menu_button_id_pressed(int p_id); + // Bottom panel Label *missing_tileset_label; Tabs *tabs; diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.cpp b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp new file mode 100644 index 0000000000..e5e7efa531 --- /dev/null +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.cpp @@ -0,0 +1,476 @@ +/*************************************************************************/ +/* tile_proxies_manager_dialog.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_proxies_manager_dialog.h" + +#include "editor/editor_scale.h" + +void TileProxiesManagerDialog::_right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list) { + ItemList *item_list = Object::cast_to<ItemList>(p_item_list); + popup_menu->set_size(Vector2(1, 1)); + popup_menu->set_position(get_position() + item_list->get_global_mouse_position()); + popup_menu->popup(); +} + +void TileProxiesManagerDialog::_menu_id_pressed(int p_id) { + if (p_id == 0) { + // Delete. + _delete_selected_bindings(); + } +} + +void TileProxiesManagerDialog::_delete_selected_bindings() { + undo_redo->create_action("Remove Tile Proxies"); + + Vector<int> source_level_selected = source_level_list->get_selected_items(); + for (int i = 0; i < source_level_selected.size(); i++) { + int key = source_level_list->get_item_metadata(source_level_selected[i]); + int val = tile_set->get_source_level_tile_proxy(key); + undo_redo->add_do_method(*tile_set, "remove_source_level_tile_proxy", key); + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", key, val); + } + + Vector<int> coords_level_selected = coords_level_list->get_selected_items(); + for (int i = 0; i < coords_level_selected.size(); i++) { + Array key = coords_level_list->get_item_metadata(coords_level_selected[i]); + Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + undo_redo->add_do_method(*tile_set, "remove_coords_level_tile_proxy", key[0], key[1]); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", key[0], key[1], val[0], val[1]); + } + + Vector<int> alternative_level_selected = alternative_level_list->get_selected_items(); + for (int i = 0; i < alternative_level_selected.size(); i++) { + Array key = alternative_level_list->get_item_metadata(alternative_level_selected[i]); + Array val = tile_set->get_coords_level_tile_proxy(key[0], key[1]); + undo_redo->add_do_method(*tile_set, "remove_alternative_level_tile_proxy", key[0], key[1], key[2]); + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", key[0], key[1], key[2], val[0], val[1], val[2]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); + + commited_actions_count += 1; +} + +void TileProxiesManagerDialog::_update_lists() { + source_level_list->clear(); + coords_level_list->clear(); + alternative_level_list->clear(); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s", proxy[0]).rpad(5) + "-> " + vformat("%s", proxy[1]); + int id = source_level_list->add_item(text); + source_level_list->set_item_metadata(id, proxy[0]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s, %s", proxy[0], proxy[1]).rpad(17) + "-> " + vformat("%s, %s", proxy[2], proxy[3]); + int id = coords_level_list->add_item(text); + coords_level_list->set_item_metadata(id, proxy.slice(0, 2)); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + String text = vformat("%s, %s, %s", proxy[0], proxy[1], proxy[2]).rpad(24) + "-> " + vformat("%s, %s, %s", proxy[3], proxy[4], proxy[5]); + int id = alternative_level_list->add_item(text); + alternative_level_list->set_item_metadata(id, proxy.slice(0, 3)); + } +} + +void TileProxiesManagerDialog::_update_enabled_property_editors() { + if (from.source_id == TileSet::INVALID_SOURCE) { + from.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + to.set_atlas_coords(TileSetSource::INVALID_ATLAS_COORDS); + from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + coords_from_property_editor->hide(); + coords_to_property_editor->hide(); + alternative_from_property_editor->hide(); + alternative_to_property_editor->hide(); + } else if (from.get_atlas_coords().x == -1 || from.get_atlas_coords().y == -1) { + from.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + to.alternative_tile = TileSetSource::INVALID_TILE_ALTERNATIVE; + coords_from_property_editor->show(); + coords_to_property_editor->show(); + alternative_from_property_editor->hide(); + alternative_to_property_editor->hide(); + } else { + coords_from_property_editor->show(); + coords_to_property_editor->show(); + alternative_from_property_editor->show(); + alternative_to_property_editor->show(); + } + + source_from_property_editor->update_property(); + source_to_property_editor->update_property(); + coords_from_property_editor->update_property(); + coords_to_property_editor->update_property(); + alternative_from_property_editor->update_property(); + alternative_to_property_editor->update_property(); +} + +void TileProxiesManagerDialog::_property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing) { + _set(p_path, p_value); +} + +void TileProxiesManagerDialog::_add_button_pressed() { + if (from.source_id != TileSet::INVALID_SOURCE && to.source_id != TileSet::INVALID_SOURCE) { + Vector2i from_coords = from.get_atlas_coords(); + Vector2i to_coords = to.get_atlas_coords(); + if (from_coords.x >= 0 && from_coords.y >= 0 && to_coords.x >= 0 && to_coords.y >= 0) { + if (from.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE && to.alternative_tile != TileSetSource::INVALID_TILE_ALTERNATIVE) { + undo_redo->create_action("Create Alternative-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile, to.source_id, to.get_atlas_coords(), to.alternative_tile); + if (tile_set->has_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile)) { + Array a = tile_set->get_alternative_level_tile_proxy(from.source_id, from.get_atlas_coords(), from.alternative_tile); + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", to.source_id, to.get_atlas_coords(), to.alternative_tile, a[0], a[1], a[2]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_alternative_level_tile_proxy", from.source_id, from.get_atlas_coords(), from.alternative_tile); + } + } else { + undo_redo->create_action("Create Coords-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_coords_level_tile_proxy", from.source_id, from.get_atlas_coords(), to.source_id, to.get_atlas_coords()); + if (tile_set->has_coords_level_tile_proxy(from.source_id, from.get_atlas_coords())) { + Array a = tile_set->get_coords_level_tile_proxy(from.source_id, from.get_atlas_coords()); + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", to.source_id, to.get_atlas_coords(), a[0], a[1]); + } else { + undo_redo->add_undo_method(*tile_set, "remove_coords_level_tile_proxy", from.source_id, from.get_atlas_coords()); + } + } + } else { + undo_redo->create_action("Create source-level Tile Proxy"); + undo_redo->add_do_method(*tile_set, "set_source_level_tile_proxy", from.source_id, to.source_id); + if (tile_set->has_source_level_tile_proxy(from.source_id)) { + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", to.source_id, tile_set->get_source_level_tile_proxy(from.source_id)); + } else { + undo_redo->add_undo_method(*tile_set, "remove_source_level_tile_proxy", from.source_id); + } + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); + commited_actions_count++; + } +} + +void TileProxiesManagerDialog::_clear_invalid_button_pressed() { + undo_redo->create_action("Delete All Invalid Tile Proxies"); + + undo_redo->add_do_method(*tile_set, "cleanup_invalid_tile_proxies"); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); +} + +void TileProxiesManagerDialog::_clear_all_button_pressed() { + undo_redo->create_action("Delete All Tile Proxies"); + + undo_redo->add_do_method(*tile_set, "clear_tile_proxies"); + + Array proxies = tile_set->get_source_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_source_level_tile_proxy", proxy[0], proxy[1]); + } + + proxies = tile_set->get_coords_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_coords_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3]); + } + + proxies = tile_set->get_alternative_level_tile_proxies(); + for (int i = 0; i < proxies.size(); i++) { + Array proxy = proxies[i]; + undo_redo->add_undo_method(*tile_set, "set_alternative_level_tile_proxy", proxy[0], proxy[1], proxy[2], proxy[3], proxy[4], proxy[5]); + } + undo_redo->add_do_method(this, "_update_lists"); + undo_redo->add_undo_method(this, "_update_lists"); + undo_redo->commit_action(); +} + +bool TileProxiesManagerDialog::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "from_source") { + from.source_id = MAX(int(p_value), -1); + } else if (p_name == "from_coords") { + from.set_atlas_coords(Vector2i(p_value).max(Vector2i(-1, -1))); + } else if (p_name == "from_alternative") { + from.alternative_tile = MAX(int(p_value), -1); + } else if (p_name == "to_source") { + to.source_id = MAX(int(p_value), 0); + } else if (p_name == "to_coords") { + to.set_atlas_coords(Vector2i(p_value).max(Vector2i(0, 0))); + } else if (p_name == "to_alternative") { + to.alternative_tile = MAX(int(p_value), 0); + } else { + return false; + } + _update_enabled_property_editors(); + return true; +} + +bool TileProxiesManagerDialog::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "from_source") { + r_ret = from.source_id; + } else if (p_name == "from_coords") { + r_ret = from.get_atlas_coords(); + } else if (p_name == "from_alternative") { + r_ret = from.alternative_tile; + } else if (p_name == "to_source") { + r_ret = to.source_id; + } else if (p_name == "to_coords") { + r_ret = to.get_atlas_coords(); + } else if (p_name == "to_alternative") { + r_ret = to.alternative_tile; + } else { + return false; + } + return true; +} + +void TileProxiesManagerDialog::_unhandled_key_input(Ref<InputEvent> p_event) { + ERR_FAIL_COND(p_event.is_null()); + + if (p_event->is_pressed() && !p_event->is_echo() && (Object::cast_to<InputEventKey>(p_event.ptr()) || Object::cast_to<InputEventJoypadButton>(p_event.ptr()) || Object::cast_to<InputEventAction>(*p_event))) { + if (!is_inside_tree() || !is_visible()) { + return; + } + + if (popup_menu->activate_item_by_event(p_event, false)) { + set_input_as_handled(); + } + } +} + +void TileProxiesManagerDialog::cancel_pressed() { + for (int i = 0; i < commited_actions_count; i++) { + undo_redo->undo(); + } + commited_actions_count = 0; +} + +void TileProxiesManagerDialog::_bind_methods() { + ClassDB::bind_method(D_METHOD("_update_lists"), &TileProxiesManagerDialog::_update_lists); + ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &TileProxiesManagerDialog::_unhandled_key_input); +} + +void TileProxiesManagerDialog::update_tile_set(Ref<TileSet> p_tile_set) { + ERR_FAIL_COND(!p_tile_set.is_valid()); + tile_set = p_tile_set; + commited_actions_count = 0; + _update_lists(); +} + +TileProxiesManagerDialog::TileProxiesManagerDialog() { + // Tile proxy management window. + set_title(TTR("Tile Proxies Management")); + set_process_unhandled_key_input(true); + + to.source_id = 0; + to.set_atlas_coords(Vector2i()); + to.alternative_tile = 0; + + VBoxContainer *vbox_container = memnew(VBoxContainer); + vbox_container->set_h_size_flags(Control::SIZE_EXPAND_FILL); + vbox_container->set_v_size_flags(Control::SIZE_EXPAND_FILL); + add_child(vbox_container); + + Label *source_level_label = memnew(Label); + source_level_label->set_text(TTR("Source-level proxies")); + vbox_container->add_child(source_level_label); + + source_level_list = memnew(ItemList); + source_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + source_level_list->set_select_mode(ItemList::SELECT_MULTI); + source_level_list->set_allow_rmb_select(true); + source_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(source_level_list)); + vbox_container->add_child(source_level_list); + + Label *coords_level_label = memnew(Label); + coords_level_label->set_text(TTR("Coords-level proxies")); + vbox_container->add_child(coords_level_label); + + coords_level_list = memnew(ItemList); + coords_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + coords_level_list->set_select_mode(ItemList::SELECT_MULTI); + coords_level_list->set_allow_rmb_select(true); + coords_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(coords_level_list)); + vbox_container->add_child(coords_level_list); + + Label *alternative_level_label = memnew(Label); + alternative_level_label->set_text(TTR("Alternative-level proxies")); + vbox_container->add_child(alternative_level_label); + + alternative_level_list = memnew(ItemList); + alternative_level_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + alternative_level_list->set_select_mode(ItemList::SELECT_MULTI); + alternative_level_list->set_allow_rmb_select(true); + alternative_level_list->connect("item_rmb_selected", callable_mp(this, &TileProxiesManagerDialog::_right_clicked), varray(alternative_level_list)); + vbox_container->add_child(alternative_level_list); + + popup_menu = memnew(PopupMenu); + popup_menu->add_shortcut(ED_GET_SHORTCUT("ui_text_delete")); + popup_menu->connect("id_pressed", callable_mp(this, &TileProxiesManagerDialog::_menu_id_pressed)); + add_child(popup_menu); + + // Add proxy panel. + HSeparator *h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); + + Label *add_label = memnew(Label); + add_label->set_text(TTR("Add a new tile proxy:")); + vbox_container->add_child(add_label); + + HBoxContainer *hboxcontainer = memnew(HBoxContainer); + vbox_container->add_child(hboxcontainer); + + // From + VBoxContainer *vboxcontainer_from = memnew(VBoxContainer); + vboxcontainer_from->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hboxcontainer->add_child(vboxcontainer_from); + + source_from_property_editor = memnew(EditorPropertyInteger); + source_from_property_editor->set_label(TTR("From Source")); + source_from_property_editor->set_object_and_property(this, "from_source"); + source_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + source_from_property_editor->set_selectable(false); + source_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + source_from_property_editor->setup(-1, 99999, 1, true, false); + vboxcontainer_from->add_child(source_from_property_editor); + + coords_from_property_editor = memnew(EditorPropertyVector2i); + coords_from_property_editor->set_label(TTR("From Coords")); + coords_from_property_editor->set_object_and_property(this, "from_coords"); + coords_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + coords_from_property_editor->set_selectable(false); + coords_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + coords_from_property_editor->setup(-1, 99999, true); + coords_from_property_editor->hide(); + vboxcontainer_from->add_child(coords_from_property_editor); + + alternative_from_property_editor = memnew(EditorPropertyInteger); + alternative_from_property_editor->set_label(TTR("From Alternative")); + alternative_from_property_editor->set_object_and_property(this, "from_alternative"); + alternative_from_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + alternative_from_property_editor->set_selectable(false); + alternative_from_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + alternative_from_property_editor->setup(-1, 99999, 1, true, false); + alternative_from_property_editor->hide(); + vboxcontainer_from->add_child(alternative_from_property_editor); + + // To + VBoxContainer *vboxcontainer_to = memnew(VBoxContainer); + vboxcontainer_to->set_h_size_flags(Control::SIZE_EXPAND_FILL); + hboxcontainer->add_child(vboxcontainer_to); + + source_to_property_editor = memnew(EditorPropertyInteger); + source_to_property_editor->set_label(TTR("To Source")); + source_to_property_editor->set_object_and_property(this, "to_source"); + source_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + source_to_property_editor->set_selectable(false); + source_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + source_to_property_editor->setup(-1, 99999, 1, true, false); + vboxcontainer_to->add_child(source_to_property_editor); + + coords_to_property_editor = memnew(EditorPropertyVector2i); + coords_to_property_editor->set_label(TTR("To Coords")); + coords_to_property_editor->set_object_and_property(this, "to_coords"); + coords_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + coords_to_property_editor->set_selectable(false); + coords_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + coords_to_property_editor->setup(-1, 99999, true); + coords_to_property_editor->hide(); + vboxcontainer_to->add_child(coords_to_property_editor); + + alternative_to_property_editor = memnew(EditorPropertyInteger); + alternative_to_property_editor->set_label(TTR("To Alternative")); + alternative_to_property_editor->set_object_and_property(this, "to_alternative"); + alternative_to_property_editor->connect("property_changed", callable_mp(this, &TileProxiesManagerDialog::_property_changed)); + alternative_to_property_editor->set_selectable(false); + alternative_to_property_editor->set_h_size_flags(Control::SIZE_EXPAND_FILL); + alternative_to_property_editor->setup(-1, 99999, 1, true, false); + alternative_to_property_editor->hide(); + vboxcontainer_to->add_child(alternative_to_property_editor); + + Button *add_button = memnew(Button); + add_button->set_text(TTR("Add")); + add_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + add_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_add_button_pressed)); + vbox_container->add_child(add_button); + + h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); + + // Generic actions. + Label *generic_actions_label = memnew(Label); + generic_actions_label->set_text(TTR("Global actions:")); + vbox_container->add_child(generic_actions_label); + + hboxcontainer = memnew(HBoxContainer); + vbox_container->add_child(hboxcontainer); + + Button *clear_invalid_button = memnew(Button); + clear_invalid_button->set_text(TTR("Clear Invalid")); + clear_invalid_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + clear_invalid_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_clear_invalid_button_pressed)); + hboxcontainer->add_child(clear_invalid_button); + + Button *clear_all_button = memnew(Button); + clear_all_button->set_text(TTR("Clear All")); + clear_all_button->set_h_size_flags(Control::SIZE_SHRINK_CENTER); + clear_all_button->connect("pressed", callable_mp(this, &TileProxiesManagerDialog::_clear_all_button_pressed)); + hboxcontainer->add_child(clear_all_button); + + h_separator = memnew(HSeparator); + vbox_container->add_child(h_separator); +} diff --git a/editor/plugins/tiles/tile_proxies_manager_dialog.h b/editor/plugins/tiles/tile_proxies_manager_dialog.h new file mode 100644 index 0000000000..f6898e960b --- /dev/null +++ b/editor/plugins/tiles/tile_proxies_manager_dialog.h @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* tile_proxies_manager_dialog.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_PROXIES_MANAGER_DIALOG_H +#define TILE_PROXIES_MANAGER_DIALOG_H + +#include "editor/editor_node.h" +#include "editor/editor_properties.h" + +#include "scene/gui/dialogs.h" +#include "scene/gui/item_list.h" +#include "scene/resources/tile_set.h" + +class TileProxiesManagerDialog : public ConfirmationDialog { + GDCLASS(TileProxiesManagerDialog, ConfirmationDialog); + +private: + int commited_actions_count = 0; + Ref<TileSet> tile_set; + + UndoRedo *undo_redo = EditorNode::get_singleton()->get_undo_redo(); + + TileMapCell from; + TileMapCell to; + + // GUI + ItemList *source_level_list; + ItemList *coords_level_list; + ItemList *alternative_level_list; + + EditorPropertyInteger *source_from_property_editor; + EditorPropertyVector2i *coords_from_property_editor; + EditorPropertyInteger *alternative_from_property_editor; + EditorPropertyInteger *source_to_property_editor; + EditorPropertyVector2i *coords_to_property_editor; + EditorPropertyInteger *alternative_to_property_editor; + + PopupMenu *popup_menu; + void _right_clicked(int p_item, Vector2 p_local_mouse_pos, Object *p_item_list); + void _menu_id_pressed(int p_id); + void _delete_selected_bindings(); + void _update_lists(); + void _update_enabled_property_editors(); + void _property_changed(const String &p_path, const Variant &p_value, const String &p_name, bool p_changing); + void _add_button_pressed(); + + void _clear_invalid_button_pressed(); + void _clear_all_button_pressed(); + +protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; + void _unhandled_key_input(Ref<InputEvent> p_event); + virtual void cancel_pressed() override; + static void _bind_methods(); + +public: + void update_tile_set(Ref<TileSet> p_tile_set); + + TileProxiesManagerDialog(); +}; + +#endif // TILE_PROXIES_MANAGER_DIALOG_H diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp index 6b617cf4d8..de910dfd32 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.cpp +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -745,7 +745,7 @@ void TileSetAtlasSourceEditor::_update_atlas_view() { button->add_theme_style_override("hover", memnew(StyleBoxEmpty)); button->add_theme_style_override("focus", memnew(StyleBoxEmpty)); button->add_theme_style_override("pressed", memnew(StyleBoxEmpty)); - button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, -1)); + button->connect("pressed", callable_mp(tile_set_atlas_source, &TileSetAtlasSource::create_alternative_tile), varray(tile_id, TileSetSource::INVALID_TILE_ALTERNATIVE)); button->set_rect(Rect2(Vector2(pos.x, pos.y + (y_increment - texture_region_base_size.y) / 2.0), Vector2(texture_region_base_size_min, texture_region_base_size_min))); button->set_expand_icon(true); diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h index dbb0756a16..501416c340 100644 --- a/editor/plugins/tiles/tile_set_atlas_source_editor.h +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -65,7 +65,7 @@ private: private: Ref<TileSet> tile_set; TileSetAtlasSource *tile_set_atlas_source = nullptr; - int source_id = -1; + int source_id = TileSet::INVALID_SOURCE; protected: bool _set(const StringName &p_name, const Variant &p_value); @@ -108,7 +108,7 @@ private: Ref<TileSet> tile_set; TileSetAtlasSource *tile_set_atlas_source = nullptr; - int tile_set_atlas_source_id = -1; + int tile_set_atlas_source_id = TileSet::INVALID_SOURCE; UndoRedo *undo_redo = EditorNode::get_undo_redo(); diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index 76ce6f0065..ba98a7d6b3 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -50,7 +50,7 @@ void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, C if (p_from == sources_list) { // Handle dropping a texture in the list of atlas resources. - int source_id = -1; + int source_id = TileSet::INVALID_SOURCE; int added = 0; Dictionary d = p_data; Vector<String> files = d["files"]; @@ -77,7 +77,7 @@ void TileSetEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, C } // Update the selected source (thus triggering an update). - _update_atlas_sources_list(source_id); + _update_sources_list(source_id); } } @@ -114,11 +114,11 @@ bool TileSetEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_dat return false; } -void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { +void TileSetEditor::_update_sources_list(int force_selected_id) { ERR_FAIL_COND(!tile_set.is_valid()); // Get the previously selected id. - int old_selected = -1; + int old_selected = TileSet::INVALID_SOURCE; if (sources_list->get_current() >= 0) { int source_id = sources_list->get_item_metadata(sources_list->get_current()); if (tile_set->has_source(source_id)) { @@ -126,7 +126,7 @@ void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { } } - int to_select = -1; + int to_select = TileSet::INVALID_SOURCE; if (force_selected_id >= 0) { to_select = force_selected_id; } else if (old_selected >= 0) { @@ -200,7 +200,7 @@ void TileSetEditor::_update_atlas_sources_list(int force_selected_id) { _source_selected(sources_list->get_current()); // Synchronize the lists. - TilesEditor::get_singleton()->set_atlas_sources_lists_current(sources_list->get_current()); + TilesEditor::get_singleton()->set_sources_lists_current(sources_list->get_current()); } void TileSetEditor::_source_selected(int p_source_index) { @@ -235,6 +235,23 @@ void TileSetEditor::_source_selected(int p_source_index) { } } +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_sources_list(); +} + void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { ERR_FAIL_COND(!tile_set.is_valid()); @@ -251,7 +268,7 @@ void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { undo_redo->add_undo_method(*tile_set, "remove_source", source_id); undo_redo->commit_action(); - _update_atlas_sources_list(source_id); + _update_sources_list(source_id); } break; case 1: { int source_id = tile_set->get_next_source_id(); @@ -264,28 +281,26 @@ void TileSetEditor::_source_add_id_pressed(int p_id_pressed) { undo_redo->add_undo_method(*tile_set, "remove_source", source_id); undo_redo->commit_action(); - _update_atlas_sources_list(source_id); + _update_sources_list(source_id); } break; default: ERR_FAIL(); } } -void TileSetEditor::_source_delete_pressed() { +void TileSetEditor::_sources_advanced_menu_id_pressed(int p_id_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(); + switch (p_id_pressed) { + case 0: { + atlas_merging_dialog->update_tile_set(tile_set); + atlas_merging_dialog->popup_centered_ratio(0.5); + } break; + case 1: { + tile_proxies_manager_dialog->update_tile_set(tile_set); + tile_proxies_manager_dialog->popup_centered_ratio(0.5); + } break; + } } void TileSetEditor::_notification(int p_what) { @@ -294,6 +309,7 @@ void TileSetEditor::_notification(int p_what) { case NOTIFICATION_THEME_CHANGED: sources_delete_button->set_icon(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons"))); sources_add_button->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons"))); + sources_advanced_menu_button->set_icon(get_theme_icon(SNAME("GuiTabMenuHl"), SNAME("EditorIcons"))); missing_texture_texture = get_theme_icon(SNAME("TileSet"), SNAME("EditorIcons")); break; case NOTIFICATION_INTERNAL_PROCESS: @@ -301,7 +317,7 @@ void TileSetEditor::_notification(int p_what) { if (tile_set.is_valid()) { tile_set->set_edited(true); } - _update_atlas_sources_list(); + _update_sources_list(); tile_set_changed_needs_update = false; } break; @@ -414,7 +430,7 @@ void TileSetEditor::edit(Ref<TileSet> 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(); + _update_sources_list(); } tile_set_atlas_source_editor->hide(); @@ -447,8 +463,8 @@ TileSetEditor::TileSetEditor() { sources_list->set_h_size_flags(SIZE_EXPAND_FILL); sources_list->set_v_size_flags(SIZE_EXPAND_FILL); sources_list->connect("item_selected", callable_mp(this, &TileSetEditor::_source_selected)); - sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_atlas_sources_lists_current)); - sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_sources_list), varray(sources_list)); + sources_list->connect("item_selected", callable_mp(TilesEditor::get_singleton(), &TilesEditor::set_sources_lists_current)); + sources_list->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_sources_list), varray(sources_list)); sources_list->set_texture_filter(CanvasItem::TEXTURE_FILTER_NEAREST); sources_list->set_drag_forwarding(this); split_container_left_side->add_child(sources_list); @@ -470,6 +486,19 @@ TileSetEditor::TileSetEditor() { sources_add_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_source_add_id_pressed)); sources_bottom_actions->add_child(sources_add_button); + sources_advanced_menu_button = memnew(MenuButton); + sources_advanced_menu_button->set_flat(true); + sources_advanced_menu_button->get_popup()->add_item(TTR("Open Atlas Merging Tool")); + sources_advanced_menu_button->get_popup()->add_item(TTR("Manage Tile Proxies")); + sources_advanced_menu_button->get_popup()->connect("id_pressed", callable_mp(this, &TileSetEditor::_sources_advanced_menu_id_pressed)); + sources_bottom_actions->add_child(sources_advanced_menu_button); + + atlas_merging_dialog = memnew(AtlasMergingDialog); + add_child(atlas_merging_dialog); + + tile_proxies_manager_dialog = memnew(TileProxiesManagerDialog); + add_child(tile_proxies_manager_dialog); + // Right side container. VBoxContainer *split_container_right_side = memnew(VBoxContainer); split_container_right_side->set_h_size_flags(SIZE_EXPAND_FILL); @@ -489,7 +518,7 @@ TileSetEditor::TileSetEditor() { 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)); + tile_set_atlas_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list)); split_container_right_side->add_child(tile_set_atlas_source_editor); tile_set_atlas_source_editor->hide(); @@ -497,7 +526,7 @@ TileSetEditor::TileSetEditor() { 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)); + tile_set_scenes_collection_source_editor->connect("source_id_changed", callable_mp(this, &TileSetEditor::_update_sources_list)); split_container_right_side->add_child(tile_set_scenes_collection_source_editor); tile_set_scenes_collection_source_editor->hide(); diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h index 9e50aca62f..970e3fabb6 100644 --- a/editor/plugins/tiles/tile_set_editor.h +++ b/editor/plugins/tiles/tile_set_editor.h @@ -31,8 +31,10 @@ #ifndef TILE_SET_EDITOR_H #define TILE_SET_EDITOR_H +#include "atlas_merging_dialog.h" #include "scene/gui/box_container.h" #include "scene/resources/tile_set.h" +#include "tile_proxies_manager_dialog.h" #include "tile_set_atlas_source_editor.h" #include "tile_set_scenes_collection_source_editor.h" @@ -51,16 +53,21 @@ private: UndoRedo *undo_redo = EditorNode::get_undo_redo(); - void _update_atlas_sources_list(int force_selected_id = -1); + void _update_sources_list(int force_selected_id = -1); - // -- Sources management -- + // Sources management. Button *sources_delete_button; MenuButton *sources_add_button; + MenuButton *sources_advanced_menu_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 _source_add_id_pressed(int p_id_pressed); + void _sources_advanced_menu_id_pressed(int p_id_pressed); + + AtlasMergingDialog *atlas_merging_dialog; + TileProxiesManagerDialog *tile_proxies_manager_dialog; void _tile_set_changed(); diff --git a/editor/plugins/tiles/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp index 339efc7b99..79b869b511 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.cpp +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -118,11 +118,11 @@ void TilesEditor::_update_editors() { CanvasItemEditor::get_singleton()->update_viewport(); } -void TilesEditor::set_atlas_sources_lists_current(int p_current) { +void TilesEditor::set_sources_lists_current(int p_current) { atlas_sources_lists_current = p_current; } -void TilesEditor::synchronize_atlas_sources_list(Object *p_current) { +void TilesEditor::synchronize_sources_list(Object *p_current) { ItemList *item_list = Object::cast_to<ItemList>(p_current); ERR_FAIL_COND(!item_list); diff --git a/editor/plugins/tiles/tiles_editor_plugin.h b/editor/plugins/tiles/tiles_editor_plugin.h index 6cc6f51598..f976d68938 100644 --- a/editor/plugins/tiles/tiles_editor_plugin.h +++ b/editor/plugins/tiles/tiles_editor_plugin.h @@ -76,8 +76,8 @@ public: 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_sources_lists_current(int p_current); + void synchronize_sources_list(Object *p_current); void set_atlas_view_transform(float p_zoom, Vector2 p_scroll); void synchronize_atlas_view(Object *p_current); |