feat(editor): hand pick per base, z-sort, amaga entitats en TILE, HUD 2-files centrat

This commit is contained in:
JailDesigner committed 2026-05-02 12:42:47 +02:00
1 parent c0362abf67
commit a254fdd669
4 files changed
+94 -35

No files matched your search

+67 -27
View File
@@ -6,11 +6,11 @@
#include <algorithm>
#include <filesystem>
#include <numeric>
#include <sstream>
#include "core/input/key_config.hpp"
#include "core/locale/locale.hpp"
#include "core/rendering/hud_constants.hpp"
#include "core/rendering/notification_presets.hpp"
#include "core/rendering/screen.hpp"
#include "core/rendering/surface.hpp"
@@ -180,14 +180,17 @@ namespace {
void Editor::enter() {
if (active_) { return; }
active_ = true;
// Estat de novella: tornem a Mode::TILE sense brush ni picker. Els
// brushs (tile/entity) i la selecció de la mà no es preserven entre
// sessions del editor: cada entrada parteix net.
mode_ = Mode::TILE;
// Estat de novella: comencem en mode HAND (selecció/manipulació
// d'entitats existents) — el cas d'ús més habitual quan obres
// l'editor és inspeccionar/moure/borrar el que ja hi ha. Els
// brushs i la selecció no es preserven entre sessions: cada
// entrada parteix net.
mode_ = Mode::HAND;
picker_ = Picker::NONE;
brush_tile_ = -1;
brush_entity_.clear();
hand_selected_idx_ = -1;
hand_hover_idx_ = -1;
ensureEditableEntities();
ensureHudFont();
Notification::notify(Locale::get().text("input.editor_on"));
@@ -544,15 +547,20 @@ void Editor::updateEntityBrush(EntityManager& entities, int /*canvas_x*/, int /*
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).
// Index de l'entitat sota el píxel donat. -1 si cap. Usem el rect
// de col·lisió (caixa base alineada al tilemap) i NO el sprite
// sencer: a entitats amb falsa perspectiva (mòmia 16×32, sarc
// 48×32) la part alta del sprite és "altura visual" que tapa
// cel·les transitables darrere. Si el hit-test usés el sprite
// sencer, no es podria seleccionar/col·locar res a aquelles
// cel·les. Per a entitats sense caixa explícita (torch, hole),
// `getCollisionRect` retorna el `pixel_rect` complet, així que el
// criteri segueix sent intuïtiu ("clic on es veu").
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();
const auto R = list[i]->getCollisionRect();
if (FX >= R.x && FX < R.x + R.w && FY >= R.y && FY < R.y + R.h) { return i; }
}
return -1;
@@ -662,8 +670,13 @@ void Editor::deleteSelected(EntityManager& entities) {
void Editor::renderEditorHud(const EntityManager& entities) const {
if (hud_font_ == nullptr || Map::hud_h <= 0) { return; }
auto& screen = Screen::get();
const auto& pal = Resource::Cache::get().getPalette("ui");
screen.fillRect(0, 0, GameCanvas::WIDTH, Map::hud_h, pal[UiConfig::get().hudBg()]);
// Fons opcional: respectem el flag global del HUD del joc per a
// coherència visual quan el dissenyador alterna entre joc i editor.
if (UiConfig::get().hudBgEnabled()) {
const auto& pal = Resource::Cache::get().getPalette("ui");
screen.fillRect(0, 0, GameCanvas::WIDTH, Map::hud_h, pal[UiConfig::get().hudBg()]);
}
std::string mode_str;
switch (mode_) {
@@ -700,28 +713,55 @@ void Editor::renderEditorHud(const EntityManager& entities) const {
const auto LBL = UiConfig::get().hudLabel();
const auto VAL = UiConfig::get().hudValue();
const auto FLASH = UiConfig::get().hudFlash();
const int KERN = UiConfig::get().hudFont().kerning;
const int Y = (Map::hud_h - Rendering::Hud::FONT_SIZE_PX) / 2;
int x = 2;
// Mateixos Y que el HUD del joc per simetria visual: línia 1 = 4,
// línia 2 = 13. La font és proporcional, així que mesurem cada línia
// sencera abans de centrar-la.
constexpr int LINE1_Y = 4;
constexpr int LINE2_Y = 13;
constexpr int W = GameCanvas::WIDTH;
auto draw = [&](const std::string& s, std::uint8_t color) {
hud_font_->writeColored(x, Y, s, color, KERN);
x += hud_font_->length(s, KERN);
struct Segment {
std::string text;
std::uint8_t color;
};
auto draw_line = [&](const std::vector<Segment>& segs, int y) {
const int TOTAL = std::accumulate(segs.begin(), segs.end(), 0, [&](int acc, const Segment& s) { return acc + hud_font_->length(s.text, KERN); });
int x = (W - TOTAL) / 2;
for (const auto& s : segs) {
hud_font_->writeColored(x, y, s.text, s.color, KERN);
x += hud_font_->length(s.text, KERN);
}
};
draw("MODE:", LBL);
draw(" " + mode_str, VAL);
draw(" " + brush_label, LBL);
draw(" " + brush_value, VAL);
draw(" " + COORD, VAL);
draw(" DOOR:", LBL);
draw(" " + std::to_string(countByName(entities, "door")), VAL);
draw(" SARCS:", LBL);
draw(" " + std::to_string(countByName(entities, "sarcophagus")), VAL);
// Línia 1: estat actiu (mode + brush/selecció + cursor).
draw_line({{.text = "MODE:", .color = LBL},
{.text = " " + mode_str, .color = VAL},
{.text = " " + brush_label, .color = LBL},
{.text = " " + brush_value, .color = VAL},
{.text = " " + COORD, .color = VAL}},
LINE1_Y);
// Línia 2: comptadors de constraints. Color "flash" del HUD del
// joc quan no es compleixen — mateix vocabulari visual que les
// alertes del marcador.
const int DOOR_C = countByName(entities, "door");
const int SARC_C = countByName(entities, "sarcophagus");
const auto DOOR_COLOR = (DOOR_C == 1) ? VAL : FLASH;
const auto SARC_COLOR = (SARC_C >= 1) ? VAL : FLASH;
draw_line({{.text = "DOOR:", .color = LBL},
{.text = " " + std::to_string(DOOR_C), .color = DOOR_COLOR},
{.text = " SARCS:", .color = LBL},
{.text = " " + std::to_string(SARC_C), .color = SARC_COLOR}},
LINE2_Y);
}
void Editor::renderEntityFrame(const Entity& e, std::uint32_t argb) {
const auto R = e.getPixelRect();
// El marc l'alimenta el rect de col·lisió perquè coincideixi amb
// el que `entityAtPixel` considera "clicable". Així el dissenyador
// veu exactament la zona sensible al hover/selecció.
const auto R = e.getCollisionRect();
auto& screen = Screen::get();
const auto X = static_cast<int>(R.x);
const auto Y = static_cast<int>(R.y);