Slate blurred border shadow

Hello,

I want to create UI’s with dynamic blurred shadows, like those seen in CSS’s box-shadow feature. The logical thing to do would be bake it into an image. But I really do want it done procedurally so I can animate it.

I can’t seem to find out how to do this, so I am left with the assumption that it’s not a feature of slate (I’d love it if you corrected me here)

I haven’t got the first clue of how to implement such a rendering feature to slate though. So if it is really not a feature and I have to implement it myself, can someone point me in the right direction? Where do I start? The slate API docs seem rather lacking at the moment so it’s hard to tell what to do.

1 Like

Hi,

I think the best way to do it is going to be to have a image border with the shadow and animating its content padding.

First thing (most obvious) to try out is to use Padding attribute in SBorder calss. You should be able to create a getter function for it that looks something like this:

FMargin GetShadowBorderPadding() const
{
return FMargin(Your_dynamic_values_here);
} 

then the definition of Padding parameter would look like this:

SNew(SBorder)
.Padding(this, &SShadowBorder::GetShadowBorderPadding)
[
....
]

It’s all in the SShadowBorder - your custom widget class.

Now all you need is function to set/animate padding values and you should get what you need. If not - you can always try to override either OnPaint or ArrangeChildren function and then precisely set padding values.

It’s actually interesting how it can turn out and I think I’ll implement it myself ; )

Hope this helps.

Thats an awesome idea. Thank you!

Hey!

Since SBorder wasnt quite enough to get the desired result I made use of SOverlay widget and ended up with something like this: (mouse cursor imitates ‘light source’ so the shadow moves around)

44077-ezgif.com-gif-maker+(1).gif

The shadow itself is not blurred but it’s just the matter of used brush ; )

Heres the code if anyone needs it (note that I’m using Editor style in this example so you will have to add needed dependencies to your project for it to compile. Also, remove Tick function from SShadowBorder class to remove sample shadow updates)

#pragma once

#include "SlateBasics.h"
#include "EditorStyle.h"

class SShadowBorder : public SCompoundWidget
{
public:
	SLATE_BEGIN_ARGS(SShadowBorder)
		: _Content()
		, _DefaultPadding(10)
	{}
		SLATE_DEFAULT_SLOT(FArguments, Content)
		SLATE_ATTRIBUTE(float, DefaultPadding)
		SLATE_ATTRIBUTE(FMargin, DynamicPadding)
	SLATE_END_ARGS()
	
	void Construct(const FArguments& InArgs)
	{
		DefaultPadding = InArgs._DefaultPadding;
		DynamicPadding = InArgs._DynamicPadding;

		ChildSlot
		[
			SNew(SOverlay)
			// First, add slot with actual shadow border that will move around based on DynamicPadding
			+SOverlay::Slot()
			.Padding(TAttribute<FMargin>::Create(TAttribute<FMargin>::FGetter::CreateRaw(this, &SShadowBorder::GetDynamicPadding)))
			[
				SNew(SBorder)
				.BorderImage(FEditorStyle::Get().GetBrush("ProgressBar.Background"))
				.BorderBackgroundColor(FColor::Black)
			]
			// Now, on the top of the shadow, add actual content
			+SOverlay::Slot()
			.Padding(DefaultPadding.Get())
			[
				InArgs._Content.Widget
			]
		];
	}

	virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override
	{
		// Use position of the cursor like a 'light source' to imitate shadow casting by content
		FVector2D MousePos = FSlateApplication::Get().GetCursorPos();

		FVector2D AbsoluteWidgetPos = AllottedGeometry.LocalToAbsolute(FVector2D::ZeroVector);
		FVector2D WidgetSize = AllottedGeometry.GetLocalSize();
		
		FVector2D WidgetCenter = AbsoluteWidgetPos + WidgetSize / 2.0f;

		FVector2D Delta = MousePos - WidgetCenter;
		Delta = Delta / 10.0f;

		float MaxPadding = 2 * DefaultPadding.Get();

		FMargin newPadding;
		newPadding.Left = FMath::Clamp<float>(DefaultPadding.Get() - Delta.X, 0, MaxPadding);
		newPadding.Top = FMath::Clamp<float>(DefaultPadding.Get() - Delta.Y, 0, MaxPadding);
		newPadding.Right = FMath::Clamp<float>(DefaultPadding.Get() + Delta.X, 0, MaxPadding);
		newPadding.Bottom = FMath::Clamp<float>(DefaultPadding.Get() + Delta.Y, 0, MaxPadding);

		DynamicPadding.Set(newPadding);

		SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
	}

	FMargin GetDynamicPadding()  const
	{
		return DynamicPadding.Get();
	}

	TAttribute<FMargin> DynamicPadding;
	TAttribute<float> DefaultPadding;
};

Cheers!

2 Likes