Sweep over 48 files (Editor/ excluded): remove comments that restate the code, condense class summaries, fix three stale doc statements (HsvColorPicker alpha bar, PaintHud layoutPrefab, MasterVolume FallbackDefault). Inline two single-use wrappers (IsPointerOverUi, BuildModes), drop an unused using, replace a magic literal with the existing ParallaxBase constant, and convert ~10 UnityEngine.Object null checks to the implicit-bool convention. No serialized fields, signatures, RPC/NetworkVariable names, defaults, or #if blocks touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
113 lines
4.5 KiB
C#
113 lines
4.5 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.EventSystems;
|
|
|
|
/// <summary>
|
|
/// Makes a UI panel draggable from any non-interactive surface; presses that begin on a widget
|
|
/// (<see cref="Selectable"/> or <see cref="UiPointerSurface"/>) keep their own click/drag behaviour.
|
|
/// Keeps a slice of the panel on screen and raises it above siblings; canvas-scale/anchor/pivot-agnostic.
|
|
/// </summary>
|
|
[RequireComponent(typeof(RectTransform))]
|
|
public class DraggablePanel : MonoBehaviour, IPointerDownHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
|
|
{
|
|
[Tooltip("Panel moved by the drag. Empty = this object's RectTransform.")]
|
|
public RectTransform target;
|
|
|
|
[Tooltip("Panel pixels (canvas units) kept on screen on every edge so it can never be lost.")]
|
|
public float keepVisible = 48f;
|
|
|
|
// Raised once when a real panel drag ends (not for gestures that began on a widget). The HUD uses
|
|
// this to persist the layout the moment a panel is dropped. Optional - left null by other users.
|
|
public System.Action Moved;
|
|
|
|
private RectTransform _area; // the panel's parent; pointer positions are tracked in its local space
|
|
private Vector2 _grabOffset;
|
|
private bool _dragging;
|
|
|
|
private void Awake() => Resolve();
|
|
|
|
private void Resolve()
|
|
{
|
|
if (!target) target = transform as RectTransform;
|
|
if (target) _area = target.parent as RectTransform;
|
|
}
|
|
|
|
public void OnPointerDown(PointerEventData eventData)
|
|
{
|
|
if (!StartedOnWidget(eventData)) BringToFront();
|
|
}
|
|
|
|
public void OnBeginDrag(PointerEventData eventData)
|
|
{
|
|
_dragging = false;
|
|
if (!target) Resolve();
|
|
if (!target || !_area) return;
|
|
if (StartedOnWidget(eventData)) return; // a toggle/slider/picker owns this gesture, not the panel
|
|
|
|
_dragging = true;
|
|
BringToFront();
|
|
// Capture grab offset; the constant frame difference between the area pivot and the panel anchor
|
|
// cancels out because we only ever apply the pointer delta on top of this offset.
|
|
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|
_area, eventData.position, eventData.pressEventCamera, out var local))
|
|
_grabOffset = target.anchoredPosition - local;
|
|
}
|
|
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
if (!_dragging || !target || !_area) return;
|
|
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|
_area, eventData.position, eventData.pressEventCamera, out var local))
|
|
return;
|
|
target.anchoredPosition = Clamp(local + _grabOffset);
|
|
}
|
|
|
|
public void OnEndDrag(PointerEventData eventData)
|
|
{
|
|
if (!_dragging) return; // gesture began on a widget -> not our drag, nothing moved
|
|
_dragging = false;
|
|
Moved?.Invoke();
|
|
}
|
|
|
|
// True when the press started on an interactive widget inside the panel (which then owns the gesture).
|
|
private bool StartedOnWidget(PointerEventData eventData)
|
|
{
|
|
var hit = eventData.pointerPressRaycast.gameObject;
|
|
for (var t = hit ? hit.transform : null; t && t != target; t = t.parent)
|
|
if (t.GetComponent<Selectable>() || t.GetComponent<UiPointerSurface>())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
private void BringToFront()
|
|
{
|
|
if (target) target.SetAsLastSibling();
|
|
}
|
|
|
|
// Anchor/pivot-agnostic clamp: keep at least `keepVisible` px of the panel inside the parent on every
|
|
// edge, computed from the panel's real corners so it is correct wherever the panel is anchored.
|
|
private Vector2 Clamp(Vector2 pos)
|
|
{
|
|
if (!_area) return pos;
|
|
Rect area = _area.rect;
|
|
Rect size = target.rect;
|
|
Vector2 pivot = target.pivot;
|
|
|
|
// Anchor reference point in the area's local space (panels are point-anchored: anchorMin == anchorMax).
|
|
Vector2 anchorRef = new Vector2(
|
|
area.xMin + target.anchorMin.x * area.width,
|
|
area.yMin + target.anchorMin.y * area.height);
|
|
Vector2 pivotToCorner = new Vector2(pivot.x * size.width, pivot.y * size.height);
|
|
|
|
// Bottom-left corner of the panel for this anchoredPosition.
|
|
Vector2 corner = anchorRef + pos - pivotToCorner;
|
|
|
|
float keepX = Mathf.Min(keepVisible, size.width);
|
|
float keepY = Mathf.Min(keepVisible, size.height);
|
|
corner.x = Mathf.Clamp(corner.x, area.xMin + keepX - size.width, area.xMax - keepX);
|
|
corner.y = Mathf.Clamp(corner.y, area.yMin + keepY - size.height, area.yMax - keepY);
|
|
|
|
return corner + pivotToCorner - anchorRef;
|
|
}
|
|
}
|