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.
Basically, ElementAt(1) is the “contentViewContainer” element, which is transformed (positioned and scaled) when you zoom / drag.
The evt parameter contains a localMousePosition that is in screenspace, with which you can calculate the “world position” .
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 );
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)
) );
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.
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;
}
}
I tried all the methods from the answers above… None worked correctly,
but my solution below creates a node right under the mouse pointer in the graph editor: