Kingdom/empire capital labels only ever drew on the 3D globe -- neither the on-screen 2D map nor exportMapImage()/exportAtlasImage() showed them. drawMapOverlays() now has a 2D realm-label block mirroring the existing 3D one; exportAtlasImage() adds nation names as label candidates in its decluttered pass (offset above the capital's own settlement-name anchor so the two don't collide), shown regardless of the current colour mode since the atlas is a reference, not "what you're looking at". Also split the Territory view's political borders from its alliance/rivalry diplomacy arcs, which previously always showed together with no way to hide just the arcs. New independent showDiplomacy toggle (key A, default on, a toolbar checkbox in Overlays) gates only the ally/rival arc draw calls. Verified end to end: exported both Map and Atlas images from a live world with several realms and confirmed realm names render correctly (cleanly decluttered in the Atlas, alongside geography and settlement names). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TMfyZv91tonDnPJqbaTVJE
428 lines
26 KiB
C++
428 lines
26 KiB
C++
#include "Viewer.hpp"
|
|
#include "Picking.hpp"
|
|
#include "Map2D.hpp" // wrapPi
|
|
#include "Projection.hpp" // EqualEarth, lonLatToDir
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
|
|
// One frame of input: camera orbit/zoom, hover picking (panel subtile -> 3D ray
|
|
// -> 2D map), click-to-select, and key handling. Writes the per-frame picking
|
|
// members (mp/onPause/hovered/hasHoverSub/hoverSub/hoveredSubIdx) for the render
|
|
// pass, and may step / regenerate / save / load the world.
|
|
void Viewer::handleInput() {
|
|
mp = GetMousePosition();
|
|
bool in3D = (mp.x < view3DW && mp.y < view3DH); // top-left quadrant
|
|
bool inMap = CheckCollisionPointRec(mp, mapRect);
|
|
bool inPanel = (selectedCell >= 0) && CheckCollisionPointRec(mp, panelRect);
|
|
bool inLiveInfo = liveWorld && CheckCollisionPointRec(mp, liveInfoRect);
|
|
onPause = CheckCollisionPointRec(mp, pauseBtn);
|
|
// The raygui toolbar sidebar (Toolbar.cpp) reads real mouse state itself when drawn later this
|
|
// frame; gate it out of camera orbit/pick/zoom here so interacting with it doesn't also spin
|
|
// the globe or select a tile underneath (mirrors inPanel/inLiveInfo below).
|
|
bool inToolbar = CheckCollisionPointRec(mp, toolbarToggleBtn)
|
|
|| (showToolbar && CheckCollisionPointRec(mp, toolbarRect));
|
|
|
|
// --- Camera input (LMB drag orbits; tracks drag distance for clicks) --
|
|
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) {
|
|
if (phase3Prompt) { // modal: only the two buttons act
|
|
if (CheckCollisionPointRec(mp, p3StartBtn)) {
|
|
phase3 = true; phase3Prompt = false; paused = true; // switching stage auto-pauses
|
|
phase3PromptAt = elapsedMy; setStatus("Hydrology started - paused (SPACE to run)");
|
|
refreshView();
|
|
} else if (CheckCollisionPointRec(mp, p3ContinueBtn)) {
|
|
phase3Prompt = false; paused = false;
|
|
phase3PromptAt = elapsedMy + planet.cfg.phase3AfterMy;
|
|
setStatus("Continuing world-building");
|
|
}
|
|
} else {
|
|
dragDist = 0.0f;
|
|
pressInMap = inMap && !inLiveInfo; // drag started on the map -> pan it
|
|
if (onPause) pauseAction(); // clickable pause / re-evolve button
|
|
if (inLiveInfo) {
|
|
for (size_t i = 0; i < liveInfoTabRects.size(); ++i)
|
|
if (CheckCollisionPointRec(mp, liveInfoTabRects[i])) { liveInfoTab = (int)i; break; }
|
|
if (liveInfoTab == 3) {
|
|
for (size_t i = 0; i < eventRowRects.size() && i < eventRowIndices.size(); ++i) {
|
|
if (!CheckCollisionPointRec(mp, eventRowRects[i])) continue;
|
|
int ei = eventRowIndices[i];
|
|
if (ei >= 0 && ei < (int)events.size())
|
|
focusCell(events[ei].cell, events[ei].title);
|
|
break;
|
|
}
|
|
} else if (liveInfoTab >= 4) { // Atlas/Eco/Civ: click a row to fly there
|
|
for (size_t i = 0; i < eventRowRects.size() && i < atlasRowCells.size(); ++i) {
|
|
if (!CheckCollisionPointRec(mp, eventRowRects[i])) continue;
|
|
if (atlasRowCells[i] >= 0) focusCell(atlasRowCells[i], "");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (editMode && inPanel) handleEditClick();
|
|
}
|
|
}
|
|
// Live World: is the camera following a storm? (look up by stable id; release if dissipated)
|
|
const WeatherSystem* followed = nullptr;
|
|
if (liveWorld && followId != 0) {
|
|
for (const auto& ws : planet.storms()) if (ws.id == followId) { followed = &ws; break; }
|
|
if (!followed) followId = 0;
|
|
}
|
|
bool following = (followed != nullptr);
|
|
|
|
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && !phase3Prompt) {
|
|
Vector2 d = GetMouseDelta();
|
|
dragDist += fabsf(d.x) + fabsf(d.y);
|
|
if (in3D && !onPause && !following && !inToolbar) { // orbit (disabled while following a storm)
|
|
camYaw += d.x * 0.005f;
|
|
camPitch += d.y * 0.005f;
|
|
camPitch = std::clamp(camPitch, -1.5f, 1.5f);
|
|
}
|
|
if (pressInMap) { // drag the map: pan when zoomed, else rotate lon
|
|
if (mapZoom > 1.0) {
|
|
double w = mapRect.width * mapZoom, h = mapRect.height * mapZoom;
|
|
mapPanX = std::clamp(mapPanX + d.x, -(w - mapRect.width) * 0.5, (w - mapRect.width) * 0.5);
|
|
mapPanY = std::clamp(mapPanY + d.y, -(h - mapRect.height) * 0.5, (h - mapRect.height) * 0.5);
|
|
} else {
|
|
mapLon = wrapPi(mapLon + d.x * (2.0 * M_PI / mapRect.width));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Wheel: zoom the 2D map toward the cursor when hovering it, else zoom the camera.
|
|
float wheel = GetMouseWheelMove();
|
|
if (inToolbar) {
|
|
// leave the wheel value alone; the toolbar's GuiScrollPanel reads it later this frame
|
|
} else if (inMap && wheel != 0.0f) {
|
|
Rectangle vr = mapViewRect();
|
|
double u = (mp.x - vr.x) / vr.width, v = (mp.y - vr.y) / vr.height; // projection coord under cursor
|
|
double nz = std::clamp(mapZoom * (wheel > 0 ? 1.2 : 1.0 / 1.2), 1.0, 8.0);
|
|
if (nz <= 1.0001) { mapZoom = 1.0; mapPanX = mapPanY = 0.0; } // back to the whole map
|
|
else {
|
|
double w = mapRect.width * nz, h = mapRect.height * nz;
|
|
mapPanX = std::clamp(mp.x - u * w - mapRect.x - (mapRect.width - w) * 0.5, -(w - mapRect.width) * 0.5, (w - mapRect.width) * 0.5);
|
|
mapPanY = std::clamp(mp.y - v * h - mapRect.y - (mapRect.height - h) * 0.5, -(h - mapRect.height) * 0.5, (h - mapRect.height) * 0.5);
|
|
mapZoom = nz;
|
|
}
|
|
} else if (editMode && inPanel && wheel != 0.0f && nudgeEditField(wheel)) {
|
|
// consumed by the edit panel (a numeric field under the cursor was nudged)
|
|
} else {
|
|
camDist -= wheel * 0.4f;
|
|
camDist = std::clamp(camDist, 2.6f, 14.0f);
|
|
}
|
|
|
|
if (following) { // point the camera straight at the storm
|
|
Vec3 wd = rotateZ(followed->pos, planet.cfg.axialTilt); // model -> world (axial tilt)
|
|
camPitch = std::clamp((float)std::asin(std::clamp(wd.y, -1.0, 1.0)), -1.5f, 1.5f);
|
|
camYaw = (float)std::atan2(wd.x, wd.z);
|
|
}
|
|
cam.position = { camDist * cosf(camPitch) * sinf(camYaw),
|
|
camDist * sinf(camPitch),
|
|
camDist * cosf(camPitch) * cosf(camYaw) };
|
|
|
|
// --- Hover picking: tile panel subtile, else 3D ray, else 2D map ------
|
|
// The globe is rendered tilted by axialTilt about world Z; the picking sphere is
|
|
// rotation-invariant, so un-rotate the world-space hit direction by -tilt to get
|
|
// the model-space direction used to match cells/subcells (hitModel).
|
|
hovered = -1; hasHoverSub = false; hoveredSubIdx = -1;
|
|
bool have3DHit = false; Vec3 hitUnit, hitModel;
|
|
if (inPanel) {
|
|
if (!editMode && !subgrids.empty() && CheckCollisionPointRec(mp, gridRect)) {
|
|
const auto& sg = subgrids[0]; int R = sg->res;
|
|
int i = std::clamp((int)((mp.x - gridRect.x) / (gridRect.width / R)), 0, R - 1);
|
|
int j = std::clamp((int)((mp.y - gridRect.y) / (gridRect.height / R)), 0, R - 1);
|
|
hoveredSubIdx = j * R + i;
|
|
hoverSub = sg->sub[hoveredSubIdx]; hasHoverSub = true; // marks it on the globe
|
|
}
|
|
} else if (in3D && !inToolbar) {
|
|
Vec3 d = rayDirFromMouse(cam.position, cam.target, cam.fovy,
|
|
mp.x, mp.y, (float)view3DW, (float)view3DH);
|
|
Vec3 o{cam.position.x, cam.position.y, cam.position.z};
|
|
if (raySphere(o, d, visBase, hitUnit)) {
|
|
have3DHit = true;
|
|
hitModel = rotateZ(hitUnit, -planet.cfg.axialTilt); // world -> model (undo tilt)
|
|
hovered = nearestCell(planet, hitModel);
|
|
}
|
|
} else if (inMap && !inLiveInfo) {
|
|
Rectangle vr = mapViewRect(); // account for 2D zoom/pan
|
|
double nx = (mp.x - vr.x) / vr.width, ny = (mp.y - vr.y) / vr.height;
|
|
double X = (nx * 2.0 - 1.0) * EqualEarth::halfWidth();
|
|
double Y = (1.0 - 2.0 * ny) * EqualEarth::halfHeight();
|
|
double lon, lat;
|
|
if (EqualEarth::inverse(X, Y, lon, lat))
|
|
hovered = nearestCell(planet, lonLatToDir(wrapPi(lon - mapLon), lat));
|
|
}
|
|
if (have3DHit && !subgrids.empty()) { // prefer subcell under cursor
|
|
double best = -2.0; const SubCell* bs = nullptr;
|
|
for (auto& sg : subgrids) {
|
|
if (!sg) continue;
|
|
for (auto& s : sg->sub) { double dd = s.unit.dot(hitModel); if (dd > best) { best = dd; bs = &s; } }
|
|
}
|
|
if (bs && angBetween(bs->unit, hitModel) < selectedThresh) { hoverSub = *bs; hasHoverSub = true; }
|
|
}
|
|
|
|
// --- Click = select a tile (ignored over panel/button / while dragging) -
|
|
if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT) && dragDist < 6.0f && !inPanel && !inLiveInfo && !onPause && !phase3Prompt && !inToolbar && hovered >= 0)
|
|
selectCell(hovered);
|
|
|
|
// --- Keys -------------------------------------------------------------
|
|
// Edit-mode typed numeric entry swallows all other keys while a field is focused.
|
|
if (editMode && editPanel.editingField != EditField::None) {
|
|
int ch = GetCharPressed();
|
|
while (ch > 0) {
|
|
if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') editPanel.editBuffer += (char)ch;
|
|
ch = GetCharPressed();
|
|
}
|
|
if (IsKeyPressed(KEY_BACKSPACE) && !editPanel.editBuffer.empty()) editPanel.editBuffer.pop_back();
|
|
if (IsKeyPressed(KEY_ENTER)) commitEditField();
|
|
if (IsKeyPressed(KEY_ESCAPE)) { editPanel.editingField = EditField::None; editPanel.editBuffer.clear(); }
|
|
return;
|
|
}
|
|
if (IsKeyPressed(KEY_SPACE) && !phase3Prompt) pauseAction();
|
|
if (IsKeyPressed(KEY_ONE)) { mode = ColorMode::Elevation; recolor(); }
|
|
if (IsKeyPressed(KEY_TWO)) { mode = ColorMode::Plate; recolor(); }
|
|
if (IsKeyPressed(KEY_THREE)) { mode = ColorMode::Age; recolor(); }
|
|
if (IsKeyPressed(KEY_FOUR)) { mode = ColorMode::Crust; recolor(); }
|
|
if (IsKeyPressed(KEY_FIVE)) { mode = ColorMode::Biome; recolor(); }
|
|
if (IsKeyPressed(KEY_SIX)) { // cycle temperature sub-views: mean->summer->winter->seasonality
|
|
mode = (mode == ColorMode::Temperature) ? ColorMode::TempSummer
|
|
: (mode == ColorMode::TempSummer) ? ColorMode::TempWinter
|
|
: (mode == ColorMode::TempWinter) ? ColorMode::Seasonality
|
|
: ColorMode::Temperature;
|
|
recolor();
|
|
}
|
|
if (IsKeyPressed(KEY_SEVEN)) { mode = ColorMode::Precip; recolor(); }
|
|
if (IsKeyPressed(KEY_EIGHT)) { mode = ColorMode::FloraDensity; recolor(); }
|
|
if (IsKeyPressed(KEY_NINE)) { mode = ColorMode::FaunaDensity; recolor(); }
|
|
if (IsKeyPressed(KEY_ZERO)) { mode = ColorMode::FungaDensity; recolor(); }
|
|
if (IsKeyPressed(KEY_B)) showBorders = !showBorders;
|
|
if (IsKeyPressed(KEY_D)) showDrift = !showDrift;
|
|
if (IsKeyPressed(KEY_G)) showGrat = !showGrat;
|
|
if (IsKeyPressed(KEY_J)) showRivers = !showRivers;
|
|
if (IsKeyPressed(KEY_A)) showDiplomacy = !showDiplomacy; // alliance/rivalry arcs with the Territory view
|
|
if (IsKeyPressed(KEY_H)) toggleHydrology();
|
|
if (IsKeyPressed(KEY_L)) generateOrRegenerateBiota();
|
|
if (IsKeyPressed(KEY_E)) toggleEcoregionView();
|
|
if (IsKeyPressed(KEY_I)) toggleHabitabilityView();
|
|
if (IsKeyPressed(KEY_U)) placeOrToggleSettlements();
|
|
if (IsKeyPressed(KEY_P)) toggleTerritoryView();
|
|
if (IsKeyPressed(KEY_X)) toggleCultureView();
|
|
if (IsKeyPressed(KEY_Z)) toggleTradeView();
|
|
if (IsKeyPressed(KEY_W)) enterOrLeaveLiveWorld();
|
|
if (IsKeyPressed(KEY_Y)) cycleFollowStorm();
|
|
if (IsKeyPressed(KEY_N)) dayNightOn = !dayNightOn; // toggle the day/night terminator
|
|
if (IsKeyPressed(KEY_T)) showTides = !showTides; // toggle tide-coloured coastline
|
|
if (IsKeyPressed(KEY_O)) showCurrents = !showCurrents; // toggle ocean current arrows
|
|
if (IsKeyPressed(KEY_K)) showClouds = !showClouds; // toggle weather cloud/rain cover
|
|
if (IsKeyPressed(KEY_V)) showVolcanoes = !showVolcanoes; // toggle volcano markers (Live World)
|
|
if (IsKeyPressed(KEY_M)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) reshuffleNames();
|
|
else toggleNames();
|
|
}
|
|
if (IsKeyPressed(KEY_F3)) toggleEditMode();
|
|
if (IsKeyPressed(KEY_C)) {
|
|
selectedCell = -1; subgrids.clear();
|
|
editPanel.editingField = EditField::None; editPanel.editBuffer.clear(); editPanel.pickerField = EditField::None;
|
|
}
|
|
if (IsKeyPressed(KEY_R)) reseed();
|
|
if (IsKeyPressed(KEY_S)) stepAction();
|
|
// Live World clock stepper: step by one rate-unit (liveRate hours). Forward integrates weather;
|
|
// backward restores the snapshot from the last forward step -> everything (incl. weather +
|
|
// storms) steps back, within the current paused stepping session.
|
|
if (IsKeyPressed(KEY_PERIOD) && liveWorld) { liveStepForward(); setStatus("Step forward"); }
|
|
if (IsKeyPressed(KEY_COMMA) && liveWorld) { liveStepBack(); } // liveStepBack sets its own status
|
|
if (IsKeyPressed(KEY_F)) fastForward();
|
|
if (IsKeyPressed(KEY_F1)) showToolbar = !showToolbar; // additive: toggle the toolbar sidebar
|
|
if (IsKeyPressed(KEY_EQUAL) && cfg.subdivisions < 7) { cfg.subdivisions++; regen(); }
|
|
if (IsKeyPressed(KEY_MINUS) && cfg.subdivisions > 1) { cfg.subdivisions--; regen(); }
|
|
if (IsKeyPressed(KEY_F2)) {
|
|
if (loadConfig(configPath, cfg)) {
|
|
std::string cerr = validateConfig(cfg);
|
|
if (!cerr.empty()) { cfg = PlanetConfig{}; setStatus("Bad config — using defaults"); }
|
|
regen();
|
|
setStatus(cerr.empty() ? std::string("Reloaded ") + configPath : "Bad config — using defaults");
|
|
} else setStatus(std::string("No ") + configPath); }
|
|
if (IsKeyPressed(KEY_F5)) saveGame(SAVE_PATH);
|
|
if (IsKeyPressed(KEY_F9)) loadGame(SAVE_PATH);
|
|
if (IsKeyPressed(KEY_F12)) { TakeScreenshot("screenshot.png"); setStatus("Screenshot saved to screenshot.png"); }
|
|
if (IsKeyPressed(KEY_F11)) {
|
|
if (IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT)) exportAtlasImage();
|
|
else exportMapImage();
|
|
}
|
|
// Speed control: Live World ramps the live clock (sim hours/s, ~hour -> month);
|
|
// otherwise it sets the drift rate (My simulated per real second).
|
|
if (IsKeyPressed(KEY_RIGHT_BRACKET)) {
|
|
if (liveWorld) liveRate = std::min(liveRate * 1.5, liveRateMax());
|
|
else driftRate = std::min(driftRate * 1.5, 80.0);
|
|
}
|
|
if (IsKeyPressed(KEY_LEFT_BRACKET)) {
|
|
if (liveWorld) liveRate = std::max(liveRate / 1.5, 0.25);
|
|
else driftRate = std::max(driftRate / 1.5, 0.5);
|
|
}
|
|
}
|
|
|
|
// Dispatch a click that landed inside the edit panel (editMode && inPanel). drawEditPanel()
|
|
// rebuilt editPanel's hit-rects this same frame; this just resolves which one was hit and calls
|
|
// the matching Planet::setXxx()/setXxxLock() (PlanetEdit.cpp). Order mirrors visual stacking:
|
|
// tabs first, then an open picker (which otherwise swallows the whole panel), then locks/fields/
|
|
// remove buttons.
|
|
void Viewer::handleEditClick() {
|
|
if (selectedCell < 0) return;
|
|
const int cellIdx = selectedCell;
|
|
const int si = (cellIdx < (int)planet.cellSettlement().size()) ? planet.cellSettlement()[cellIdx] : -1;
|
|
|
|
for (size_t i = 0; i < editPanel.tabRects.size(); ++i)
|
|
if (CheckCollisionPointRec(mp, editPanel.tabRects[i])) {
|
|
editPanel.tab = (int)i;
|
|
editPanel.pickerField = EditField::None;
|
|
editPanel.editingField = EditField::None; editPanel.editBuffer.clear();
|
|
return;
|
|
}
|
|
|
|
if (editPanel.pickerField != EditField::None) {
|
|
for (size_t i = 0; i < editPanel.pickerRects.size(); ++i) {
|
|
if (!CheckCollisionPointRec(mp, editPanel.pickerRects[i])) continue;
|
|
int v = editPanel.pickerValues[i];
|
|
switch (editPanel.pickerField) {
|
|
case EditField::PlateId: planet.setPlateId(cellIdx, v); break;
|
|
case EditField::Biome: planet.setBiome(cellIdx, (Biome)v); break;
|
|
case EditField::Allegiance: if (si >= 0) planet.setSettlementAllegiance(si, v); break;
|
|
case EditField::Culture: if (si >= 0) planet.setSettlementCulture(si, v); break;
|
|
case EditField::AddFlora: planet.addOrganism(cellIdx, BiotaKind::Flora, v); break;
|
|
case EditField::AddFauna: planet.addOrganism(cellIdx, BiotaKind::Fauna, v); break;
|
|
case EditField::AddFunga: planet.addOrganism(cellIdx, BiotaKind::Funga, v); break;
|
|
default: break;
|
|
}
|
|
editPanel.pickerField = EditField::None;
|
|
refreshView();
|
|
return;
|
|
}
|
|
return; // inside the panel but not on a picker row: swallow the click, stay open
|
|
}
|
|
|
|
for (size_t i = 0; i < editPanel.lockRects.size(); ++i) {
|
|
if (!CheckCollisionPointRec(mp, editPanel.lockRects[i])) continue;
|
|
EditField f = editPanel.lockIds[i];
|
|
uint16_t bit = 0;
|
|
switch (f) {
|
|
case EditField::LockElevation: bit = LockElevation; break;
|
|
case EditField::LockPlate: bit = LockPlate; break;
|
|
case EditField::LockCrust: bit = LockCrust; break;
|
|
case EditField::LockBiome: bit = LockBiome; break;
|
|
case EditField::LockClimate: bit = LockClimate; break;
|
|
case EditField::LockBiota: bit = LockBiota; break;
|
|
case EditField::LockHabitability: bit = LockHabitability; break;
|
|
case EditField::LockPopulation: bit = LockPopulation; break;
|
|
case EditField::LockAllegiance: bit = LockAllegiance; break;
|
|
case EditField::LockCulture: bit = LockCulture; break;
|
|
default: break;
|
|
}
|
|
bool want = (planet.editLockAt(cellIdx) & bit) == 0; // toggle
|
|
switch (f) {
|
|
case EditField::LockElevation: planet.setElevationLock(cellIdx, want); break;
|
|
case EditField::LockPlate: planet.setPlateLock(cellIdx, want); break;
|
|
case EditField::LockCrust: planet.setCrustLock(cellIdx, want); break;
|
|
case EditField::LockBiome: planet.setBiomeLock(cellIdx, want); break;
|
|
case EditField::LockClimate: planet.setClimateLock(cellIdx, want); break;
|
|
case EditField::LockBiota: planet.setBiotaDensityLock(cellIdx, want); break;
|
|
case EditField::LockHabitability: planet.setHabitabilityLock(cellIdx, want); break;
|
|
case EditField::LockPopulation: if (si >= 0) planet.setPopulationLock(si, want); break;
|
|
case EditField::LockAllegiance: if (si >= 0) planet.setAllegianceLock(si, want); break;
|
|
case EditField::LockCulture: if (si >= 0) planet.setCultureLock(si, want); break;
|
|
default: break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < editPanel.removeRects.size(); ++i) {
|
|
if (!CheckCollisionPointRec(mp, editPanel.removeRects[i])) continue;
|
|
planet.removeOrganism(cellIdx, (BiotaKind)editPanel.removeKind[i], editPanel.removeIndex[i]);
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0; i < editPanel.fieldRects.size(); ++i) {
|
|
if (!CheckCollisionPointRec(mp, editPanel.fieldRects[i])) continue;
|
|
EditField f = editPanel.fieldIds[i];
|
|
if (f == EditField::Crust) {
|
|
planet.setCrust(cellIdx, !planet.cells[cellIdx].oceanic);
|
|
refreshView();
|
|
return;
|
|
}
|
|
if (f == EditField::PlateId || f == EditField::Biome || f == EditField::Allegiance || f == EditField::Culture
|
|
|| f == EditField::AddFlora || f == EditField::AddFauna || f == EditField::AddFunga) {
|
|
editPanel.pickerField = f;
|
|
return;
|
|
}
|
|
// Numeric: focus it for typed entry, seeded with the current value.
|
|
editPanel.editingField = f;
|
|
const Cell& c = planet.cells[cellIdx];
|
|
switch (f) {
|
|
case EditField::Elevation: editPanel.editBuffer = TextFormat("%.0f", c.elevation); break;
|
|
case EditField::GeoAge: editPanel.editBuffer = TextFormat("%.0f", c.geoAge); break;
|
|
case EditField::Temperature: editPanel.editBuffer = TextFormat("%.1f", cellIdx < (int)planet.temperature().size() ? planet.temperature()[cellIdx] : 0.0); break;
|
|
case EditField::Moisture: editPanel.editBuffer = TextFormat("%.0f", cellIdx < (int)planet.moisture().size() ? planet.moisture()[cellIdx] * 100.0 : 0.0); break;
|
|
case EditField::FloraDensity: editPanel.editBuffer = TextFormat("%.0f", cellIdx < (int)planet.floraDensity().size() ? planet.floraDensity()[cellIdx] * 100.0 : 0.0); break;
|
|
case EditField::FaunaDensity: editPanel.editBuffer = TextFormat("%.0f", cellIdx < (int)planet.faunaDensity().size() ? planet.faunaDensity()[cellIdx] * 100.0 : 0.0); break;
|
|
case EditField::FungaDensity: editPanel.editBuffer = TextFormat("%.0f", cellIdx < (int)planet.fungaDensity().size() ? planet.fungaDensity()[cellIdx] * 100.0 : 0.0); break;
|
|
case EditField::Habitability: editPanel.editBuffer = TextFormat("%.0f", cellIdx < (int)planet.habitability().size() ? planet.habitability()[cellIdx] * 100.0 : 0.0); break;
|
|
case EditField::Population: if (si >= 0) editPanel.editBuffer = TextFormat("%.0f", planet.settlements[si].population); break;
|
|
default: break;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Mouse wheel over a hovered numeric field row nudges it by a fixed step, without needing focus.
|
|
// Returns false (and leaves the wheel free for camera zoom) if nothing nudge-able is under the cursor.
|
|
bool Viewer::nudgeEditField(float wheel) {
|
|
if (selectedCell < 0 || editPanel.pickerField != EditField::None) return false;
|
|
const int cellIdx = selectedCell;
|
|
for (size_t i = 0; i < editPanel.fieldRects.size(); ++i) {
|
|
if (!CheckCollisionPointRec(mp, editPanel.fieldRects[i])) continue;
|
|
EditField f = editPanel.fieldIds[i];
|
|
const int si = (cellIdx < (int)planet.cellSettlement().size()) ? planet.cellSettlement()[cellIdx] : -1;
|
|
double dir = (wheel > 0.0f) ? 1.0 : -1.0;
|
|
switch (f) {
|
|
case EditField::Elevation: planet.setElevation(cellIdx, planet.cells[cellIdx].elevation + dir * 100.0); break;
|
|
case EditField::GeoAge: planet.setGeoAge(cellIdx, planet.cells[cellIdx].geoAge + dir * 10.0); break;
|
|
case EditField::Temperature: planet.setTemperature(cellIdx, (cellIdx < (int)planet.temperature().size() ? planet.temperature()[cellIdx] : 0.0) + dir * 1.0); break;
|
|
case EditField::Moisture: planet.setMoisture(cellIdx, (cellIdx < (int)planet.moisture().size() ? planet.moisture()[cellIdx] : 0.0) + dir * 0.05); break;
|
|
case EditField::FloraDensity: planet.setFloraDensity(cellIdx, (cellIdx < (int)planet.floraDensity().size() ? planet.floraDensity()[cellIdx] : 0.0) + dir * 0.05); break;
|
|
case EditField::FaunaDensity: planet.setFaunaDensity(cellIdx, (cellIdx < (int)planet.faunaDensity().size() ? planet.faunaDensity()[cellIdx] : 0.0) + dir * 0.05); break;
|
|
case EditField::FungaDensity: planet.setFungaDensity(cellIdx, (cellIdx < (int)planet.fungaDensity().size() ? planet.fungaDensity()[cellIdx] : 0.0) + dir * 0.05); break;
|
|
case EditField::Habitability: planet.setHabitability(cellIdx, (cellIdx < (int)planet.habitability().size() ? planet.habitability()[cellIdx] : 0.0) + dir * 0.05); break;
|
|
case EditField::Population: if (si >= 0) planet.setSettlementPopulation(si, planet.settlements[si].population * (1.0 + dir * 0.1)); break;
|
|
default: return false; // not a nudge-able field (Crust/PlateId/Biome/Allegiance/Culture/Add*)
|
|
}
|
|
refreshView();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Parse editPanel.editBuffer and apply it to the currently focused field (Enter, or a field
|
|
// switch). Silently discards on a bad/empty number, matching a text-field's usual "just stop
|
|
// editing" behaviour rather than raising an error for a hand-rolled widget.
|
|
void Viewer::commitEditField() {
|
|
if (selectedCell < 0 || editPanel.editingField == EditField::None) {
|
|
editPanel.editingField = EditField::None; editPanel.editBuffer.clear(); return;
|
|
}
|
|
double v = 0.0;
|
|
try { v = std::stod(editPanel.editBuffer); }
|
|
catch (...) { editPanel.editingField = EditField::None; editPanel.editBuffer.clear(); return; }
|
|
const int cellIdx = selectedCell;
|
|
const int si = (cellIdx < (int)planet.cellSettlement().size()) ? planet.cellSettlement()[cellIdx] : -1;
|
|
switch (editPanel.editingField) {
|
|
case EditField::Elevation: planet.setElevation(cellIdx, v); break;
|
|
case EditField::GeoAge: planet.setGeoAge(cellIdx, v); break;
|
|
case EditField::Temperature: planet.setTemperature(cellIdx, v); break;
|
|
case EditField::Moisture: planet.setMoisture(cellIdx, v / 100.0); break;
|
|
case EditField::FloraDensity: planet.setFloraDensity(cellIdx, v / 100.0); break;
|
|
case EditField::FaunaDensity: planet.setFaunaDensity(cellIdx, v / 100.0); break;
|
|
case EditField::FungaDensity: planet.setFungaDensity(cellIdx, v / 100.0); break;
|
|
case EditField::Habitability: planet.setHabitability(cellIdx, v / 100.0); break;
|
|
case EditField::Population: if (si >= 0) planet.setSettlementPopulation(si, v); break;
|
|
default: break;
|
|
}
|
|
editPanel.editingField = EditField::None; editPanel.editBuffer.clear();
|
|
refreshView();
|
|
}
|