map_editor:
- tilepicker s'obri ara amb la T - brush de varios tiles - eyedropper amb botó dret (de uno o varios tiles)
This commit is contained in:
@@ -131,7 +131,7 @@ auto MapEditor::showGrid(bool show) -> std::string {
|
||||
|
||||
auto MapEditor::setEditingCollision(bool collision) -> std::string {
|
||||
editing_collision_ = collision;
|
||||
brush_tile_ = NO_BRUSH; // Resetear brush al cambiar de modo
|
||||
brush_.clear(); // Resetear brush al cambiar de modo
|
||||
return editing_collision_ ? "Editing: collision" : "Editing: draw";
|
||||
}
|
||||
|
||||
@@ -230,8 +230,9 @@ void MapEditor::enter(std::shared_ptr<Room> room, std::shared_ptr<Player> player
|
||||
// Resetear estado (preservar modo de edición en re-enter)
|
||||
drag_ = {};
|
||||
selection_.clear();
|
||||
eyedropper_ = {};
|
||||
if (!is_reenter) {
|
||||
brush_tile_ = NO_BRUSH;
|
||||
brush_.clear();
|
||||
painting_ = false;
|
||||
editing_collision_ = false;
|
||||
}
|
||||
@@ -311,7 +312,7 @@ auto MapEditor::revert() -> std::string {
|
||||
}
|
||||
|
||||
selection_.clear();
|
||||
brush_tile_ = NO_BRUSH;
|
||||
brush_.clear();
|
||||
return "Reverted to original";
|
||||
}
|
||||
|
||||
@@ -359,26 +360,9 @@ void MapEditor::update(float delta_time) {
|
||||
updateDrag();
|
||||
}
|
||||
|
||||
// Si estamos pintando tiles, pintar en la posición actual del ratón
|
||||
if (painting_ && brush_tile_ != NO_BRUSH) {
|
||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
||||
if (editing_collision_) {
|
||||
// Pintar en el mapa de colisiones
|
||||
if (tile_index >= 0 && tile_index < static_cast<int>(room_data_.collision_tile_map.size())) {
|
||||
if (room_data_.collision_tile_map[tile_index] != brush_tile_) {
|
||||
room_data_.collision_tile_map[tile_index] = brush_tile_;
|
||||
room_->setCollisionTile(tile_index, brush_tile_);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Pintar en el mapa de dibujo
|
||||
if (tile_index >= 0 && tile_index < static_cast<int>(room_data_.tile_map.size())) {
|
||||
if (room_data_.tile_map[tile_index] != brush_tile_) {
|
||||
room_data_.tile_map[tile_index] = brush_tile_;
|
||||
room_->setTile(tile_index, brush_tile_);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Si estamos pintando tiles, estampar el patrón en la posición del ratón
|
||||
if (painting_ && !brush_.isEmpty()) {
|
||||
stampBrushAt(mouse_tile_x_, mouse_tile_y_);
|
||||
}
|
||||
|
||||
// Actualizar la barra de estado
|
||||
@@ -434,6 +418,16 @@ void MapEditor::render() {
|
||||
// Renderizar highlight de selección (encima de los sprites)
|
||||
renderSelectionHighlight();
|
||||
|
||||
// Preview del brush bajo el cursor o rect del eyedropper en progreso
|
||||
// (solo cuando no hay overlays a pantalla completa abiertos)
|
||||
if (!tile_picker_.isOpen() && !mini_map_visible_) {
|
||||
if (eyedropper_.active) {
|
||||
renderEyedropperRect();
|
||||
} else {
|
||||
renderBrushPreview();
|
||||
}
|
||||
}
|
||||
|
||||
// Tile picker o mini mapa (encima de todo en el play area)
|
||||
if (tile_picker_.isOpen()) {
|
||||
tile_picker_.render();
|
||||
@@ -467,15 +461,25 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
return;
|
||||
}
|
||||
|
||||
// ESC: desactivar brush
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && brush_tile_ != NO_BRUSH) {
|
||||
brush_tile_ = NO_BRUSH;
|
||||
// ESC: cancelar eyedropper en progreso (sin tocar el brush previo)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && eyedropper_.active) {
|
||||
eyedropper_.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// E: toggle borrador
|
||||
// ESC: desactivar brush
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && !brush_.isEmpty()) {
|
||||
brush_.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// E: toggle borrador (alterna entre brush vacío y brush 1x1 ERASE)
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_E && static_cast<int>(event.key.repeat) == 0) {
|
||||
brush_tile_ = (brush_tile_ == ERASER_BRUSH) ? NO_BRUSH : ERASER_BRUSH;
|
||||
if (brush_.width == 1 && brush_.height == 1 && !brush_.tiles.empty() && brush_.tiles[0] == BrushPattern::ERASE) {
|
||||
brush_.clear();
|
||||
} else {
|
||||
brush_.setSingle(BrushPattern::ERASE);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -524,41 +528,54 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
}
|
||||
}
|
||||
|
||||
// Click derecho: abrir TilePicker
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_RIGHT) {
|
||||
// T: abrir TilePicker
|
||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_T && static_cast<int>(event.key.repeat) == 0) {
|
||||
// Deseleccionar entidades
|
||||
selection_.clear();
|
||||
|
||||
const std::string tileset_name = editing_collision_ ? "collision.gif" : room_->getTileSetFile();
|
||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
||||
int current = 0;
|
||||
if (editing_collision_) {
|
||||
// Abrir tile picker del collision tileset
|
||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
||||
int current = (tile_index >= 0 && tile_index < static_cast<int>(room_data_.collision_tile_map.size()))
|
||||
current = (tile_index >= 0 && tile_index < static_cast<int>(room_data_.collision_tile_map.size()))
|
||||
? room_data_.collision_tile_map[tile_index]
|
||||
: 0;
|
||||
|
||||
tile_picker_.on_select = [this](int tile) {
|
||||
brush_tile_ = tile;
|
||||
};
|
||||
tile_picker_.open("collision.gif", current, 0);
|
||||
} else {
|
||||
// Abrir tile picker del mapa de dibujo
|
||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
||||
int current = (tile_index >= 0 && tile_index < static_cast<int>(room_data_.tile_map.size()))
|
||||
current = (tile_index >= 0 && tile_index < static_cast<int>(room_data_.tile_map.size()))
|
||||
? room_data_.tile_map[tile_index]
|
||||
: -1;
|
||||
|
||||
tile_picker_.on_select = [this](int tile) {
|
||||
brush_tile_ = tile;
|
||||
};
|
||||
tile_picker_.open(room_->getTileSetFile(), current, 0);
|
||||
}
|
||||
|
||||
tile_picker_.on_select = [this, tileset_name](int col, int row, int width, int height) {
|
||||
brush_ = buildPatternFromTileset(tileset_name, col, row, width, height);
|
||||
};
|
||||
tile_picker_.open(tileset_name, current, 0, -1, -1, 0, 1, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Click derecho: eyedropper (sample del nivel). Drag = rect, click = single tile.
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_RIGHT) {
|
||||
// Cancelar painting en curso si lo hubiese
|
||||
if (painting_) {
|
||||
painting_ = false;
|
||||
autosave();
|
||||
}
|
||||
selection_.clear();
|
||||
eyedropper_.active = true;
|
||||
eyedropper_.start_tile_x = mouse_tile_x_;
|
||||
eyedropper_.start_tile_y = mouse_tile_y_;
|
||||
return;
|
||||
}
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_RIGHT && eyedropper_.active) {
|
||||
commitEyedropper();
|
||||
eyedropper_.active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Click izquierdo
|
||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
||||
// Si hay brush activo y no hacemos hit en ninguna entidad → pintar
|
||||
if (brush_tile_ != NO_BRUSH) {
|
||||
if (!brush_.isEmpty()) {
|
||||
// Comprobar si hay hit en alguna entidad primero
|
||||
bool hit_entity = false;
|
||||
SDL_FRect player_rect = player_->getRect();
|
||||
@@ -572,20 +589,9 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
||||
}
|
||||
|
||||
if (!hit_entity) {
|
||||
// Pintar tile y entrar en modo painting
|
||||
// Estampar el patrón y entrar en modo painting
|
||||
painting_ = true;
|
||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
||||
if (editing_collision_) {
|
||||
if (tile_index >= 0 && tile_index < static_cast<int>(room_data_.collision_tile_map.size())) {
|
||||
room_data_.collision_tile_map[tile_index] = brush_tile_;
|
||||
room_->setCollisionTile(tile_index, brush_tile_);
|
||||
}
|
||||
} else {
|
||||
if (tile_index >= 0 && tile_index < static_cast<int>(room_data_.tile_map.size())) {
|
||||
room_data_.tile_map[tile_index] = brush_tile_;
|
||||
room_->setTile(tile_index, brush_tile_);
|
||||
}
|
||||
}
|
||||
stampBrushAt(mouse_tile_x_, mouse_tile_y_);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -951,6 +957,158 @@ void MapEditor::renderSelectionHighlight() {
|
||||
game_surface->drawRectBorder(&border, DRAG_COLOR);
|
||||
}
|
||||
|
||||
// Estampa el patrón del brush en la posición indicada (anclaje top-left)
|
||||
void MapEditor::stampBrushAt(int tile_x, int tile_y) {
|
||||
if (brush_.isEmpty()) { return; }
|
||||
for (int dy = 0; dy < brush_.height; ++dy) {
|
||||
for (int dx = 0; dx < brush_.width; ++dx) {
|
||||
int value = brush_.at(dx, dy);
|
||||
if (value == BrushPattern::TRANSPARENT) { continue; } // skip — no tocar destino
|
||||
int tx = tile_x + dx;
|
||||
int ty = tile_y + dy;
|
||||
if (tx < 0 || tx >= Map::WIDTH || ty < 0 || ty >= Map::HEIGHT) { continue; }
|
||||
int idx = (ty * Map::WIDTH) + tx;
|
||||
// value es >= 0 (tile real) o BrushPattern::ERASE (escribe -1)
|
||||
if (editing_collision_) {
|
||||
if (idx >= 0 && idx < static_cast<int>(room_data_.collision_tile_map.size())) {
|
||||
if (room_data_.collision_tile_map[idx] != value) {
|
||||
room_data_.collision_tile_map[idx] = value;
|
||||
room_->setCollisionTile(idx, value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (idx >= 0 && idx < static_cast<int>(room_data_.tile_map.size())) {
|
||||
if (room_data_.tile_map[idx] != value) {
|
||||
room_data_.tile_map[idx] = value;
|
||||
room_->setTile(idx, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Confirma el eyedropper: muestrea el rect del nivel y lo carga en el brush
|
||||
void MapEditor::commitEyedropper() {
|
||||
int x1 = std::clamp(eyedropper_.start_tile_x, 0, Map::WIDTH - 1);
|
||||
int y1 = std::clamp(eyedropper_.start_tile_y, 0, Map::HEIGHT - 1);
|
||||
int x2 = std::clamp(mouse_tile_x_, 0, Map::WIDTH - 1);
|
||||
int y2 = std::clamp(mouse_tile_y_, 0, Map::HEIGHT - 1);
|
||||
int xmin = std::min(x1, x2);
|
||||
int ymin = std::min(y1, y2);
|
||||
int xmax = std::max(x1, x2);
|
||||
int ymax = std::max(y1, y2);
|
||||
brush_ = sampleBrush(xmin, ymin, xmax, ymax);
|
||||
}
|
||||
|
||||
// Lee del tilemap activo (draw o collision) un rect y devuelve un BrushPattern.
|
||||
// Las celdas vacías (-1) en el tilemap origen se mapean a BrushPattern::TRANSPARENT
|
||||
// para que reestampar el brush no borre contenido del destino.
|
||||
auto MapEditor::sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPattern {
|
||||
BrushPattern p;
|
||||
p.width = (x2 - x1) + 1;
|
||||
p.height = (y2 - y1) + 1;
|
||||
p.tiles.reserve(static_cast<size_t>(p.width * p.height));
|
||||
const auto& src = editing_collision_ ? room_data_.collision_tile_map : room_data_.tile_map;
|
||||
for (int y = y1; y <= y2; ++y) {
|
||||
for (int x = x1; x <= x2; ++x) {
|
||||
int idx = (y * Map::WIDTH) + x;
|
||||
int value = (idx >= 0 && idx < static_cast<int>(src.size())) ? src[idx] : -1;
|
||||
// -1 en el tilemap = vacío → TRANSPARENT (no borra al estampar)
|
||||
p.tiles.push_back((value == -1) ? BrushPattern::TRANSPARENT : value);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// Construye un BrushPattern leyendo tiles consecutivos de un tileset.
|
||||
// Usado por el TilePicker cuando se hace selección rectangular.
|
||||
auto MapEditor::buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) const -> BrushPattern {
|
||||
BrushPattern p;
|
||||
auto surface = Resource::Cache::get()->getSurface(tileset_name);
|
||||
if (!surface || width <= 0 || height <= 0) { return p; }
|
||||
int cols = static_cast<int>(surface->getWidth()) / Tile::SIZE;
|
||||
if (cols <= 0) { return p; }
|
||||
p.width = width;
|
||||
p.height = height;
|
||||
p.tiles.reserve(static_cast<size_t>(width * height));
|
||||
for (int dy = 0; dy < height; ++dy) {
|
||||
for (int dx = 0; dx < width; ++dx) {
|
||||
int tile = ((row + dy) * cols) + (col + dx);
|
||||
p.tiles.push_back(tile);
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
// Renderiza la previsualización del brush bajo el cursor (snapped al grid)
|
||||
void MapEditor::renderBrushPreview() {
|
||||
if (brush_.isEmpty()) { return; }
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
|
||||
const std::string tileset_name = editing_collision_ ? std::string("collision.gif") : room_->getTileSetFile();
|
||||
auto tileset = Resource::Cache::get()->getSurface(tileset_name);
|
||||
int cols = (tileset) ? (static_cast<int>(tileset->getWidth()) / Tile::SIZE) : 0;
|
||||
|
||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||
for (int dy = 0; dy < brush_.height; ++dy) {
|
||||
for (int dx = 0; dx < brush_.width; ++dx) {
|
||||
int value = brush_.at(dx, dy);
|
||||
if (value == BrushPattern::TRANSPARENT) { continue; }
|
||||
int tx = mouse_tile_x_ + dx;
|
||||
int ty = mouse_tile_y_ + dy;
|
||||
if (tx < 0 || tx >= Map::WIDTH || ty < 0 || ty >= Map::HEIGHT) { continue; }
|
||||
float dst_x = static_cast<float>(tx) * TS;
|
||||
float dst_y = static_cast<float>(ty) * TS;
|
||||
if (value == BrushPattern::ERASE) {
|
||||
SDL_FRect erase_cell = {.x = dst_x, .y = dst_y, .w = TS, .h = TS};
|
||||
game_surface->fillRect(&erase_cell, static_cast<Uint8>(room_data_.bg_color));
|
||||
} else if (tileset && cols > 0) {
|
||||
SDL_FRect src = {
|
||||
.x = static_cast<float>(value % cols) * TS,
|
||||
.y = static_cast<float>(value / cols) * TS,
|
||||
.w = TS,
|
||||
.h = TS};
|
||||
SDL_FRect dst = {.x = dst_x, .y = dst_y, .w = TS, .h = TS};
|
||||
tileset->render(&src, &dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Border alrededor del rectángulo completo del brush (blanco)
|
||||
float bx = static_cast<float>(mouse_tile_x_) * TS;
|
||||
float by = static_cast<float>(mouse_tile_y_) * TS;
|
||||
SDL_FRect border = {
|
||||
.x = bx - 1.0F,
|
||||
.y = by - 1.0F,
|
||||
.w = (static_cast<float>(brush_.width) * TS) + 2.0F,
|
||||
.h = (static_cast<float>(brush_.height) * TS) + 2.0F};
|
||||
game_surface->drawRectBorder(&border, 15);
|
||||
}
|
||||
|
||||
// Renderiza el rectángulo del eyedropper en progreso (cyan brillante)
|
||||
void MapEditor::renderEyedropperRect() {
|
||||
auto game_surface = Screen::get()->getRendererSurface();
|
||||
if (!game_surface) { return; }
|
||||
int x1 = std::clamp(eyedropper_.start_tile_x, 0, Map::WIDTH - 1);
|
||||
int y1 = std::clamp(eyedropper_.start_tile_y, 0, Map::HEIGHT - 1);
|
||||
int x2 = std::clamp(mouse_tile_x_, 0, Map::WIDTH - 1);
|
||||
int y2 = std::clamp(mouse_tile_y_, 0, Map::HEIGHT - 1);
|
||||
int xmin = std::min(x1, x2);
|
||||
int ymin = std::min(y1, y2);
|
||||
int xmax = std::max(x1, x2);
|
||||
int ymax = std::max(y1, y2);
|
||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||
SDL_FRect rect = {
|
||||
.x = static_cast<float>(xmin) * TS,
|
||||
.y = static_cast<float>(ymin) * TS,
|
||||
.w = static_cast<float>((xmax - xmin) + 1) * TS,
|
||||
.h = static_cast<float>((ymax - ymin) + 1) * TS};
|
||||
constexpr Uint8 EYEDROPPER_COLOR = 21; // BRIGHT_CYAN
|
||||
game_surface->drawRectBorder(&rect, EYEDROPPER_COLOR);
|
||||
}
|
||||
|
||||
// Alinea un valor a la cuadrícula de 8x8
|
||||
auto MapEditor::snapToGrid(float value) -> float {
|
||||
return std::round(value / static_cast<float>(Tile::SIZE)) * static_cast<float>(Tile::SIZE);
|
||||
@@ -1315,10 +1473,13 @@ void MapEditor::updateStatusBarInfo() { // NOLINT(readability-function-cognitiv
|
||||
|
||||
// Línea 4: brush activo
|
||||
std::string line4;
|
||||
if (brush_tile_ == ERASER_BRUSH) {
|
||||
line4 = "brush: eraser (e)";
|
||||
} else if (brush_tile_ != NO_BRUSH) {
|
||||
line4 = "brush: tile " + std::to_string(brush_tile_);
|
||||
if (!brush_.isEmpty()) {
|
||||
if (brush_.width == 1 && brush_.height == 1) {
|
||||
int v = brush_.tiles[0];
|
||||
line4 = (v == BrushPattern::ERASE) ? "brush: eraser (e)" : ("brush: tile " + std::to_string(v));
|
||||
} else {
|
||||
line4 = "brush: " + std::to_string(brush_.width) + "x" + std::to_string(brush_.height);
|
||||
}
|
||||
}
|
||||
|
||||
statusbar_->setLine2(line2);
|
||||
@@ -1989,8 +2150,14 @@ void MapEditor::openTilePicker(const std::string& tileset_name, int current_tile
|
||||
Console::get()->toggle();
|
||||
}
|
||||
|
||||
tile_picker_.on_select = [this](int tile) {
|
||||
tile_picker_.on_select = [this, tileset_name](int col, int row, int /*width*/, int /*height*/) {
|
||||
if (!hasSelectedItem()) { return; }
|
||||
// Items solo usan un tile: tomamos siempre la celda superior-izquierda del rect
|
||||
auto surface = Resource::Cache::get()->getSurface(tileset_name);
|
||||
if (!surface) { return; }
|
||||
int cols = static_cast<int>(surface->getWidth()) / Tile::SIZE;
|
||||
if (cols <= 0) { return; }
|
||||
int tile = (row * cols) + col;
|
||||
room_data_.items[selection_.index].tile = tile;
|
||||
room_->getItemManager()->getItem(selection_.index)->setTile(tile);
|
||||
autosave();
|
||||
@@ -1999,7 +2166,7 @@ void MapEditor::openTilePicker(const std::string& tileset_name, int current_tile
|
||||
Uint8 preview_color = hasSelectedItem()
|
||||
? room_data_.items[selection_.index].color1
|
||||
: Item::DEFAULT_COLOR1;
|
||||
tile_picker_.open(tileset_name, current_tile, room_data_.bg_color, 1, preview_color);
|
||||
tile_picker_.open(tileset_name, current_tile, room_data_.bg_color, 1, preview_color, 0, 1, true);
|
||||
}
|
||||
|
||||
// Crea un nuevo item con valores por defecto, centrado en la habitación
|
||||
|
||||
Reference in New Issue
Block a user