UMG Reusable Draggable Windows

Hey how would you guys approach creating a reusable draggable window on UMG?

I currently have a Game Interface UMG Widget and a SkillsWindow Widget which I added in the Game Interface one. I need users to be able to drag the SkillsWindow around.

Im going to create other types of windows and throw them on Game Interface and I want those to be draggable too.

Whats the best practice on this?

Cheers

I would probably make the low level framework in C++, and expose it to Blueprints for styling. Here’s sort of a parts breakdown,

SFloatingWindowManager - Slate widget that manages windows. Internally it manages a list of SFloatingWindows, it performs layout for them, does painting…etc. Looking at SCanvas or SConstraintCanvas would be a good starting point, except you don’t want to expose it publicly as a SPanelWidget, as you don’t want people directly attaching children, not sure, i’d need to start implementing it to know for sure. Either way, users don’t create the SFloatingWindows that SFloatingWindowManager creates, instead they give the manager an SWidget to convert into a window. The manager in turn creates the window, and set the content to the passed in widget. You could overcomplicate that by having delegates, maybe an interface? that windows represent, allowing users to factory their own IFloatingWindow widget, with their own buttons…etc. But I’d only go down that route if you were making this a widget that shipped as part of the base set of widgets / plugin. But in this case we’d just simplify it and have the SFloatingWindowManager just make an SFloatingWindow directly.

SFloatingWindow - Works with the SFloatingWindowManager, you’ll no doubt need to do things like handling keys and such for each window, you’ll need some kind of container widget for that. Additionally you’ll use this widget for all styling / chrome / title bar / title bar buttons. You could probably expose some options on the SFloatingWindowManager for how to style the windows, which it in turn passes to the SFloatingWindow. The SFloatingWindow would have a tight connection with SFloatingWindowManger, it will need to do things like react to input, focus change…etc, and then notify the manager of changes like, I got focus. When that happens the manager would need to adjust window sort / draw order and move the newly focused window to the front of the stack.

UFloatingWindowManager - This is the only part we expose to UMG. It’s a UWidget that creates the SFloatingWindowManager. It has a bunch of methods exposed for adding a new child widget, that then goes through the window creation process explained above. I’d do all requests based on the widgets themselves, the windows would just sort of be invisible. e.g lets say I wanted to get a list of all child window widgets, you’d return an array of UUserWidget*'s (the same ones users wanted to create windows for). Lets say you want to close a window, you’d ask the window manager to close the window owning UuserWidget X, and it would do the search internally to find the window and remove it.

Now if you want to do this completely in blueprints it’s possible. Your window manager would more or less function the same, except because we don’t expose the layout portion of SlateWidgets to custom UUserWidgets, you’d substitute that and painting for making a Canvas in your blueprint and creating window widgets that have functions for setting a content for them that you create internally and add to the canvas.

Now the one sort of painful part is that the SConstraintCanvas (the UMG Canvas), lacks the ability in 4.8 to do the painting like you need. The SConstraintCanvas, in order to maximize batching doesn’t increment the layerid for each child drawn. I’m reconsidering this for 4.9, possibly introduce some kind of overlap testing that increments layer so that the paint order matches Z+Logical order sorting in the widgets, or maybe some other clever way like base it on if the user touched ZOrder, each ZOrder gets their own layerId, that might be a good way. Right now though they are sorted via Logical+ZOrder for drawing, but without the incrementing of the layer id, it’s ultimately up to the batcher to decide the order they draw in. So that’s one good reason I’d do at least the manager in C++, because you want the drawing to function more like the SOverlay, each child gets their own layer id, and the resulting max of drawing each child becomes the starting layer id for the next child drawn.