Repository: godot
Issue: Setting window position right after size, if resizing took time, may cause window.position to be stuck to previous value 1 frame later
Labels: bug,platform:linuxbsd,topic:porting

### Tested versions

- Reproducible in v4.2.1.stable.official [b09f793f5]

### System information

Godot v4.2.1.stable - Ubuntu 22.04.4 LTS 22.04 - X11 - GLES3 (Compatibility) - Mesa Intel(R) HD Graphics 4600 (HSW GT2) () - Intel(R) Core(TM) i7-4710HQ CPU @ 2.50GHz (8 Threads)

### Issue description

Setting a Window's position just after setting its size (i.e. a just below in code, without waiting extra frames) sometimes cause the `window.position` to oddly reset to its previous value 1 frame later (and stay like this), causing further usage of `window.position` to fail.

This may only happen on slower systems. I could not reproduce on an empty scene, but adding a bunch of sprites in my scene I managed to slow down window resize just enough to reproduce the bug if I spam window resize many times.

(it originally happened on a real game with many sprites)

This can be observed with this kind of code:

```
# calculate wanted new_window_size
# calculate wanted new_window_position
var window := get_window()
window.size = new_window_size
window.position = new_window_position
```

The MWE contains a more complex version of it that sets the window position precisely to preserve the previous window center, since setting window.size will preserve its top-left (it's a real case usage), as well as various parameters to use as workarounds, described below.

**Workarounds**

a. I tried to wait a few frames before setting the window position. It worked, but the more complex the scene / slower the computer/system window manager, the higher you need to wait (up to 4-5 frames). In the MWE, this is tuned with parameter `safety_frames_between_scale_and_reposition_window`

b. I tried to force move the window by 1px and then back, it reduces odds that window.position becomes invalid later, but doesn't work at 100%. In the MWE, this is done by checking flag `plus_minus_trick`

c. A brutal custom solution is to store the window position directly by code, with MWE flag `use_stored_last_window`. This works at 100% since it doesn't rely on `window.position` anymore except on the first frame to initialise the stored position. Then everyone is done via position math and updating the stored position. Currently it doesn't support user manually moving the window (it would reset window to old position on next resize) but it could be done with _process detecting any window position change.

d. Finally, the solution was to avoid setting position *after* size altogether. Setting it *before* size avoids the bug entirely. This is done with the flag `set_size_after_position` in MWE. This is the easiest, works 100%, avoids lag and supports user moving window manually, I recommend this in production.

EDIT: workaround d. has a flaw: if you switch from a big window to a smaller window, and want to recenter it by moving it toward the bottom-right of the screen, moving it first toward bottom-right as a big window will make it hit the window corner and be clamped. Then the window will be resized down but end up more toward top-left than it should be. I tried to set size *before* and *after* position, but then it retriggers the bug we were trying to avoid in the first place. It seems that the only reliable workaround is to wait frames (a.) but it loses too much in responsiveness, so right now I still prefer d. with this known imperfection... User will have to manually drag the window back.

### Steps to reproduce

1. Open MWE demo scene
2. Run the game
3. Press 1 & 3 (or Y and I) repeatedly to resize window to x1 and x3 scaling (also 2 or U for x2 but I recommend testing extreme scales to see things better)
4. Continue until the window changes center position
5. Observe in Output log a message like:

> Detected invalid window.position after 1 frame(s): window.position: (1072, 597) VS last_window_position: (1232, 687)
last_window_position != previous_window_position

This shows that window.position reverted to an old value, different from the stored value.

6. Repeat 2-3 after setting each of the workaround parameters. Select the Demo node > Inspector > edit custom fields. We recommend testing one workaround at a time.
7. Set `safety_frames_between_scale_and_reposition_window` to 5 (this will disable invalid window position detection due to frame lag, so the absence of error doesn't mean it's OK - you should look at the window instead and verify that it stopped moving center). Lower values may allow window to keep moving center from times to times...
8. Check `plus_minus_trick` > error occurs less frequently
9. Check `use_stored_last_window` > error is fixed but if user moves window manually, position will be restored on next resize
10. Check `set_size_after_position` for the ultimate fix: no bug anymore

### Minimal reproduction project (MRP)

[v4.2.1 - Move window right after resize complex scene fails.zip](https://github.com/godotengine/godot/files/14968318/v4.2.1.-.Move.window.right.after.resize.complex.scene.fails.zip)