Tutorial: Move/Scale “Window Widgets”

If you’re like me, you might have expected more out-of-the-box UMG functionality in regards to prompts. So, as my first tutorial (and one of my first real forays into UE4), I want to share what I came up with in the way of movable and sizable “window” widgets:

Getting Started

First, you will need to start or open a project. What template you use is largely irrelevant, as this should apply to any. Next, create a new Widget Blueprint in your asset manager. (I recommend creating a folder as well.)

newwidget

Designer

For your new widget to work, you need several key attributes:

widget_design widget_heirarchy

  • Top layer should be a Canvas Panel
  • A main SizeBox (Make it a variable) to hold everything
  • Borders to give visuals (I used these for background and visual border)
  • GridPanel with 9 slots to keep everything in line when resized
  • SizeBoxes in the GridPanel to, again, make sure the respective grid slots remain the size you want
  • SizeBox in the center GridPanel slot to hold the content of your window
  • An Image to work as a “MoveHandle”
  • An Image in the lower-right grid slot to work as a “Resize Handle”

If you are also a beginner with UMG, or you just want to skip to the good part, feel free to use this above example layout. You can paste the layout, in text form, onto your own Canvas Panel. 

Layout pastebin
Example of how to paste it in:

Functions

Before clicking on your Graph editor, let’s first Bind your widget’s handles to new Functions. In my above layout, I made two images: MoveHandle and TransHandle. These are the only two you need to bind functions to. The name of your function does not matter outside of neatness and your own sanity. Here’s how:

With that done, you should now be in the Graph editor. We will be making two Override Functions first. To do this, place your cursor in the Functions panel on the left side, making the Override option appear. Select two from the dropdown, OnMouseMove and OnMouseButtonUp. Next, use the +Function button to create three more standard functions: WinTrans, WinMove, and SnapToGrid. Should look like this:

functions

OnMouseButtonUp: What the widget does when you release the click
OnMouseMove: What the widget does when you move your cursor
MoveHandle_Press: What the widget does when you press your move handle
TransHandle_Press: What the widget does when you press your resize handle
WinTrans: Function to do all the resizing. Separated to keep organized
WinMove: Function to do all the moving. Separated to keep organized
SnapToGrid: Function to make the widget line up with other windows… Will explain later

Variables:

When working with variables, it’s a good idea to get a good grasp on the color coordination. This particular example uses four different kinds of variables: Boolean (Red), Float (Green), Blue (Vector2D), and Light-Blue Circle (Widget). The Widget variable references the Main SizeBox you created in the layout. This is necessary to communicate with it from blueprint. The breakdown:

variables

MoveHandlePressed (Boolean): Is the move handle pressed? True/false
TransHandlePressed (Boolean): Is the resize handle pressed? True/false
MinWidth (Float): Minimum width of your window
MaxWidth (Float): Maximum width of your window
MinHeight (Float): Minimum height of your window
MaxHeight (Float): Maximum height of your window
GridSnap (Float): The size of the grid to which your window will snap
PressOffset (Vector2D): The original location of your mouse when you first press it
InitialMousePos (Vector2D): Mouse position relative to the *canvas*
MainSizeBox (Widget): Reference to the size box in which everything is stored. This is how we communicate with the widget component. (Tell it to move, resize, etc.)

Additionally, WinTrans and WinMove functions both require a Vector2D input variable. (I call it MousePos) You will see why later on.

MoveHandle_Press Function

Open up your MoveHandle_Press function. Let’s get started.

movehandlepress

What’s going on: First, we see the MoveHandlePressed boolean being changed to TRUE. This let’s us know later when when “move” should be happening.

Next, there is a very small calculation being done to find where your cursor is in relation to the window. This is done in order for the window to move more gracefully, rather than locking its upper-left corner directly on the cursor. The subtraction box with three blue dots is a Vector 2D – Vector 2D node, which basically subtracts the X’s and Y’s from both vectors and gives you a result as vector. It is then Set to the PressOffset variable for later use.

The DetectDraggedIfPressed node will allow for your functions to work as intended so long as the mouse is held down. This is important.

Lastly, we have CaptureMouse telling the widget to continually poll the mouse for any changes in its position.

TransHandle_Press Function

This one is considerably less complicated. Same logic as with the previous function, sans the offset.

transhandlepress

OnMouseMove Function

This is where it gets a bit more complicated.

mousemove

What’s going on: Firstly, we are finding the real location of the cursor by using the geometry of the canvas. This is being used to set the InitialMousePos variable as well, which should look familiar from MoveHandle_Press.

Next, there are two branches. These branches are tied to the True/False state of your Move/Trans boolean variables. (As set in the previous steps.) This logic dictates that, if the MoveHandle is pressed, execute function WinMove. If that one isn’t being pressed, check TransHandle and execute WinTrans if pressed. If neither are pressed, just skip to the end. Note: these two functions have InitialMousePos fed into them. In order to this, you must configure Vector2D Input variables on your functions.

WinMove Function

So your mouse is moving, the MoveHandle is pressed, and you’re passing the mouse position to WinMove. What does this function do?

winmove

Pretty straight-forward, really. First, it subtracts the PressOffset (which we calculated when we pressed the button) from the current MousePos. The resulting Vector2D is then sent to SetPosition, which is using the MainSizeBox variable to talk to the SizeBox component. Note: In order to use SetPosition, you need to use a Slot as CanvasSlot node.

Simple, huh? The next function is even simpler.

WinTrans Function

OK, I lied.

wintrans

It looks scary, but the logic is simple if you can decipher my attempt at rerouting.

What’s going on: While technically this is not the first thing that happens execution-wise, it might be a good idea to explain first. The window’s position is subtracted from the current MousePos, giving us a current desired size depending on where the cursor is. If we sent this Vector2D straight to the SizeBox, it would work just fine.. Only the window would be able to resize to negative values or clear off the screen. We don’t want that.

To remedy this problem, we break the Vector2D into X and Y, sending both on routes to InRange(Float) nodes. These nodes, with our aforementioned Min/Max Width/Height variables attached, perform a “check” on the numbers to make sure they are not out of range. If they are found to be IN range, they output a TRUE boolean. (Red lines.)

These red lines actually take us to the execution steps of the function. The first branch checks if the desired width is in-range, and executes a SetWidthOverride if so. The width is pulled from where we Broke the Vector2D, and the Target is pulled from the MainSizeBox variable. If the number is not in range, it proceeds to check the height as well in its own respective branch and SetHeightOverride node.

OnMouseButtonUp Function

This is largely a ‘cleaup’ function that takes place after you finally release the mouse button.

mousebuttonup

What’s going on: First and foremost, since we know that neither button should be pressed at this point, we set both Handle variables to FALSE. Next, we DetectDragifPressed, which you wouldn’t think would be necessary.. But I have seen inconsistent results with not executing it at the end of a drag/drop operation. Then, we ReleaseMouseCapture.. and finally..

SnapToGrid Function (Extra Credit)

This function may not be necessary for your application. Its purpose is to ensure that the size and position of your window is “snapped” into a grid size of your choosing. I recommend making your GridSnap variable a multiple of 16. Doing so would allow for windows to line up just right:

snaptogrid

Making it all work

Open up your Character blueprint. In my case, I use ThirdPersonCharacter.

character

What’s going on: First, I am binding an execution to the “L” key press. This creates an instance of your fancy new widget, adds to it the viewport, and sets its visibility to SelfHitTestInvisible. This visibility is very important, because otherwise, you would not be able to click on anything under the screen-covering Canvas of your window widget.

Next, I am binding “I” key press to do a Flip/Flop operation. This is cool. The flip consists of setting the input mode and mouse cursor for UI. The flop consists of setting the input mode and mouse cursor for just game (Mouselook).

That’s it! Give it a play.

There are some nuances in these pictures, such as how the nodes are routed, so please look closely before asking for clarification. (Which I will be happy to do.)

Good luck!