Skip to content

Instantly share code, notes, and snippets.

@sinbad
Last active September 25, 2021 17:25
Show Gist options
  • Save sinbad/7204edaba7453e48c8c1e837505d054d to your computer and use it in GitHub Desktop.
Save sinbad/7204edaba7453e48c8c1e837505d054d to your computer and use it in GitHub Desktop.
Simple Unity UI tooltip system (requires DoTween)
using DG.Tweening;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
// Attach this component to a UI object which will be the tooltip visual
// Must include some text somewhere of course, and optionally a CanvasGroup if you want fading
public class TooltipPanel : MonoBehaviour {
[Tooltip("The text object which will display the tooltip string")]
public TextMeshProUGUI tooltipText;
[Tooltip("Whether to move the tooltip with the mouse after it's visible")]
public bool moveWithMouse;
[Tooltip("The distance from the mouse position the tooltip will appear at (relative to tooltip pivot)")]
public Vector2 positionOffset;
[Tooltip("The margins from the edge of the screen which the tooltip will stay within")]
public Vector2 margins;
[Tooltip("The fade in/out duration of the tooltip: requires that this object has a CanvasGroup component, ignored if it doesn't")]
public float fadeDuration = 0.25f;
private Canvas parentCanvas;
private RectTransform rectTransform;
private CanvasGroup canvasGroup;
private Tweener fadeTween;
private RectTransform triggerObject;
private void Awake() {
rectTransform = GetComponent<RectTransform>();
canvasGroup = GetComponent<CanvasGroup>();
parentCanvas = GetComponentInParent<Canvas>();
}
private void Start() {
gameObject.SetActive(false);
}
private void Update() {
if (moveWithMouse)
Reposition();
}
private void Reposition() {
Vector2 screenPos = Input.mousePosition;
// world position origin is wherever the pivot is
Vector2 newPos = screenPos + positionOffset;
float maxX = Screen.width - margins.x;
float minX = margins.x;
float maxY = Screen.height - margins.y;
float minY = margins.y;
float rightEdge = newPos.x + (1f - rectTransform.pivot.x) * rectTransform.rect.width * parentCanvas.scaleFactor;
if (rightEdge > maxX) {
newPos.x -= rightEdge - maxX;
}
float leftEdge = newPos.x - rectTransform.pivot.x * rectTransform.rect.width * parentCanvas.scaleFactor;
if (leftEdge < minX) {
newPos.x += minX - leftEdge;
}
// y is measured from top
float topEdge = newPos.y + (1f - rectTransform.pivot.y) * rectTransform.rect.height * parentCanvas.scaleFactor;
if (topEdge > maxY) {
newPos.y -= topEdge - maxY;
}
float bottomEdge = newPos.y - rectTransform.pivot.y * rectTransform.rect.height * parentCanvas.scaleFactor;
if (bottomEdge < minY) {
newPos.y += minY - bottomEdge;
}
var cam = parentCanvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : parentCanvas.worldCamera;
RectTransformUtility.ScreenPointToWorldPointInRectangle(triggerObject, newPos, cam, out var worldPoint);
rectTransform.position = worldPoint;
}
public void Show(string text, RectTransform triggeredBy) {
if (gameObject.activeSelf)
return;
triggerObject = triggeredBy;
tooltipText.text = text;
if (fadeTween != null) {
fadeTween.Kill();
fadeTween = null;
}
bool fade = canvasGroup != null && fadeDuration > 0;
if (fade)
canvasGroup.alpha = 0;
gameObject.SetActive(true);
if (fade) {
fadeTween = canvasGroup.DOFade(1, fadeDuration)
.SetUpdate(true)
.OnComplete(() => { fadeTween = null; });
}
// in case we need to resize (e.g. content fitter)
LayoutRebuilder.ForceRebuildLayoutImmediate(rectTransform);
Reposition();
}
public void Hide() {
if (!gameObject.activeSelf)
return;
if (fadeTween != null) {
fadeTween.Kill();
fadeTween = null;
}
bool fade = canvasGroup != null && fadeDuration > 0;
if (fade) {
// Fade out
fadeTween = canvasGroup.DOFade(1, fadeDuration)
.SetUpdate(true)
.OnComplete(() => {
gameObject.SetActive(false);
fadeTween = null;
});
} else {
gameObject.SetActive(false);
}
}
}
using UnityEngine;
using UnityEngine.EventSystems;
[RequireComponent(typeof(RectTransform))]
public class TooltipTrigger : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler {
[Tooltip("The tooltip canvas which will display the tooltip. If not specified, we'll try to find one.")]
public TooltipPanel tooltipPanel;
[Tooltip("The text to display in the tooltip")]
public string text;
[Tooltip("How long the mouse must linger on us before showing the tooltip")]
public float delay = 1f;
private bool mouseIsHovering;
private float mouseHoverTime;
private RectTransform rectTransform;
private void Awake() {
rectTransform = GetComponent<RectTransform>();
}
private void Start() {
if (tooltipPanel == null) {
var canvases = Resources.FindObjectsOfTypeAll<TooltipPanel>();
if (canvases.Length > 0)
tooltipPanel = canvases[0];
}
}
public void OnPointerEnter(PointerEventData eventData) {
mouseIsHovering = true;
mouseHoverTime = 0;
}
public void OnPointerExit(PointerEventData eventData) {
mouseIsHovering = false;
HideTooltip();
}
private void Update() {
// Cancel on any mouse down
// not via event because we might not receive it
if (Input.GetMouseButtonDown(0)) {
mouseIsHovering = false;
HideTooltip();
}
if (mouseIsHovering) {
mouseHoverTime += Time.unscaledDeltaTime;
if (mouseHoverTime >= delay)
ShowTooltip();
}
}
private void ShowTooltip() {
if (tooltipPanel != null)
tooltipPanel.Show(text, rectTransform);
}
private void HideTooltip() {
if (tooltipPanel != null)
tooltipPanel.Hide();
}
}
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
@BirukTes
Copy link

Could provide an example, it does not work correctly on my setup. The TMP is disappearing and not appearing. Looking for behaviour that when hovered on specified UI object the tip is shown for that object...

@sinbad
Copy link
Author

sinbad commented Mar 3, 2021

It worked at the time, probably a Unity change. I don't use Unity anymore so won't be updating it, sorry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment