From 0f40d61bb81a41449ec007a45d296e828cd490f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Apr 2026 16:03:07 +0200 Subject: [PATCH] Fix skin editor origin dropdown options applying origin in wrong coordinate space (#37219) Closes https://github.com/ppy/osu/issues/37185. The checkboxes in the context menu's ternary states were supposed to always show the origin "in local space", even if anchor is set to "closest". The issue here was reusing a method that only really made sense with closest anchor active for explicit application of "local-space" origin. To recap: - Below I will use concepts of "local-space origin" and "screen-space origin". To understand the difference, let's use an example: Say there's a drawable with 180 degree rotation. Suppose it has the "local origin" of `TopCentre`. The "local origin" is just the `Drawable` notion of origin; you'd literally set `d.Origin = Anchor.TopCentre`. The "screen-space origin" of this drawable is `BottomCentre`, because due to the rotation, that's how the component will visually behave when its position is altered. The same sort of distinction applies forth to things like flips / negative scale and such. - When you have closest anchor selected, you can only choose the anchor to snap to. The drawable will snap to that anchor, and choose an origin closest to it *in screen space* such that the "closest" in "closest anchor" works as users would expect it to. In this state, if you open the context menu for origin, all items will be disabled, but the ternary menu items will show the origin state *as translated back to local space*. - When you have an explicit anchor selected, you can choose both the anchor and origin. In that case, the origin picked is always picked in local space. In the end, this is all consistent with how the `Origin` property on `Drawable` works, and also with what is serialised to skin jsons. --- .../SkinEditor/SkinSelectionHandler.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 838b5ff2f0..f01533641e 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.SkinEditor var closest = getClosestAnchor(drawable); applyAnchor(drawable, closest); - applyOrigin(drawable, closest); + applyScreenSpaceOrigin(drawable, closest); } protected override void OnSelectionChanged() @@ -236,10 +236,7 @@ namespace osu.Game.Overlays.SkinEditor { var drawable = (Drawable)item; - applyOrigin(drawable, origin); - - if (!item.UsesFixedAnchor) - ApplyClosestAnchorOrigin(drawable); + applyLocalSpaceOrigin(drawable, origin); } OnOperationEnded(); @@ -320,7 +317,17 @@ namespace osu.Game.Overlays.SkinEditor drawable.Position -= drawable.AnchorPosition - previousAnchor; } - private static void applyOrigin(Drawable drawable, Anchor screenSpaceOrigin) + private static void applyLocalSpaceOrigin(Drawable drawable, Anchor localSpaceOrigin) + { + if (localSpaceOrigin == drawable.Origin) + return; + + Vector2 offset = drawable.ToParentSpace(localSpaceOrigin.PositionOnQuad(drawable.DrawRectangle)) - drawable.ToParentSpace(drawable.Origin.PositionOnQuad(drawable.DrawRectangle)); + drawable.Origin = localSpaceOrigin; + drawable.Position += offset; + } + + private static void applyScreenSpaceOrigin(Drawable drawable, Anchor screenSpaceOrigin) { var boundingBox = drawable.ScreenSpaceDrawQuad.AABBFloat;