From 600681c3e179053f1e3f045f982752860d6e30f3 Mon Sep 17 00:00:00 2001 From: Orkun Manap <31966136+manaporkun@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:19:35 +0200 Subject: [PATCH 1/5] feat: block handle interaction start when pointer is over uGUI A handle interaction no longer starts while the pointer is over a uGUI element, so clicking a button or panel above the scene does not begin a drag. An interaction already in progress is never interrupted. Exposed as TransformHandleManager.BlockWhenPointerOverUI (serialized, default on) and the overridable IsPointerOverUI() seam. uGUI is an optional dependency via the TH_UGUI version define (mirrors the Input System integration): projects without com.unity.ugui compile cleanly and the guard is a no-op. The 2021.3 floor CI project gains com.unity.ugui so the guarded path is exercised on the floor leg too. --- .../CHANGELOG.md | 7 ++++ .../README.md | 13 ++++++ .../Runtime/Scripts/TransformHandleManager.cs | 42 ++++++++++++++++++- ...rkunmanap.runtime-transform-handles.asmdef | 8 +++- README.md | 12 ++++++ ci/floor-2021/Packages/manifest.json | 1 + 6 files changed, 81 insertions(+), 2 deletions(-) diff --git a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md index 6353f7b..8606b7b 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added +- UI-occlusion guard: handle interactions no longer start while the pointer is over a uGUI + element. Controlled by `TransformHandleManager.BlockWhenPointerOverUI` (serialized, default on); + an interaction already in progress is never interrupted. Requires the uGUI package and an + `EventSystem` in the scene — projects without uGUI are unaffected (the guard is a no-op via the + `TH_UGUI` version define). Override `IsPointerOverUI()` to integrate a non-uGUI UI stack. + ## [3.1.0] - 2026-06-11 ### Added diff --git a/Packages/com.orkunmanap.runtime-transform-handles/README.md b/Packages/com.orkunmanap.runtime-transform-handles/README.md index c1fdcef..669b915 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/README.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/README.md @@ -92,6 +92,19 @@ type/space/axes, and highlight color, then assign it: TransformHandleManager.Instance.Settings = mySettings; ``` +### Blocking interaction over UI + +By default a handle interaction will not start while the pointer is over a uGUI element, so +clicking a button or panel rendered above the scene does not begin a drag (an interaction already +in progress is never interrupted). This needs an `EventSystem` in the scene and the uGUI package; +projects without uGUI are unaffected. Toggle it at runtime or in the Inspector: + +```csharp +TransformHandleManager.Instance.BlockWhenPointerOverUI = false; +``` + +For a non-uGUI UI stack, subclass and override `IsPointerOverUI()`. + ## Default keyboard shortcuts | Key | Action | diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs index d3fffb8..bc30d10 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs @@ -41,6 +41,11 @@ public Camera MainCamera [SerializeField] private string handleLayerName = "TransformHandle"; [SerializeField] private Color highlightColor = Color.white; + [Tooltip("When enabled, a handle interaction will not start while the pointer is over a " + + "uGUI element (requires an EventSystem in the scene). An interaction already in " + + "progress is never interrupted, even if the pointer moves over UI.")] + [SerializeField] private bool blockWhenPointerOverUI = true; + [Header("Shortcuts (used when no Settings asset is assigned)")] [SerializeField] private KeyCode positionShortcut = KeyCode.W; [SerializeField] private KeyCode rotationShortcut = KeyCode.E; @@ -59,6 +64,17 @@ public TransformHandleSettings Settings set => settings = value; } + /// + /// When true, a handle interaction will not start while the pointer is over a uGUI element + /// (requires the uGUI package and an EventSystem in the scene). An in-progress interaction + /// is never interrupted. Defaults to true. Has no effect when uGUI is not installed. + /// + public bool BlockWhenPointerOverUI + { + get => blockWhenPointerOverUI; + set => blockWhenPointerOverUI = value; + } + // Properties that check settings first, then fall back to serialized fields private bool ShortcutsEnabled => settings == null || settings.EnableShortcuts; private KeyCode PositionKey => settings != null ? settings.PositionKey : positionShortcut; @@ -407,7 +423,14 @@ protected virtual void Update() _hoveredHandle = null; _handleHitPoint = Vector3.zero; - GetHandle(ref _hoveredHandle, ref _handleHitPoint); + // Leaving the hovered handle null while the pointer is over UI both clears the + // highlight and prevents MouseInput from starting a drag (it requires a hovered + // handle). An interaction already in progress is unaffected — this branch only + // runs when not dragging. + if (!IsPointerOverUI()) + { + GetHandle(ref _hoveredHandle, ref _handleHitPoint); + } HandleOverEffect(_hoveredHandle); } @@ -416,6 +439,23 @@ protected virtual void Update() KeyboardInput(); } + /// + /// Whether the pointer is currently over a uGUI element that should suppress starting a + /// handle interaction. Returns false when is disabled, + /// no EventSystem is present, or the uGUI package is not installed. Override to plug in a + /// different UI stack. + /// + protected virtual bool IsPointerOverUI() + { + if (!blockWhenPointerOverUI) return false; +#if TH_UGUI + var eventSystem = UnityEngine.EventSystems.EventSystem.current; + return eventSystem != null && eventSystem.IsPointerOverGameObject(); +#else + return false; +#endif + } + protected virtual void GetHandle(ref HandleBase handle, ref Vector3 hitPoint) { // Unity's overloaded == reports a destroyed camera as null, so this proactive check diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/com.orkunmanap.runtime-transform-handles.asmdef b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/com.orkunmanap.runtime-transform-handles.asmdef index cb57178..f689afa 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/com.orkunmanap.runtime-transform-handles.asmdef +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/com.orkunmanap.runtime-transform-handles.asmdef @@ -2,7 +2,8 @@ "name": "com.orkunmanap.runtime-transform-handles", "rootNamespace": "TransformHandles", "references": [ - "GUID:75469ad4d38634e559750d17036d5f7c" + "GUID:75469ad4d38634e559750d17036d5f7c", + "GUID:2bafac87e7f4b9b418d9448d219b01ab" ], "includePlatforms": [], "excludePlatforms": [], @@ -16,6 +17,11 @@ "name": "com.unity.inputsystem", "expression": "1.0.0", "define": "TH_INPUTSYSTEM" + }, + { + "name": "com.unity.ugui", + "expression": "1.0.0", + "define": "TH_UGUI" } ], "noEngineReferences": false diff --git a/README.md b/README.md index 66c4ba0..c16f65b 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,18 @@ TransformHandleManager.Instance.Settings = mySettings; If no settings asset is assigned, the manager uses its serialized field values. +### Blocking Interaction Over UI + +By default a handle interaction will not start while the pointer is over a uGUI element, so +clicking a button or panel above the scene does not begin a drag (an in-progress interaction is +never interrupted). Requires an `EventSystem` and the uGUI package; projects without uGUI are +unaffected. Toggle in the Inspector or via code, and override `IsPointerOverUI()` for a non-uGUI +UI stack: + +```csharp +TransformHandleManager.Instance.BlockWhenPointerOverUI = false; +``` + ## Default Keyboard Shortcuts | Key | Action | diff --git a/ci/floor-2021/Packages/manifest.json b/ci/floor-2021/Packages/manifest.json index b9a3808..24b9b23 100644 --- a/ci/floor-2021/Packages/manifest.json +++ b/ci/floor-2021/Packages/manifest.json @@ -2,6 +2,7 @@ "dependencies": { "com.orkunmanap.runtime-transform-handles": "file:../../../Packages/com.orkunmanap.runtime-transform-handles", "com.unity.test-framework": "1.1.33", + "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", From cef0f8739ed38b7fce5717348815bde7ab47e9c9 Mon Sep 17 00:00:00 2001 From: Orkun Manap <31966136+manaporkun@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:27:48 +0200 Subject: [PATCH 2/5] feat: demonstrate UI-occlusion guard in the Demo sample The Demo now spawns a uGUI Canvas + panel (and an EventSystem with the input module matching the active backend) and adds a HUD toggle for BlockWhenPointerOverUI, so the guard is directly testable: clicking the panel must not move objects behind it while the guard is on. The sample's own target picking now also respects UI occlusion. uGUI/Input System are referenced by the sample asmdef and gated behind TH_UGUI/TH_INPUTSYSTEM. --- .../CHANGELOG.md | 2 + .../Samples~/Demo/HandleDemo.cs | 76 +++++++++++++++++++ .../Demo/TransformHandles.Samples.Demo.asmdef | 17 ++++- 3 files changed, 93 insertions(+), 2 deletions(-) diff --git a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md index 8606b7b..9999846 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 an interaction already in progress is never interrupted. Requires the uGUI package and an `EventSystem` in the scene — projects without uGUI are unaffected (the guard is a no-op via the `TH_UGUI` version define). Override `IsPointerOverUI()` to integrate a non-uGUI UI stack. + The Demo sample now spawns a uGUI panel and an EventSystem and exposes a HUD toggle so the + guard can be tried directly: with it on, clicking the panel does not disturb objects behind it. ## [3.1.0] - 2026-06-11 diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs index b93b5ab..c12d7e1 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs @@ -3,6 +3,10 @@ using TransformHandles; using TransformHandles.Utils; using UnityEngine; +#if TH_UGUI +using UnityEngine.EventSystems; +using UnityEngine.UI; +#endif /// /// Self-contained showcase that exercises the whole public surface of the package: @@ -105,6 +109,73 @@ private void Start() l.type = LightType.Directional; lightGo.transform.rotation = Quaternion.Euler(50f, -30f, 0f); } + + BuildUiOcclusionOverlay(); + } + + // Builds a real uGUI panel (+ an EventSystem if the scene lacks one) so the + // TransformHandleManager.BlockWhenPointerOverUI guard is demonstrable: with the guard on, + // clicking the panel must not select targets or start a handle drag underneath it. + private void BuildUiOcclusionOverlay() + { +#if TH_UGUI +#if UNITY_2023_1_OR_NEWER + var hasEventSystem = Object.FindAnyObjectByType() != null; +#else + var hasEventSystem = Object.FindObjectOfType() != null; +#endif + if (!hasEventSystem) + { + var esGo = new GameObject("EventSystem"); + esGo.AddComponent(); +#if ENABLE_INPUT_SYSTEM && TH_INPUTSYSTEM + esGo.AddComponent().AssignDefaultActions(); +#else + esGo.AddComponent(); +#endif + } + + var canvasGo = new GameObject("Demo UI Canvas"); + var canvas = canvasGo.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvasGo.AddComponent(); + canvasGo.AddComponent(); + + var panelGo = new GameObject("UI Occlusion Test Panel"); + panelGo.transform.SetParent(canvasGo.transform, false); + var panel = panelGo.AddComponent(); + panel.color = new Color(0.15f, 0.45f, 0.85f, 0.85f); // raycast target by default + var rect = panel.rectTransform; + rect.anchorMin = rect.anchorMax = new Vector2(0.5f, 1f); + rect.pivot = new Vector2(0.5f, 1f); + rect.sizeDelta = new Vector2(420f, 64f); + rect.anchoredPosition = new Vector2(0f, -12f); + + var labelGo = new GameObject("Label"); + labelGo.transform.SetParent(panelGo.transform, false); + var label = labelGo.AddComponent(); + label.text = "uGUI panel — clicks here must NOT move objects\n(toggle the guard in the HUD)"; + label.alignment = TextAnchor.MiddleCenter; + label.color = Color.white; + label.font = Resources.GetBuiltinResource("LegacyRuntime.ttf") + ?? Resources.GetBuiltinResource("Arial.ttf"); + var labelRect = label.rectTransform; + labelRect.anchorMin = Vector2.zero; + labelRect.anchorMax = Vector2.one; + labelRect.offsetMin = labelRect.offsetMax = Vector2.zero; +#endif + } + + // True while the pointer is over a uGUI element, so the demo's own target picking respects + // UI occlusion the same way the handle manager does. No-op without uGUI. + private bool PointerOverUi() + { +#if TH_UGUI + var es = EventSystem.current; + return es != null && es.IsPointerOverGameObject(); +#else + return false; +#endif } private void Update() @@ -118,6 +189,7 @@ private void Update() private void HandleSelectionInput() { if (_interacting) return; // don't pick targets mid-drag + if (PointerOverUi()) return; // clicks over the uGUI panel must not pick targets // Left click: select (Shift adds to the current handle's group). if (InputWrapper.GetMouseButtonDown(0) && TryPickTarget(out var picked)) @@ -332,6 +404,10 @@ private void OnGUI() var useSettings = GUILayout.Toggle(_useSettings, " Apply runtime Settings asset"); if (useSettings != _useSettings) { _useSettings = useSettings; if (_useSettings && _activeHandle != null) _activeHandle.ApplySettings(_settings); } + var blockUi = GUILayout.Toggle(_manager.BlockWhenPointerOverUI, " Block interaction over UI"); + if (blockUi != _manager.BlockWhenPointerOverUI) _manager.BlockWhenPointerOverUI = blockUi; + GUILayout.Label("Click the blue panel (top): blocked when on, drags through when off."); + GUILayout.Space(6); GUILayout.BeginHorizontal(); if (GUILayout.Button("Group all")) GroupAll(); diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/TransformHandles.Samples.Demo.asmdef b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/TransformHandles.Samples.Demo.asmdef index fb086df..19a0586 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/TransformHandles.Samples.Demo.asmdef +++ b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/TransformHandles.Samples.Demo.asmdef @@ -2,7 +2,9 @@ "name": "TransformHandles.Samples.Demo", "rootNamespace": "", "references": [ - "com.orkunmanap.runtime-transform-handles" + "com.orkunmanap.runtime-transform-handles", + "GUID:2bafac87e7f4b9b418d9448d219b01ab", + "GUID:75469ad4d38634e559750d17036d5f7c" ], "includePlatforms": [], "excludePlatforms": [], @@ -11,6 +13,17 @@ "precompiledReferences": [], "autoReferenced": false, "defineConstraints": [], - "versionDefines": [], + "versionDefines": [ + { + "name": "com.unity.ugui", + "expression": "1.0.0", + "define": "TH_UGUI" + }, + { + "name": "com.unity.inputsystem", + "expression": "1.0.0", + "define": "TH_INPUTSYSTEM" + } + ], "noEngineReferences": false } From 0e7365c0401b890c199b6d9e322c0cdbfee8fa97 Mon Sep 17 00:00:00 2001 From: Orkun Manap <31966136+manaporkun@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:41:39 +0200 Subject: [PATCH 3/5] fix: make UI-occlusion guard touch-aware EventSystem.IsPointerOverGameObject() with no argument only queries the mouse pointer, but the package treats the first active touch as the primary pointer (InputWrapper), so a tap over uGUI on mobile bypassed the guard and started a drag. IsPointerOverUI now also tests the active finger's pointer id via the new InputWrapper.PrimaryTouchPointerId; the demo's own picking guard mirrors it. Reported independently by Cursor Bugbot and Codex on PR #42. --- .../Runtime/Scripts/TransformHandleManager.cs | 8 ++++++- .../Runtime/Scripts/Utils/InputWrapper.cs | 23 +++++++++++++++++++ .../Samples~/Demo/HandleDemo.cs | 5 +++- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs index bc30d10..9083c67 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs @@ -450,7 +450,13 @@ protected virtual bool IsPointerOverUI() if (!blockWhenPointerOverUI) return false; #if TH_UGUI var eventSystem = UnityEngine.EventSystems.EventSystem.current; - return eventSystem != null && eventSystem.IsPointerOverGameObject(); + if (eventSystem == null) return false; + + // The no-arg overload only queries the mouse pointer. The package treats the first + // active touch as the primary pointer (see InputWrapper), so on touch devices also + // test that finger's pointer id — otherwise a tap over UI bypasses the guard. + if (eventSystem.IsPointerOverGameObject()) return true; + return HasActiveTouch && eventSystem.IsPointerOverGameObject(PrimaryTouchPointerId); #else return false; #endif diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Utils/InputWrapper.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Utils/InputWrapper.cs index a418332..519ccf9 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Utils/InputWrapper.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/Utils/InputWrapper.cs @@ -73,6 +73,29 @@ public static Vector2 TouchPosition } } + /// + /// Pointer id of the primary active touch, for passing to + /// EventSystem.IsPointerOverGameObject(int). Returns -1 when no touch is active + /// (-1 is the mouse/default pointer id, so the result also covers the mouse case). + /// Only meaningful while is true. + /// + public static int PrimaryTouchPointerId + { + get + { +#if ENABLE_INPUT_SYSTEM && TH_INPUTSYSTEM + EnsureTouchInitialized(); + if (Touch.activeTouches.Count > 0) + return Touch.activeTouches[0].touchId; + return -1; +#else + if (Input.touchCount > 0) + return Input.GetTouch(0).fingerId; + return -1; +#endif + } + } + /// /// Gets the current pointer position (mouse or touch) in screen coordinates. /// Prioritizes touch input on touch devices when a touch is active. diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs index c12d7e1..96d7033 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs @@ -172,7 +172,10 @@ private bool PointerOverUi() { #if TH_UGUI var es = EventSystem.current; - return es != null && es.IsPointerOverGameObject(); + if (es == null) return false; + if (es.IsPointerOverGameObject()) return true; // mouse / default pointer + // Also test the active finger so the guard works under touch (mouse-only otherwise). + return InputWrapper.HasActiveTouch && es.IsPointerOverGameObject(InputWrapper.PrimaryTouchPointerId); #else return false; #endif From dd401c5962c5c0f36743964600a6a6d9980b21f2 Mon Sep 17 00:00:00 2001 From: Orkun Manap <31966136+manaporkun@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:47:36 +0200 Subject: [PATCH 4/5] fix: demo picking respects BlockWhenPointerOverUI toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HandleSelectionInput bailed unconditionally when the pointer was over UI, so toggling the guard off in the HUD still blocked target picking over the panel while handle drags passed through — contradicting the on-screen hint. Gate the demo's own picking on the same flag the manager uses. Reported by Cursor Bugbot on PR #42. --- .../Samples~/Demo/HandleDemo.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs index 96d7033..8f273a0 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs @@ -192,7 +192,9 @@ private void Update() private void HandleSelectionInput() { if (_interacting) return; // don't pick targets mid-drag - if (PointerOverUi()) return; // clicks over the uGUI panel must not pick targets + // Respect the same toggle the handle manager uses, so flipping it off in the HUD lets + // both picking and handle drags pass through the panel (matching the on-screen hint). + if (_manager.BlockWhenPointerOverUI && PointerOverUi()) return; // Left click: select (Shift adds to the current handle's group). if (InputWrapper.GetMouseButtonDown(0) && TryPickTarget(out var picked)) From 6159cbdb50282b075e8e6f2ebe238d076621e93c Mon Sep 17 00:00:00 2001 From: Orkun Manap <31966136+manaporkun@users.noreply.github.com> Date: Thu, 11 Jun 2026 15:49:18 +0200 Subject: [PATCH 5/5] feat: make UI-occlusion guard opt-in (off by default) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changing interaction behavior on by default in a minor release could surprise existing users, so BlockWhenPointerOverUI now defaults to false — enabling the package never silently alters input. The Demo sample enables it at startup so it still showcases the feature out of the box. --- .../CHANGELOG.md | 12 +++++++----- .../README.md | 13 +++++++------ .../Runtime/Scripts/TransformHandleManager.cs | 16 +++++++++------- .../Samples~/Demo/HandleDemo.cs | 3 +++ README.md | 15 ++++++++------- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md index 9999846..9f98cbe 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/CHANGELOG.md @@ -8,11 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- UI-occlusion guard: handle interactions no longer start while the pointer is over a uGUI - element. Controlled by `TransformHandleManager.BlockWhenPointerOverUI` (serialized, default on); - an interaction already in progress is never interrupted. Requires the uGUI package and an - `EventSystem` in the scene — projects without uGUI are unaffected (the guard is a no-op via the - `TH_UGUI` version define). Override `IsPointerOverUI()` to integrate a non-uGUI UI stack. +- Optional UI-occlusion guard: when enabled, handle interactions do not start while the pointer + is over a uGUI element. Opt-in via `TransformHandleManager.BlockWhenPointerOverUI` (serialized, + **off by default** so it never silently changes existing input behavior); an interaction already + in progress is never interrupted, and touch is handled via the active finger's pointer id. + Requires the uGUI package and an `EventSystem` in the scene — projects without uGUI are + unaffected (no-op via the `TH_UGUI` version define). Override `IsPointerOverUI()` to integrate a + non-uGUI UI stack. The Demo sample now spawns a uGUI panel and an EventSystem and exposes a HUD toggle so the guard can be tried directly: with it on, clicking the panel does not disturb objects behind it. diff --git a/Packages/com.orkunmanap.runtime-transform-handles/README.md b/Packages/com.orkunmanap.runtime-transform-handles/README.md index 669b915..6dd23d4 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/README.md +++ b/Packages/com.orkunmanap.runtime-transform-handles/README.md @@ -92,15 +92,16 @@ type/space/axes, and highlight color, then assign it: TransformHandleManager.Instance.Settings = mySettings; ``` -### Blocking interaction over UI +### Blocking interaction over UI (opt-in) -By default a handle interaction will not start while the pointer is over a uGUI element, so -clicking a button or panel rendered above the scene does not begin a drag (an interaction already -in progress is never interrupted). This needs an `EventSystem` in the scene and the uGUI package; -projects without uGUI are unaffected. Toggle it at runtime or in the Inspector: +Optionally, a handle interaction can be prevented from starting while the pointer is over a uGUI +element, so clicking a button or panel above the scene does not begin a drag (an interaction +already in progress is never interrupted; touch uses the active finger). It is **off by default** +so it never silently changes input behavior. Needs an `EventSystem` and the uGUI package; projects +without uGUI are unaffected. Enable it at runtime or in the Inspector: ```csharp -TransformHandleManager.Instance.BlockWhenPointerOverUI = false; +TransformHandleManager.Instance.BlockWhenPointerOverUI = true; ``` For a non-uGUI UI stack, subclass and override `IsPointerOverUI()`. diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs index 9083c67..5312c51 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Runtime/Scripts/TransformHandleManager.cs @@ -41,10 +41,11 @@ public Camera MainCamera [SerializeField] private string handleLayerName = "TransformHandle"; [SerializeField] private Color highlightColor = Color.white; - [Tooltip("When enabled, a handle interaction will not start while the pointer is over a " + - "uGUI element (requires an EventSystem in the scene). An interaction already in " + - "progress is never interrupted, even if the pointer moves over UI.")] - [SerializeField] private bool blockWhenPointerOverUI = true; + [Tooltip("Opt-in: when enabled, a handle interaction will not start while the pointer is " + + "over a uGUI element (requires an EventSystem in the scene). An interaction " + + "already in progress is never interrupted, even if the pointer moves over UI. " + + "Off by default so it never silently changes existing input behavior.")] + [SerializeField] private bool blockWhenPointerOverUI; [Header("Shortcuts (used when no Settings asset is assigned)")] [SerializeField] private KeyCode positionShortcut = KeyCode.W; @@ -65,9 +66,10 @@ public TransformHandleSettings Settings } /// - /// When true, a handle interaction will not start while the pointer is over a uGUI element - /// (requires the uGUI package and an EventSystem in the scene). An in-progress interaction - /// is never interrupted. Defaults to true. Has no effect when uGUI is not installed. + /// Opt-in. When true, a handle interaction will not start while the pointer is over a uGUI + /// element (requires the uGUI package and an EventSystem in the scene). An in-progress + /// interaction is never interrupted. Defaults to false so enabling the package never + /// silently changes input behavior. Has no effect when uGUI is not installed. /// public bool BlockWhenPointerOverUI { diff --git a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs index 8f273a0..7df7b10 100644 --- a/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs +++ b/Packages/com.orkunmanap.runtime-transform-handles/Samples~/Demo/HandleDemo.cs @@ -119,6 +119,9 @@ private void Start() private void BuildUiOcclusionOverlay() { #if TH_UGUI + // The guard is opt-in (off by default on the manager); turn it on here so the sample + // demonstrates it out of the box. Flip it from the HUD to compare on/off behavior. + _manager.BlockWhenPointerOverUI = true; #if UNITY_2023_1_OR_NEWER var hasEventSystem = Object.FindAnyObjectByType() != null; #else diff --git a/README.md b/README.md index c16f65b..688451e 100644 --- a/README.md +++ b/README.md @@ -163,16 +163,17 @@ TransformHandleManager.Instance.Settings = mySettings; If no settings asset is assigned, the manager uses its serialized field values. -### Blocking Interaction Over UI +### Blocking Interaction Over UI (opt-in) -By default a handle interaction will not start while the pointer is over a uGUI element, so -clicking a button or panel above the scene does not begin a drag (an in-progress interaction is -never interrupted). Requires an `EventSystem` and the uGUI package; projects without uGUI are -unaffected. Toggle in the Inspector or via code, and override `IsPointerOverUI()` for a non-uGUI -UI stack: +Optionally, a handle interaction can be prevented from starting while the pointer is over a uGUI +element, so clicking a button or panel above the scene does not begin a drag (an in-progress +interaction is never interrupted; touch uses the active finger). It is **off by default** so it +never silently changes input behavior. Requires an `EventSystem` and the uGUI package; projects +without uGUI are unaffected. Enable in the Inspector or via code, and override `IsPointerOverUI()` +for a non-uGUI UI stack: ```csharp -TransformHandleManager.Instance.BlockWhenPointerOverUI = false; +TransformHandleManager.Instance.BlockWhenPointerOverUI = true; ``` ## Default Keyboard Shortcuts