From a3dda2df85bf3e3ef82dbe1c2377640b9f3fd9c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilles=20Roudi=C3=A8re?= Date: Fri, 7 May 2021 15:41:39 +0200 Subject: Rework the TileSet resource and TileMap nodes: - Move most properties from TileMap to TileSet, - Make TileSet more flexible, supporting more feature (several collision layers, etc...), - Fusion both the TileMap and TileSet editor, - Implement TileSetSources, and thus a new way to index tiles in the TileSet, - Rework the TileSet and TileMap editors completely, - Implement an editor zoom widget (and use it in several places) --- editor/plugins/tiles/SCsub | 5 + editor/plugins/tiles/tile_atlas_view.cpp | 649 ++++ editor/plugins/tiles/tile_atlas_view.h | 153 + editor/plugins/tiles/tile_data_editors.cpp | 218 ++ editor/plugins/tiles/tile_data_editors.h | 110 + editor/plugins/tiles/tile_map_editor.cpp | 3368 ++++++++++++++++++++ editor/plugins/tiles/tile_map_editor.h | 328 ++ .../plugins/tiles/tile_set_atlas_source_editor.cpp | 1858 +++++++++++ .../plugins/tiles/tile_set_atlas_source_editor.h | 260 ++ editor/plugins/tiles/tile_set_editor.cpp | 520 +++ editor/plugins/tiles/tile_set_editor.h | 94 + editor/plugins/tiles/tiles_editor_plugin.cpp | 276 ++ editor/plugins/tiles/tiles_editor_plugin.h | 114 + 13 files changed, 7953 insertions(+) create mode 100644 editor/plugins/tiles/SCsub create mode 100644 editor/plugins/tiles/tile_atlas_view.cpp create mode 100644 editor/plugins/tiles/tile_atlas_view.h create mode 100644 editor/plugins/tiles/tile_data_editors.cpp create mode 100644 editor/plugins/tiles/tile_data_editors.h create mode 100644 editor/plugins/tiles/tile_map_editor.cpp create mode 100644 editor/plugins/tiles/tile_map_editor.h create mode 100644 editor/plugins/tiles/tile_set_atlas_source_editor.cpp create mode 100644 editor/plugins/tiles/tile_set_atlas_source_editor.h create mode 100644 editor/plugins/tiles/tile_set_editor.cpp create mode 100644 editor/plugins/tiles/tile_set_editor.h create mode 100644 editor/plugins/tiles/tiles_editor_plugin.cpp create mode 100644 editor/plugins/tiles/tiles_editor_plugin.h (limited to 'editor/plugins/tiles') 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..4de2f962bc --- /dev/null +++ b/editor/plugins/tiles/tile_atlas_view.cpp @@ -0,0 +1,649 @@ +/*************************************************************************/ +/* 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/center_container.h" +#include "scene/gui/label.h" +#include "scene/gui/panel.h" +#include "scene/gui/texture_rect.h" + +#include "editor/editor_scale.h" + +void TileAtlasView::_gui_input(const Ref &p_event) { + bool ctrl = Input::get_singleton()->is_key_pressed(KEY_CONTROL); + + Ref b = p_event; + if (b.is_valid()) { + if (ctrl && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN) { + // Zoom out + zoom_widget->set_zoom_by_increments(-2); + emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); + _update_zoom(zoom_widget->get_zoom(), true); + accept_event(); + } + + if (ctrl && b->is_pressed() && b->get_button_index() == MOUSE_BUTTON_WHEEL_UP) { + // Zoom in + zoom_widget->set_zoom_by_increments(2); + emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); + _update_zoom(zoom_widget->get_zoom(), true); + accept_event(); + } + } +} + +Size2i TileAtlasView::_compute_base_tiles_control_size() { + // Update the texture. + Vector2i size; + Ref 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(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(float p_zoom, bool p_zoom_on_mouse_pos, Vector2i p_scroll) { + // 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) * p_zoom); + + Size2i alternative_tiles_control_size = _compute_alternative_tiles_control_size(); + alternative_tiles_root_control->set_custom_minimum_size(Vector2(alternative_tiles_control_size) * p_zoom); + + // Set the texture for the base tiles. + Ref 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(p_zoom, p_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(p_zoom, p_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] * p_zoom); + } + + // Update the backgrounds. + background_left->update(); + background_right->update(); + + if (p_scroll != Vector2i(-1, -1)) { + scroll_container->set_h_scroll(p_scroll.x); + scroll_container->set_v_scroll(p_scroll.y); + } + + // Zoom on the position. + if (previous_zoom != p_zoom) { + // TODO: solve this. + // There is however an issue with scrollcainter preventing this, as it seems + // that the scrollbars are not updated right aways after its children update. + + // Compute point on previous area. + /*Vector2 max = Vector2(scroll_container->get_h_scrollbar()->get_max(), scroll_container->get_v_scrollbar()->get_max()); + Vector2 min = Vector2(scroll_container->get_h_scrollbar()->get_min(), scroll_container->get_v_scrollbar()->get_min()); + Vector2 value = Vector2(scroll_container->get_h_scrollbar()->get_value(), scroll_container->get_v_scrollbar()->get_value()); + + Vector2 old_max = max * previous_zoom / p_zoom; + + Vector2 max_pixel_change = max - old_max; + Vector2 ratio = ((value + scroll_container->get_local_mouse_position()) / old_max).max(Vector2()).min(Vector2(1,1)); + Vector2 offset = max_pixel_change * ratio; + + print_line("--- ZOOMED ---"); + print_line(vformat("max: %s", max)); + print_line(vformat("min: %s", min)); + print_line(vformat("value: %s", value)); + print_line(vformat("size: %s", scroll_container->get_size())); + print_line(vformat("mouse_pos: %s", scroll_container->get_local_mouse_position())); + + print_line(vformat("ratio: %s", ratio)); + print_line(vformat("max_pixel_change: %s", max_pixel_change)); + print_line(vformat("offset: %s", offset)); + + + print_line(vformat("value before: %s", Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()))); + scroll_container->set_h_scroll(10000);//scroll_container->get_h_scroll()+offset.x); + scroll_container->set_v_scroll(10000);//scroll_container->get_v_scroll()+offset.y); + print_line(vformat("value after: %s", Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll()))); + */ + + previous_zoom = p_zoom; + } +} + +void TileAtlasView::_scroll_changed() { + emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); +} + +void TileAtlasView::_zoom_widget_changed() { + _update_zoom(zoom_widget->get_zoom()); + emit_signal("transform_changed", zoom_widget->get_zoom(), Vector2(scroll_container->get_h_scroll(), scroll_container->get_v_scroll())); +} + +void TileAtlasView::_base_tiles_root_control_gui_input(const Ref &p_event) { + base_tiles_root_control->set_tooltip(""); + + Ref 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 != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + coords = tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetAtlasSource::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 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) == TileSetAtlasSource::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 + rect.position = Vector2i(0, margins.y + (grid_size.y * texture_region_size.y)); + 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. + rect.position = Vector2i(margins.x + (grid_size.x * texture_region_size.x), 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. + TileSetAtlasPluginRendering::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 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 != TileSetAtlasSource::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 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 == TileSetAtlasSource::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. + 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), Color(1.0, 0.5, 0.2, 0.8)); + } +} + +void TileAtlasView::_alternative_tiles_root_control_gui_input(const Ref &p_event) { + alternative_tiles_root_control->set_tooltip(""); + + Ref 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 != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative_id != TileSetAtlasSource::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 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(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. + TileSetAtlasPluginRendering::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 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 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(zoom_widget->get_zoom()); + + // 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(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(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_scroll) { + zoom_widget->set_zoom(p_zoom); + _update_zoom(zoom_widget->get_zoom(), false, p_scroll); +}; + +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 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 TileSetAtlasSource::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(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(); + } + 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>::Element *E_coords = alternative_tiles_rect_cache.front(); E_coords; E_coords = E_coords->next()) { + for (Map::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(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::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() { + scroll_container->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::_bind_methods() { + ADD_SIGNAL(MethodInfo("transform_changed", PropertyInfo(Variant::FLOAT, "zoom"), PropertyInfo(Variant::VECTOR2, "scroll"))); +} + +TileAtlasView::TileAtlasView() { + Panel *panel_container = memnew(Panel); + panel_container->set_h_size_flags(SIZE_EXPAND_FILL); + panel_container->set_v_size_flags(SIZE_EXPAND_FILL); + panel_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + add_child(panel_container); + + //Scrolling + scroll_container = memnew(ScrollContainer); + scroll_container->get_h_scrollbar()->connect("value_changed", callable_mp(this, &TileAtlasView::_scroll_changed).unbind(1)); + scroll_container->get_v_scrollbar()->connect("value_changed", callable_mp(this, &TileAtlasView::_scroll_changed).unbind(1)); + panel_container->add_child(scroll_container); + scroll_container->set_anchors_and_offsets_preset(Control::PRESET_WIDE); + + 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)); + + CenterContainer *center_container = memnew(CenterContainer); + center_container->set_h_size_flags(SIZE_EXPAND_FILL); + center_container->set_v_size_flags(SIZE_EXPAND_FILL); + center_container->connect("gui_input", callable_mp(this, &TileAtlasView::_gui_input)); + scroll_container->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); + center_container->add_child(margin_container); + + hbox = memnew(HBoxContainer); + hbox->add_theme_constant_override("separation", 10); + hbox->hide(); + margin_container->add_child(hbox); + + VBoxContainer *left_vbox = memnew(VBoxContainer); + hbox->add_child(left_vbox); + + VBoxContainer *right_vbox = memnew(VBoxContainer); + hbox->add_child(right_vbox); + + // Base tiles. + Label *base_tile_label = memnew(Label); + 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_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_anchors_and_offsets_preset(Control::PRESET_WIDE); + background_left->set_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); + background_left->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + 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_anchors_and_offsets_preset(Control::PRESET_WIDE); + base_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST); + base_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_root_control->add_child(base_tiles_drawing_root); + + base_tiles_draw = memnew(Control); + 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_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_drawing_root->add_child(base_tiles_draw); + + base_tiles_texture_grid = memnew(Control); + 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_texture_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_drawing_root->add_child(base_tiles_texture_grid); + + base_tiles_shape_grid = memnew(Control); + 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_shape_grid->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_drawing_root->add_child(base_tiles_shape_grid); + + base_tiles_dark = memnew(Control); + 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_dark->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + base_tiles_drawing_root->add_child(base_tiles_dark); + + // Alternative tiles. + Label *alternative_tiles_label = memnew(Label); + 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->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_texture_repeat(TextureRepeat::TEXTURE_REPEAT_ENABLED); + background_right->connect("draw", callable_mp(this, &TileAtlasView::_draw_background_right)); + background_right->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + alternative_tiles_root_control->add_child(background_right); + + alternative_tiles_drawing_root = memnew(Control); + alternative_tiles_drawing_root->set_texture_filter(TEXTURE_FILTER_NEAREST); + alternative_tiles_drawing_root->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + alternative_tiles_root_control->add_child(alternative_tiles_drawing_root); + + alternatives_draw = memnew(Control); + alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives)); + alternatives_draw->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + 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..28fd3ed1e0 --- /dev/null +++ b/editor/plugins/tiles/tile_atlas_view.h @@ -0,0 +1,153 @@ +/*************************************************************************/ +/* 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/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; + + float previous_zoom = 1.0; + EditorZoomWidget *zoom_widget; + void _zoom_widget_changed(); + void _scroll_changed(); + void _update_zoom(float p_zoom, bool p_zoom_on_mouse_pos = false, Vector2i p_scroll = Vector2i(-1, -1)); + void _gui_input(const Ref &p_event); + + Map> alternative_tiles_rect_cache; + void _update_alternative_tiles_rect_cache(); + + ScrollContainer *scroll_container; + 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 &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 &p_event); + + Control *alternative_tiles_drawing_root; + + Control *alternatives_draw; + void _draw_alternatives(); + + Size2i _compute_alternative_tiles_control_size(); + +protected: + static void _bind_methods(); + +public: + // Global. + void set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + + ScrollContainer *get_scroll_container() { return scroll_container; }; + + float get_zoom() const; + void set_transform(float p_zoom, Vector2i p_scroll); + + 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..0b674bec39 --- /dev/null +++ b/editor/plugins/tiles/tile_data_editors.cpp @@ -0,0 +1,218 @@ +/*************************************************************************/ +/* 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" + +TileData *TileDataEditor::_get_tile_data(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile) { + ERR_FAIL_COND_V(!p_tile_set, nullptr); + ERR_FAIL_COND_V(!p_tile_set->has_source(p_atlas_source_id), nullptr); + + TileData *td = nullptr; + TileSetSource *source = *p_tile_set->get_source(p_atlas_source_id); + TileSetAtlasSource *atlas_source = Object::cast_to(source); + if (atlas_source) { + ERR_FAIL_COND_V(!atlas_source->has_tile(p_atlas_coords), nullptr); + ERR_FAIL_COND_V(!atlas_source->has_alternative_tile(p_atlas_coords, p_alternative_tile), nullptr); + td = Object::cast_to(atlas_source->get_tile_data(p_atlas_coords, p_alternative_tile)); + } + + return td; +} + +void TileDataEditor::edit(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { +} + +void TileDataTextureOffsetEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + bool valid; + Variant value = tile_data->get(p_property, &valid); + if (!valid) { + return; + } + ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I); + + Vector2i tile_set_tile_size = p_tile_set->get_tile_size(); + Rect2i rect = Rect2i(-tile_set_tile_size / 2, tile_set_tile_size); + p_tile_set->draw_tile_shape(p_canvas_item, p_transform.xform(rect), Color(1.0, 0.0, 0.0)); +} + +void TileDataIntegerEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + bool valid; + Variant value = tile_data->get(p_property, &valid); + if (!valid) { + return; + } + ERR_FAIL_COND(value.get_type() != Variant::INT); + + Ref font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); + int height = font->get_height(); + int width = 200; + p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-width / 2, height / 2), vformat("%d", value), HALIGN_CENTER, width, -1, Color(1, 1, 1), 1, Color(0, 0, 0, 1)); +} + +void TileDataFloatEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + bool valid; + Variant value = tile_data->get(p_property, &valid); + if (!valid) { + return; + } + ERR_FAIL_COND(value.get_type() != Variant::FLOAT); + + Ref font = TileSetEditor::get_singleton()->get_theme_font("bold", "EditorFonts"); + int height = font->get_height(); + int width = 200; + p_canvas_item->draw_string(font, p_transform.get_origin() + Vector2i(-width / 2, height / 2), vformat("%.2f", value), HALIGN_CENTER, width, -1, Color(1, 1, 1), 1, Color(0, 0, 0, 1)); +} + +void TileDataPositionEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + bool valid; + Variant value = tile_data->get(p_property, &valid); + if (!valid) { + return; + } + ERR_FAIL_COND(value.get_type() != Variant::VECTOR2I && value.get_type() != Variant::VECTOR2); + + Ref position_icon = TileSetEditor::get_singleton()->get_theme_icon("EditorPosition", "EditorIcons"); + p_canvas_item->draw_texture(position_icon, p_transform.get_origin() + Vector2(value) - position_icon->get_size() / 2); +} + +void TileDataOcclusionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + Vector components = String(p_property).split("/", true); + if (components[0].begins_with("occlusion_layer_") && components[0].trim_prefix("occlusion_layer_").is_valid_integer()) { + int occlusion_layer = components[0].trim_prefix("occlusion_layer_").to_int(); + if (occlusion_layer >= 0 && occlusion_layer < p_tile_set->get_occlusion_layers_count()) { + // Draw all shapes. + Vector debug_occlusion_color; + debug_occlusion_color.push_back(Color(0.5, 0, 0, 0.6)); + + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + Ref 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()); + } + } +} + +void TileDataCollisionShapeEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + Vector components = String(p_property).split("/", true); + if (components[0].begins_with("physics_layer_") && components[0].trim_prefix("physics_layer_").is_valid_integer()) { + int physics_layer = components[0].trim_prefix("physics_layer_").to_int(); + if (physics_layer >= 0 && physics_layer < p_tile_set->get_physics_layers_count()) { + // Draw all shapes. + Color debug_collision_color = p_canvas_item->get_tree()->get_debug_collisions_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_shapes_count(physics_layer); i++) { + Ref shape = tile_data->get_collision_shape_shape(physics_layer, i); + if (shape.is_valid()) { + shape->draw(p_canvas_item->get_canvas_item(), debug_collision_color); + } + } + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), Transform2D()); + } + } +} + +void TileDataTerrainsEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + Vector components = String(p_property).split("/", true); + if (components[0] == "terrain_mode" || components[0] == "terrain" || components[0] == "terrains_peering_bit") { + TileSetAtlasPluginTerrain::draw_terrains(p_canvas_item, p_transform, p_tile_set, tile_data); + } +} + +void TileDataNavigationPolygonEditor::draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) { + TileData *tile_data = _get_tile_data(p_tile_set, p_atlas_source_id, p_atlas_coords, p_alternative_tile); + ERR_FAIL_COND(!tile_data); + + Vector components = String(p_property).split("/", true); + if (components[0].begins_with("navigation_layer_") && components[0].trim_prefix("navigation_layer_").is_valid_integer()) { + int navigation_layer = components[0].trim_prefix("navigation_layer_").to_int(); + if (navigation_layer >= 0 && navigation_layer < p_tile_set->get_navigation_layers_count()) { + // Draw all shapes. + RenderingServer::get_singleton()->canvas_item_add_set_transform(p_canvas_item->get_canvas_item(), p_transform); + + Ref navigation_polygon = tile_data->get_navigation_polygon(navigation_layer); + if (navigation_polygon.is_valid()) { + Vector verts = navigation_polygon->get_vertices(); + if (verts.size() < 3) { + return; + } + + Color color = p_canvas_item->get_tree()->get_debug_navigation_color(); + + RandomPCG rand; + for (int i = 0; i < navigation_polygon->get_polygon_count(); i++) { + // An array of vertices for this polygon. + Vector polygon = navigation_polygon->get_polygon(i); + Vector 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 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..f4f9c25157 --- /dev/null +++ b/editor/plugins/tiles/tile_data_editors.h @@ -0,0 +1,110 @@ +/*************************************************************************/ +/* 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 "scene/gui/control.h" +#include "scene/resources/tile_set.h" + +class TileDataEditor : public Control { + GDCLASS(TileDataEditor, Control); + +protected: + TileData *tile_data; + String property; + + TileData *_get_tile_data(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile); + +public: + // Edits a TileData property. + void edit(TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property); + + // Used to draw the value over a tile. + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property){}; +}; + +class TileDataTextureOffsetEditor : public TileDataEditor { + GDCLASS(TileDataTextureOffsetEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +class TileDataIntegerEditor : public TileDataEditor { + GDCLASS(TileDataIntegerEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +class TileDataFloatEditor : public TileDataEditor { + GDCLASS(TileDataFloatEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +class TileDataPositionEditor : public TileDataEditor { + GDCLASS(TileDataPositionEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +class TileDataOcclusionShapeEditor : public TileDataEditor { + GDCLASS(TileDataOcclusionShapeEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +class TileDataCollisionShapeEditor : public TileDataEditor { + GDCLASS(TileDataCollisionShapeEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +class TileDataTerrainsEditor : public TileDataEditor { + GDCLASS(TileDataTerrainsEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +class TileDataNavigationPolygonEditor : public TileDataEditor { + GDCLASS(TileDataNavigationPolygonEditor, TileDataEditor); + +public: + virtual void draw_over_tile(CanvasItem *p_canvas_item, Transform2D p_transform, TileSet *p_tile_set, int p_atlas_source_id, Vector2i p_atlas_coords, int p_alternative_tile, String p_property) override; +}; + +#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..cfc4dff329 --- /dev/null +++ b/editor/plugins/tiles/tile_map_editor.cpp @@ -0,0 +1,3368 @@ +/*************************************************************************/ +/* 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_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")); + + missing_texture_texture = get_theme_icon("TileSet", "EditorIcons"); + break; + case NOTIFICATION_VISIBILITY_CHANGED: + _stop_dragging(); + } +} + +void TileMapEditorTilesPlugin::tile_set_changed() { + _update_fix_selected_and_hovered(); + _update_bottom_panel(); + _update_tile_set_sources_list(); + _update_atlas_view(); +} + +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::_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(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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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); + + // TODO: handle with virtual functions + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to(source); + if (atlas_source) { + Ref texture = atlas_source->get_texture(); + if (texture.is_valid()) { + sources_list->add_item(vformat("%s - (id:%d)", texture->get_path().get_file(), source_id), texture); + } else { + sources_list->add_item(vformat("No texture atlas source - (id:%d)", source_id), missing_texture_texture); + } + } else { + sources_list->add_item(vformat("Unknown type source - (id:%d)", source_id), missing_texture_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_atlas_view() { + // Update the atlas display. + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + tile_atlas_view->hide(); + return; + } + + int source_index = sources_list->get_current(); + if (source_index >= 0 && source_index < sources_list->get_item_count()) { + int source_id = sources_list->get_item_metadata(source_index); + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to(source); + if (atlas_source) { + tile_atlas_view->set_atlas_source(*tile_map->get_tileset(), atlas_source, source_id); + tile_atlas_view->show(); + } + } else { + tile_atlas_view->hide(); + } + + // Synchronize atlas view. + TilesEditor::get_singleton()->synchronize_atlas_view(tile_atlas_view); + + tile_atlas_control->update(); +} + +void TileMapEditorTilesPlugin::_update_bottom_panel() { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + Ref tile_set = tile_map->get_tileset(); + + // Update the tabs. + missing_source_label->set_visible(tile_set.is_valid() && tile_set->get_source_count() == 0); + atlas_sources_split_container->set_visible(tile_set.is_valid() && tile_set->get_source_count() > 0); +} + +bool TileMapEditorTilesPlugin::forward_canvas_gui_input(const Ref &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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return false; + } + + Ref 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 coords_array; + for (Set::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::Element *E = tile_map_selection.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::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::Element *E = tile_map_selection.front(); E; E = E->next()) { + undo_redo->add_do_method(tile_map, "set_cell", E->get(), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::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 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 to_draw = _draw_line(drag_start_mouse_pos, drag_last_mouse_pos, mpos); + for (Map::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 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 to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); + for (Map::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 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->get_shift()) { + // Move the selection + drag_type = DRAG_TYPE_MOVE; + drag_modified.clear(); + for (Set::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, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::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 to_draw = _draw_line(drag_start_mouse_pos, mpos, mpos); + for (Map::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 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 to_draw = _draw_bucket_fill(line[i], bucket_continuous_checkbox->is_pressed()); + for (Map::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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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_CONTROL) && !Input::get_singleton()->is_key_pressed(KEY_SHIFT))) { + // Do nothing + } else { + tile_map->draw_cells_outline(p_overlay, tile_map_selection, Color(0.0, 0.0, 1.0), 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 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 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::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 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 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::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. + 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)); + tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 0.5, 0.2, 0.5 * opacity), false); + } + } + } + + // Draw the preview. + for (Map::Element *E = preview.front(); E; E = E->next()) { + if (!erase_button->is_pressed() && random_tile_checkbox->is_pressed()) { + 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)); + + 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(source); + if (atlas_source) { + // Get tile data. + TileData *tile_data = Object::cast_to(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 { + 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)); + + 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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return TileMapCell(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return TileMapCell(); + } + + TypedArray 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(source); + if (atlas_source) { + sum += Object::cast_to(atlas_source->get_tile_data(atlas_coords, alternative_tile))->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(source); + if (atlas_source) { + current += Object::cast_to(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 TileMapEditorTilesPlugin::_draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2i p_to_mouse_pos) { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map(); + } + + // Get or create the pattern. + TileMapPattern erase_pattern; + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + + Map 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 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 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 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 TileMapEditorTilesPlugin::_draw_rect(Vector2i p_start_cell, Vector2i p_end_cell) { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map(); + } + + // 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, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::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 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 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 TileMapEditorTilesPlugin::_draw_bucket_fill(Vector2i p_coords, bool p_contiguous) { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map(); + } + + // Get or create the pattern. + TileMapPattern erase_pattern; + erase_pattern.set_cell(Vector2i(0, 0), -1, TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + TileMapPattern *pattern = erase_button->is_pressed() ? &erase_pattern : selection_pattern; + + Map 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 already_checked; + List 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 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 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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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_CONTROL)) { + 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_CONTROL)) { + 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::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 selection_used_cells = selection_pattern->get_used_cells(); + + Vector2i coords; + Map 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 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::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::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 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::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 to_draw = _draw_line(drag_start_mouse_pos, drag_start_mouse_pos, mpos); + undo_redo->create_action(TTR("Paint tiles")); + for (Map::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 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::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::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 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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); + return; + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::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(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::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(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + } + + // Selection if needed. + for (Set::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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + memdelete(selection_pattern); + + TypedArray coords_array; + for (Set::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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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> per_source; + for (Set::Element *E = tile_set_selection.front(); E; E = E->next()) { + per_source[E->get().source_id].push_back(&(E->get())); + } + + for (Map>::Element *E_source = per_source.front(); E_source; E_source = E_source->next()) { + // Per source. + List unorganized; + Rect2i encompassing_rect_coords; + Map organized_pattern; + + TileSetSource *source = *tile_set->get_source(E_source->key()); + TileSetAtlasSource *atlas_source = Object::cast_to(source); + if (atlas_source) { + // Organize using coordinates. + for (List::Element *E_cell = E_source->get().front(); E_cell; E_cell = E_cell->next()) { + const TileMapCell *current = E_cell->get(); + if (organized_pattern.has(current->get_atlas_coords())) { + if (current->alternative_tile < organized_pattern[current->get_atlas_coords()]->alternative_tile) { + unorganized.push_back(organized_pattern[current->get_atlas_coords()]); + organized_pattern[current->get_atlas_coords()] = current; + } else { + unorganized.push_back(current); + } + } else { + organized_pattern[current->get_atlas_coords()] = current; + } + } + + // Compute the encompassing rect for the organized pattern. + Map::Element *E_cell = organized_pattern.front(); + 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::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::Element *E_cell = organized_pattern.front(); E_cell; E_cell = E_cell->next()) { + selection_pattern->set_cell(E_cell->key() - encompassing_rect_coords.position, E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + } + Vector2i organized_size = selection_pattern->get_size(); + for (List::Element *E_cell = unorganized.front(); E_cell; E_cell = E_cell->next()) { + selection_pattern->set_cell(Vector2(organized_size.x, 0), E_cell->get()->source_id, E_cell->get()->get_atlas_coords(), E_cell->get()->alternative_tile); + } + } + CanvasItemEditor::get_singleton()->update_viewport(); +} + +void TileMapEditorTilesPlugin::_update_tileset_selection_from_selection_pattern() { + tile_set_selection.clear(); + TypedArray 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_atlas_view(); +} + +void TileMapEditorTilesPlugin::_tile_atlas_control_draw() { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Draw the selection. + for (Set::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()), Color(0.0, 0.0, 1.0), false); + } + } + + // Draw the hovered tile. + if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::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 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 != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + to_draw.insert(tile); + } + } + } + + for (Set::Element *E = to_draw.front(); E; E = E->next()) { + tile_atlas_control->draw_rect(atlas->get_tile_texture_region(E->get()), Color(0.8, 0.8, 1.0), false); + } + } +} + +void TileMapEditorTilesPlugin::_tile_atlas_control_mouse_exited() { + hovered_tile.source_id = -1; + hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + tile_set_dragging_selection = false; + tile_atlas_control->update(); +} + +void TileMapEditorTilesPlugin::_tile_atlas_control_gui_input(const Ref &p_event) { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Update the hovered tile + hovered_tile.source_id = source_id; + hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(tile_atlas_control->get_local_mouse_position()); + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + coords = atlas->get_tile_at_coords(coords); + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + hovered_tile.set_atlas_coords(coords); + hovered_tile.alternative_tile = 0; + } + } + + Ref mm = p_event; + if (mm.is_valid()) { + tile_atlas_control->update(); + alternative_tiles_control->update(); + } + + Ref 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->get_shift()) { + tile_set_selection.clear(); + } + + if (hovered_tile.get_atlas_coords() != TileSetAtlasSource::INVALID_ATLAS_COORDS && hovered_tile.alternative_tile == 0) { + if (mb->get_shift() && 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->get_shift()) { + 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 != TileSetAtlasSource::INVALID_ATLAS_COORDS && end_tile != TileSetAtlasSource::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->get_shift() && start_coords != TileSetAtlasSource::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 != TileSetAtlasSource::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 != TileSetAtlasSource::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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Draw the selection. + for (Set::Element *E = tile_set_selection.front(); E; E = E->next()) { + if (E->get().source_id == source_id && E->get().get_atlas_coords() != TileSetAtlasSource::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() != TileSetAtlasSource::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(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + tile_set_dragging_selection = false; + alternative_tiles_control->update(); +} + +void TileMapEditorTilesPlugin::_tile_alternatives_control_gui_input(const Ref &p_event) { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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(*tile_set->get_source(source_id)); + if (!atlas) { + return; + } + + // Update the hovered tile + hovered_tile.source_id = source_id; + hovered_tile.set_atlas_coords(TileSetAtlasSource::INVALID_ATLAS_COORDS); + hovered_tile.alternative_tile = TileSetAtlasSource::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 != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + hovered_tile.set_atlas_coords(coords); + hovered_tile.alternative_tile = alternative; + } + + Ref mm = p_event; + if (mm.is_valid()) { + tile_atlas_control->update(); + alternative_tiles_control->update(); + } + + Ref mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MOUSE_BUTTON_LEFT) { + if (mb->is_pressed()) { // Pressed + // Left click pressed. + if (!mb->get_shift()) { + tile_set_selection.clear(); + } + + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && alternative != TileSetAtlasSource::INVALID_TILE_ALTERNATIVE) { + if (mb->get_shift() && 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 &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 TileMapEditorTilesPlugin::_get_tile_map_selection() const { + TypedArray output; + for (Set::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; + + // Clean the selection. + tile_set_selection.clear(); + tile_map_selection.clear(); + selection_pattern->clear(); +} + +void TileMapEditorTilesPlugin::_bind_methods() { + 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); + + HBoxContainer *tilemap_tiles_tools_buttons = memnew(HBoxContainer); + + tool_buttons_group.instance(); + + 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); + + // 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->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_fix_selected_and_hovered).unbind(1)); + sources_list->connect("item_selected", callable_mp(this, &TileMapEditorTilesPlugin::_update_atlas_view).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->set_drag_forwarding(this); + atlas_sources_split_container->add_child(sources_list); + + 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)); + //tile_atlas_view->connect("visibility_changed", callable_mp(TilesEditor::get_singleton(), &TilesEditor::synchronize_atlas_view), varray(tile_atlas_view)); + 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); + + _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(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 TileMapEditorTerrainsPlugin::Constraint::get_overlapping_coords_and_peering_bits() const { + Map output; + Ref 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 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::_get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set p_constraints) const { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Set(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Set(); + } + + // Returns all tiles compatible with the given constraints. + Set compatible_terrain_tile_patterns; + for (Map>::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::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::_get_constraints_from_removed_cells_list(const Set &p_to_replace, int p_terrain_set) const { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Set(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Set(); + } + + ERR_FAIL_INDEX_V(p_terrain_set, tile_set->get_terrain_sets_count(), Set()); + + // Build a set of dummy constraints get the constrained points. + Set dummy_constraints; + for (Set::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 constraints; + for (Set::Element *E = dummy_constraints.front(); E; E = E->next()) { + Constraint c = E->get(); + + Map terrain_count; + + // Count the number of occurences per terrain. + Map overlapping_terrain_bits = c.get_overlapping_coords_and_peering_bits(); + for (Map::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 occurences. + int max = 0; + int max_terrain = -1; + for (Map::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::_get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Set(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Set(); + } + + // Compute the constraints needed from the surrounding tiles. + Set 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 TileMapEditorTerrainsPlugin::_wave_function_collapse(const Set &p_to_replace, int p_terrain_set, const Set p_constraints) const { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map(); + } + + // Copy the constraints set. + Set constraints = p_constraints; + + // Compute all acceptable tiles for each cell. + Map> per_cell_acceptable_tiles; + for (Set::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); + } + + // Ouput map. + Map output; + + // Add all positions to a set. + Set to_replace = Set(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>::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 to_choose_from; + for (Map>::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 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::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 new_constraints = _get_constraints_from_added_tile(selected_cell_to_replace, p_terrain_set, selected_terrain_tile_pattern); + for (Set::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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return TileMapCell(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return TileMapCell(); + } + + // Count the sum of probabilities. + double sum = 0.0; + Set set = per_terrain_terrains_tile_patterns_tiles[p_terrain_set][p_terrain_tile_pattern]; + for (Set::Element *E = set.front(); E; E = E->next()) { + if (E->get().source_id >= 0) { + Ref source = tile_set->get_source(E->get().source_id); + + Ref atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = Object::cast_to(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::Element *E = set.front(); E; E = E->next()) { + if (E->get().source_id >= 0) { + Ref source = tile_set->get_source(E->get().source_id); + + Ref atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = Object::cast_to(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 TileMapEditorTerrainsPlugin::_draw_terrains(const Map &p_to_paint, int p_terrain_set) const { + TileMap *tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return Map(); + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return Map(); + } + + Map output; + + // Add the constraints from the added tiles. + Set added_tiles_constraints_set; + for (Map::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 cell_constraints = _get_constraints_from_added_tile(coords, p_terrain_set, terrains_tile_pattern); + for (Set::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 potential_to_replace; + for (Map::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 to_replace; + + // Add the central tiles to the one to replace. TODO: maybe change that. + for (Map::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 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> source_tiles_of_constraint; + for (Set::Element *E = removed_cells_constraints_set.front(); E; E = E->next()) { + Map sources_of_constraint = E->get().get_overlapping_coords_and_peering_bits(); + for (Map::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::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>::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 constraints = removed_cells_constraints_set; + for (Set::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 wfc_output = _wave_function_collapse(to_replace, p_terrain_set, constraints); + + // Use the WFC run for the output. + for (Map::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 acutally painted. + for (Map::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 &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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return false; + } + + Ref 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 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 line = TileMapEditor::get_line(tile_map, tile_map->world_to_map(drag_last_mouse_pos), tile_map->world_to_map(mpos)); + Map to_draw; + for (int i = 0; i < line.size(); i++) { + to_draw[line[i]] = selected_terrains_tile_pattern; + } + Map modified = _draw_terrains(to_draw, selected_terrain_set); + for (Map::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 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 terrains_to_draw; + terrains_to_draw[tile_map->world_to_map(mpos)] = selected_terrains_tile_pattern; + + Map to_draw = _draw_terrains(terrains_to_draw, selected_terrain_set); + for (Map::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_children(); 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::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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return TerrainsTilePattern(); + } + + Ref 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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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 source = tile_set->get_source(source_id); + + Ref 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(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(TileSetAtlasSource::INVALID_ATLAS_COORDS); + empty_cell.alternative_tile = TileSetAtlasSource::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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref tile_set = tile_map->get_tileset(); + if (!tile_set.is_valid()) { + return; + } + + // Fill in the terrain list. + 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++) { + // Compute the terrains_tile_pattern used for terrain preview (whenever possible). + TerrainsTilePattern terrains_tile_pattern; + int max_bit_count = -1; + for (Set::Element *E = per_terrain_terrains_tile_patterns[terrain_set_index][terrain_index].front(); E; E = E->next()) { + int count = 0; + for (int i = 0; i < E->get().size(); i++) { + if (int(E->get()[i]) == terrain_index) { + count++; + } + } + if (count > max_bit_count) { + terrains_tile_pattern = E->get(); + max_bit_count = count; + } + } + + // Get the preview. + Ref icon; + Rect2 region; + if (max_bit_count >= 0) { + double max_probability = -1.0; + for (Set::Element *E = per_terrain_terrains_tile_patterns_tiles[terrain_set_index][terrains_tile_pattern].front(); E; E = E->next()) { + Ref source = tile_set->get_source(E->get().source_id); + + Ref atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = Object::cast_to(atlas_source->get_tile_data(E->get().get_atlas_coords(), E->get().alternative_tile)); + if (tile_data->get_probability() > max_probability) { + icon = atlas_source->get_texture(); + region = atlas_source->get_tile_texture_region(E->get().get_atlas_coords()); + max_probability = tile_data->get_probability(); + } + } + } + } else { + Ref image; + image.instance(); + image->create(1, 1, false, Image::FORMAT_RGBA8); + image->set_pixel(0, 0, tile_set->get_terrain_color(terrain_set_index, terrain_index)); + Ref image_texture; + image_texture.instance(); + image_texture->create_from_image(image); + image_texture->set_size_override(Size2(32, 32) * EDSCALE); + icon = image_texture; + } + + // 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, icon); + terrain_tree_item->set_icon_region(0, region); + 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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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> sorted; + for (Set::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>::Element *E_set = sorted.back(); E_set; E_set = E_set->prev()) { + for (Set::Element *E = E_set->get().front(); E; E = E->next()) { + TerrainsTilePattern terrains_tile_pattern = E->get(); + + // Get the icon. + Ref icon; + Rect2 region; + bool transpose = false; + + double max_probability = -1.0; + for (Set::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 source = tile_set->get_source(E_tile_map_cell->get().source_id); + + Ref atlas_source = source; + if (atlas_source.is_valid()) { + TileData *tile_data = Object::cast_to(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.instance(); + + 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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + Ref 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 TileMapEditor::get_line(TileMap *p_tile_map, Vector2i p_from_cell, Vector2i p_to_cell) { + ERR_FAIL_COND_V(!p_tile_map, Vector()); + + Ref tile_set = p_tile_map->get_tileset(); + ERR_FAIL_COND_V(!tile_set.is_valid(), Vector()); + + 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 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(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 &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(ObjectDB::get_instance(tile_map_id)); + if (!tile_map) { + return; + } + + Ref 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 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. + 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)); + tile_set->draw_tile_shape(p_overlay, cell_region, Color(1.0, 0.5, 0.2, 0.5 * opacity), false); + } + } + + // Draw the IDs for debug. + /*Ref 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(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(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->add_child(memnew(VSeparator)); + tilemap_toolbar->add_child(tabs); + //tilemap_toolbar->add_child(memnew(VSeparator)); + 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..f780686b82 --- /dev/null +++ b/editor/plugins/tiles/tile_map_editor.h @@ -0,0 +1,328 @@ +/*************************************************************************/ +/* 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 &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 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); + + 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 drag_modified; + + TileMapCell _pick_random_tile(const TileMapPattern *p_pattern); + Map _draw_line(Vector2 p_start_drag_mouse_pos, Vector2 p_from_mouse_pos, Vector2i p_to_mouse_pos); + Map _draw_rect(Vector2i p_start_mouse_pos, Vector2i p_end_mouse_pos); + Map _draw_bucket_fill(Vector2i p_coords, bool p_contiguous); + void _stop_dragging(); + + // Selection system. + Set tile_map_selection; + TileMapPattern *tile_map_clipboard = memnew(TileMapPattern); + TileMapPattern *selection_pattern = memnew(TileMapPattern); + void _set_tile_map_selection(const TypedArray &p_selection); + TypedArray _get_tile_map_selection() const; + + Set 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. + bool tile_set_dragging_selection = false; + Vector2i tile_set_drag_start_mouse_pos; + + Label *missing_source_label; + HSplitContainer *atlas_sources_split_container; + + ItemList *sources_list; + TileAtlasView *tile_atlas_view; + Ref missing_texture_texture; + void _update_tile_set_sources_list(); + void _update_atlas_view(); + + void _update_bottom_panel(); + + TileMapCell hovered_tile; + + Control *tile_atlas_control; + void _tile_atlas_control_mouse_exited(); + void _tile_atlas_control_gui_input(const Ref &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 &p_event); + + // 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 &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 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 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 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 _get_valid_terrains_tile_patterns_for_constraints(int p_terrain_set, const Vector2i &p_position, Set p_constraints) const; + Set _get_constraints_from_removed_cells_list(const Set &p_to_replace, int p_terrain_set) const; + Set _get_constraints_from_added_tile(Vector2i p_position, int p_terrain_set, TerrainsTilePattern p_terrains_tile_pattern) const; + Map _wave_function_collapse(const Set &p_to_replace, int p_terrain_set, const Set p_constraints) const; + TileMapCell _get_random_tile_from_pattern(int p_terrain_set, TerrainsTilePattern p_terrain_tile_pattern) const; + Map _draw_terrains(const Map &p_to_paint, int p_terrain_set) const; + + // Cached data. + + TerrainsTilePattern _build_terrains_tile_pattern(TileData *p_tile_data); + LocalVector>> per_terrain_terrains_tile_patterns_tiles; + LocalVector>> per_terrain_terrains_tile_patterns; + + Map terrain_tiles; + LocalVector 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 &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 tile_map_editor_plugins; + + // Toolbar. + HBoxContainer *tilemap_toolbar; + + // Bottom panel + Label *missing_tileset_label; + Tabs *tabs; + void _update_bottom_panel(); + + // TileMap + Ref missing_tile_texture; + Ref 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 &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 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..8492202122 --- /dev/null +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.cpp @@ -0,0 +1,1858 @@ +/*************************************************************************/ +/* 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 atlas 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 atlas 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 *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 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::TileProxyObject::_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, TileSetAtlasSource::INVALID_ATLAS_COORDS, as_vector2i), false); + + tile_set_atlas_source->move_tile_in_atlas(coords, TileSetAtlasSource::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::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(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::TileProxyObject::_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::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(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::TileProxyObject::_get_property_list(List *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 usage; + + List data_list; + for (Set::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(tile_set_atlas_source->get_tile_data(coords, alternative)); + ERR_FAIL_COND(!tile_data); + + List list; + tile_data->get_property_list(&list); + + Map counts; // Counts the number of time a property appears (useful for groups that may appear more than once) + for (List::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::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::TileProxyObject::edit(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id, Set p_tiles) { + ERR_FAIL_COND(!p_tile_set_atlas_source); + ERR_FAIL_COND(p_source_id < 0); + ERR_FAIL_COND(p_tiles.is_empty()); + for (Set::Element *E = p_tiles.front(); E; E = E->next()) { + ERR_FAIL_COND(E->get().tile == TileSetAtlasSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(E->get().alternative < 0); + } + + // Disconnect to changes. + for (Set::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(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; + source_id = p_source_id; + tiles = Set(p_tiles); + + // Connect to changes. + for (Set::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(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::TileProxyObject::_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(); +} + +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::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 = TileSetAtlasSource::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(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE); + } +} + +void TileSetAtlasSourceEditor::_update_tile_inspector() { + bool has_atlas_tile_selected = (tools_button_group->get_pressed_button() == tool_select_button) && !selection.is_empty(); + + // Update the proxy object. + if (has_atlas_tile_selected) { + tile_proxy_object->edit(tile_set_atlas_source, tile_set_atlas_source_id, selection); + } + + // Update visibility. + tile_inspector_label->set_visible(has_atlas_tile_selected); + tile_inspector->set_visible(has_atlas_tile_selected); +} + +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() { + // Hide all settings. + for (int i = 0; i < tool_settings->get_child_count(); i++) { + Object::cast_to(tool_settings->get_child(i))->hide(); + } + + // SHow only the correct settings. + if (tools_button_group->get_pressed_button() == tool_select_button) { + } else if (tools_button_group->get_pressed_button() == tool_add_remove_button) { + tool_settings_vsep->show(); + tools_settings_erase_button->show(); + } else if (tools_button_group->get_pressed_button() == tool_add_remove_rect_button) { + tool_settings_vsep->show(); + tools_settings_erase_button->show(); + } +} + +void TileSetAtlasSourceEditor::_tile_atlas_control_mouse_exited() { + hovered_base_tile_coords = TileSetAtlasSource::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 &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()); + + // Handle the event. + Ref 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 != TileSetAtlasSource::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 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]) == TileSetAtlasSource::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 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 != TileSetAtlasSource::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(); + } + } 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(); + } + } + + // 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 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_add_remove_button) { + if (tools_settings_erase_button->is_pressed()) { + // 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 != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + coords = tile_set_atlas_source->get_tile_at_coords(coords); + } + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + drag_modified_tiles.insert(coords); + } + } else { + if (mb->get_shift()) { + // Create a big tile. + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::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 { + // 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 != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::INVALID_ATLAS_COORDS) { + tile_set_atlas_source->create_tile(coords); + drag_modified_tiles.insert(coords); + } + } + } + } else if (tools_button_group->get_pressed_button() == tool_add_remove_rect_button) { + if (tools_settings_erase_button->is_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 { + if (mb->get_shift()) { + // Create a big tile. + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS && tile_set_atlas_source->get_tile_at_coords(coords) == TileSetAtlasSource::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 { + // 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 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 != TileSetAtlasSource::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 = { TileSetAtlasSource::INVALID_ATLAS_COORDS, TileSetAtlasSource::INVALID_TILE_ALTERNATIVE }; + Vector2i coords = tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos); + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + coords = tile_set_atlas_source->get_tile_at_coords(coords); + if (coords != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + selected = { coords, 0 }; + } + } + + bool shift = mb->get_shift(); + if (!shift && selection.size() == 1 && selected.tile != TileSetAtlasSource::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) { + if (mb->is_pressed()) { + // Right click pressed. + + TileSelection selected = { tile_atlas_view->get_atlas_tile_coords_at_pos(mouse_local_pos), 0 }; + if (selected.tile != TileSetAtlasSource::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 != TileSetAtlasSource::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(); + } + } + + // Pops up the correct menu, depending on whether we have a tile or not. + if (selected.tile != TileSetAtlasSource::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 != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + // We don't have a tile, but can create one. + menu_option_coords = hovered_base_tile_coords; + menu_option_alternative = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + empty_base_tile_popup_menu->popup(Rect2i(get_global_mouse_position(), Size2i())); + } + } 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::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 list; + tile_set_atlas_source->get_property_list(&list); + Map> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + undo_redo->create_action(TTR("Remove tiles")); + for (Set::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::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) == TileSetAtlasSource::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 list; + tile_set_atlas_source->get_property_list(&list); + Map> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + + Set 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 != TileSetAtlasSource::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::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::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 == TileSetAtlasSource::INVALID_ATLAS_COORDS); + ERR_FAIL_COND(new_base_tiles_coords == TileSetAtlasSource::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 != TileSetAtlasSource::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 != TileSetAtlasSource::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(); + undo_redo->add_do_method(this, "_set_selection_from_array", _get_selection_as_array()); + undo_redo->commit_action(false); + 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> TileSetAtlasSourceEditor::_group_properties_per_tiles(const List &r_list, const TileSetAtlasSource *p_atlas) { + // Group properties per tile. + Map> per_tile; + for (const List::Element *E_property = r_list.front(); E_property; E_property = E_property->next()) { + Vector components = String(E_property->get().name).split("/", true, 1); + if (components.size() >= 1) { + Vector coord_arr = components[0].split(":"); + if (coord_arr.size() == 2 && coord_arr[0].is_valid_integer() && coord_arr[1].is_valid_integer()) { + 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 list; + tile_set_atlas_source->get_property_list(&list); + Map> per_tile = _group_properties_per_tiles(list, tile_set_atlas_source); + undo_redo->create_action(TTR("Remove tile")); + + // Remove tiles + Set removed; + for (Set::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::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::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::Element *E_property = per_tile[selected.tile].front(); E_property; E_property = E_property->next()) { + Vector components = E_property->get()->name.split("/", true, 2); + if (components.size() >= 2 && components[1].is_valid_integer() && 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::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 &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(); +} + +Array TileSetAtlasSourceEditor::_get_selection_as_array() { + Array output; + for (Set::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() { + // Draw the selected tile. + if (tools_button_group->get_pressed_button() == tool_select_button) { + for (Set::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, Color(0.2, 0.2, 1.0), 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::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 = Color(0.5, 0.5, 1.0); + } + + Set 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 != TileSetAtlasSource::INVALID_ATLAS_COORDS) { + to_paint.insert(coords); + } + } + } + + for (Set::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) == TileSetAtlasSource::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 != TileSetAtlasSource::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_add_remove_button || tools_button_group->get_pressed_button() == tool_add_remove_rect_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() { + // Draw the preview of the selected property. + TileDataEditor *tile_data_editor = TileSetEditor::get_singleton()->get_tile_data_editor(selected_property); + if (tile_data_editor && tile_inspector->is_visible_in_tree()) { + 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); + + tile_data_editor->draw_over_tile(tile_atlas_control_unscaled, xform, *tile_set, tile_set_atlas_source_id, coords, 0, selected_property); + } + } +} + +void TileSetAtlasSourceEditor::_tile_alternatives_control_gui_input(const Ref &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()); + + Ref 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 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 != TileSetAtlasSource::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 != TileSetAtlasSource::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(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::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() { + // 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 != TileSetAtlasSource::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::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, Color(0.2, 0.2, 1.0), false); + } + } + } + } +} + +void TileSetAtlasSourceEditor::_tile_alternatives_control_unscaled_draw() { + //TODO +} + +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(p_undo_redo); + ERR_FAIL_COND(!undo_redo); + +#define ADD_UNDO(obj, property) undo_redo->add_undo_property(obj, property, tile_data->get(property)); + + TileProxyObject *tile_data = Object::cast_to(p_edited); + if (tile_data) { + Vector components = String(p_property).split("/", true, 2); + if (components.size() == 2 && components[1] == "shapes_count") { + int layer_index = components[0].trim_prefix("physics_layer_").to_int(); + int new_shapes_count = p_new_value; + int old_shapes_count = tile_data->get(vformat("physics_layer_%d/shapes_count", layer_index)); + if (new_shapes_count < old_shapes_count) { + for (int i = new_shapes_count - 1; i < old_shapes_count; i++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/shape", layer_index, i)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way", layer_index, i)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way_margin", layer_index, i)); + } + } + } + } +#undef ADD_UNDO +} + +void TileSetAtlasSourceEditor::edit(Ref 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_tile_inspector(); +} + +void TileSetAtlasSourceEditor::init_source() { + confirm_auto_create_tiles->popup_centered(); +} + +void TileSetAtlasSourceEditor::_auto_create_tiles() { + if (!tile_set_atlas_source) { + return; + } + + Ref 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) == TileSetAtlasSource::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 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 list; + tile_set_atlas_source->get_property_list(&list); + Map> 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::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_select_button->set_icon(get_theme_icon("ToolSelect", "EditorIcons")); + tool_add_remove_button->set_icon(get_theme_icon("EditAddRemove", "EditorIcons")); + tool_add_remove_rect_button->set_icon(get_theme_icon("RectangleAddRemove", "EditorIcons")); + + tools_settings_erase_button->set_icon(get_theme_icon("Eraser", "EditorIcons")); + + tool_advanced_menu_buttom->set_icon(get_theme_icon("Tools", "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_tile_inspector(); + + 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:")); + tile_inspector_label->hide(); + middle_vbox_container->add_child(tile_inspector_label); + + tile_proxy_object = memnew(TileProxyObject(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); + + // 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.instance(); + + toolbox = memnew(HBoxContainer); + right_panel->add_child(toolbox); + + tool_select_button = memnew(Button); + tool_select_button->set_flat(true); + tool_select_button->set_toggle_mode(true); + tool_select_button->set_pressed(true); + tool_select_button->set_button_group(tools_button_group); + tool_select_button->set_tooltip(TTR("Select tiles")); + tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles)); + tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label)); + tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector)); + tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view)); + tool_select_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar)); + toolbox->add_child(tool_select_button); + + tool_add_remove_button = memnew(Button); + tool_add_remove_button->set_flat(true); + tool_add_remove_button->set_toggle_mode(true); + tool_add_remove_button->set_button_group(tools_button_group); + tool_add_remove_button->set_tooltip(TTR("Add/Remove tiles tool (use the shift key to create big tiles)")); + tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles)); + tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label)); + tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector)); + tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view)); + tool_add_remove_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar)); + toolbox->add_child(tool_add_remove_button); + + tool_add_remove_rect_button = memnew(Button); + tool_add_remove_rect_button->set_flat(true); + tool_add_remove_rect_button->set_toggle_mode(true); + tool_add_remove_rect_button->set_button_group(tools_button_group); + tool_add_remove_rect_button->set_tooltip(TTR("Add/Remove tiles rectangle tool (use the shift key to create big tiles)")); + tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_fix_selected_and_hovered_tiles)); + tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_id_label)); + tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_tile_inspector)); + tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_atlas_view)); + tool_add_remove_rect_button->connect("pressed", callable_mp(this, &TileSetAtlasSourceEditor::_update_toolbar)); + toolbox->add_child(tool_add_remove_rect_button); + + // Tool settings. + tool_settings = memnew(HBoxContainer); + toolbox->add_child(tool_settings); + + tool_settings_vsep = memnew(VSeparator); + tool_settings->add_child(tool_settings_vsep); + + 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); + + VSeparator *tool_advanced_vsep = memnew(VSeparator); + toolbox->add_child(tool_advanced_vsep); + + 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_atlas_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..ff68aa8288 --- /dev/null +++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h @@ -0,0 +1,260 @@ +/*************************************************************************/ +/* 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 "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 = TileSetAtlasSource::INVALID_ATLAS_COORDS; + int alternative = TileSetAtlasSource::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 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 *p_list) const; + static void _bind_methods(); + + public: + void set_id(int p_id); + int get_id(); + + void edit(Ref p_tile_set, TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id); + }; + + // -- Proxy object for a tile, needed by the inspector -- + class TileProxyObject : public Object { + GDCLASS(TileProxyObject, Object); + + private: + TileSetAtlasSourceEditor *tiles_set_atlas_source_editor; + + TileSetAtlasSource *tile_set_atlas_source = nullptr; + int source_id; + Set tiles = Set(); + + 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 *p_list) const; + + static void _bind_methods(); + + public: + // Update the proxyed object. + void edit(TileSetAtlasSource *p_tile_set_atlas_source, int p_source_id = -1, Set p_tiles = Set()); + + TileProxyObject(TileSetAtlasSourceEditor *p_tiles_editor_source_tab) { + tiles_set_atlas_source_editor = p_tiles_editor_source_tab; + } + }; + + Ref 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; + + // -- Inspector -- + TileProxyObject *tile_proxy_object; + Label *tile_inspector_label; + EditorInspector *tile_inspector; + 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, + + // 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 drag_modified_tiles; + void _end_dragging(); + + Map> _group_properties_per_tiles(const List &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 = TileSetAtlasSource::INVALID_TILE_ALTERNATIVE; + void _menu_option(int p_option); + + // Tool buttons. + Ref tools_button_group; + Button *tool_select_button; + Button *tool_add_remove_button; + Button *tool_add_remove_rect_button; + Label *tool_tile_id_label; + + HBoxContainer *tool_settings; + VSeparator *tool_settings_vsep; + Button *tools_settings_erase_button; + + MenuButton *tool_advanced_menu_buttom; + + // Selection. + Set 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 = TileSetAtlasSource::INVALID_ATLAS_COORDS; + + PopupMenu *base_tile_popup_menu; + PopupMenu *empty_base_tile_popup_menu; + Ref resize_handle; + Ref 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 &p_event); + void _tile_atlas_view_transform_changed(); + + // A control over the alternative tiles. + Vector3i hovered_alternative_tile_coords = Vector3i(TileSetAtlasSource::INVALID_ATLAS_COORDS.x, TileSetAtlasSource::INVALID_ATLAS_COORDS.y, TileSetAtlasSource::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 &p_event); + + // -- Update functions -- + void _update_tile_id_label(); + void _update_source_inspector(); + void _update_fix_selected_and_hovered_tiles(); + void _update_tile_inspector(); + void _update_manage_tile_properties_button(); + void _update_atlas_view(); + void _update_toolbar(); + + // -- input events -- + void _unhandled_key_input(const Ref &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 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..8a5890e9a4 --- /dev/null +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -0,0 +1,520 @@ +/*************************************************************************/ +/* 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 files = d["files"]; + for (int i = 0; i < files.size(); i++) { + Ref 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 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 trigerring 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 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); + + // TODO: handle with virtual functions + TileSetSource *source = *tile_set->get_source(source_id); + TileSetAtlasSource *atlas_source = Object::cast_to(source); + if (atlas_source) { + Ref texture = atlas_source->get_texture(); + if (texture.is_valid()) { + sources_list->add_item(vformat("%s - (id:%d)", texture->get_path().get_file(), source_id), texture); + } else { + sources_list->add_item(vformat("No texture atlas source - (id:%d)", source_id), missing_texture_texture); + } + } else { + sources_list->add_item(vformat("Unknown type source - (id:%d)", source_id), missing_texture_texture); + } + sources_list->set_item_metadata(sources_list->get_item_count() - 1, 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(*tile_set->get_source(source_id)); + if (atlas_source) { + tile_set_atlas_source_editor->edit(*tile_set, atlas_source, source_id); + no_source_selected_label->hide(); + tile_set_atlas_source_editor->show(); + } else { + no_source_selected_label->show(); + tile_set_atlas_source_editor->hide(); + } + } else { + no_source_selected_label->show(); + tile_set_atlas_source_editor->hide(); + } +} + +void TileSetEditor::_source_add_pressed() { + ERR_FAIL_COND(!tile_set.is_valid()); + + int source_id = tile_set->get_next_source_id(); + + Ref 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); +} + +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 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) { + _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(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(p_edited); + if (tile_set) { + Vector 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 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(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/shapes_count", physics_layer_index)); + for (int shape_index = 0; shape_index < tile_data->get_collision_shapes_count(physics_layer_index); shape_index++) { + ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/shape", physics_layer_index, shape_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way", physics_layer_index, shape_index)); + ADD_UNDO(tile_data, vformat("physics_layer_%d/shape_%d/one_way_margin", physics_layer_index, shape_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_integer() && components[1] == "mode") || + (components.size() == 2 && components[0].begins_with("terrain_set_") && components[0].trim_prefix("terrain_set_").is_valid_integer() && 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"); + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/right_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_RIGHT_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/right_corner"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/bottom_right_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_RIGHT_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/bottom_right_corner"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/bottom_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/bottom_corner"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/bottom_left_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_BOTTOM_LEFT_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/bottom_left_corner"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/left_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_LEFT_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/left_corner"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/top_left_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_LEFT_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/top_left_corner"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/top_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/top_corner"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_SIDE)) { + ADD_UNDO(tile_data, "terrains_peering_bit/top_right_side"); + } + if (tile_data->is_valid_peering_bit_terrain(TileSet::CELL_NEIGHBOR_TOP_RIGHT_CORNER)) { + ADD_UNDO(tile_data, "terrains_peering_bit/top_right_corner"); + } + } 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_integer() && components[1] == "type") { + int custom_data_layer = components[0].trim_prefix("custom_data_layer_").is_valid_integer(); + 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); +} + +TileDataEditor *TileSetEditor::get_tile_data_editor(String p_property) { + Vector components = String(p_property).split("/", true); + + if (p_property == "z_index") { + return tile_data_integer_editor; + } else if (p_property == "probability") { + return tile_data_float_editor; + } else if (p_property == "y_sort_origin") { + return tile_data_position_editor; + } else if (p_property == "texture_offset") { + return tile_data_texture_offset_editor; + } else if (components.size() >= 1 && components[0].begins_with("occlusion_layer_")) { + return tile_data_occlusion_shape_editor; + } else if (components.size() >= 1 && components[0].begins_with("physics_layer_")) { + return tile_data_collision_shape_editor; + } else if (p_property == "mode" || p_property == "terrain" || (components.size() >= 1 && components[0] == "terrains_peering_bit")) { + return tile_data_terrains_editor; + } else if (components.size() >= 1 && components[0].begins_with("navigation_layer_")) { + return tile_data_navigation_polygon_editor; + } + + return nullptr; +} + +void TileSetEditor::edit(Ref 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(); + 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_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(Button); + sources_add_button->set_flat(true); + sources_add_button->connect("pressed", callable_mp(this, &TileSetEditor::_source_add_pressed)); + sources_bottom_actions->add_child(sources_add_button); + + // 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->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->add_child(tile_set_atlas_source_editor); + tile_set_atlas_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)); + } + + // Delete tile data editors. + memdelete(tile_data_texture_offset_editor); + memdelete(tile_data_position_editor); + memdelete(tile_data_integer_editor); + memdelete(tile_data_float_editor); + memdelete(tile_data_occlusion_shape_editor); + memdelete(tile_data_collision_shape_editor); + memdelete(tile_data_terrains_editor); + memdelete(tile_data_navigation_polygon_editor); +} diff --git a/editor/plugins/tiles/tile_set_editor.h b/editor/plugins/tiles/tile_set_editor.h new file mode 100644 index 0000000000..c4aebb40a2 --- /dev/null +++ b/editor/plugins/tiles/tile_set_editor.h @@ -0,0 +1,94 @@ +/*************************************************************************/ +/* 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_data_editors.h" +#include "tile_set_atlas_source_editor.h" + +class TileSetEditor : public VBoxContainer { + GDCLASS(TileSetEditor, VBoxContainer); + + static TileSetEditor *singleton; + +private: + Ref tile_set; + bool tile_set_changed_needs_update = false; + + Label *no_source_selected_label; + TileSetAtlasSourceEditor *tile_set_atlas_source_editor; + + UndoRedo *undo_redo = EditorNode::get_undo_redo(); + + void _update_atlas_sources_list(int force_selected_id = -1); + + // List of tile data editors. + TileDataTextureOffsetEditor *tile_data_texture_offset_editor = memnew(TileDataTextureOffsetEditor); + TileDataPositionEditor *tile_data_position_editor = memnew(TileDataPositionEditor); + TileDataIntegerEditor *tile_data_integer_editor = memnew(TileDataIntegerEditor); + TileDataFloatEditor *tile_data_float_editor = memnew(TileDataFloatEditor); + TileDataOcclusionShapeEditor *tile_data_occlusion_shape_editor = memnew(TileDataOcclusionShapeEditor); + TileDataCollisionShapeEditor *tile_data_collision_shape_editor = memnew(TileDataCollisionShapeEditor); + TileDataTerrainsEditor *tile_data_terrains_editor = memnew(TileDataTerrainsEditor); + TileDataNavigationPolygonEditor *tile_data_navigation_polygon_editor = memnew(TileDataNavigationPolygonEditor); + + // -- Sources management -- + Button *sources_delete_button; + Button *sources_add_button; + ItemList *sources_list; + Ref missing_texture_texture; + void _source_selected(int p_source_index); + void _source_add_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; } + + TileDataEditor *get_tile_data_editor(String property); + void edit(Ref 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/tiles_editor_plugin.cpp b/editor/plugins/tiles/tiles_editor_plugin.cpp new file mode 100644 index 0000000000..971ff15073 --- /dev/null +++ b/editor/plugins/tiles/tiles_editor_plugin.cpp @@ -0,0 +1,276 @@ +/*************************************************************************/ +/* 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(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(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(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(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(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(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(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(); + if (p_object) { + if (p_object->is_class("TileMap")) { + tile_map_id = p_object->get_instance_id(); + tile_map = Object::cast_to(ObjectDB::get_instance(tile_map_id)); + tile_set = tile_map->get_tileset(); + } else if (p_object->is_class("TileSet")) { + tile_set = Ref(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); + 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 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 &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 &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 -- cgit v1.2.3