RectManipulator.cs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. using System;
  2. using UnityEngine;
  3. using UnityEngine.EventSystems;
  4. using UnityEngine.UI;
  5. namespace SoftMasking.Samples {
  6. [RequireComponent(typeof(RectTransform))]
  7. public class RectManipulator : UIBehaviour,
  8. IPointerEnterHandler,
  9. IPointerExitHandler,
  10. IBeginDragHandler,
  11. IDragHandler,
  12. IEndDragHandler {
  13. [Flags] public enum ManipulationType {
  14. None = 0,
  15. Move = 1 << 0,
  16. ResizeLeft = 1 << 1,
  17. ResizeUp = 1 << 2,
  18. ResizeRight = 1 << 3,
  19. ResizeDown = 1 << 4,
  20. ResizeUpLeft = ResizeUp | ResizeLeft,
  21. ResizeUpRight = ResizeUp | ResizeRight,
  22. ResizeDownLeft = ResizeDown | ResizeLeft,
  23. ResizeDownRight = ResizeDown | ResizeRight,
  24. Rotate = 1 << 5
  25. }
  26. public RectTransform targetTransform;
  27. public ManipulationType manipulation;
  28. public ShowOnHover showOnHover;
  29. [Header("Limits")]
  30. public Vector2 minSize;
  31. [Header("Display")]
  32. public Graphic icon;
  33. public float normalAlpha = 0.2f;
  34. public float selectedAlpha = 1f;
  35. public float transitionDuration = 0.2f;
  36. bool _isManipulatedNow;
  37. Vector2 _startAnchoredPosition;
  38. Vector2 _startSizeDelta;
  39. float _startRotation;
  40. public void OnPointerEnter(PointerEventData eventData) {
  41. HighlightIcon(true);
  42. }
  43. public void OnPointerExit(PointerEventData eventData) {
  44. if (!_isManipulatedNow)
  45. HighlightIcon(false);
  46. }
  47. void HighlightIcon(bool highlight, bool instant = false) {
  48. if (icon) {
  49. var targetAlpha = highlight ? selectedAlpha : normalAlpha;
  50. var duration = instant ? 0f : transitionDuration;
  51. icon.CrossFadeAlpha(targetAlpha, duration, true);
  52. }
  53. if (showOnHover)
  54. showOnHover.forcedVisible = highlight;
  55. }
  56. protected override void Start() {
  57. base.Start();
  58. HighlightIcon(false, instant: true);
  59. }
  60. public void OnBeginDrag(PointerEventData eventData) {
  61. _isManipulatedNow = true;
  62. RememberStartTransform();
  63. }
  64. void RememberStartTransform() {
  65. if (targetTransform) {
  66. _startAnchoredPosition = targetTransform.anchoredPosition;
  67. _startSizeDelta = targetTransform.sizeDelta;
  68. _startRotation = targetTransform.localRotation.eulerAngles.z;
  69. }
  70. }
  71. public void OnDrag(PointerEventData eventData) {
  72. if (targetTransform == null || parentTransform == null || !_isManipulatedNow)
  73. return;
  74. var startPoint = ToParentSpace(eventData.pressPosition, eventData.pressEventCamera);
  75. var curPoint = ToParentSpace(eventData.position, eventData.pressEventCamera);
  76. DoRotate(startPoint, curPoint);
  77. var parentSpaceMovement = curPoint - startPoint;
  78. DoMove(parentSpaceMovement);
  79. DoResize(parentSpaceMovement);
  80. }
  81. Vector2 ToParentSpace(Vector2 position, Camera eventCamera) {
  82. Vector2 localPosition;
  83. RectTransformUtility.ScreenPointToLocalPointInRectangle(
  84. parentTransform, position, eventCamera, out localPosition);
  85. return localPosition;
  86. }
  87. RectTransform parentTransform {
  88. get { return targetTransform.parent as RectTransform; }
  89. }
  90. void DoMove(Vector2 parentSpaceMovement) {
  91. if (Is(ManipulationType.Move))
  92. MoveTo(_startAnchoredPosition + parentSpaceMovement);
  93. }
  94. bool Is(ManipulationType expected) {
  95. return (manipulation & expected) == expected;
  96. }
  97. void MoveTo(Vector2 desiredAnchoredPosition) {
  98. targetTransform.anchoredPosition = ClampPosition(desiredAnchoredPosition);
  99. }
  100. Vector2 ClampPosition(Vector2 position) {
  101. var parentSize = parentTransform.rect.size;
  102. var halfSize = parentSize / 2;
  103. return new Vector2(
  104. Mathf.Clamp(position.x, -halfSize.x, halfSize.x),
  105. Mathf.Clamp(position.y, -halfSize.y, halfSize.y));
  106. }
  107. void DoRotate(Vector2 startParentPoint, Vector2 targetParentPoint) {
  108. if (Is(ManipulationType.Rotate)) {
  109. var startLever = startParentPoint - (Vector2)targetTransform.localPosition;
  110. var targetLever = targetParentPoint - (Vector2)targetTransform.localPosition;
  111. var additionalRotation = DeltaRotation(startLever, targetLever);
  112. targetTransform.localRotation = Quaternion.AngleAxis(_startRotation + additionalRotation, Vector3.forward);
  113. }
  114. }
  115. float DeltaRotation(Vector2 startLever, Vector2 endLever) {
  116. var startAngle = Mathf.Atan2(startLever.y, startLever.x) * Mathf.Rad2Deg;
  117. var endAngle = Mathf.Atan2(endLever.y, endLever.x) * Mathf.Rad2Deg;
  118. return Mathf.DeltaAngle(startAngle, endAngle);
  119. }
  120. void DoResize(Vector2 parentSpaceMovement) {
  121. var localSpaceMovement = Quaternion.Inverse(targetTransform.rotation) * parentSpaceMovement;
  122. var projectedOffset = ProjectResizeOffset(localSpaceMovement);
  123. if (projectedOffset.sqrMagnitude > 0f)
  124. SetSizeDirected(projectedOffset, ResizeSign());
  125. }
  126. Vector2 ProjectResizeOffset(Vector2 localOffset) {
  127. var isHorizontal = Is(ManipulationType.ResizeLeft) || Is(ManipulationType.ResizeRight);
  128. var isVertical = Is(ManipulationType.ResizeUp) || Is(ManipulationType.ResizeDown);
  129. return new Vector2(
  130. isHorizontal ? localOffset.x : 0f,
  131. isVertical ? localOffset.y : 0f);
  132. }
  133. Vector2 ResizeSign() {
  134. return new Vector2(
  135. Is(ManipulationType.ResizeLeft) ? -1f : 1f,
  136. Is(ManipulationType.ResizeDown) ? -1f : 1f);
  137. }
  138. void SetSizeDirected(Vector2 localOffset, Vector2 sizeSign) {
  139. var newSize = ClampSize(_startSizeDelta + Vector2.Scale(localOffset, sizeSign));
  140. targetTransform.sizeDelta = newSize;
  141. var actualSizeOffset = newSize - _startSizeDelta;
  142. var localMoveOffset = Vector2.Scale(actualSizeOffset / 2, sizeSign);
  143. MoveTo(_startAnchoredPosition + (Vector2)targetTransform.TransformVector(localMoveOffset));
  144. }
  145. Vector2 ClampSize(Vector2 size) {
  146. return new Vector2(
  147. Mathf.Max(size.x, minSize.x),
  148. Mathf.Max(size.y, minSize.y));
  149. }
  150. public void OnEndDrag(PointerEventData eventData) {
  151. _isManipulatedNow = false;
  152. if (!eventData.hovered.Contains(gameObject))
  153. HighlightIcon(false);
  154. }
  155. }
  156. }