Search Unity

Question How do you move a VisualElement to the position of a mouse?

Discussion in 'UI Toolkit' started by Yecats, Jan 24, 2021.

  1. Yecats

    Yecats

    Joined:
    Jul 13, 2014
    Posts:
    69
    I need to move a VisualElement to the location of the mouse. I cannot use any of the OnMouse or OnPointer events to do this - I need to be able to read the mouse's position and set the VisualElement's position to it.

    I have tried doing the following, but the result is that the element is placed no where near the actual location of the mouse:

    Code (CSharp):
    1. Vector2 pos = Mouse.current.position.ReadValue();
    2.  
    3. DragViewer.style.top = pos.y;
    4. DragViewer.style.left = pos.x;
    Edit: It seems that the two systems are using different coordinates, but I have not been able to successfully convert.
     
    Last edited: Jan 25, 2021
    1000Nettles likes this.
  2. uBenoitA

    uBenoitA

    Unity Technologies

    Joined:
    Apr 15, 2020
    Posts:
    220
    First point is that the InputSystem's coordinate system uses bottom as 0 for y coordinates, with positives going up on the screen, whereas UI Toolkit's coordinate system uses top as 0 with positives going downward. Use
    pos.y = Screen.height - pos.y
    to flip them.

    Second point is that you need to either use DragViewer.style.position = Position.Absolute, or to use
    pos = DragViewer.WorldToLocal(pos)
    to get the position element-relative coordinates.
     
  3. Yecats

    Yecats

    Joined:
    Jul 13, 2014
    Posts:
    69
    Thank you, @uBenoitA!

    This is an area that I'd suggest looking at improving the documentation for. I spent so many hours trying to find the answer to this and came up empty handed. It seems likely that it'd be a common workflow that folks would want to do.
     
  4. Jan_K

    Jan_K

    Joined:
    Oct 11, 2019
    Posts:
    1
    I had used this solution and windows' display zoom was messing with it. With an absolute pixel position it was correct on 100% zoom but wrong on other settings. I had to use Screen.width and height to convert the position to a percent value instead of a pixel value to get it to work. Panel settings -> Scale mode was on "constant physical size" for this, "constant pixel size" did not have this problem but was unsuitable for our project otherwise. Versions were 1.0.0-preview.14 on Unity 2021.1.0f1.
     
  5. manuelgoellnitz

    manuelgoellnitz

    Joined:
    Feb 15, 2017
    Posts:
    397
    I Think it is a bug that WorldToLocal doesn't include the scaling of the ui in its calculation
     
    Last edited: Jun 22, 2021
  6. FoodFish_

    FoodFish_

    Joined:
    May 3, 2018
    Posts:
    58
    void OnDragPerformEvent(DragPerformEvent e)
    {
    ......

    Vector2 localPos = (e.currentTarget as VisualElement).ChangeCoordinatesTo(contentViewContainer, e.localMousePosition);
    ....


    }

    This is the best I have found.
     
  7. sol-erides

    sol-erides

    Joined:
    Dec 8, 2018
    Posts:
    17
    Found a solution when dealing with Scale Mode: Scale with Screen Size - this seems to get the desired local position.
    Code (CSharp):
    1. RuntimePanelUtils.ScreenToPanel(someVisualElement.panel, mousePosition);
    reference: https://docs.unity3d.com/Packages/com.unity.ui@1.0/api/UnityEngine.UIElements.RuntimePanelUtils.html

    Would be nice for Unity to make this function more accessible instead of hiding it in an extension class that's barely documented - it seems like a very common use case whenever you want to make UI that follows or reacts with the real cursor position.
     
  8. poprev-3d

    poprev-3d

    Joined:
    Apr 12, 2019
    Posts:
    72
    Awesome, thanks for the tip @sol-erides, it's extremely useful.

    Code (CSharp):
    1. var mousePosition = ** take mouse position from unity input system **;
    2. Vector2 mousePositionCorrected = new Vector2(mousePosition.x, Screen.height - mousePosition.y);
    3. mousePositionCorrected = RuntimePanelUtils.ScreenToPanel(defaultCanvasUIDocument.rootVisualElement.panel, mousePositionCorrected);
     
  9. Xelnath

    Xelnath

    Joined:
    Jan 31, 2015
    Posts:
    402
    If anyone uses a mouse click for raytrace, you can move a game object to where the ray hits, then use this to make the UI follow an in-world transform.

    Code (CSharp):
    1.  
    2. public class UITrackTransform : MonoBehaviour
    3. {
    4.     public Transform TransformToFollow;
    5.     private VisualElement m_Bar;
    6.     private Camera m_MainCamera;
    7.     private void Start()
    8.     {
    9.         m_MainCamera = Camera.main;
    10.         m_Bar = GetComponent<UIDocument>().rootVisualElement.Q("Container");
    11.         SetPosition();
    12.     }
    13.  
    14.     public void SetPosition()
    15.     {
    16.         Vector2 newPosition = RuntimePanelUtils.CameraTransformWorldToPanel(
    17.             m_Bar.panel, TransformToFollow.position, m_MainCamera);
    18.         newPosition.x = (newPosition.x - m_Bar.layout.width / 2);
    19.         m_Bar.transform.position = newPosition;
    20.     }
    21.  
    22.     private void LateUpdate()
    23.     {
    24.         if (TransformToFollow != null)
    25.         {
    26.             SetPosition();
    27.         }
    28.     }
    29. }
    30.  
     
    randomdragon likes this.