Repository: godot
Issue: Control.PivotOffset assignment is destructive with existing Rotation or Scale
Labels: documentation,topic:gui

### Tested versions

4.3.1-rc [ff9bc0422], custom build (`scons module_mono_enabled=yes debug_symbols=yes ccflags="-fvar-tracking -fvar-tracking-assignments"`)

### System information

Godot v4.3.1.rc.mono (ff9bc0422) - Debian GNU/Linux trixie/sid trixie - X11 - Vulkan (Mobile) - dedicated NVIDIA GeForce RTX 4060 Laptop GPU - 13th Gen Intel(R) Core(TM) i7-13650HX (20 Threads)

### Issue description

Assigning to a `Control.PivotOffset` will change the `Control`'s transform immediately if a non-default `Scale` or `Rotation` is in effect, rather than updating the other properties to keep the `Transform` constant while `PivotOffset` changes.

The small attached project lets one interactively compare what currently happens, versus what I expected to happen.

Controls:
 - Scrollwheel scales the Godot logo
 - Shift+Scrollwheel rotates the Godot logo
 - Drag pans it around

Uncheck `TestingContainer`'s `"Use workaround"` property in the Editor to see the current behavior.

This is the implementation of the workaround for what I believe is more developer-friendly behaviour, which just updates the `Control`'s position to preserve the current transform, in a way I tried to make forwards-compatible:

```cs
    private static void ChangePivotOffset(Control control, Vector2 newPivot) {
        /*
         * Scale and Rotation will correctly apply themselves relative to the
         * pivot offset, so they can be freely set without altering other
         * properties.
         *
         * If there is no Scale or Rotation set, then PivotOffset can be set
         * non-destructively; the net transform will not change, and the
         * Control will not move.
         *
         * However, if Scale or Rotation is set, then changing the PivotOffset
         * will move the Control, as Scale, Rotation, and Position values are
         * kept constant, and Scale and Rotation are applied relative to the
         * PivotOffset, leading to different results.
         *
         * This function attempts to change the PivotOffset without moving the
         * Control, or otherwise causing any change in the overall Transform,
         * by adjusting the Position of the Control to compensate for the
         * change in the PivotOffset.
         */
        GD.Print("Control position before:", control.Position);
        Vector2 originPre = control.GetTransform().Origin;
        control.PivotOffset = newPivot;
        Vector2 originPost = control.GetTransform().Origin;
        GD.Print("Origin change:", originPre, " -> ", originPost, " net: ", originPost - originPre);
        GD.Print("Control position after:", control.Position);
        control.Position -= originPost - originPre;
        GD.Print("Control position after correction:", control.Position);
    }
```

I'm happy to implement this in C++, if someone can show me where to look. There's currently a `Control::_edit_set_pivot` method which does some adjustment to `Position` and might be equivalent, but a breakpoint on it never seems to be hit, only `Control::set_pivot_offset`. I could also try implementing this as a new `Control` method, if the existing behaviour is likely to be relied on?

Possibly related: #52383


### Steps to reproduce

- Create a `Control`, and assign a non-default `Scale` and/or `Rotation`
- Assign a different value to PivotOffset
- `Position`, `Scale`, and `Rotation` are unchanged, but given that `Scale` and `Rotation` take effect from the new `PivotOffset`, the Control's transform has changed.

### Minimal reproduction project (MRP)

[test-pivot.zip](https://github.com/user-attachments/files/18094821/test-pivot.zip)