2 Commits

Author SHA1 Message Date
3ce6e6d599 Tint not-yet-buildable asteroid area 2026-07-13 21:42:59 +02:00
80a2622267 Redefine camera scroll as view center 2026-07-13 21:31:52 +02:00
5 changed files with 34 additions and 9 deletions

View File

@@ -349,6 +349,7 @@ selection_rect = "#00ff00" # box-drag selection rectangle (REQ-UI-MULTI-SELE
tile_highlight = "#ffffff22" # tile under cursor tile_highlight = "#ffffff22" # tile under cursor
selected_outline = "#ffff00" # outline drawn around currently-selected building(s) selected_outline = "#ffff00" # outline drawn around currently-selected building(s)
copy_config = "#33ccff66" # copy-settings eligible-target tint + copy/paste flash (REQ-BLD-COPY-CONFIG-FEEDBACK) copy_config = "#33ccff66" # copy-settings eligible-target tint + copy/paste flash (REQ-BLD-COPY-CONFIG-FEEDBACK)
locked_asteroid = "#0000007f" # tint over the asteroid left of the buildable edge (not yet unlocked by expansion)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Schematic-drop toasts (REQ-UI-SCHEMATIC-TOAST) # Schematic-drop toasts (REQ-UI-SCHEMATIC-TOAST)

View File

@@ -293,7 +293,7 @@ void GameWorldView::onFrame()
// Apply held scroll // Apply held scroll
{ {
// Pan speed depends on where the view is centered (REQ-UI-SCROLL-SPEED). // Pan speed depends on where the view is centered (REQ-UI-SCROLL-SPEED).
const float viewCenterX = m_scrollXTiles + viewportWidthTiles() / 2.0f; const float viewCenterX = m_scrollXTiles;
const float delta = panSpeedTilesPerSecondAt(viewCenterX) const float delta = panSpeedTilesPerSecondAt(viewCenterX)
* static_cast<float>(elapsed) / 1000.0f; * static_cast<float>(elapsed) / 1000.0f;
const float scrollBefore = m_scrollXTiles; const float scrollBefore = m_scrollXTiles;
@@ -435,10 +435,15 @@ float GameWorldView::viewportWidthTiles() const
return static_cast<float>(width()) / tilePx(); return static_cast<float>(width()) / tilePx();
} }
float GameWorldView::viewLeftTiles() const
{
return m_scrollXTiles - viewportWidthTiles() / 2.0f;
}
QPointF GameWorldView::worldToWidget(QVector2D worldPos) const QPointF GameWorldView::worldToWidget(QVector2D worldPos) const
{ {
return QPointF( return QPointF(
static_cast<qreal>((worldPos.x() - m_scrollXTiles) * tilePx()), static_cast<qreal>((worldPos.x() - viewLeftTiles()) * tilePx()),
static_cast<qreal>(worldPos.y() * tilePx())); static_cast<qreal>(worldPos.y() * tilePx()));
} }
@@ -450,14 +455,14 @@ QPointF GameWorldView::tileToWidget(QPoint tile) const
QPoint GameWorldView::widgetToTile(QPoint widgetPt) const QPoint GameWorldView::widgetToTile(QPoint widgetPt) const
{ {
const float wx = static_cast<float>(widgetPt.x()) / tilePx() + m_scrollXTiles; const float wx = static_cast<float>(widgetPt.x()) / tilePx() + viewLeftTiles();
const float wy = static_cast<float>(widgetPt.y()) / tilePx(); const float wy = static_cast<float>(widgetPt.y()) / tilePx();
return QPoint(static_cast<int>(std::floor(wx)), static_cast<int>(std::floor(wy))); return QPoint(static_cast<int>(std::floor(wx)), static_cast<int>(std::floor(wy)));
} }
QVector2D GameWorldView::widgetToWorld(QPoint widgetPt) const QVector2D GameWorldView::widgetToWorld(QPoint widgetPt) const
{ {
const float wx = static_cast<float>(widgetPt.x()) / tilePx() + m_scrollXTiles; const float wx = static_cast<float>(widgetPt.x()) / tilePx() + viewLeftTiles();
const float wy = static_cast<float>(widgetPt.y()) / tilePx(); const float wy = static_cast<float>(widgetPt.y()) / tilePx();
return QVector2D(wx, wy); return QVector2D(wx, wy);
} }
@@ -471,9 +476,9 @@ QRectF GameWorldView::tileRect(QPoint tile) const
QRect GameWorldView::viewportRect() const QRect GameWorldView::viewportRect() const
{ {
const int left = static_cast<int>(std::floor(m_scrollXTiles)) - 1; const int left = static_cast<int>(std::floor(viewLeftTiles())) - 1;
const int top = 0; const int top = 0;
const int right = static_cast<int>(std::ceil(m_scrollXTiles + viewportWidthTiles())) + 1; const int right = static_cast<int>(std::ceil(viewLeftTiles() + viewportWidthTiles())) + 1;
const int bottom = m_config->world.heightTiles; const int bottom = m_config->world.heightTiles;
return QRect(left, top, right - left, bottom - top); return QRect(left, top, right - left, bottom - top);
} }
@@ -546,8 +551,11 @@ float GameWorldView::panSpeedTilesPerSecondAt(float viewCenterXTiles) const
void GameWorldView::clampScroll() void GameWorldView::clampScroll()
{ {
// m_scrollXTiles is the view center, so the pan limits are the edges themselves:
// the view can pan left until the buildable/asteroid edge is centered, and right
// until the enemy stations are centered (revealing a little space beyond them).
const float leftBound = asteroidLeftEdge(); const float leftBound = asteroidLeftEdge();
const float rightBound = enemyStationRightEdge() - viewportWidthTiles(); const float rightBound = enemyStationRightEdge();
m_scrollXTiles = std::max(leftBound, std::min(m_scrollXTiles, rightBound)); m_scrollXTiles = std::max(leftBound, std::min(m_scrollXTiles, rightBound));
} }
@@ -906,19 +914,29 @@ void GameWorldView::drawPortGlyph(QPainter& painter, QPoint bodyTile,
void GameWorldView::drawTiles(QPainter& painter) void GameWorldView::drawTiles(QPainter& painter)
{ {
const int leftTile = static_cast<int>(std::floor(m_scrollXTiles)) - 1; const int leftTile = static_cast<int>(std::floor(viewLeftTiles())) - 1;
const int rightTile = leftTile + static_cast<int>(std::ceil(viewportWidthTiles())) + 2; const int rightTile = leftTile + static_cast<int>(std::ceil(viewportWidthTiles())) + 2;
const int bottomTile = m_config->world.heightTiles; const int bottomTile = m_config->world.heightTiles;
// Asteroid columns left of the buildable edge are not yet unlocked by
// expansion; tint them so the player sees the reachable-but-locked area.
const int buildableLeftX = -m_sim->currentAsteroidWidth_tiles();
painter.setPen(Qt::NoPen); painter.setPen(Qt::NoPen);
for (int x = leftTile; x <= rightTile; ++x) for (int x = leftTile; x <= rightTile; ++x)
{ {
const QColor& fill = (x < 0) const QColor& fill = (x < 0)
? m_visuals->asteroid.fill ? m_visuals->asteroid.fill
: m_visuals->space.fill; : m_visuals->space.fill;
const bool locked = (x < buildableLeftX);
for (int y = 0; y < bottomTile; ++y) for (int y = 0; y < bottomTile; ++y)
{ {
painter.fillRect(tileRect(QPoint(x, y)), fill); const QRectF rect = tileRect(QPoint(x, y));
painter.fillRect(rect, fill);
if (locked)
{
painter.fillRect(rect, m_visuals->overlays.lockedAsteroid);
}
} }
} }
} }

View File

@@ -134,6 +134,9 @@ private:
float tilePx() const; float tilePx() const;
float viewportWidthTiles() const; float viewportWidthTiles() const;
// World-X (tiles) at the left edge of the viewport. m_scrollXTiles stores the
// view center; this derives the left edge the world<->widget conversions need.
float viewLeftTiles() const;
QPointF worldToWidget(QVector2D worldPos) const; QPointF worldToWidget(QVector2D worldPos) const;
QPointF tileToWidget(QPoint tile) const; QPointF tileToWidget(QPoint tile) const;
QPoint widgetToTile(QPoint widgetPt) const; QPoint widgetToTile(QPoint widgetPt) const;
@@ -218,6 +221,7 @@ private:
std::mt19937 m_rng; std::mt19937 m_rng;
double m_gameSpeedMultiplier; double m_gameSpeedMultiplier;
double m_prevNonZeroSpeed; double m_prevNonZeroSpeed;
// World-X (tiles) at the center of the viewport (see viewLeftTiles()).
float m_scrollXTiles; float m_scrollXTiles;
QTimer* m_renderTimer; QTimer* m_renderTimer;

View File

@@ -49,6 +49,7 @@ struct OverlayVisuals
QColor tileHighlight; QColor tileHighlight;
QColor selectedOutline; QColor selectedOutline;
QColor copyConfig; QColor copyConfig;
QColor lockedAsteroid;
}; };
struct ToastVisuals struct ToastVisuals

View File

@@ -225,6 +225,7 @@ VisualsConfig VisualsLoader::load(const std::string& path)
cfg.overlays.tileHighlight = parseColor(requireString(ov, "tile_highlight", "overlays"), "overlays.tile_highlight"); cfg.overlays.tileHighlight = parseColor(requireString(ov, "tile_highlight", "overlays"), "overlays.tile_highlight");
cfg.overlays.selectedOutline = parseColor(requireString(ov, "selected_outline", "overlays"), "overlays.selected_outline"); cfg.overlays.selectedOutline = parseColor(requireString(ov, "selected_outline", "overlays"), "overlays.selected_outline");
cfg.overlays.copyConfig = parseColor(requireString(ov, "copy_config", "overlays"), "overlays.copy_config"); cfg.overlays.copyConfig = parseColor(requireString(ov, "copy_config", "overlays"), "overlays.copy_config");
cfg.overlays.lockedAsteroid = parseColor(requireString(ov, "locked_asteroid", "overlays"), "overlays.locked_asteroid");
} }
// Toast // Toast