How to get the correct contextual menu mouse position on Graph View when I have ContentDragger & ContentZoomer manipulators?
Asked Answered
D

8

0

I’ve added the following manipulators to the GraphView:

this.AddManipulator(new ContentZoomer());
this.AddManipulator(new SelectionDragger());
this.AddManipulator(new ContentDragger());
this.AddManipulator(new RectangleSelector());

Then I’ve added a manipulator to add a Contextual Menu to add a node, like so:

this.AddManipulator(CreateContextualMenu("Title"));
  
private ContextualMenuManipulator CreateContextualMenu(string contextualMenuText)
{
    return new ContextualMenuManipulator(menuEvent =>
        menuEvent.menu.AppendAction(contextualMenuText, actionEvent =>
            AddElement(CreateNode(actionEvent.eventInfo.mousePosition)), DropdownMenuAction.AlwaysEnabled));
}

The CreateNode method receives the mouse position and inserts the node at the correct place, until I drag the graph view (due to the content dragger) and try to create a new node.

I’ve inserted a log to see the position and it seems that the actionEvent.eventInfo.mousePosition (and others) only goes up to the current window size, and therefore instead of adding on the current position it creates in the closest to the window size (which in terms of graph position, it might be in a position that isn’t showing after I drag).

The contextual menu is where I’ve clicked to add a node, and the node on the left is where it was added.

I’ve tried adding a MouseMove/Up/DownEvent to both the GraphView and GridBackground but it still does the same.
I’ve also tried using Mouse.current.position.ReadValue() but the same happens.

Is there a way to fix this?

Diagnose answered 22/3, 2024 at 11:26 Comment(0)
D
0

I was able to kind of solve it (isn’t perfect but works better) by adding these lines:

Vector2 worldMousePosition = editorWindow.rootVisualElement.ChangeCoordinatesTo(editorWindow.rootVisualElement.parent, position - editorWindow.position.position);
Vector2 localMousePosition = contentViewContainer.WorldToLocal(worldMousePosition);

Taken from this tutorial:

Diagnose answered 22/3, 2024 at 11:23 Comment(0)
T
0

I found a solution that works perfect in my case (I use it as the position for newly created Nodes).


public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
    VisualElement contentViewContainer = ElementAt(1);
    Vector3 screenMousePosition = evt.localMousePosition;
    Vector2 worldMousePosition = screenMousePosition - contentViewContainer.transform.position;
    worldMousePosition *= 1 / contentViewContainer.transform.scale.x;
    // add context menu entries here...
}

Basically, ElementAt(1) is the “contentViewContainer” element, which is transformed (positioned and scaled) when you zoom / drag.
184609-screenshot-2021-08-10-175218.png
The evt parameter contains a localMousePosition that is in screenspace, with which you can calculate the “world position” .

Tropopause answered 6/6, 2023 at 4:45 Comment(0)
B
0
var position = viewTransform.matrix.inverse.MultiplyPoint(evt.localMousePosition);
Barney answered 18/4, 2024 at 10:3 Comment(1)

This works like a charm, but I'll add that you should cache the localMousePosition of the event before modifying the event, otherwise in some versions of graphview it goes back to (0,0): Vector2 localMousePos = evt.localMousePosition; evt.menu.AppendAction(...) //And other possible manipulations... Vector2 actualGraphPosition = viewTransform.matrix.inverse.MultiplyPoint(localMousePos );

Guillemot
G
0

(link is broken) http://answers.unity.com/answers/1861624/view.html

This works like a charm, but I’ll add that you should cache the localMousePosition of the event before modifying the event, otherwise in some versions of Graph View it goes back to (0,0):

Vector2 localMousePos = evt.localMousePosition;
evt.menu.AppendAction(...) //And other possible manipulations...
Vector2 actualGraphPosition = viewTransform.matrix.inverse.MultiplyPoint(localMousePos );
Guillemot answered 22/3, 2024 at 11:26 Comment(0)
K
0

For anyone trying to do this while using the new Input-System - the mouse position (Mouse.current.position.ReadValue()) works in the editor too, so makes this problem a lot easier.

The editor mouse position combined with graph viewInstance.contentViewContainer.WorldToLocal() gives you the position of the mouse in the graph view.

newNode.SetPosition( new Rect(
    this.contentViewContainer.WorldToLocal( Mouse.current.position.ReadValue() ),
    new Vector2 (100,100)
) );
Kendallkendell answered 22/3, 2024 at 11:22 Comment(0)
L
0

Another very simple solution

public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
    var mousePos = evt.localMousePosition;
    var worldPos = ElementAt(0).LocalToWorld(mousePos);
    var localPos = ElementAt(1).WorldToLocal(worldPos);
}

Basically we just convert mouse position from one element’s coordinate system into the other. Works perfectly with dragger and zoomer.

Edit

Even simpler:

public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
    localMousePos = ElementAt(0).ChangeCoordinatesTo(ElementAt(1), evt.localMousePosition);
    base.BuildContextualMenu(evt);
}

Edit 2

It’s as simple as:

void AddBackgroundGrid()
{
    styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>("Assets/Styles/GridBackground.uss"));
    grid = new GridBackground();
    Insert(0, grid);
}
public override void BuildContextualMenu(ContextualMenuPopulateEvent evt)
{
    localMousePosition = evt.localMousePosition;
    base.BuildContextualMenu(evt);
}
  
public bool AddNewNodeToGraph(SearchTreeEntry searchTreeEntry)
{
    var newMousePos = grid.ChangeCoordinatesTo(contentViewContainer, localMousePosition);
    var node = new Node();
    node.SetPosition(new Rect(newMousePos, Vector2.one));
    AddElement(node);
    return true;
}
Logsdon answered 22/3, 2024 at 11:27 Comment(0)
K
0

None of the solutions worked for me.
I tried to copy-paste node, so I needed the position of mouse on Paste action.

My solution

  1. Subscribe on mouse action.
RegisterCallback<MouseUpEvent>(MouseUp);
  1. Store mouse Position
void MouseUp(MouseUpEvent evt)
{
    _storedMousePosition = evt.mousePosition;
}
  1. Use it
Vector2 mousePosition = contentViewContainer.WorldToLocal(_storedMousePosition); 
Krenn answered 22/3, 2024 at 12:0 Comment(0)
O
0

Here is the cleanest solution I’ve found. This is a 100% accurate position to place the search window as well as the node to be created.

public class MyGraphView : GraphView
{
	private Vector2 _mousePosition;
	private MySearchWindow _searchWindow;
	
	public MyGraphView()
	{
		RegisterCallback<ContextualMenuPopulateEvent>( _ => 
			// Cache the event's mouse position when right-clicking.
			_mousePosition = Event.current.mousePosition 
		);
		
		if (_searchWindow == null )
		{
			_searchWindow = ScriptableObject.CreateInstance<MySearchWindow>();
		}
		
		nodeCreationRequest = context =>
		{
			if ( Event.current != null )
			{
				// Cache the mouse position if the search window was opened by right-clicking.
				// Definitely check if the Event.current is null because this can be opened by pressing the space key.
				_mousePosition = Event.current.mousePosition;
			}

			SearchWindow.Open(
				// Still use the context's mouse position.
				new SearchWindowContext( context.screenMousePosition ), 
				_searchWindow 
			);
		};
	}
	
	private Node CreateNode()
	{		
		Node newNode = new Node();
		
		// Use the Graph's contentViewContainer to convert the cached mouse event's position into local space.
		var position = contentViewContainer.WorldToLocal( _mousePosition );
		newNode.SetPosition( new Rect( position, Vector2.zero ) );
		
		AddElement( newNode );
		
		return newNode;
	}
}
Outlet answered 22/3, 2024 at 3:20 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.