feat(editor): mode mà — selecció, mou, borra i marc d'entitats

This commit is contained in:
JailDesigner committed 2026-05-02 11:28:41 +02:00
1 parent 9b09ccb765
commit b4e36944d7
5 files changed
+182 -9

No files matched your search

+131 -6
View File
@@ -453,11 +453,123 @@ void Editor::updateEntityBrush(EntityManager& entities, int /*canvas_x*/, int /*
}
}
// cppcheck-suppress constParameterReference
// Manté firma simètrica amb les altres branques d'update; la lògica
// real arriba al pas 6.
void Editor::updateHand(EntityManager& /*entities*/, int /*canvas_x*/, int /*canvas_y*/) {
// Pas 6: hover, select, move, delete, marc d'entitat seleccionada.
namespace {
// Index de l'entitat sota el píxel donat. -1 si cap. Usem el rect de
// pintat (`getPixelRect`) i no el de col·lisió perquè el dissenyador
// espera "fer clic on es veu", encara que el cos físic sigui més
// estret (típic en sarc/porta amb falsa perspectiva).
auto entityAtPixel(const std::vector<Entity*>& list, int px, int py) -> int {
const auto FX = static_cast<float>(px);
const auto FY = static_cast<float>(py);
for (int i = static_cast<int>(list.size()) - 1; i >= 0; --i) {
const auto R = list[i]->getPixelRect();
if (FX >= R.x && FX < R.x + R.w && FY >= R.y && FY < R.y + R.h) { return i; }
}
return -1;
}
auto isDeleteKey(const SDL_Event& ev) -> bool {
if (ev.type != SDL_EVENT_KEY_DOWN) { return false; }
const auto SC = KeyConfig::get().scancode("editor_delete");
return SC != SDL_SCANCODE_UNKNOWN && ev.key.scancode == SC;
}
} // namespace
void Editor::updateHand(EntityManager& entities, int canvas_x, int canvas_y) {
const auto LIST = entities.getEntities();
hand_hover_idx_ = entityAtPixel(LIST, canvas_x, canvas_y);
if (hand_selected_idx_ >= static_cast<int>(LIST.size())) { hand_selected_idx_ = -1; }
for (const auto& ev : EventBuffer::events) {
const bool IS_MOUSE_DOWN = (ev.type == SDL_EVENT_MOUSE_BUTTON_DOWN);
if (IS_MOUSE_DOWN && ev.button.button == SDL_BUTTON_LEFT) {
handleHandLeftClick(entities);
} else if (IS_MOUSE_DOWN && ev.button.button == SDL_BUTTON_RIGHT) {
handleHandRightClick();
} else if (isDeleteKey(ev) && hand_selected_idx_ >= 0) {
deleteSelected(entities);
}
}
}
void Editor::handleHandLeftClick(EntityManager& entities) {
if (hand_selected_idx_ < 0) {
// Sense selecció: si pulsem sobre una entitat, seleccionem.
if (hand_hover_idx_ >= 0) { hand_selected_idx_ = hand_hover_idx_; }
return;
}
if (hand_hover_idx_ == hand_selected_idx_) {
// Mateixa entitat: deselecciona.
hand_selected_idx_ = -1;
return;
}
if (hand_hover_idx_ >= 0) {
// Una altra entitat: canvia la selecció.
hand_selected_idx_ = hand_hover_idx_;
return;
}
// Cel·la sense entitat: intenta moure-hi la seleccionada.
if (tryMoveSelected(entities)) { hand_selected_idx_ = -1; }
}
void Editor::handleHandRightClick() {
if (hand_selected_idx_ >= 0) {
hand_selected_idx_ = -1;
} else {
mode_ = Mode::TILE;
}
}
auto Editor::tryMoveSelected(EntityManager& entities) const -> bool {
if (hand_selected_idx_ < 0 || cursor_col_ < 0 || cursor_row_ < 0) { return false; }
auto list = entities.getEntities();
if (hand_selected_idx_ >= static_cast<int>(list.size())) { return false; }
auto* target = list[hand_selected_idx_];
const std::string TYPE = target->getName();
// Construïm el nou spawn excloent l'entitat que mou del check de
// solapaments: si no, sempre xocaria amb ella mateixa. Per a la
// porta, el "moviment" actualment és no-op (no portem encara la
// columna a YAML), així que el descartem amb claredat.
if (TYPE == "door") { return false; }
std::vector<Entity*> filtered;
filtered.reserve(list.size() - 1);
std::ranges::copy_if(list, std::back_inserter(filtered), [target](const Entity* e) { return e != target; });
const auto STR = buildSpawnYaml(TYPE, cursor_col_, cursor_row_, filtered);
if (STR.empty()) { return false; }
if (!entities.removeEntity(target)) { return false; }
try {
auto node = fkyaml::node::deserialize(STR.begin(), STR.end());
return entities.addSpawn(node) != nullptr;
} catch (const std::exception&) {
return false;
}
}
void Editor::deleteSelected(EntityManager& entities) {
auto list = entities.getEntities();
if (hand_selected_idx_ < 0 || hand_selected_idx_ >= static_cast<int>(list.size())) { return; }
auto* target = list[hand_selected_idx_];
if (entities.removeEntity(target)) { hand_selected_idx_ = -1; }
}
void Editor::renderEntityFrame(const Entity& e, std::uint32_t argb) {
const auto R = e.getPixelRect();
auto& screen = Screen::get();
const auto X = static_cast<int>(R.x);
const auto Y = static_cast<int>(R.y);
const auto W = static_cast<int>(R.w);
const auto H = static_cast<int>(R.h);
if (W <= 0 || H <= 0) { return; }
screen.fillRect(X, Y, W, 1, argb);
screen.fillRect(X, Y + H - 1, W, 1, argb);
screen.fillRect(X, Y, 1, H, argb);
screen.fillRect(X + W - 1, Y, 1, H, argb);
}
auto Editor::tryPlaceEntity(EntityManager& entities) -> bool {
@@ -586,7 +698,7 @@ void Editor::updateTilePicker(const Tilemap& tilemap, int canvas_x, int canvas_y
}
}
void Editor::render(const Tilemap& tilemap) const {
void Editor::render(const Tilemap& tilemap, const EntityManager& entities) const {
if (!active_) { return; }
auto& screen = Screen::get();
constexpr int W = GameCanvas::WIDTH;
@@ -601,6 +713,19 @@ void Editor::render(const Tilemap& tilemap) const {
const int PX = tilemap.getOriginX() + (cursor_col_ * tilemap.getTileSize());
const int PY = tilemap.getOriginY() + (cursor_row_ * tilemap.getTileSize());
tilemap.renderTilePreview(PX, PY, brush_tile_);
} else if (mode_ == Mode::HAND && picker_ == Picker::NONE) {
// Marcs d'entitat: hover (tenue) i selecció (brillant). El
// pickerHoverArgb el reusem com a "selecció brillant"; el
// border (tipus blau) com a "hover tenue".
const auto LIST = entities.getEntities();
const auto HOVER_ARGB = EditorConfig::get().borderArgb();
const auto SELECT_ARGB = EditorConfig::get().pickerHoverArgb();
if (hand_hover_idx_ >= 0 && hand_hover_idx_ < static_cast<int>(LIST.size()) && hand_hover_idx_ != hand_selected_idx_) {
renderEntityFrame(*LIST[hand_hover_idx_], HOVER_ARGB);
}
if (hand_selected_idx_ >= 0 && hand_selected_idx_ < static_cast<int>(LIST.size())) {
renderEntityFrame(*LIST[hand_selected_idx_], SELECT_ARGB);
}
}
const auto BORDER = EditorConfig::get().borderArgb();