Files
OpenFrontIO/src/client/graphics
VariableVince 496f1008bb Fix: icons structure icons and others at wrong location (#3453)
## Description:

Fix for all structure icons suddenly being displaced from the actual
structure location. And in some cases, structure itself created at wrong
location, or coordinate grid, nuke trajectory preview target, nuke
circles, naval landing target, or pop-up text of gold earned from train
or tradeship.

- Was triggered on iOS almost exclusively, but was also possible on
other devices when a top/left margin was present. Like when an ad was
shown. Why noticed almost only on iOS? Because of different behaviors
where it shifts the DOM elements around relative to the screen
temporarily, so we get a top/left offset on getBoundingClientRect for
the canvas. Possible culprit is overscroll which lets you scroll outside
of the viewport for several hundred pixels before snapping back. Which
was triggered by mistake by dragging instead of tapping somewhere, or
so.

- Some of the bug reports:
https://discord.com/channels/1284581928254701718/1451393982159523982,
https://discord.com/channels/1284581928254701718/1463548362526822649,
https://discord.com/channels/1284581928254701718/1378672255336189964,
fixes https://github.com/openfrontio/OpenFrontIO/issues/3406

- The fix brings a little performance win as well because we need to be
doing less calculations. It is basically "if drawing on a canvas, work
with canvas coordinates and not with screen coordinates". Was stress
tested by two players and me, see below for reproduction.

- (BTW. When researching if the current logic was intended in any way, I
found CodeRabbit had already noticed part of the bug twice. One of them
was
[resolved](https://github.com/openfrontio/OpenFrontIO/pull/2059#discussion_r2396277710),
the other [left
open](https://github.com/openfrontio/OpenFrontIO/pull/2059#discussion_r2370413213).
Another reminder that we need to heed Rabbits calls!)

**CONTAINS**
- StructureIconsLayer > computeNewLocation and StructureDrawingUtils >
createUnitContainer. In renderLayer, when TransformHandler.hasChanged
(after onZoom, goTo, onMove), computeNewLocation recalculates the
position of all structure icons. Sometimes all icons would suddenly be
displaced, not above their map position but somewhere else. New single
icons/levels/sprites would be placed wrongly too by computeNewLocation
and createUnitContainer.
-- Fix: don't use TransformHandler > worldToScreenCoordinates but the
new worldToCanvasCoordinates. Because worldToScreenCoordinates adds the
canvas boundingRect top/left offset. When the main canvas is already
shifted down with a margin, the icons also get shifted within the
canvas. So they're moved twice as much as they should be. We only need
to know at what canvas location to put the icons, the main GameRender
canvas already handles the overall displacement.

- TransformHandler > worldToCanvasCoordinates
-- Use the new worldToCanvasCoordinates too instead of
worldToScreenCoordinates in CoordinateGridLayer, MoveIncicatorUI,
NavalTarget, NukeTeleGraph, TextIndicator. They were affected by the
same bug as the shifting Structure icons because the boundingRect
top/left offset was applied twice, but it was noticed less. I have seen
reports of NavalTarget or MobveIndicatorUI (for warships) not being in
the correct spot though iirc. And just like for
StructureIconsLayer/StructureDrawingUtils, it's only logical. Since
we're drawing on the canvas, we only need to know where to place things
within that canvas.

- TransformHandler > worldToScreenCoordinates
-- Split in two sub-functions. New seperate function
worldToCanvasCoordinates was needed for the above fix. For
canvasToScreenCoordinates the reason is explained below.

- TransformHandler > screenToWorldCoordinates: this function already
subtracts the canvas boundingRect top/left offset. Some callers were
themselves getting the boundaryRect and subtracting top/left, before
calling screenToWorldCoordinates. Not only unnecessary. But also, when
there was more than 0 top/left offset, it would be subtracted twice from
the mouse/tap position. Meaning a (ghost) structure or nuke trajectory
preview target would not be put where the mouse/tap was. Same bug as
above but reversed.
-- Checked all callers. Most did it right. Fixes where needed in
StructureIconsLayer > createStructure and renderGhost, and in
NukeTrajectoryPreviewLayer > updateTrajectoryPreview and
updateTrajectoryPath.
-- Removed comment in screenToWorldCoordinates that talked about zoom.
It doesn't belong there because we do more than zooming there, it was
probably copied once from onZoom() which has the exact same comment and
it belongs in that function.
-- Small fix in caller BuildMenu when checking all callers of
screenToWorldCoordinates: it checked if clickedCell was null, but
screenToWorldCoordinates never returns null.

- TransformHandler > added public helper functions
screenToCanvasCoordinates and canvasToScreenCoordinates to make code
re-usable
-- screenToCanvasCoordinates is used in screenToWorldCoordinates and
onZoom in TransformHandler itself
-- screenToCanvasCoordinates is now also used also in moveGhost and
createGhostStructure in StructureIconsLayer. No bugs there, but the same
calculation was done (getting boundingRect, subtracting left/top from
mouse/tap position). So they now use the centralized function which also
adds to their readability.
-- canvasToScreenCoordinates is (for now) only used in
worldToScreenCoordinates in TransformHandler. It makes the function more
readable imo. And, since it has such a similar calculation to
screenToWorldCoordinates, it only seemed neat to have them both have
their own function.

**BEFORE** (only showing "all structure icons get displaced", but the
cause and fix is basically the same for all)
https://youtu.be/CfDdUwIRQeE

**AFTER**
https://youtu.be/w5w_wvh5V0g 

## Please complete the following:

- [x] I have added screenshots for all UI updates
- [x] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [x] I have added relevant tests to the test directory
- [x] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

tryout33
2026-03-23 16:27:46 -07:00
..
2026-01-12 20:56:17 -08:00