Slate - How do I interact with my scene whilst I have an in-game menu up?

I’ve created a feature where the player is able to interact with their scene, selecting objects, switching from pawn to movable camera and a toggleable mouse cursor to select the objects in the world. So now I’ve created the slate menu to overlay this but now I’m unable to interact with the world, I’ve tried a couple of things to get around this such as :



FSlateApplication::Get().SetFocusToGameViewport();


But not having much luck without making the slate widgets hidden completely to get my other features back. Does anyone have any ideas of how to tackle this?

Thanks

EDIT: Also I’ve noticed that when my cursor is hovering over my menu widgets I am able to use my other features again such as selecting actors in the background of the scene etc but only when hovering over my widgets

Most likely your widgets are capturing mouse input in some way. Cant tell much without looking into your code.

To hide (and disable interaction) widget and all its children use SetVisibility(EVisibility::Collapsed).

Hi szyszek, thanks for the reply.

Yeah my widgets are capturing mouse input as most are buttons. I don’t really want to hide my widgets they will need to be accessed most of the time by the player.

I’ve tried spawning a new widget after clicking a button and found that focus only remains on the newly spawned widget now. It must be the way I’m creating them or something that’s forcing focus?

Here’s my code when creating the widgets:




if(!MySandboxMainWidget.IsValid())
{
	// Spawn our Sandbox UI
	SAssignNew(MySandboxMainWidget, SMySandboxMainWidget)
	.OwnerHUD(TWeakObjectPtr<AMyHUD>(this))
	.Cursor(EMouseCursor::Default);

	if (MySandboxMainWidget.IsValid())
	{
                //  This part adds the content to screen but also forces focus on the spawned widget.
		GEngine->GameViewport->AddViewportWidgetContent( SNew(SWeakWidget).PossiblyNullContent(MySandboxMainWidget.ToSharedRef()) ); 
	}
}


Cheers

EDIT: Oh and for mouse interaction I’m doing a simple PC->bShowCursor = true. Is this the correct method?

Yes.

But could you post SMySandboxMainWidget code? Or at least tell me what class do you derive from in SMySandboxMainWidget. If you just add a widget to a viewport it will take whole allotted space of viewport. You can track widget size/alignment using widget reflector (type WidgetReflector in console during the game or find it somewhere in editor view options (I dont quite remember the path:P))

Sure, here’s my code:

.h:



#pragma once
 
#include "Slate.h"
#include "SMySandboxActorsSubWidget.h"
 
class SMySandboxMainWidget : public SCompoundWidget
{
	SLATE_BEGIN_ARGS(SMySandboxMainWidget)
	: _OwnerHUD()
	{}
        /*See private declaration of OwnerHUD below.*/
	SLATE_ARGUMENT(TWeakObjectPtr<AMyHUD>, OwnerHUD)

		/** called when the button is clicked */
	SLATE_EVENT(FOnClicked, OnClicked)
 
	SLATE_END_ARGS()
 
public:

	void Construct(const FArguments& InArgs);

	/** mouse button down callback */
	virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) OVERRIDE;

	/** mouse button up callback */
	virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) OVERRIDE;

	FReply DrawActorsSubMenu();

	FReply DrawPropertiesSubMenu();

	FReply DrawSaveLoadSubMenu();

	FReply HitTakeControl();

	FReply HitQuitButton();

	TSharedPtr<SMySandboxActorsSubWidget> ActorsSubMenu;

protected:

	/** the delegate to execute when the button is clicked */
	FOnClicked OnClicked;

	// Main menu
	TSharedPtr<SButton> TakeControlButton;
	TSharedPtr<SButton> SaveLoadButton;
	TSharedPtr<SButton> ExitButton;
	TSharedPtr<SButton> PropertiesButton;
	TSharedPtr<SButton> ActorsButton;

	
private:

	// Pointer to our parent HUD
	TWeakObjectPtr<class AMyHUD> OwnerHUD;
};


and my .cpp:




#include "MyGame.h"
#include "SMySandboxActorsSubWidget.h"
#include "SMySandboxMainWidget.h"

DEFINE_LOG_CATEGORY_STATIC(LogWidget, All, All)
 
void SMySandboxMainWidget::Construct(const FArguments& InArgs)
{
	OwnerHUD = InArgs._OwnerHUD;
	OnClicked = InArgs._OnClicked;

	/////////
	ChildSlot
	.VAlign(VAlign_Fill)
	.HAlign(HAlign_Fill)
	
		SNew(SHorizontalBox)
		+SHorizontalBox::Slot()
		.AutoWidth()
		.HAlign(HAlign_Left)
		.VAlign(VAlign_Top)
		
			SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
			.Padding(FMargin(0,0,0,0))
			
				SAssignNew( ActorsButton, SButton )
				.OnClicked(this, &SMySandboxMainWidget::DrawActorsSubMenu)
				
					SNew(SBox)
					
						SNew(STextBlock)
						.ShadowColorAndOpacity(FLinearColor::Black)
						.ColorAndOpacity(FLinearColor::White)
						.ShadowOffset(FIntPoint(-1, 1))
						.Font(FSlateFontInfo("Veranda", 20))
						.Text(FText::FromString("ACTORS"))
					]
				]
			]
		]


		// PROPERTIES
		+SHorizontalBox::Slot()
		.AutoWidth()
		.HAlign(HAlign_Left)
		.VAlign(VAlign_Top)
		
			SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
			.Padding(FMargin(0,0,0,0))
			
				SNew(SOverlay)
				+SOverlay::Slot()
				.HAlign(HAlign_Center)
				.VAlign(VAlign_Center)
				
					SAssignNew( PropertiesButton, SButton )
					.OnClicked(this, &SMySandboxMainWidget::DrawPropertiesSubMenu)
					
						SNew(SBox)
						
							SNew(STextBlock)
							.ShadowColorAndOpacity(FLinearColor::Black)
							.ColorAndOpacity(FLinearColor::White)
							.ShadowOffset(FIntPoint(-1, 1))
							.Font(FSlateFontInfo("Veranda", 20)) 
							.Text(FText::FromString("PROPERTIES"))
						]
					]
				]
			]
		]


		// SAVE/LOAD
		+SHorizontalBox::Slot()
		.AutoWidth()
		.HAlign(HAlign_Left)
		.VAlign(VAlign_Top)
		
			SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
			.Padding(FMargin(0,0,0,0))
			
				SNew(SOverlay)
				+SOverlay::Slot()
				.HAlign(HAlign_Center)
				.VAlign(VAlign_Center)
				
					SAssignNew( SaveLoadButton, SButton )
					.OnClicked(this, &SMySandboxMainWidget::DrawSaveLoadSubMenu)
					
						SNew(SBox)
						
							SNew(STextBlock)
							.ShadowColorAndOpacity(FLinearColor::Black)
							.ColorAndOpacity(FLinearColor::White)
							.ShadowOffset(FIntPoint(-1, 1))
							.Font(FSlateFontInfo("Veranda", 20)) 
							.Text(FText::FromString("SAVE/LOAD"))
						]
					]
				]
			]
		]


		// TAKE CONTROL
		+SHorizontalBox::Slot()
		.AutoWidth()
		.HAlign(HAlign_Left)
		.VAlign(VAlign_Top)
		
			SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
			.Padding(FMargin(0,0,0,0))
			
				SNew(SOverlay)
				+SOverlay::Slot()
				.HAlign(HAlign_Center)
				.VAlign(VAlign_Center)
				
					SAssignNew( TakeControlButton, SButton )
					.OnClicked(this, &SMySandboxMainWidget::HitTakeControl)
					
						SNew(SBox)
						
							SNew(STextBlock)
							.ShadowColorAndOpacity(FLinearColor::Black)
							.ColorAndOpacity(FLinearColor::White)
							.ShadowOffset(FIntPoint(-1, 1))
							.Font(FSlateFontInfo("Veranda", 20)) 
							.Text(FText::FromString("TAKE CONTROL"))
						]
					]
				]
			]
		]


		// EXIT
		+SHorizontalBox::Slot()
		.AutoWidth()
		.HAlign(HAlign_Right)
		.VAlign(VAlign_Top)
		
			SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
			.Padding(FMargin(0,0,0,0))
			
				SNew(SOverlay)
				+SOverlay::Slot()
				.HAlign(HAlign_Center)
				.VAlign(VAlign_Center)
				
					SAssignNew( ExitButton, SButton )
					.OnClicked(this, &SMySandboxMainWidget::HitQuitButton)
					
						SNew(SBox)
						
							SNew(STextBlock)
							.ShadowColorAndOpacity(FLinearColor::Black)
							.ColorAndOpacity(FLinearColor::White)
							.ShadowOffset(FIntPoint(-1, 1))
							.Font(FSlateFontInfo("Veranda", 20)) 
							.Text(FText::FromString("EXIT"))
						]
					]
				]
			]
		]
	];

	// TOOLTIPS //

	PropertiesButton->SetToolTip(
		SNew(SToolTip)
		
			SNew(STextBlock)
			  .Text(FText::FromString("Edit various properties of a selected actor."))
			  .Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 12))
			  .ColorAndOpacity(FLinearColor(1,1,1,1))
			  .HighlightColor(FLinearColor(1,1,0,1))
			  .ShadowColorAndOpacity(FLinearColor::Black)
			  .ShadowOffset(FIntPoint(-2, 2))
		]
	);

	TakeControlButton->SetToolTip(
		SNew(SToolTip)
		
			SNew(STextBlock)
			  .Text(FText::FromString("Drop in the level to test the level."))
			  .Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 12))
			  .ColorAndOpacity(FLinearColor(1,1,1,1))
			  .HighlightColor(FLinearColor(1,1,0,1))
			  .ShadowColorAndOpacity(FLinearColor::Black)
			  .ShadowOffset(FIntPoint(-2, 2))
		]
	);

	ExitButton->SetToolTip(
		SNew(SToolTip)
		
			SNew(STextBlock)
			  .Text(FText::FromString("Quit to the main menu."))
			  .Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 12))
			  .ColorAndOpacity(FLinearColor(1,1,1,1))
			  .HighlightColor(FLinearColor(1,1,0,1))
			  .ShadowColorAndOpacity(FLinearColor::Black)
			  .ShadowOffset(FIntPoint(-2, 2))
		]
	);
}

FReply SMySandboxMainWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
	const FKey OurKey = MouseEvent.GetEffectingButton();
	if(OurKey == EKeys::RightMouseButton)
	{
		SetVisibility(EVisibility::HitTestInvisible);
		FSlateApplication::Get().SetFocusToGameViewport();
		AMyPlayerController* PC = Cast<AMyPlayerController>(OwnerHUD->PlayerOwner);
		if(PC)
		{
			AMyCharacterPlayer* Character = Cast<AMyCharacterPlayer>(PC->GetCharacter());
			if(Character)
			{
				Character->OnStartFireSecondary();

				UE_LOG( LogWidget, All, TEXT( "--- SetFocusToGameViewport" ));

				return FReply::Handled();
			}
		}
	}

	return FReply::Handled();
}


FReply SMySandboxMainWidget::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
	const FKey OurKey = MouseEvent.GetEffectingButton();
	if(OurKey == EKeys::RightMouseButton)
	{
		AMyPlayerController* PC = Cast<AMyPlayerController>(OwnerHUD->PlayerOwner);
		if(PC)
		{
			AMyCharacterPlayer* Character = Cast<AMyCharacterPlayer>(PC->GetCharacter());
			if(Character)
			{
				Character->OnStopFireSecondary();
				SetVisibility(EVisibility::Visible);

				UE_LOG( LogWidget, All, TEXT( "--- Right Mouse Button Up!" ));

				return FReply::Handled();
			}
		}
	}

	return FReply::Handled();
}

FReply SMySandboxMainWidget::DrawActorsSubMenu()
{
	UE_LOG( LogWidget, All, TEXT( "--- Draw Actors Sub Menu" ));

	if(!ActorsSubMenu.IsValid())
	{
		SAssignNew( ActorsSubMenu, SMySandboxActorsSubWidget )
		.OwnerHUD(OwnerHUD);
	}

	if(ActorsSubMenu.IsValid())
	{
		GEngine->GameViewport->AddViewportWidgetContent( SNew(SWeakWidget).PossiblyNullContent(ActorsSubMenu.ToSharedRef()) );
		ActorsSubMenu->SetVisibility(EVisibility::Visible);
	}

	return FReply::Handled();
}

FReply SMySandboxMainWidget::DrawPropertiesSubMenu()
{
	UE_LOG( LogWidget, All, TEXT( "--- Draw Properties Sub Menu" ));

	return FReply::Handled();
}

FReply SMySandboxMainWidget::DrawSaveLoadSubMenu()
{
	UE_LOG( LogWidget, All, TEXT( "--- Draw Save/Load Sub Menu" ));

	return FReply::Handled();
}

FReply SMySandboxMainWidget::HitTakeControl()
{
	UE_LOG( LogWidget, All, TEXT( "--- Hit Take Control" ));

	AMyPlayerController* PC = Cast<AMyPlayerController>(OwnerHUD->PlayerOwner);
	if(PC)
	{
		AMyCharacterPlayer* Character = Cast<AMyCharacterPlayer>(PC->GetCharacter());
		if(Character)
		{
			SetVisibility(EVisibility::Collapsed);
			Character->SandboxSwitch();

			return FReply::Handled();
		}
	}

	return FReply::Handled();
}

FReply SMySandboxMainWidget::HitQuitButton()
{
	UE_LOG( LogWidget, All, TEXT( "--- Hit QUIT BUTTON" ));

	AMyPlayerController* PC = Cast<AMyPlayerController>(OwnerHUD->PlayerOwner);
	if(PC)
	{
		PC->ConsoleCommand("quit");
	}

	return FReply::Handled();
}


In case your wondering about the above code, I’m using the RightMouseButton as camera movement when held down in my CharacterPlayer class so I was trying a work-around to re-enable that function whilst the Slate UI is on screen, but no joy.

Ok so you’re managing PLAYER input in slate widget. That’s definitely a bad design. To control player mouse input better use InputComponent, .ini files and BindAction method.

Besides that, your OnMouseButtonDown() method always returns FReply::Handled() reply. If widget returns Handled() reply that means parent widgets wont be able to receive the event. So viewport will never receive OnMouseButtonDown() event in this case. Same thing applies to OnMouseButtonUp().

And one more thing



SNew(SBorder)
			.BorderImage(FCoreStyle::Get().GetBrush("NoBorder"))
			.Padding(FMargin(0,0,0,0))
			
				SAssignNew( ActorsButton, SButton )
				.OnClicked(this, &SMySandboxMainWidget::DrawActorsSubMenu)
				
					SNew(SBox)
					
						SNew(STextBlock)
						.ShadowColorAndOpacity(FLinearColor::Black)
						.ColorAndOpacity(FLinearColor::White)
						.ShadowOffset(FIntPoint(-1, 1))
						.Font(FSlateFontInfo("Veranda", 20))
						.Text(FText::FromString("ACTORS"))
					]
				]
			]


you copy/paste same code like 5 times in this class. It would be much better if you could create separate class for your button and just type all of this once. Also, I’m not sure whats the idea behind all these STextBoxes inside SBoxes inside SButton inside SOverlay inside invisible SBorder (lol). Why wont you just create SButton inside SHorizontalBox::Slot()? To control SButton apperance you can/should use SlateButtonWidget Style (you can then set font and overall look of SButton), same thing applies to Tooltips.

So to sum up:

  1. Do not manage player input inside slate widgets (unless it has direct connection to the widget - like button click, in any other case try to use delegates)
  2. Split your code into more classes to avoid copy/paste code.
  3. Try using already offered solutions (like Widget Styles)

Edit: Next week (hopefully) I’ll make some kind of advanced Slate tutorial, let me know what kind of issues would you like me to describe/solve there if you’re interested and I’ll do my best ; )

If you wouldn’t mind including how to show the game viewport in an SViewport, that would be great. I’ve been looking for help in this thread.

No I’m not managing player input via the Slate Widget. I’ve put that in there to try and override/trigger it as a test due to finding that if my Slate Widget is on screen then it was taking focus and I couldn’t move my player character etc. I have various control functions in my CharacterPlayer class such as holding right mouse button should free aim the camera etc but I’m unable to do this when the Slate Widget is on screen and Visible. So this is the problem as I need the Slate Widget on screen and visible and to control my character at the same time, but it’s blocking all input from my CharacterPlayer class. What could the solution be?

Ok thanks for the tip. I’m still learning Slate so this is definitely something I needed to know for optimization purposes :slight_smile:

I can’t get my head around the Widget Style class, it’s something that I need some more documentation on tbh

Thanks

Try to replace


return FReply::Handled();

with


return FReply::Unhandled();

at the bottom of your OnMousebuttonUp and OnMouseButtonDown functions.

I’ll look into that