I’m trying to toggle a ‘ChooseTeam’ menu in the ShooterHUD with a keyboard key and also provide a ‘Cancel’ button within the menu to close it out as well.
Here’s the code I use to Toggle it:
void AShooterHUD::ToggleChooseTeam()
{
AShooterPlayerController* ShooterPC = Cast<AShooterPlayerController>(PlayerOwner);
if (ShooterPC->IsGameMenuVisible())
{
return;
}
if (!bIsChooseTeamVisible)
{
bIsChooseTeamVisible = true;
SAssignNew(ChooseTeamWidgetOverlay, SOverlay)
+ SOverlay::Slot()
.HAlign(EHorizontalAlignment::HAlign_Center)
.VAlign(EVerticalAlignment::VAlign_Center)
.Padding(FMargin(50))
SAssignNew(ChooseTeamWidget, SShooterChooseTeamWidget)
.PCOwner(TWeakObjectPtr<APlayerController>(PlayerOwner))
.MatchState(GetMatchState())
.Cursor(EMouseCursor::Default)
];
GEngine->GameViewport->AddViewportWidgetContent(
SAssignNew(ChooseTeamWidgetContainer, SWeakWidget)
.PossiblyNullContent(ChooseTeamWidgetOverlay));
ChooseTeamWidget->ShowMouseCursor();
ChooseTeamWidget->OnMenuHidden.BindUObject(this, &AShooterHUD::ToggleChooseTeam);
FSlateApplication::Get().SetKeyboardFocus(ChooseTeamWidget);
}
else if (ChooseTeamWidgetContainer.IsValid())
{
bIsChooseTeamVisible = false;
ChooseTeamWidget->HideMouseCursor();
GEngine->GameViewport->RemoveViewportWidgetContent(ChooseTeamWidgetContainer.ToSharedRef());
FSlateApplication::Get().SetFocusToGameViewport();
}
else{
bIsChooseTeamVisible = false;
}
}
As you can see I hooked up a delegate to ‘OnMenuHidden’ such that clicking the cancel button will result in a call to ToggleChooseTeam()
Both methods of dismissing the SCompoundWidget menu work great except for one small but annoying difference.
When the user toggles the menu with the keyboard key, it properly enables/disables mouse look so that they can move their cursor around the menu without their player’s pitch/yaw changing.
However, enabling it with the keyboard key and dismissing it by clicking the ‘Cancel’ button does not properly return mouse look to the user. If you click the cancel button, the menu properly calls the delegate and the SCompoundWidget is removed from the screen. However, the user cannot affect the pitch/yaw with their mouse input once the menu disappears. Only by firing or aiming (left/right clicking) does it regain control and the user can mouse look again.
I feel like this may be a very nuanced issue where the SButton click may be interfering with properly returning focus to the viewport?
I’ve provided my code below for my SShooterChooseTeamWidget, only been working with Slate for a few days now so please excuse some of the poor code.
I appreciate any and all suggestions!
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Slate.h"
//class declare
class SShooterChooseTeamWidget : public SCompoundWidget
{
SLATE_BEGIN_ARGS(SShooterChooseTeamWidget)
{}
SLATE_ARGUMENT(TWeakObjectPtr<APlayerController>, PCOwner)
SLATE_ATTRIBUTE(EShooterMatchState::Type, MatchState)
SLATE_END_ARGS()
/** needed for every widget */
void Construct(const FArguments& InArgs);
/** update PlayerState maps with every tick when choose team is shown */
virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) OVERRIDE;
/** to have the mouse cursor show up at all times, we need the widget to handle all mouse events */
virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) OVERRIDE;
/** says that we can support keyboard focus */
virtual bool SupportsKeyboardFocus() const OVERRIDE{ return true; }
/** The menu sets up the appropriate mouse settings upon focus */
virtual FReply OnKeyboardFocusReceived(const FGeometry& MyGeometry, const FKeyboardFocusEvent& InKeyboardFocusEvent) OVERRIDE;
/** Shows the mouse cursor */
void ShowMouseCursor();
/** Hides the mouse cursor */
void HideMouseCursor();
/** delegate declaration */
DECLARE_DELEGATE(FOnMenuHidden);
/** delegate, which is executed when cancel button is clicked */
FOnMenuHidden OnMenuHidden;
protected:
/** updates widgets when players leave or join */
void UpdateTeamInfo();
/** choose team tint color */
FLinearColor ChooseTeamTint;
/** when the choose team was brought up. */
double ChooseTeamStartTime;
/** player count in each team in the last tick */
TArray<int32> LastTeamPlayerCount;
/** get state of current match */
EShooterMatchState::Type MatchState;
/** pointer to our parent HUD */
TWeakObjectPtr<class APlayerController> PCOwner;
/** style for the choose team */
const struct FShooterChooseTeamStyle *ChooseTeamStyle;
/*Pointer to resources in GameModule*/
TSharedPtr<FSlateGameResources> MyUIResources;
const FSlateBrush * GetTeamDescriptionImageBrush() const;
TSharedPtr<SButton> Team1Button;
TSharedPtr<SButton> Team2Button;
/** callback for when one of the N buttons is clicked */
FReply ButtonClicked(int32 ButtonIndex);
};
// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved.
#include "ShooterGame.h"
#include "SShooterChooseTeamWidget.h"
#include "ShooterStyle.h"
#include "ShooterChooseTeamWidgetStyle.h"
#define LOCTEXT_NAMESPACE "ShooterChooseTeam"
void SShooterChooseTeamWidget::Construct(const FArguments& InArgs)
{
////////////////////////////////////////////////////////////////////////////////////////////////////
/////Get handle on game resources from GameModule
MyUIResources = FModuleManager::LoadModuleChecked<FShooterGameModule>(FName("ShooterGame")).GetSlateGameResources();
////////////////////////////////////////////////////////////////////////////////////////////////////
/////Get handle on Slate Brush
/////name: member_icon
const FSlateBrush *m_allied_flag_icon = MyUIResources->GetBrush(FName("AlliedTeamIcon"));
const FSlateBrush *m_axis_flag_icon = MyUIResources->GetBrush(FName("AxisTeamIcon"));
const FSlateBrush *m_allied_description = MyUIResources->GetBrush(FName("AlliedTeamDescription"));
const FSlateBrush *m_axis_description = MyUIResources->GetBrush(FName("AxisTeamDescription"));
const FSlateBrush *m_map_description = MyUIResources->GetBrush(FName("MapDescription"));
ChooseTeamStyle = &FShooterStyle::Get().GetWidgetStyle<FShooterChooseTeamStyle>("DefaultShooterChooseTeamStyle");
PCOwner = InArgs._PCOwner;
ChooseTeamTint = FLinearColor(0.0f, 0.0f, 0.0f, 0.4f);
ChooseTeamStartTime = FPlatformTime::Seconds();
MatchState = InArgs._MatchState.Get();
FMargin ButtonPadding = FMargin(10);
uint16 ButtonFontSize = 16;
const TSharedRef<SVerticalBox> ChooseTeamMenu = SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight();
/*################################# CREATE MENU ROWS #####################################################*/
TSharedPtr<SHorizontalBox> TitleRow;
//Create title row for 'Choose a Team' and 'Game Name' textblocks
ChooseTeamMenu->AddSlot().AutoHeight().VAlign(VAlign_Center).HAlign(HAlign_Left)
SAssignNew(TitleRow, SHorizontalBox)
];
TSharedPtr<SHorizontalBox> ButtonsRow;
//Create button row for selecting Team 1, Team 2, Spectate, Auto-Assign
ChooseTeamMenu->AddSlot().AutoHeight().VAlign(VAlign_Center).HAlign(HAlign_Left)
SAssignNew(ButtonsRow, SHorizontalBox)
];
TSharedPtr<SHorizontalBox> DescriptionRow;
//Create description row for the team and map
ChooseTeamMenu->AddSlot().AutoHeight().VAlign(VAlign_Center).HAlign(HAlign_Left)
SAssignNew(DescriptionRow, SHorizontalBox)
];
TSharedPtr<SHorizontalBox> AlliedCountHBox;
TSharedPtr<SHorizontalBox> AxisCountHBox;
//Create row to display count of players on each team
ChooseTeamMenu->AddSlot().AutoHeight().VAlign(VAlign_Fill).HAlign(HAlign_Fill)
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().HAlign(HAlign_Fill)
SAssignNew(AlliedCountHBox, SHorizontalBox)
]
+ SHorizontalBox::Slot().HAlign(HAlign_Fill)
SAssignNew(AxisCountHBox, SHorizontalBox)
]
];
AlliedCountHBox->AddSlot().VAlign(VAlign_Fill).HAlign(HAlign_Left).AutoWidth()
SNew(SImage).Image(m_allied_flag_icon)
];
AlliedCountHBox->AddSlot().VAlign(VAlign_Fill).HAlign(HAlign_Fill)
SNew(STextBlock)
.Text(LOCTEXT("AlliedTeamName", "American:").ToString())
//.TextStyle(FShooterStyle::Get(), "ShooterGame.DefaultChooseTeam.Row.HeaderTextStyle")
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 16))
];
AxisCountHBox->AddSlot().VAlign(VAlign_Fill).HAlign(HAlign_Left).AutoWidth()
SNew(SImage).Image(m_axis_flag_icon)
];
AxisCountHBox->AddSlot().VAlign(VAlign_Fill).HAlign(HAlign_Fill)
SNew(STextBlock)
.Text(LOCTEXT("AxisTeamName", "German:").ToString())
//.TextStyle(FShooterStyle::Get(), "ShooterGame.DefaultChooseTeam.Row.HeaderTextStyle")
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 16))
];
TSharedPtr<SHorizontalBox> FooterRow;
//Create footer row for cancel button
ChooseTeamMenu->AddSlot().AutoHeight().VAlign(VAlign_Center).HAlign(HAlign_Left)
SAssignNew(FooterRow, SHorizontalBox)
];
/*################################### POPULATE TITLE ROW #################################################*/
TitleRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth().Padding(5)
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().Padding(5)
SNew(STextBlock)
.Text(LOCTEXT("ChooseATeam", "CHOOSE A TEAM").ToString())
//.TextStyle(FShooterStyle::Get(), "ShooterGame.DefaultChooseTeam.Row.HeaderTextStyle")
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 32))
]
];
TitleRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Right).AutoWidth().Padding(5)
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().Padding(5)
SNew(STextBlock)
.Text(LOCTEXT("GameName", "GameName").ToString())
//.TextStyle(FShooterStyle::Get(), "ShooterGame.DefaultChooseTeam.Row.HeaderTextStyle")
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), 32))
]
];
/*################################### POPULATE BUTTON ROW ################################################*/
//Add button to ButtonsRow for Team 1
ButtonsRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Center).AutoWidth().Padding(5)
SAssignNew(Team1Button,SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
//.OnClicked(this, &SDDFileTree::RefreshButtonPressed)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ForegroundColor(FSlateColor::UseForeground())
.OnClicked(this, &SShooterChooseTeamWidget::ButtonClicked,0)
SNew(SBorder)
.Padding(ButtonPadding)
SNew(STextBlock)
.Text(FString("Team 1"))
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), ButtonFontSize))
]
]
];
//Add button to ButtonsRow for Team 2
ButtonsRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Center).AutoWidth().Padding(5)
SAssignNew(Team2Button, SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
//.OnClicked(this, &SDDFileTree::RefreshButtonPressed)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ForegroundColor(FSlateColor::UseForeground())
.OnClicked(this, &SShooterChooseTeamWidget::ButtonClicked, 1)
SNew(SBorder)
.Padding(ButtonPadding)
SNew(STextBlock)
.Text(FString("Team 2"))
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), ButtonFontSize))
]
]
];
//Add button to ButtonsRow for 'Spectate'
ButtonsRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Center).AutoWidth().Padding(5)
SNew(SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
//.OnClicked(this, &SDDFileTree::RefreshButtonPressed)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ForegroundColor(FSlateColor::UseForeground())
.OnClicked(this, &SShooterChooseTeamWidget::ButtonClicked, 2)
SNew(SBorder)
.Padding(ButtonPadding)
SNew(STextBlock)
.Text(FString("3 Spectate"))
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), ButtonFontSize))
]
]
];
//Add button to ButtonsRow for 'Auto-Assign'
ButtonsRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Center).AutoWidth().Padding(5)
SNew(SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
//.OnClicked(this, &SDDFileTree::RefreshButtonPressed)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ForegroundColor(FSlateColor::UseForeground())
.OnClicked(this, &SShooterChooseTeamWidget::ButtonClicked, 3)
SNew(SBorder)
.Padding(ButtonPadding)
SNew(STextBlock)
.Text(FString("4 Auto-Assign"))
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), ButtonFontSize))
]
]
];
/*################################### POPULATE DESCRIPTION ROW ###########################################*/
DescriptionRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth().Padding(5)
SNew(SImage).Image(this, &SShooterChooseTeamWidget::GetTeamDescriptionImageBrush)
];
DescriptionRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Left).AutoWidth().Padding(5)
SNew(SImage)
.Image(m_map_description)
];
/*################################### POPULATE FOOTER ROW ################################################*/
//Add cancel button to FooterRow
FooterRow->AddSlot().VAlign(VAlign_Center).HAlign(HAlign_Center).AutoWidth().Padding(5)
SNew(SButton)
.ButtonStyle(FCoreStyle::Get(), "NoBorder")
//.OnClicked(this, &SDDFileTree::RefreshButtonPressed)
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.ForegroundColor(FSlateColor::UseForeground())
.OnClicked(this, &SShooterChooseTeamWidget::ButtonClicked, 4)
SNew(SBorder)
.Padding(ButtonPadding)
SNew(STextBlock)
.Text(FString("Cancel"))
.Font(FSlateFontInfo(FPaths::EngineContentDir() / TEXT("Slate/Fonts/Roboto-Bold.ttf"), ButtonFontSize))
]
]
];
ChildSlot
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
SNew(SBorder)
.BorderImage(&ChooseTeamStyle->ItemBorderBrush)
.BorderBackgroundColor(ChooseTeamTint)
ChooseTeamMenu
]
];
}
void SShooterChooseTeamWidget::ShowMouseCursor()
{
PCOwner->bShowMouseCursor = true;
PCOwner->SetIgnoreLookInput(true);
}
void SShooterChooseTeamWidget::HideMouseCursor()
{
PCOwner->bShowMouseCursor = false;
PCOwner->SetIgnoreLookInput(false);
}
const FSlateBrush * SShooterChooseTeamWidget::GetTeamDescriptionImageBrush() const
{
SButton * Team1ButtonPtr = Team1Button.Get();
if (Team1ButtonPtr->IsHovered()){
return MyUIResources->GetBrush(FName("AlliedTeamDescription"));
}
return MyUIResources->GetBrush(FName("AxisTeamDescription"));
}
FReply SShooterChooseTeamWidget::ButtonClicked(int32 ButtonIndex)
{
UE_LOG(LogTemp, Warning, TEXT("SShooterChooseTeamWidget::ButtonClicked(int32 ButtonIndex): %d"),ButtonIndex);
if (ButtonIndex == 4)
{
//Send event, so menu can be removed
OnMenuHidden.ExecuteIfBound();
}
return FReply::Handled();
}
void SShooterChooseTeamWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime)
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
}
FReply SShooterChooseTeamWidget::OnKeyboardFocusReceived(const FGeometry& MyGeometry, const FKeyboardFocusEvent& InKeyboardFocusEvent)
{
return FReply::Handled().ReleaseMouseCapture().CaptureJoystick(SharedThis(this), true);
}
FReply SShooterChooseTeamWidget::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent)
{
//Set the keyboard focus
return FReply::Handled()
.SetKeyboardFocus(SharedThis(this), EKeyboardFocusCause::SetDirectly);
}
#undef LOCTEXT_NAMESPACE