diff --git a/Scripts/ToolTips/BoundTooltip/BoundTooltipItem.cs b/Scripts/ToolTips/BoundTooltip/BoundTooltipItem.cs index 1f8b890..db9b093 100644 --- a/Scripts/ToolTips/BoundTooltip/BoundTooltipItem.cs +++ b/Scripts/ToolTips/BoundTooltip/BoundTooltipItem.cs @@ -3,7 +3,7 @@ namespace UnityEngine.UI.Extensions { - [AddComponentMenu("UI/Extensions/Bound Tooltip/Tooltip Item")] + [AddComponentMenu("UI/Extensions/Bound Tooltip/Bound Tooltip Item")] public class BoundTooltipItem : MonoBehaviour { public bool IsActive diff --git a/Scripts/ToolTips/BoundTooltip/BoundTooltipTrigger.cs b/Scripts/ToolTips/BoundTooltip/BoundTooltipTrigger.cs index e91de24..051341e 100644 --- a/Scripts/ToolTips/BoundTooltip/BoundTooltipTrigger.cs +++ b/Scripts/ToolTips/BoundTooltip/BoundTooltipTrigger.cs @@ -4,7 +4,7 @@ using UnityEngine.EventSystems; namespace UnityEngine.UI.Extensions { - [AddComponentMenu("UI/Extensions/Bound Tooltip/Tooltip Trigger")] + [AddComponentMenu("UI/Extensions/Bound Tooltip/Bound Tooltip Trigger")] public class BoundTooltipTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, ISelectHandler, IDeselectHandler { [TextAreaAttribute] diff --git a/Scripts/ToolTips/ToolTip.cs b/Scripts/ToolTips/ToolTip.cs index e921b1c..3d67cd3 100644 --- a/Scripts/ToolTips/ToolTip.cs +++ b/Scripts/ToolTips/ToolTip.cs @@ -1,7 +1,8 @@ /// Credit drHogan /// Sourced from - http://forum.unity3d.com/threads/screenspace-camera-tooltip-controller-sweat-and-tears.293991/#post-1938929 /// updated ddreaper - refactored code to be more performant. -/// *Note - only works for Screenspace Camera canvases at present, needs updating to include Screenspace and Worldspace! +/// updated lucasvinbr - mixed with BoundTooltip, should work with Screenspace Camera (non-rotated) and Overlay +/// *Note - only works for non-rotated Screenspace Camera and Screenspace Overlay canvases at present, needs updating to include rotated Screenspace Camera and Worldspace! //ToolTip is written by Emiliano Pastorelli, H&R Tallinn (Estonia), http://www.hammerandravens.com //Copyright (c) 2015 Emiliano Pastorelli, H&R - Hammer&Ravens, Tallinn, Estonia. @@ -22,144 +23,292 @@ namespace UnityEngine.UI.Extensions { [RequireComponent(typeof(RectTransform))] - [AddComponentMenu("UI/Extensions/Tooltip")] + [AddComponentMenu("UI/Extensions/Tooltip/Tooltip")] public class ToolTip : MonoBehaviour { //text of the tooltip private Text _text; - private RectTransform _rectTransform; + private RectTransform _rectTransform, canvasRectTransform; + + [Tooltip("The canvas used by the tooltip as positioning and scaling reference. Should usually be the root Canvas of the hierarchy this component is in")] + public Canvas canvas; + + [Tooltip("Sets if tooltip triggers will run ForceUpdateCanvases and refresh the tooltip's layout group " + + "(if any) when hovered, in order to prevent momentaneous misplacement sometimes caused by ContentSizeFitters")] + public bool tooltipTriggersCanForceCanvasUpdate = false; + + /// + /// the tooltip's Layout Group, if any + /// + private LayoutGroup _layoutGroup; //if the tooltip is inside a UI element private bool _inside; - // private bool _xShifted, _yShifted = false; - private float width, height;//, canvasWidth, canvasHeight; - // private int screenWidth, screenHeight; + public float YShift,xShift; - private float YShift,xShift; - - private RenderMode _guiMode; + [HideInInspector] + public RenderMode guiMode; private Camera _guiCamera; + public Camera GuiCamera + { + get + { + if (!_guiCamera) { + _guiCamera = Camera.main; + } + + return _guiCamera; + } + } + + private Vector3 screenLowerLeft, screenUpperRight, shiftingVector; + + /// + /// a screen-space point where the tooltip would be placed before applying X and Y shifts and border checks + /// + private Vector3 baseTooltipPos; + + private Vector3 newTTPos; + private Vector3 adjustedNewTTPos; + private Vector3 adjustedTTLocalPos; + private Vector3 shifterForBorders; + + private float borderTest; + + // Standard Singleton Access + private static ToolTip instance; + + public static ToolTip Instance + { + get + { + if (instance == null) + instance = FindObjectOfType(); + return instance; + } + } + + + void Reset() { + canvas = GetComponentInParent(); + canvas = canvas.rootCanvas; + } + // Use this for initialization public void Awake() { - var _canvas = GetComponentInParent(); - _guiCamera = _canvas.worldCamera; - _guiMode = _canvas.renderMode; + instance = this; + if (!canvas) { + canvas = GetComponentInParent(); + canvas = canvas.rootCanvas; + } + + _guiCamera = canvas.worldCamera; + guiMode = canvas.renderMode; _rectTransform = GetComponent(); + canvasRectTransform = canvas.GetComponent(); + _layoutGroup = GetComponentInChildren(); _text = GetComponentInChildren(); _inside = false; - //size of the screen - // screenWidth = Screen.width; - // screenHeight = Screen.height; - - xShift = 0f; - YShift = -30f; - - // _xShifted = _yShifted = false; - this.gameObject.SetActive(false); } //Call this function externally to set the text of the template and activate the tooltip - public void SetTooltip(string ttext) + public void SetTooltip(string ttext, Vector3 basePos, bool refreshCanvasesBeforeGetSize = false) { - if (_guiMode == RenderMode.ScreenSpaceCamera) - { - //set the text and fit the tooltip panel to the text size + baseTooltipPos = basePos; + + //set the text + if (_text) { _text.text = ttext; - - _rectTransform.sizeDelta = new Vector2(_text.preferredWidth + 40f, _text.preferredHeight + 25f); - - OnScreenSpaceCamera(); - } + else { + Debug.LogWarning("[ToolTip] Couldn't set tooltip text, tooltip has no child Text component"); + } + + ContextualTooltipUpdate(refreshCanvasesBeforeGetSize); + } //call this function on mouse exit to deactivate the template public void HideTooltip() { - if (_guiMode == RenderMode.ScreenSpaceCamera) - { - this.gameObject.SetActive(false); - _inside = false; - } + gameObject.SetActive(false); + _inside = false; } // Update is called once per frame - void FixedUpdate() + void Update() { if (_inside) { - if (_guiMode == RenderMode.ScreenSpaceCamera) - { - OnScreenSpaceCamera(); - } + ContextualTooltipUpdate(); } } - //main tooltip edge of screen guard and movement - public void OnScreenSpaceCamera() + /// + /// forces rebuilding of Canvases in order to update the tooltip's content size fitting. + /// Can prevent the tooltip from being visibly misplaced for one frame when being resized. + /// Only runs if tooltipTriggersCanForceCanvasUpdate is true + /// + public void RefreshTooltipSize() { + if (tooltipTriggersCanForceCanvasUpdate) { + Canvas.ForceUpdateCanvases(); + + if (_layoutGroup) { + _layoutGroup.enabled = false; + _layoutGroup.enabled = true; + } + + } + + } + + /// + /// Runs the appropriate tooltip placement method, according to the parent canvas's render mode + /// + /// + public void ContextualTooltipUpdate(bool refreshCanvasesBeforeGettingSize = false) { + switch (guiMode) { + case RenderMode.ScreenSpaceCamera: + OnScreenSpaceCamera(refreshCanvasesBeforeGettingSize); + break; + case RenderMode.ScreenSpaceOverlay: + OnScreenSpaceOverlay(refreshCanvasesBeforeGettingSize); + break; + } + } + + //main tooltip edge of screen guard and movement - camera + public void OnScreenSpaceCamera(bool refreshCanvasesBeforeGettingSize = false) { - Vector3 newPos = _guiCamera.ScreenToViewportPoint(Input.mousePosition - new Vector3(xShift, YShift, 0f)); - Vector3 newPosWVP = _guiCamera.ViewportToWorldPoint(newPos); + shiftingVector.x = xShift; + shiftingVector.y = YShift; + + baseTooltipPos.z = canvas.planeDistance; + + newTTPos = GuiCamera.ScreenToViewportPoint(baseTooltipPos - shiftingVector); + adjustedNewTTPos = GuiCamera.ViewportToWorldPoint(newTTPos); + + gameObject.SetActive(true); + + if (refreshCanvasesBeforeGettingSize) RefreshTooltipSize(); + + //consider scaled dimensions when comparing against the edges + width = transform.lossyScale.x * _rectTransform.sizeDelta[0]; + height = transform.lossyScale.y * _rectTransform.sizeDelta[1]; + + // check and solve problems for the tooltip that goes out of the screen on the horizontal axis + + RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRectTransform, Vector2.zero, GuiCamera, out screenLowerLeft); + RectTransformUtility.ScreenPointToWorldPointInRectangle(canvasRectTransform, new Vector2(Screen.width, Screen.height), GuiCamera, out screenUpperRight); + + + //check for right edge of screen + borderTest = (adjustedNewTTPos.x + width / 2); + if (borderTest > screenUpperRight.x) + { + shifterForBorders.x = borderTest - screenUpperRight.x; + adjustedNewTTPos.x -= shifterForBorders.x; + } + //check for left edge of screen + borderTest = (adjustedNewTTPos.x - width / 2); + if (borderTest < screenLowerLeft.x) + { + shifterForBorders.x = screenLowerLeft.x - borderTest; + adjustedNewTTPos.x += shifterForBorders.x; + } + + // check and solve problems for the tooltip that goes out of the screen on the vertical axis + + //check for lower edge of the screen + borderTest = (adjustedNewTTPos.y - height / 2); + if (borderTest < screenLowerLeft.y) { + shifterForBorders.y = screenLowerLeft.y - borderTest; + adjustedNewTTPos.y += shifterForBorders.y; + } + + //check for upper edge of the screen + borderTest = (adjustedNewTTPos.y + height / 2); + if (borderTest > screenUpperRight.y) + { + shifterForBorders.y = borderTest - screenUpperRight.y; + adjustedNewTTPos.y -= shifterForBorders.y; + } + + //failed attempt to circumvent issues caused when rotating the camera + adjustedNewTTPos = transform.rotation * adjustedNewTTPos; + + transform.position = adjustedNewTTPos; + adjustedTTLocalPos = transform.localPosition; + adjustedTTLocalPos.z = 0; + transform.localPosition = adjustedTTLocalPos; + + _inside = true; + } + + + //main tooltip edge of screen guard and movement - overlay + public void OnScreenSpaceOverlay(bool refreshCanvasesBeforeGettingSize = false) { + shiftingVector.x = xShift; + shiftingVector.y = YShift; + newTTPos = (baseTooltipPos - shiftingVector) / canvas.scaleFactor; + adjustedNewTTPos = newTTPos; + + gameObject.SetActive(true); + + if (refreshCanvasesBeforeGettingSize) RefreshTooltipSize(); width = _rectTransform.sizeDelta[0]; height = _rectTransform.sizeDelta[1]; // check and solve problems for the tooltip that goes out of the screen on the horizontal axis - float val; - - Vector3 lowerLeft = _guiCamera.ViewportToWorldPoint(new Vector3(0.0f, 0.0f, 0.0f)); - Vector3 upperRight = _guiCamera.ViewportToWorldPoint(new Vector3(1.0f, 1.0f, 0.0f)); + //screen's 0 = overlay canvas's 0 (always?) + screenLowerLeft = Vector3.zero; + screenUpperRight = canvasRectTransform.sizeDelta; //check for right edge of screen - val = (newPosWVP.x + width / 2); - if (val > upperRight.x) - { - Vector3 shifter = new Vector3(val - upperRight.x, 0f, 0f); - Vector3 newWorldPos = new Vector3(newPosWVP.x - shifter.x, newPos.y, 0f); - newPos.x = _guiCamera.WorldToViewportPoint(newWorldPos).x; + borderTest = (newTTPos.x + width / 2); + if (borderTest > screenUpperRight.x) { + shifterForBorders.x = borderTest - screenUpperRight.x; + adjustedNewTTPos.x -= shifterForBorders.x; } //check for left edge of screen - val = (newPosWVP.x - width / 2); - if (val < lowerLeft.x) - { - Vector3 shifter = new Vector3(lowerLeft.x - val, 0f, 0f); - Vector3 newWorldPos = new Vector3(newPosWVP.x + shifter.x, newPos.y, 0f); - newPos.x = _guiCamera.WorldToViewportPoint(newWorldPos).x; + borderTest = (adjustedNewTTPos.x - width / 2); + if (borderTest < screenLowerLeft.x) { + shifterForBorders.x = screenLowerLeft.x - borderTest; + adjustedNewTTPos.x += shifterForBorders.x; } // check and solve problems for the tooltip that goes out of the screen on the vertical axis + //check for lower edge of the screen + borderTest = (adjustedNewTTPos.y - height / 2); + if (borderTest < screenLowerLeft.y) { + shifterForBorders.y = screenLowerLeft.y - borderTest; + adjustedNewTTPos.y += shifterForBorders.y; + } + //check for upper edge of the screen - val = (newPosWVP.y + height / 2); - if (val > upperRight.y) - { - Vector3 shifter = new Vector3(0f, 35f + height / 2, 0f); - Vector3 newWorldPos = new Vector3(newPos.x, newPosWVP.y - shifter.y, 0f); - newPos.y = _guiCamera.WorldToViewportPoint(newWorldPos).y; + borderTest = (adjustedNewTTPos.y + height / 2); + if (borderTest > screenUpperRight.y) { + shifterForBorders.y = borderTest - screenUpperRight.y; + adjustedNewTTPos.y -= shifterForBorders.y; } - //check for lower edge of the screen (if the shifts of the tooltip are kept as in this code, no need for this as the tooltip always appears above the mouse bu default) - val = (newPosWVP.y - height / 2); - if (val < lowerLeft.y) - { - Vector3 shifter = new Vector3(0f, 35f + height / 2, 0f); - Vector3 newWorldPos = new Vector3(newPos.x, newPosWVP.y + shifter.y, 0f); - newPos.y = _guiCamera.WorldToViewportPoint(newWorldPos).y; - } + //remove scale factor for the actual positioning of the TT + adjustedNewTTPos *= canvas.scaleFactor; + transform.position = adjustedNewTTPos; - this.transform.position = new Vector3(newPosWVP.x, newPosWVP.y, 0f); - this.gameObject.SetActive(true); _inside = true; } } diff --git a/Scripts/ToolTips/TooltipTrigger.cs b/Scripts/ToolTips/TooltipTrigger.cs new file mode 100644 index 0000000..b199c03 --- /dev/null +++ b/Scripts/ToolTips/TooltipTrigger.cs @@ -0,0 +1,108 @@ +using System.Collections; +///Credit Martin Nerurkar // www.martin.nerurkar.de // www.sharkbombs.com +///Sourced from - http://www.sharkbombs.com/2015/02/10/tooltips-with-the-new-unity-ui-ugui/ +using UnityEngine.EventSystems; + +namespace UnityEngine.UI.Extensions +{ + [RequireComponent(typeof(RectTransform))] + [AddComponentMenu("UI/Extensions/Tooltip/Tooltip Trigger")] + public class TooltipTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, ISelectHandler, IDeselectHandler + { + [TextAreaAttribute] + public string text; + + public enum TooltipPositioningType { + mousePosition, + mousePositionAndFollow, + transformPosition + } + + [Tooltip("Defines where the tooltip will be placed and how that placement will occur. Transform position will always be used if this element wasn't selected via mouse")] + public TooltipPositioningType tooltipPositioningType = TooltipPositioningType.mousePosition; + + /// + /// This info is needed to make sure we make the necessary translations if the tooltip and this trigger are children of different space canvases + /// + private bool isChildOfOverlayCanvas = false; + + private bool hovered = false; + + public Vector3 offset; + + + void Start() { + //attempt to check if our canvas is overlay or not and check our "is overlay" accordingly + Canvas ourCanvas = GetComponentInParent(); + if (ourCanvas && ourCanvas.renderMode == RenderMode.ScreenSpaceOverlay) { + isChildOfOverlayCanvas = true; + } + } + + /// + /// Checks if the tooltip and the transform this trigger is attached to are children of differently-spaced Canvases + /// + public bool WorldToScreenIsRequired + { + get + { + return (isChildOfOverlayCanvas && ToolTip.Instance.guiMode == RenderMode.ScreenSpaceCamera) || + (!isChildOfOverlayCanvas && ToolTip.Instance.guiMode == RenderMode.ScreenSpaceOverlay); + } + } + + public void OnPointerEnter(PointerEventData eventData) + { + switch (tooltipPositioningType) { + case TooltipPositioningType.mousePosition: + StartHover(Input.mousePosition + offset, true); + break; + case TooltipPositioningType.mousePositionAndFollow: + StartHover(Input.mousePosition + offset, true); + hovered = true; + StartCoroutine(HoveredMouseFollowingLoop()); + break; + case TooltipPositioningType.transformPosition: + StartHover((WorldToScreenIsRequired ? + ToolTip.Instance.GuiCamera.WorldToScreenPoint(transform.position) : + transform.position) + offset, true); + break; + } + } + + IEnumerator HoveredMouseFollowingLoop() { + while (hovered) { + StartHover(Input.mousePosition + offset); + yield return null; + } + } + + public void OnSelect(BaseEventData eventData) + { + StartHover((WorldToScreenIsRequired ? + ToolTip.Instance.GuiCamera.WorldToScreenPoint(transform.position) : + transform.position) + offset, true); + } + + public void OnPointerExit(PointerEventData eventData) + { + StopHover(); + } + + public void OnDeselect(BaseEventData eventData) + { + StopHover(); + } + + void StartHover(Vector3 position, bool shouldCanvasUpdate = false) + { + ToolTip.Instance.SetTooltip(text, position, shouldCanvasUpdate); + } + + void StopHover() + { + hovered = false; + ToolTip.Instance.HideTooltip(); + } + } +} diff --git a/Scripts/ToolTips/TooltipTrigger.cs.meta b/Scripts/ToolTips/TooltipTrigger.cs.meta new file mode 100644 index 0000000..67ec3ab --- /dev/null +++ b/Scripts/ToolTips/TooltipTrigger.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: abe02588cca16964c8571b21eefea5d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: