For anyone wishing to create a custom element (for example, to click and drag a GUI rectangle inside of EditorWindow), buckle-up and read this amazing post: by Max Anderson
It basically discusses how unity calls the same function (for example EditorWindow.OnGUI) with a different intention - first to allow you to compute position of elements, next to process mouse-up, then to process mouse move, and then to paint them on screen (during repaint).
So in my above example, unity would would invoke our implementation of OnGUI() 4 times.
we can grab GetControlID() in our OnGUI implementation, to let unity know which element we are currently looking at. Every time unity-invokes OnGUI (each time out of 4 times in my example), unity flushes all of the controlIDs we’ve previously requested from it.
But because we hard-code the invocations of GetControlID() in the same order in our OnGUI function , we can be assured that the newly requested controls will be assigned to the same elements, every time we re-run our OnGUI()
in case if the link ever goes down, I will copy-paste the most important text from that post:
At a high level, making a custom Handle involves understanding Events, Graphics, and “control IDs.”
Events will be “sent” to your function automatically via the static Event.current variable. Each event object has an EventType stored in the Event.type variable. The two most important event types you should familiarize yourself with are:
EventType.Layout – This is the first event sent during a repaint. You use it to determine sizes and positioning of GUI elements, or creation of control IDs, in order for future events to make sense. For example, how do you know if a mouse is inside a button if you don’t know where the button is?
EventType.Repaint – This is the last event sent during a repaint. By this point, all input events should have been handled, and you can draw the current state of your GUI based on all of the previous events. Handles.DrawLine only responds to this event, for example. There are many ways to draw to the screen during this event, but the Graphics API gives you the most control.
All other event types happen between these two, most of which are related to mouse or keyboard interactions. You can accomplish a lot with just this information, but in order to deal with multiple interactive objects from a single Editor.OnInspectorGUI call, you need to use “control IDs.”
Control IDs sound scary, but they’re really just numbers. Using them correctly is currently not well documented, so I’ll do my best to explain the process:
Before every event, Unity clears its set of Control IDs.
Each event handling function (Editor.OnInspectorGUI, Editor.OnSceneGUI, etc.) must request a Control ID for each interactible “control” in the GUI that can respond to mouse positions or keyboard focus. This is done using the GUIUtility.GetControlID function. Because this must be done for every event, the order of calls to GUIUtility.GetControlID must be the same during every frame. In other words, if you get a control ID during the Layout event, you MUST get that same ID for every other event until the next Layout event.
During the EventType.Layout event inside Editor.OnInspectorGUI, you can use theHandleUtility.AddControl function to tell Unity where each Handle is relative to the current mouse position. This part is where the “magic” of mapping mouse focus, clicks, and drags happens.
During every event, use the Event.GetTypeForControl function to determine the event type for a particular control, instead of globally. For example, a mouse drag on a single control might still look like a mouse move to all other controls.
I promise it’s easier than it sounds. As proof, here’s code that demonstrates how to properly register a handle control within Editor.OnSceneGUI:
int controlID = GUIUtility.GetControlID(FocustType.Passive);
Vector3 screenPosition = Handles.matrix.MultiplyPoint(handlePosition);
int controlID = GUIUtility.GetControlID(FocustType.Passive);
Vector3 screenPosition = Handles.matrix.MultiplyPoint(handlePosition);
switch (Event.current.GetTypeForControl(controlID))
{
case EventType.Layout:
HandleUtility.AddControl(
controlID,
HandleUtility.DistanceToCircle(screenPosition, 1.0f)
);
break;
}
Not so bad, right? The worst part of that is the HandleUtility.DistanceToCircle call, which takes the screen position of the handle and a radius, and determines the distance from the current mouse position to the (circular) handle. With this, you have a Handle in the Scene View that can recognize mouse gestures, but it doesn’t do anything yet.
To make it do something, we can add code for the appropriate mouse events to the switch statement:
case EventType.MouseDown:
if (HandleUtility.nearestControl == controlID)
{
// Respond to a press on this handle. Drag starts automatically.
GUIUtility.hotControl = controlID;
Event.current.Use();
}
break;
case EventType.MouseUp:
if (GUIUtility.hotControl == controlID)
{
// Respond to a release on this handle. Drag stops automatically.
GUIUtility.hotControl = 0;
Event.current.Use();
}
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl == controlID)
{
// Do whatever with mouse deltas here
GUI.changed = true;
Event.current.Use();
}
break;
You’ll see a few new things in that snippet:
HandleUtility.nearestControl [undocumented] – This is set automatically by Unity to the control ID closest to the mouse. It knows this from our previous HandleUtility.AddControl call.
GUIUtility.hotControl – This is a shared static variable that we can read and write to determine which control ID is “hot,” or in use. When start and stop a drag operation, we manually set and clear GUIUtility.hotControl for two reasons:
So we can determine if our handle’s control ID is hot during other events.
So every other control can know that it is NOT hot, and should not respond to mouse events.
Event.Use – This function is called when you are “consuming” an event, which sets its type to “Used.” All other GUI code should then ignore this event.
I mention that “drag starts / stops automatically” in the comments, which meansEventType.MouseDrag events will automatically be sent instead of EventType.MouseMovewhen a button is held down. You are still responsible for checking hot control IDs in order to know what handle is being dragged.
I didn’t put any useful code in there because at this point, the world is your oyster. Need to draw something? Add a case EventType.Repaint: block and draw whatever you like. Want to repaint your handle every time the mouse moves? Add a case EventType.MouseMove: block and callSceneView.RepaintAll [undocumented], which will force the Scene View to… repaint all!
Once again, you can find the post here