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:
@@ -30,8 +30,8 @@ tilemap:
|
|||||||
- [49, 50, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 380, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
- [49, 50, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 380, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
||||||
- [-1, -1, -1, -1, -1, -1, -1, 169, 169, 169, 169, 169, 169, 169, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
- [-1, -1, -1, -1, -1, -1, -1, 169, 169, 169, 169, 169, 169, 169, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
||||||
- [-1, -1, -1, -1, -1, -1, -1, -1, 168, 168, 168, 168, 168, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
- [-1, -1, -1, -1, -1, -1, -1, -1, 168, 168, 168, 168, 168, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
||||||
- [-1, -1, -1, -1, -1, -1, -1, -1, 168, 134, 168, 134, 168, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169, 169, 169, -1, -1, -1, -1, -1, 169, 169]
|
- [-1, -1, -1, -1, -1, -1, -1, -1, 168, 134, 168, 134, 168, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
||||||
- [-1, -1, -1, -1, -1, -1, -1, -1, 168, 168, 168, 168, 168, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169, 169, 169, -1, -1, -1, -1, -1, 169, 169]
|
- [-1, -1, -1, -1, -1, -1, -1, -1, 168, 168, 168, 168, 168, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 169, 169]
|
||||||
- [169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169]
|
- [169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169]
|
||||||
# Mapa de colisiones (0 = vacio, 1 = solido)
|
# Mapa de colisiones (0 = vacio, 1 = solido)
|
||||||
collision:
|
collision:
|
||||||
@@ -53,8 +53,8 @@ tilemap:
|
|||||||
- [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
- [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
||||||
- [0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
- [0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
||||||
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
||||||
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]
|
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
||||||
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]
|
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]
|
||||||
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
- [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
|
||||||
|
|
||||||
# Enemigos en esta habitación
|
# Enemigos en esta habitación
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ auto MapEditor::showGrid(bool show) -> std::string {
|
|||||||
|
|
||||||
auto MapEditor::setEditingCollision(bool collision) -> std::string {
|
auto MapEditor::setEditingCollision(bool collision) -> std::string {
|
||||||
editing_collision_ = collision;
|
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";
|
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)
|
// Resetear estado (preservar modo de edición en re-enter)
|
||||||
drag_ = {};
|
drag_ = {};
|
||||||
selection_.clear();
|
selection_.clear();
|
||||||
|
eyedropper_ = {};
|
||||||
if (!is_reenter) {
|
if (!is_reenter) {
|
||||||
brush_tile_ = NO_BRUSH;
|
brush_.clear();
|
||||||
painting_ = false;
|
painting_ = false;
|
||||||
editing_collision_ = false;
|
editing_collision_ = false;
|
||||||
}
|
}
|
||||||
@@ -311,7 +312,7 @@ auto MapEditor::revert() -> std::string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selection_.clear();
|
selection_.clear();
|
||||||
brush_tile_ = NO_BRUSH;
|
brush_.clear();
|
||||||
return "Reverted to original";
|
return "Reverted to original";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,26 +360,9 @@ void MapEditor::update(float delta_time) {
|
|||||||
updateDrag();
|
updateDrag();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si estamos pintando tiles, pintar en la posición actual del ratón
|
// Si estamos pintando tiles, estampar el patrón en la posición del ratón
|
||||||
if (painting_ && brush_tile_ != NO_BRUSH) {
|
if (painting_ && !brush_.isEmpty()) {
|
||||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
stampBrushAt(mouse_tile_x_, mouse_tile_y_);
|
||||||
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_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actualizar la barra de estado
|
// Actualizar la barra de estado
|
||||||
@@ -434,6 +418,16 @@ void MapEditor::render() {
|
|||||||
// Renderizar highlight de selección (encima de los sprites)
|
// Renderizar highlight de selección (encima de los sprites)
|
||||||
renderSelectionHighlight();
|
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)
|
// Tile picker o mini mapa (encima de todo en el play area)
|
||||||
if (tile_picker_.isOpen()) {
|
if (tile_picker_.isOpen()) {
|
||||||
tile_picker_.render();
|
tile_picker_.render();
|
||||||
@@ -467,15 +461,25 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ESC: desactivar brush
|
// ESC: cancelar eyedropper en progreso (sin tocar el brush previo)
|
||||||
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && brush_tile_ != NO_BRUSH) {
|
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_ESCAPE && eyedropper_.active) {
|
||||||
brush_tile_ = NO_BRUSH;
|
eyedropper_.active = false;
|
||||||
return;
|
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) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,41 +528,54 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click derecho: abrir TilePicker
|
// T: abrir TilePicker
|
||||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_RIGHT) {
|
if (event.type == SDL_EVENT_KEY_DOWN && event.key.key == SDLK_T && static_cast<int>(event.key.repeat) == 0) {
|
||||||
// Deseleccionar entidades
|
// Deseleccionar entidades
|
||||||
selection_.clear();
|
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_) {
|
if (editing_collision_) {
|
||||||
// Abrir tile picker del collision tileset
|
current = (tile_index >= 0 && tile_index < static_cast<int>(room_data_.collision_tile_map.size()))
|
||||||
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()))
|
|
||||||
? room_data_.collision_tile_map[tile_index]
|
? room_data_.collision_tile_map[tile_index]
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
tile_picker_.on_select = [this](int tile) {
|
|
||||||
brush_tile_ = tile;
|
|
||||||
};
|
|
||||||
tile_picker_.open("collision.gif", current, 0);
|
|
||||||
} else {
|
} else {
|
||||||
// Abrir tile picker del mapa de dibujo
|
current = (tile_index >= 0 && tile_index < static_cast<int>(room_data_.tile_map.size()))
|
||||||
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()))
|
|
||||||
? room_data_.tile_map[tile_index]
|
? room_data_.tile_map[tile_index]
|
||||||
: -1;
|
: -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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click izquierdo
|
// Click izquierdo
|
||||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && event.button.button == SDL_BUTTON_LEFT) {
|
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
|
// 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
|
// Comprobar si hay hit en alguna entidad primero
|
||||||
bool hit_entity = false;
|
bool hit_entity = false;
|
||||||
SDL_FRect player_rect = player_->getRect();
|
SDL_FRect player_rect = player_->getRect();
|
||||||
@@ -572,20 +589,9 @@ void MapEditor::handleEvent(const SDL_Event& event) { // NOLINT(readability-fun
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hit_entity) {
|
if (!hit_entity) {
|
||||||
// Pintar tile y entrar en modo painting
|
// Estampar el patrón y entrar en modo painting
|
||||||
painting_ = true;
|
painting_ = true;
|
||||||
int tile_index = (mouse_tile_y_ * 32) + mouse_tile_x_;
|
stampBrushAt(mouse_tile_x_, mouse_tile_y_);
|
||||||
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_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -951,6 +957,158 @@ void MapEditor::renderSelectionHighlight() {
|
|||||||
game_surface->drawRectBorder(&border, DRAG_COLOR);
|
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
|
// Alinea un valor a la cuadrícula de 8x8
|
||||||
auto MapEditor::snapToGrid(float value) -> float {
|
auto MapEditor::snapToGrid(float value) -> float {
|
||||||
return std::round(value / static_cast<float>(Tile::SIZE)) * static_cast<float>(Tile::SIZE);
|
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
|
// Línea 4: brush activo
|
||||||
std::string line4;
|
std::string line4;
|
||||||
if (brush_tile_ == ERASER_BRUSH) {
|
if (!brush_.isEmpty()) {
|
||||||
line4 = "brush: eraser (e)";
|
if (brush_.width == 1 && brush_.height == 1) {
|
||||||
} else if (brush_tile_ != NO_BRUSH) {
|
int v = brush_.tiles[0];
|
||||||
line4 = "brush: tile " + std::to_string(brush_tile_);
|
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);
|
statusbar_->setLine2(line2);
|
||||||
@@ -1989,8 +2150,14 @@ void MapEditor::openTilePicker(const std::string& tileset_name, int current_tile
|
|||||||
Console::get()->toggle();
|
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; }
|
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_data_.items[selection_.index].tile = tile;
|
||||||
room_->getItemManager()->getItem(selection_.index)->setTile(tile);
|
room_->getItemManager()->getItem(selection_.index)->setTile(tile);
|
||||||
autosave();
|
autosave();
|
||||||
@@ -1999,7 +2166,7 @@ void MapEditor::openTilePicker(const std::string& tileset_name, int current_tile
|
|||||||
Uint8 preview_color = hasSelectedItem()
|
Uint8 preview_color = hasSelectedItem()
|
||||||
? room_data_.items[selection_.index].color1
|
? room_data_.items[selection_.index].color1
|
||||||
: Item::DEFAULT_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
|
// Crea un nuevo item con valores por defecto, centrado en la habitación
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include <memory> // Para shared_ptr, unique_ptr
|
#include <memory> // Para shared_ptr, unique_ptr
|
||||||
#include <string> // Para string
|
#include <string> // Para string
|
||||||
|
#include <vector> // Para vector
|
||||||
|
|
||||||
#include "game/editor/mini_map.hpp" // Para MiniMap
|
#include "game/editor/mini_map.hpp" // Para MiniMap
|
||||||
#include "game/editor/tile_picker.hpp" // Para TilePicker
|
#include "game/editor/tile_picker.hpp" // Para TilePicker
|
||||||
@@ -41,6 +42,40 @@ struct Selection {
|
|||||||
[[nodiscard]] auto is(EntityType t) const -> bool { return type == t && index >= 0; }
|
[[nodiscard]] auto is(EntityType t) const -> bool { return type == t && index >= 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Brush rectangular para pintar tiles. Una sola estructura cubre:
|
||||||
|
// - brush vacío (width=0, height=0)
|
||||||
|
// - single tile (width=1, height=1, tiles={N})
|
||||||
|
// - borrador explícito (width=1, height=1, tiles={ERASE})
|
||||||
|
// - patrón rectangular sampleado del nivel o del tileset
|
||||||
|
struct BrushPattern {
|
||||||
|
static constexpr int TRANSPARENT = -2; // celda no se escribe al estampar
|
||||||
|
static constexpr int ERASE = -1; // celda escribe -1 (vacía el destino)
|
||||||
|
|
||||||
|
int width{0};
|
||||||
|
int height{0};
|
||||||
|
std::vector<int> tiles; // size = width*height, row-major
|
||||||
|
|
||||||
|
[[nodiscard]] auto isEmpty() const -> bool { return width <= 0 || height <= 0; }
|
||||||
|
[[nodiscard]] auto at(int dx, int dy) const -> int { return tiles[(dy * width) + dx]; }
|
||||||
|
void clear() {
|
||||||
|
width = 0;
|
||||||
|
height = 0;
|
||||||
|
tiles.clear();
|
||||||
|
}
|
||||||
|
void setSingle(int tile) {
|
||||||
|
width = 1;
|
||||||
|
height = 1;
|
||||||
|
tiles = {tile};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Estado del eyedropper (clic derecho con drag para samplear el nivel)
|
||||||
|
struct EyedropperState {
|
||||||
|
bool active{false};
|
||||||
|
int start_tile_x{0};
|
||||||
|
int start_tile_y{0};
|
||||||
|
};
|
||||||
|
|
||||||
class MapEditor {
|
class MapEditor {
|
||||||
public:
|
public:
|
||||||
static void init(); // [SINGLETON] Crea el objeto
|
static void init(); // [SINGLETON] Crea el objeto
|
||||||
@@ -152,9 +187,15 @@ class MapEditor {
|
|||||||
void renderEntityBoundaries();
|
void renderEntityBoundaries();
|
||||||
static void renderBoundaryMarker(float x, float y, Uint8 color);
|
static void renderBoundaryMarker(float x, float y, Uint8 color);
|
||||||
void renderSelectionHighlight();
|
void renderSelectionHighlight();
|
||||||
|
void renderBrushPreview();
|
||||||
|
void renderEyedropperRect();
|
||||||
void renderGrid() const;
|
void renderGrid() const;
|
||||||
void handleMouseDown(float game_x, float game_y);
|
void handleMouseDown(float game_x, float game_y);
|
||||||
void handleMouseUp();
|
void handleMouseUp();
|
||||||
|
void stampBrushAt(int tile_x, int tile_y);
|
||||||
|
void commitEyedropper();
|
||||||
|
[[nodiscard]] auto sampleBrush(int x1, int y1, int x2, int y2) const -> BrushPattern;
|
||||||
|
[[nodiscard]] auto buildPatternFromTileset(const std::string& tileset_name, int col, int row, int width, int height) const -> BrushPattern;
|
||||||
|
|
||||||
// Reconstruye todas las puertas vivas desde room_data_, limpiando primero
|
// Reconstruye todas las puertas vivas desde room_data_, limpiando primero
|
||||||
// los WALLs antiguos del CollisionMap. Lo usa setDoorProperty cuando un
|
// los WALLs antiguos del CollisionMap. Lo usa setDoorProperty cuando un
|
||||||
@@ -184,12 +225,11 @@ class MapEditor {
|
|||||||
// Estado del editor
|
// Estado del editor
|
||||||
bool active_{false};
|
bool active_{false};
|
||||||
DragState drag_;
|
DragState drag_;
|
||||||
Selection selection_; // Entidad seleccionada (unificada: enemy, item o platform)
|
Selection selection_; // Entidad seleccionada (unificada: enemy, item o platform)
|
||||||
static constexpr int NO_BRUSH = -2; // Sin brush activo
|
BrushPattern brush_; // Brush activo (vacío = sin brush)
|
||||||
static constexpr int ERASER_BRUSH = -1; // Brush borrador (pinta tile vacío = -1)
|
EyedropperState eyedropper_; // Estado del eyedropper (clic derecho)
|
||||||
int brush_tile_{NO_BRUSH}; // Tile activo para pintar
|
bool painting_{false}; // true mientras se está pintando con click izquierdo mantenido
|
||||||
bool painting_{false}; // true mientras se está pintando con click izquierdo mantenido
|
bool editing_collision_{false}; // true = editando collision tilemap, false = editando draw tilemap
|
||||||
bool editing_collision_{false}; // true = editando collision tilemap, false = editando draw tilemap
|
|
||||||
|
|
||||||
// Datos de la habitación
|
// Datos de la habitación
|
||||||
Room::Data room_data_;
|
Room::Data room_data_;
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
static constexpr int BORDER_PAD = 3;
|
static constexpr int BORDER_PAD = 3;
|
||||||
|
|
||||||
// Abre el picker con un tileset
|
// Abre el picker con un tileset
|
||||||
void TilePicker::open(const std::string& tileset_name, int current_tile, int bg_color, int source_color, int target_color, int tile_spacing_in, int tile_spacing_out) {
|
void TilePicker::open(const std::string& tileset_name, int current_tile, int bg_color, int source_color, int target_color, int tile_spacing_in, int tile_spacing_out, bool allow_rect_selection) {
|
||||||
tileset_ = Resource::Cache::get()->getSurface(tileset_name);
|
tileset_ = Resource::Cache::get()->getSurface(tileset_name);
|
||||||
if (!tileset_) {
|
if (!tileset_) {
|
||||||
open_ = false;
|
open_ = false;
|
||||||
@@ -23,6 +23,10 @@ void TilePicker::open(const std::string& tileset_name, int current_tile, int bg_
|
|||||||
|
|
||||||
spacing_in_ = tile_spacing_in;
|
spacing_in_ = tile_spacing_in;
|
||||||
spacing_out_ = tile_spacing_out;
|
spacing_out_ = tile_spacing_out;
|
||||||
|
allow_rect_ = allow_rect_selection;
|
||||||
|
selecting_rect_ = false;
|
||||||
|
rect_start_tile_ = -1;
|
||||||
|
last_valid_hover_ = -1;
|
||||||
|
|
||||||
// Calcular dimensiones del tileset en tiles (teniendo en cuenta spacing de entrada)
|
// Calcular dimensiones del tileset en tiles (teniendo en cuenta spacing de entrada)
|
||||||
int src_cell = Tile::SIZE + spacing_in_;
|
int src_cell = Tile::SIZE + spacing_in_;
|
||||||
@@ -145,13 +149,37 @@ void TilePicker::render() {
|
|||||||
float tileset_screen_y = frame_dst_.y + BORDER_PAD - static_cast<float>(scroll_y_);
|
float tileset_screen_y = frame_dst_.y + BORDER_PAD - static_cast<float>(scroll_y_);
|
||||||
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
constexpr auto TS = static_cast<float>(Tile::SIZE);
|
||||||
|
|
||||||
// Highlight del tile bajo el cursor (blanco)
|
// Si hay un drag rect activo, dibujar el rectángulo en progreso (blanco)
|
||||||
if (hover_tile_ >= 0) {
|
// y omitir el highlight de hover individual.
|
||||||
|
if (selecting_rect_ && rect_start_tile_ >= 0) {
|
||||||
|
int end_tile = (hover_tile_ >= 0) ? hover_tile_ : last_valid_hover_;
|
||||||
|
if (end_tile >= 0) {
|
||||||
|
int c1 = rect_start_tile_ % tileset_width_;
|
||||||
|
int r1 = rect_start_tile_ / tileset_width_;
|
||||||
|
int c2 = end_tile % tileset_width_;
|
||||||
|
int r2 = end_tile / tileset_width_;
|
||||||
|
int col_min = std::min(c1, c2);
|
||||||
|
int col_max = std::max(c1, c2);
|
||||||
|
int row_min = std::min(r1, r2);
|
||||||
|
int row_max = std::max(r1, r2);
|
||||||
|
int cells_w = col_max - col_min + 1;
|
||||||
|
int cells_h = row_max - row_min + 1;
|
||||||
|
float rx = tileset_screen_x + static_cast<float>(col_min * out_cell);
|
||||||
|
float ry = tileset_screen_y + static_cast<float>(row_min * out_cell);
|
||||||
|
float rw = static_cast<float>((cells_w * out_cell) - spacing_out_);
|
||||||
|
float rh = static_cast<float>((cells_h * out_cell) - spacing_out_);
|
||||||
|
if (ry + rh > 0 && ry < static_cast<float>(visible_height_)) {
|
||||||
|
SDL_FRect rect_box = {.x = rx, .y = ry, .w = rw, .h = rh};
|
||||||
|
game_surface->drawRectBorder(&rect_box, 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (hover_tile_ >= 0) {
|
||||||
|
// Highlight del tile bajo el cursor (blanco)
|
||||||
int col = hover_tile_ % tileset_width_;
|
int col = hover_tile_ % tileset_width_;
|
||||||
int row = hover_tile_ / tileset_width_;
|
int row = hover_tile_ / tileset_width_;
|
||||||
float hx = tileset_screen_x + static_cast<float>(col * out_cell);
|
float hx = tileset_screen_x + static_cast<float>(col * out_cell);
|
||||||
float hy = tileset_screen_y + static_cast<float>(row * out_cell);
|
float hy = tileset_screen_y + static_cast<float>(row * out_cell);
|
||||||
if (hy >= 0 && hy + TS <= visible_height_) {
|
if (hy >= 0 && hy + TS <= static_cast<float>(visible_height_)) {
|
||||||
SDL_FRect highlight = {.x = hx, .y = hy, .w = TS, .h = TS};
|
SDL_FRect highlight = {.x = hx, .y = hy, .w = TS, .h = TS};
|
||||||
game_surface->drawRectBorder(&highlight, 15);
|
game_surface->drawRectBorder(&highlight, 15);
|
||||||
}
|
}
|
||||||
@@ -163,7 +191,7 @@ void TilePicker::render() {
|
|||||||
int row = current_tile_ / tileset_width_;
|
int row = current_tile_ / tileset_width_;
|
||||||
float cx = tileset_screen_x + static_cast<float>(col * out_cell);
|
float cx = tileset_screen_x + static_cast<float>(col * out_cell);
|
||||||
float cy = tileset_screen_y + static_cast<float>(row * out_cell);
|
float cy = tileset_screen_y + static_cast<float>(row * out_cell);
|
||||||
if (cy >= 0 && cy + TS <= visible_height_) {
|
if (cy >= 0 && cy + TS <= static_cast<float>(visible_height_)) {
|
||||||
SDL_FRect cur_rect = {.x = cx, .y = cy, .w = TS, .h = TS};
|
SDL_FRect cur_rect = {.x = cx, .y = cy, .w = TS, .h = TS};
|
||||||
game_surface->drawRectBorder(&cur_rect, 9);
|
game_surface->drawRectBorder(&cur_rect, 9);
|
||||||
}
|
}
|
||||||
@@ -179,14 +207,44 @@ void TilePicker::handleEvent(const SDL_Event& event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
|
||||||
if (event.button.button == SDL_BUTTON_LEFT && hover_tile_ >= 0) {
|
if (event.button.button == SDL_BUTTON_LEFT) {
|
||||||
if (on_select) { on_select(hover_tile_); }
|
if (allow_rect_ && hover_tile_ >= 0) {
|
||||||
close();
|
// Iniciar selección rectangular: el commit se hace al soltar
|
||||||
|
selecting_rect_ = true;
|
||||||
|
rect_start_tile_ = hover_tile_;
|
||||||
|
last_valid_hover_ = hover_tile_;
|
||||||
|
} else if (!allow_rect_ && hover_tile_ >= 0) {
|
||||||
|
// Modo single-tile: confirmar inmediatamente
|
||||||
|
int col = hover_tile_ % tileset_width_;
|
||||||
|
int row = hover_tile_ / tileset_width_;
|
||||||
|
if (on_select) { on_select(col, row, 1, 1); }
|
||||||
|
close();
|
||||||
|
}
|
||||||
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
} else if (event.button.button == SDL_BUTTON_RIGHT) {
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_EVENT_MOUSE_BUTTON_UP && event.button.button == SDL_BUTTON_LEFT && selecting_rect_) {
|
||||||
|
int end_tile = (hover_tile_ >= 0) ? hover_tile_ : last_valid_hover_;
|
||||||
|
if (end_tile >= 0 && rect_start_tile_ >= 0) {
|
||||||
|
int c1 = rect_start_tile_ % tileset_width_;
|
||||||
|
int r1 = rect_start_tile_ / tileset_width_;
|
||||||
|
int c2 = end_tile % tileset_width_;
|
||||||
|
int r2 = end_tile / tileset_width_;
|
||||||
|
int col_min = std::min(c1, c2);
|
||||||
|
int col_max = std::max(c1, c2);
|
||||||
|
int row_min = std::min(r1, r2);
|
||||||
|
int row_max = std::max(r1, r2);
|
||||||
|
int width = col_max - col_min + 1;
|
||||||
|
int height = row_max - row_min + 1;
|
||||||
|
if (on_select) { on_select(col_min, row_min, width, height); }
|
||||||
|
}
|
||||||
|
selecting_rect_ = false;
|
||||||
|
rect_start_tile_ = -1;
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
|
if (event.type == SDL_EVENT_MOUSE_WHEEL) {
|
||||||
scroll_y_ -= static_cast<int>(event.wheel.y) * Tile::SIZE * 2;
|
scroll_y_ -= static_cast<int>(event.wheel.y) * Tile::SIZE * 2;
|
||||||
int max_scroll = static_cast<int>(frame_dst_.h) - visible_height_;
|
int max_scroll = static_cast<int>(frame_dst_.h) - visible_height_;
|
||||||
@@ -231,7 +289,8 @@ void TilePicker::updateMousePosition() {
|
|||||||
if (on_tile && rel_x >= 0 && rel_y >= 0 &&
|
if (on_tile && rel_x >= 0 && rel_y >= 0 &&
|
||||||
tile_x >= 0 && tile_x < tileset_width_ &&
|
tile_x >= 0 && tile_x < tileset_width_ &&
|
||||||
tile_y >= 0 && tile_y < tileset_height_) {
|
tile_y >= 0 && tile_y < tileset_height_) {
|
||||||
hover_tile_ = tile_y * tileset_width_ + tile_x;
|
hover_tile_ = (tile_y * tileset_width_) + tile_x;
|
||||||
|
last_valid_hover_ = hover_tile_;
|
||||||
} else {
|
} else {
|
||||||
hover_tile_ = -1;
|
hover_tile_ = -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class Surface;
|
|||||||
* Muestra el tileset centrado en el play area.
|
* Muestra el tileset centrado en el play area.
|
||||||
* Hover ilumina el tile bajo el cursor.
|
* Hover ilumina el tile bajo el cursor.
|
||||||
* Click selecciona el tile y cierra el picker.
|
* Click selecciona el tile y cierra el picker.
|
||||||
|
* Si allow_rect_selection es true, click + drag selecciona un rect de tiles.
|
||||||
* Mouse wheel para scroll si el tileset es más alto que el play area.
|
* Mouse wheel para scroll si el tileset es más alto que el play area.
|
||||||
* ESC o click derecho para cancelar.
|
* ESC o click derecho para cancelar.
|
||||||
*/
|
*/
|
||||||
@@ -29,15 +30,17 @@ class TilePicker {
|
|||||||
// source_color/target_color: sustitución de color (-1 = sin sustitución)
|
// source_color/target_color: sustitución de color (-1 = sin sustitución)
|
||||||
// tile_spacing_in: pixels de separación entre tiles en el fichero fuente
|
// tile_spacing_in: pixels de separación entre tiles en el fichero fuente
|
||||||
// tile_spacing_out: pixels de separación visual entre tiles al mostrar
|
// tile_spacing_out: pixels de separación visual entre tiles al mostrar
|
||||||
void open(const std::string& tileset_name, int current_tile = -1, int bg_color = -1, int source_color = -1, int target_color = -1, int tile_spacing_in = 0, int tile_spacing_out = 1);
|
// allow_rect_selection: si true, permite arrastrar para seleccionar varios tiles
|
||||||
|
void open(const std::string& tileset_name, int current_tile = -1, int bg_color = -1, int source_color = -1, int target_color = -1, int tile_spacing_in = 0, int tile_spacing_out = 1, bool allow_rect_selection = false);
|
||||||
void close();
|
void close();
|
||||||
[[nodiscard]] auto isOpen() const -> bool { return open_; }
|
[[nodiscard]] auto isOpen() const -> bool { return open_; }
|
||||||
|
|
||||||
void render();
|
void render();
|
||||||
void handleEvent(const SDL_Event& event);
|
void handleEvent(const SDL_Event& event);
|
||||||
|
|
||||||
// Callback al seleccionar un tile (índice del tile)
|
// Callback al seleccionar (col, row, w, h dentro del tileset).
|
||||||
std::function<void(int)> on_select;
|
// Para selección de un solo tile: w=h=1.
|
||||||
|
std::function<void(int, int, int, int)> on_select;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateMousePosition();
|
void updateMousePosition();
|
||||||
@@ -50,6 +53,12 @@ class TilePicker {
|
|||||||
int tileset_height_{0}; // Alto del tileset en tiles
|
int tileset_height_{0}; // Alto del tileset en tiles
|
||||||
int current_tile_{-1}; // Tile actualmente seleccionado (highlight)
|
int current_tile_{-1}; // Tile actualmente seleccionado (highlight)
|
||||||
int hover_tile_{-1}; // Tile bajo el cursor
|
int hover_tile_{-1}; // Tile bajo el cursor
|
||||||
|
int last_valid_hover_{-1}; // Último hover_tile_ válido (para soltar sobre spacing)
|
||||||
|
|
||||||
|
// Selección rectangular
|
||||||
|
bool allow_rect_{false}; // ¿Se permite drag para seleccionar rect?
|
||||||
|
bool selecting_rect_{false}; // ¿Hay un drag activo?
|
||||||
|
int rect_start_tile_{-1}; // Tile donde empezó el drag
|
||||||
|
|
||||||
// Spacing
|
// Spacing
|
||||||
int spacing_in_{0}; // Spacing en el fichero fuente
|
int spacing_in_{0}; // Spacing en el fichero fuente
|
||||||
|
|||||||
Reference in New Issue
Block a user