stop hovering when the cursor points at no tile
A cursor resting on a panel or outside the window kept whatever it last pointed at: the ghost, the tunnel preview, the deconstruct tint all stayed put, because "not hovering" was not a state the build mode could hold. Make both ghost tiles optional, clear the hover with them, and re-derive it when the cursor comes back or a mode is entered. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
This commit is contained in:
@@ -295,19 +295,12 @@ void GameWorldView::onFrame()
|
||||
const bool viewMoved =
|
||||
m_camera.advance(m_panDirection, elapsed, getScrollBounds());
|
||||
|
||||
// While the view scrolls, the world position under a stationary cursor
|
||||
// changes, so the hover state is refreshed even though no mouse move fires.
|
||||
if (viewMoved)
|
||||
// Two things no mouse move reports: the world position under a stationary
|
||||
// cursor changing as the view scrolls, and the cursor crossing onto a panel
|
||||
// or out of the window, which ends the hover (REQ-BLD-GHOST).
|
||||
if (viewMoved || isHoverLive() != m_hoverLive)
|
||||
{
|
||||
const QPoint cursorWidgetPos = mapFromGlobal(QCursor::pos());
|
||||
// A running box drag holds the button and follows the cursor wherever it
|
||||
// is, the floating panels included; hover state only tracks a cursor over
|
||||
// the world, so scrolling past a cursor resting on a panel leaves the
|
||||
// ghost where it was (REQ-UI-CONTROLS-PANEL).
|
||||
if (m_boxSelecting || underMouse())
|
||||
{
|
||||
updateHoverAt(cursorWidgetPos);
|
||||
}
|
||||
refreshHover();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,8 +750,10 @@ void GameWorldView::transferConfigTo(BuildingId id, const BlueprintBuilding& sou
|
||||
void GameWorldView::updateTunnelGhost()
|
||||
{
|
||||
// The connection preview and entry/exit switch only apply at a valid placement
|
||||
// (REQ-BLD-TUNNEL-MODE); at an invalid position the ghost stays a plain entry.
|
||||
if (!m_buildMode.isGhostValid())
|
||||
// (REQ-BLD-TUNNEL-MODE); at an invalid position, and where the cursor points at
|
||||
// no tile at all, the ghost stays a plain entry.
|
||||
const std::optional<QPoint>& ghostTile = m_buildMode.getGhostTile();
|
||||
if (!ghostTile.has_value() || !m_buildMode.isGhostValid())
|
||||
{
|
||||
m_buildMode.setTunnelGhost(BuildingType::TunnelEntry, std::nullopt);
|
||||
return;
|
||||
@@ -768,7 +763,7 @@ void GameWorldView::updateTunnelGhost()
|
||||
const TunnelLookup lookup = makeTunnelLookup(tunnels);
|
||||
|
||||
const TunnelCompletion completion =
|
||||
resolveTunnelCompletion(lookup, m_buildMode.getGhostTile(),
|
||||
resolveTunnelCompletion(lookup, *ghostTile,
|
||||
m_buildMode.getGhostRotation(),
|
||||
m_config->world.tunnelMaxDistance_tiles, m_cursorWorldPos);
|
||||
m_buildMode.setTunnelGhost(completion.resolvedType, completion.partnerTile);
|
||||
@@ -1335,8 +1330,35 @@ void GameWorldView::selectInBox(bool additive)
|
||||
if (!additive) { m_selection.clearAll(); }
|
||||
}
|
||||
|
||||
bool GameWorldView::isHoverLive() const
|
||||
{
|
||||
// underMouse() is false while the cursor sits on one of the floating panels,
|
||||
// which are siblings of this widget rather than children, and while it is
|
||||
// outside the window. A drag holding the button is the exception: it tracks the
|
||||
// cursor wherever it goes until the button comes back up (REQ-UI-MULTI-SELECT,
|
||||
// REQ-BLD-BELT-DRAG).
|
||||
return underMouse() || m_boxSelecting || m_buildMode.isDraggingBelt();
|
||||
}
|
||||
|
||||
void GameWorldView::refreshHover()
|
||||
{
|
||||
if (isHoverLive())
|
||||
{
|
||||
updateHoverAt(mapFromGlobal(QCursor::pos()));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_buildMode.clearHover();
|
||||
m_hoverLive = false;
|
||||
}
|
||||
}
|
||||
|
||||
void GameWorldView::updateHoverAt(QPoint cursorWidgetPos)
|
||||
{
|
||||
// Reached either from a mouse move, which only this widget receives, or from a
|
||||
// hover refresh that has already established the cursor is on the world.
|
||||
m_hoverLive = true;
|
||||
|
||||
const WorldCoordinates coordinates = getCoordinates();
|
||||
const QPoint tile = coordinates.widgetToTile(cursorWidgetPos);
|
||||
m_cursorWorldPos = coordinates.widgetToWorld(cursorWidgetPos);
|
||||
@@ -1555,8 +1577,14 @@ void GameWorldView::rotateGhost(bool clockwise)
|
||||
if (m_buildMode.isBuilderMode())
|
||||
{
|
||||
m_buildMode.rotateGhost(clockwise);
|
||||
|
||||
// The new facing is kept whatever the cursor is over; what it means for the
|
||||
// world is only re-resolved while the cursor points at a tile (REQ-BLD-GHOST).
|
||||
const std::optional<QPoint>& ghostTile = m_buildMode.getGhostTile();
|
||||
if (!ghostTile.has_value()) { return; }
|
||||
|
||||
m_buildMode.setGhostValidity(
|
||||
canPlaceBuildingHere(m_buildMode.getBuilderType(), m_buildMode.getGhostTile(),
|
||||
canPlaceBuildingHere(m_buildMode.getBuilderType(), *ghostTile,
|
||||
m_buildMode.getGhostRotation()));
|
||||
// A new facing changes which tunnels the ghost could complete (REQ-BLD-TUNNEL-MODE).
|
||||
if (m_buildMode.isTunnelMode()) { updateTunnelGhost(); }
|
||||
@@ -1564,7 +1592,7 @@ void GameWorldView::rotateGhost(bool clockwise)
|
||||
// without waiting for the next mouse move (REQ-BLD-BELT-DRAG).
|
||||
if (m_buildMode.isDraggingBelt())
|
||||
{
|
||||
recomputeBeltDragPath(m_buildMode.getGhostTile());
|
||||
recomputeBeltDragPath(*ghostTile);
|
||||
}
|
||||
}
|
||||
else if (m_buildMode.isBlueprintMode())
|
||||
@@ -1694,6 +1722,10 @@ void GameWorldView::handleEvent(std::shared_ptr<const BeamFiredEvent> event)
|
||||
void GameWorldView::handleEvent(std::shared_ptr<const BuildingTypeSelectedEvent> event)
|
||||
{
|
||||
m_buildMode.enterBuilderMode(event->type);
|
||||
// A mode entered by hotkey usually leaves the cursor exactly where it was, and no
|
||||
// mouse move follows to place the ghost; entered from a build button it leaves the
|
||||
// cursor on the bar, where there is nothing to hover (REQ-BLD-GHOST).
|
||||
refreshHover();
|
||||
}
|
||||
|
||||
void GameWorldView::handleEvent(std::shared_ptr<const ExitBuilderModeRequestedEvent> /*event*/)
|
||||
@@ -1704,11 +1736,13 @@ void GameWorldView::handleEvent(std::shared_ptr<const ExitBuilderModeRequestedEv
|
||||
void GameWorldView::handleEvent(std::shared_ptr<const DeconstructModeToggleRequestedEvent> /*event*/)
|
||||
{
|
||||
m_buildMode.toggleDeconstructMode();
|
||||
refreshHover();
|
||||
}
|
||||
|
||||
void GameWorldView::handleEvent(std::shared_ptr<const BlueprintPlacementRequestedEvent> event)
|
||||
{
|
||||
m_buildMode.enterBlueprintMode(event->blueprint);
|
||||
refreshHover();
|
||||
}
|
||||
|
||||
void GameWorldView::handleEvent(std::shared_ptr<const ExitBlueprintModeRequestedEvent> /*event*/)
|
||||
|
||||
Reference in New Issue
Block a user