Download

Trying to Learn Slate...

So I’ve recently been trying to learn slate, taking a little bit of what I know about UMG with me…

GEngine->GameViewport->AddViewportWidgetContent(
	SNew(SCanvas) 
	+ SCanvas::Slot()
	.HAlign(HAlign_Center)
	.VAlign(VAlign_Center)
	
	.Position(FVector2D(0,0))
	
	SNew(STextIndicator)
		.Text(FText::AsNumber(2000))
	]
	);

STextIndicator is defined as
ChildSlot

		SNew(SOverlay)
		+ SOverlay::Slot()
		
			SNew(STextBlock)
			.ShadowColorAndOpacity(FLinearColor::Black)
			.ColorAndOpacity(FLinearColor::White)
			.ShadowOffset(FIntPoint(-1, 1))
			.Font(FSlateFontInfo("Veranda", 16))
			.Text(Text)
		]
	];

I wanted to position something absolutely on the screen, so I tried using SCanvas. I figured this should show up in the center or bottom or top left corner of the screen but it doesn’t. I tried various H, V Aligns and no success. If I want to position this at an absolute position, with a set alignment in viewport (like in UMG), how can I do this with slate?

It’s the same as in UMG, since that is just a wrapper for Slate. When you can’t figure out what’s happening, toy around with a UMG prototype to try and understand.

Bear in mind that the Canvas is a bit of a unique widget in that it doesn’t return a desired size. Therefore, unlike most other widgets, you can’t just drop a canvas and expect it to give Slate a proper size, because it will always be zero. You instead have to make it take up the space granted by its parent – that is, Fill align.

Edit: Just noticed I misread your post, the alignment and positioning is indeed set on the canvas rather than the slot, so this paragraph doesn’t really apply, but I’m keeping it around for clarity’s sake. Change your SCanvas to use HAlign_Fill/VAlign_Fill. This will make it take up the entire space of its parent, i.e.: the entire viewport, thus allowing you to position child elements absolutely within the Canvas. The location and alignment you are trying to set on the Canvas are supposed to be set on the canvas’s slots, which is something that’s not immediately apparent when editing widgets through UMG.

Also, it’s important to note that UMG’s Canvas wraps SConstraintCanvas, which has different terminology and functionality than SCanvas. Alignment is provided through floating point values instead of enums, anchors allow positioning widgets relative to different points than the top left corner, you can use AutoSize, etc. Take a peek at the slots for each of those widgets and see if one fits your needs better than the other.

Finally, a very useful tool that you should be using is the Widget Reflector, accessible in the Editor through Window > Developer Tools > Widget Reflector. It’ll help you diagnose stuff like widgets collapsing due to being given a size different from their desired size, etc.

-Camille

I guess the main thing confusing me is in umg you have set viewport position and set viewport alignment , which doesn’t appear to exist here so that’s why I tried using scanvas, I tried using fill on both but I don’t see it with 0,0 position. Previously when I had no canvas and I set the full inthe overlay slot it worked but I had no positioning , I then added the canvas saw nothing, tried removing the overlay slot h v aligns still nothing and that’s where I am now.

Do you mean UUserWidget::SetPositionInViewport and UUserWidget::SetDesiredSizeInViewport? Those exist because UMG internally uses a canvas of its own when adding elements to the viewport. Conversely, if you look at UGameViewportClient::AddViewportWidgetContent, you’ll see it’s using an Overlay.

So yes, obtaining the same behaviour as UMG would require wrapping your Slate widgets in a Canvas. I believe what’s happening is that you’re giving your canvas slot a position but not a size. Try setting a dummy size after your Position attribute:



SNew(SCanvas) 
+ SCanvas::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Position(FVector2D(0,0))
.Size( FVector2D(100.0f, 40.0f) )

	SNew(STextIndicator)
	.Text(FText::AsNumber(2000))
]


If that works, you can use the actual widget’s size by calling GetDesiredSize on it:



TSharedRef<STextIndicator> TextIndicator = SNew(STextIndicator)
	.Text(FText::AsNumber(2000));

SNew(SCanvas)
+ SCanvas::Slot()
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Position(FVector2D(0,0))
.Size( TextIndicator->GetDesiredSize() )

	TextIndicator
]


The second snippet highlights well how SCanvas doesn’t really play well with “the Slate Way” – SCanvas does not care for the desired size of its slot children so you have to break out of the Slate declarative syntax to set up a layout. In comparison, as briefly mentioned in my previous post, SConstraintCanvas does support AutoSize which likely would make your life easier. I’ll leave how to actually use as an exercise if you want, it’s very similar to SCanvas. Feel free to ask if you’re stumped, though. :slight_smile:

I’ll try SConstraintCanvas, thanks let you know how it goes!

I got it to work with SConstraintCanvas after I figured out the Anchor positions were lerped between 0 and 1 (I was trying to do it like UMG at first with real position coordinates)


	GEngine->GameViewport->AddViewportWidgetContent(
		SNew(SConstraintCanvas) 
		+ SConstraintCanvas::Slot()
		.Anchors(FAnchors(0.5,0.5))
		.AutoSize(true)
		.ZOrder(99)
		
		SNew(STextIndicator)
			.Text(FText::AsNumber(2000))
		]
		);


Now I can get my widgets done… if only it didn’t take freaking 4 minutes to compile editor after one small code change =(.

Need to get a decided SSD for UE4.

There’s a two things you can tweak for fast iteration with Slate.

The first is the precompiled header file. The default PCH file (i.e.: the SomeGame.h header you have to include in every cpp file) comes with the classes header in it:


#include "SomeGameClasses.h"

This includes all UObject classes in the PCH. If you’re modifying UObject headers, this means a full recompile whenever you touch it. On the other hand, Slate is native code, not UObject, so having all classes precompiled can save you some compile time. But if you’re touching UObjects while working on your UI, you’re better off disabling it.

The second thing is unity builds, which lump all CPP files into larger CPP files, resulting in less compile units and less overhead. This is great if you’re recompiling the entire project, but not so great if you only want to modify one Slate CPP file. The Unreal build tool tries to limit unity builds when few CPP files are involved but the cutoff is very low. However, you can control it in your build.cs (SomeGame.Build.cs) through the bFasterWithoutUnity option:


bFasterWithoutUnity = true;

This will force the build tool to compile all files individually, which is slower for a full rebuild but much faster for small iterations afterwards.

These two settings, when coupled with hot reload, can pretty much reduce your turnaround time to mere seconds. So when iterating in Slate only, I turn on the Classes PCH, turn off Unity build, and use the Compile button in the editor. Note that the first compile done this way requires a full rebuild due to the considerably different nature of hot reload game modules. But once that initial rebuild is done, subsequent recompiles will be very fast.

****, your a freakin hero!
It worked :3, thanks!

Another question if you don’t mind. I have this following code:


	TSharedPtr<STextIndicator> Indicator = SNew(STextIndicator)
		.Text(FText::AsNumber(2000));

	GEngine->GameViewport->AddViewportWidgetContent(
		SNew(SConstraintCanvas) 
		+ SConstraintCanvas::Slot()
		.Anchors(FAnchors(0.5,0.5))
		.AutoSize(true)
		.ZOrder(99)
		
			Indicator->AsShared()
		]
		);

	Indicator->Float();

Notice the ‘Float’, this is a function I created that kicks off an event for this slate widget.
However, to be able to call this, I had to be unslatey - and move the construction of my object out. Is there a preprocessor command or something that will let me call functions, not just variables?

Not really. The declarative syntax is for, well… declarations. If you need to call logic, or run dynamic construction like iterating over arrays or some such, then you need to keep a reference to call this logic on it.

I suppose you’ve isolated this event because you don’t want to call it all the time at construction? Either way, you could probably just move it to the widget Construct function proper, and add an attribute that calls Float() if true. i.e.:



GEngine->GameViewport->AddViewportWidgetContent(
	SNew(SConstraintCanvas) 
	+ SConstraintCanvas::Slot()
	.Anchors(FAnchors(0.5,0.5))
	.AutoSize(true)
	.ZOrder(99)
	
		SNew(STextIndicator)
		.Text(FText::AsNumber(2000))
		.AutoInitFloat(true)
	]
);


And then in STextIndicator::Construct, just call Float() if AutoInitFloat is true. I did notice a few built-in widgets doing similar stuff, taking arguments that are only used to control construction and aren’t stored beyond that.

FYI: SNew returns a TSharedRef, since the widget is pretty much guaranteed to exist at that time. So you could just take a TSharedRef in your SNew, and pass it to the canvas slot as is without using AsShared.