Ramblings from a video game/graphics programmer on anything coding- or graphics-related

Main menu

Tag Archives: zoom

Why u no zoom?

I recently developed a couple of graph-based editors for the excellent Unity3D engine. While doing so I ran into a problem that I've noticed other similar editors available on the Unity Asset Store have run into as well. None of the ones I could find have solved this problem satisfactorily. The thing I'm talking about is zooming the contents of an editor window or part of an editor window in and out. In this post I'll describe a technique to achieve this somewhat nicely and without too much effort. My solution can easily be integrated into an existing editor window class that needs to zoom certain parts of its contents in and out.

An example of one of my zoomable graph editors.

My graph editors have a main area where the graph is displayed in the form of a bunch of boxes connected by Bezier splines (see above image). Typically on the top there is a menu bar and on the right is a panel that displays various properties of the currently selected graph node. Like any other custom editor window my graph editors inherit from the EditorWindow class. The goal now is to be able to scale the graph area of the window. I wanted to be able to do this both via a slider, where the scaling occurs centered around the top-left corner of the graph area, and with the mouse wheel, where the scaling occurs centered around the current mouse position.

How to do it easily?

There are two key insights I want to present to implement easy zooming in the Unity editor:

Arbitrary contents rendered to an editor window can be scaled by modifying the scale of GUI.matrix.

An EditorWindow's OnGUI function is always called inside an implicit GUI.BeginGroup call where the draw area is offset vertically by 21 pixels and is clipped to the screen extents of the window.

So to correctly zoom any rectangle inside of an EditorWindow without changing any of our existing draw code we need to end the implicit group that Unity begins itself before calling our OnGUI, and we need to change GUI.matrix while we draw the part of the window that we want to zoom.

The reason we need to end the group is because we need to effectively turn off clipping against the editor window extents and change it to clip against the size of our zoomed area. This is important for when the content is zoomed out really far and our drawing code, which still assumes a 1:1 correspondence between logical coordinates and editor window pixels, needs to render beyond the borders of the window itself because of the high zoom. The scale set in GUI.matrix will then scale this new clip area to the correct size.

Here are some screenshots from the example code you can find at the end of this post.

The ZoomTest editor window with a zoom scale of 1.0, i.e. no zooming.This is what you see when you open the window.

The ZoomTest editor window zoomed in to a zoom scale of 2.0.

The ZoomTest window zoomed out to a zoom scale of 0.8.

Details Please

Let's walk through the code step-by-step. If you can't wait or want to look at a full example first, skip down to the end of this post for all of the code.

First, we'll put all the code into static functions, kind of like how Unity's GUI class works. Let's call it EditorZoomArea.

With this, client code can call EditorZoomArea.Begin and EditorZoomArea.End inside OnGUI to begin and end an a zoomable draw area. EditorZoomArea.Begin receives the desired zoom scale and the rectangle in editor window screen coordinates where the zoom area is located. By screen coordinates I mean the coordinate system that has pixels as units and that starts at the top-left corner of the editor window, just below the tab that displays the title, and that extends to [Screen.width, Screen.height]. It's the standard, pixel-based coordinate system used in any editor window.

The first thing we need to do in EditorZoomArea.Begin is to end the group that Unity implicitly begins for every editor window. We do this by simply calling GUI.EndGroup. Note that when EditorZoomArea.Begin is called you must not be in between a GUI.Begin/EndGroup or GUILayout.Begin/EndArea call or else our little trick of calling GUI.EndGroup to end the implicit group that every Unity editor window has won't work.

The next step is to set up correct clipping of the zoomed draw area by calling GUI.BeginGroup. This clip area is independent of the editor window size but needs to match our zoomed in or zoomed out draw area. For example, if we were to zoom in by a factor of 2 and our zoom area has a screen width and height of 200 by 200, we would need the clip area to be 100 by 100 pixels. If we were to zoom out by half, i.e. by a factor of 0.5, we would need the clip area to be 400 by 400 pixels.

Note that we add kEditorWindowTabHeight, which has a value of 21, to the top edge of the clip area. That's to compensate for the editor window tab at the top that displays the window name. Remember, we ended the group that Unity implicitly begins that normally prevents us from rendering over it. So we need to account for that and that's why we add 21 pixels to the top edge of the clip area.

The final step is to change the GUI.matrix to do the scaling for us. To do that we need to create a composite matrix that first translates the clip area's top-left corner to the origin, then does the scaling around the origin, and finally translates the zoomed result back to where the clip area is supposed to be.

Note that for good style and to play nice we save off the old GUI.matrix and concatenate it with our scale matrix. However, the code pretty much assumes that you haven't messed with GUI.matrix before calling EditorZoomArea.Begin.

Finally, in EditorZoomArea.End we simply reset the GUI.matrix to what it was before, end the group for the clip area that we began, and begin Unity's implicit group for the editor window again. Please check the full source code below for details.

Here's a full example in form of a single C# file ZoomTestWindow.cs using the above EditorZoomArea class. It supports zooming in and out with both the slider at the top and the mouse wheel. You can also move the zoomable area around by either holding down on the middle mouse button or by using alt+left click.