There are many ways to do it, but they all come down to
- Instantiate a new widget and add it to the live widget tree (using
AddViewportWidgetContent()
is one way to do it)
- Set focus to that widget (do this AFTER you have added it to the live widget tree). Your widget must be focusable, so you will need to override
SupportsKeyboardFocus()
.
- When you are done with the widget, set focus back to game viewport and remove your widget from the live widget tree.
This actually involved a little bit of trickery depending on what behavior you want from the UI. I am planning to work on API improvements for doing exactly this sort of thing toward the end of August (tentatively; timing might change).
EDIT: For the sake of removing ambiguity, I’ll make an example.
Ideally, the Viewport would support this with a single switch. However, it does not right now. Here is how you would work around it.
Presumably, you press some button (let’s say that it is [M]) to go from CameraMode to CursorMode.
In CursorMode you want to show a widget called SItemManager
.
Your SItemManager
widget will be where the user can use the mouse to manage some item widgets.
You can add your SItemManager
widget into the game with AddViewportWidgetContent().
Going into CursorMode will amount to setting focus on the SItemManager
; you can do this via FSlateApplication.Get().SetKeyboardFocus()
. Your SItemManager
widget will be notified when it has been focused, and this event is your opportunity to make appropriate changes to appear in CursorMode. For example, you’ll want to release mouse capture. You’ll also want to show some of the widgets used for managing the items.
When you want to dismiss this screen, you will just set focus back to the viewport. The viewport will aggressively modify the cursor state back to what is needed for camera mode. This is why we cache the WidgetFocusedBeforeMe
upon being focused. That widget will be the viewport.
class SItemManager : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS( SItemManager )
{}
SLATE_END_ARGS()
void Construct(const FArguments& InArgs)
{
this->ChildSlot
[
// My Child Widgets
];
}
virtual void ToggleCursorMode()
{
if (bInCursorMode)
{
bInCursorMode = false;
FSlateApplication::Get().SetKeyboardFocus(WidgetFocusedBeforeMe);
WidgetFocusedBeforeMe.Reset();
}
else
{
// ORDER of statements MATTERS!
// Check OnKeyboardFocusReceived and SupportsKeyboardFocus before moving these statements.
bInCursorMode = true;
WidgetFocusedBeforeMe = FSlateApplication::Get().GetKeyboardFocusedWidget();
FSlateApplication::Get().SetKeyboardFocus(SharedThis(this), EKeyboardFocusCause::Keyboard);
}
}
virtual FReply ExitCursorMode()
{
if (bInCursorMode)
{
ToggleCursorMode();
}
return FReply::Handled().EndDragDrop();
}
private:
// BEGIN SWidget Interface
virtual bool SupportsKeyboardFocus() const OVERRIDE
{
return bInCursorMode;
}
virtual FReply OnKeyboardFocusReceived(
const FGeometry& MyGeometry,
const FKeyboardFocusEvent& InKeyboardFocusEvent ) OVERRIDE
{
// HACK: Work-around for held keys.
GetPlayerController()->FlushPlayerInput();
const bool bSwitchingToCursorMode = InKeyboardFocusEvent.GetCause() == EKeyboardFocusCause::Keyboard;
if (bSwitchingToCursorMode)
{
// We are switching to CursorMode...
return FReply::Handled()
// ... mouse input was captive by the game viewport
// mouse needs to be free to interact with menus
.ReleaseMouseCapture()
// Joystick should not control player while menus are up
.ReleaseJoystickCapture()
// The mouse should still not escape the boundaries of the game.
.LockMouseToWidget( SharedThis(this) );
}
else
{
// Upon focusing we were already in CursorMode, and the mouse should
// move freely around the desktop until the player clicks on the game.
return FReply::Handled().ReleaseMouseCapture().ReleaseJoystickCapture();
}
}
virtual FReply OnKeyDown(
const FGeometry& MyGeometry,
const FKeyboardEvent& InKeyboardEvent ) OVERRIDE
{
const EKey PressedKey = InKeyboardEvent.GetKey();
if(PressedKey == EKeys::Escape)
{
return ExitCursorMode();
}
else if (PressedKey != EKeys::Tilde)
{
// Incercept keyboard control; we do not want camera/character control
// while in extended HUD.
// Make an exception for system level keys, e.g., Tilde;
return FReply::Handled();
}
else
{
return FReply::Unhandled();
}
}
virtual FReply OnPreviewMouseButtonDown(
const FGeometry& MyGeometry,
const FPointerEvent& MouseEvent ) OVERRIDE
{
// When we are in cursor mode, clicking on the window should lock the cursor to the window
// but should not alter anything the click would have done (e.g. press a button).
// This preview event accomplishes that.
if (bInCursorMode)
{
return
// Notice we have NOT handled the event!
FReply::Unhandled()
// Just need to force lock the mouse to the viewport
// area without affecting any other interaction.
.LockMouseToWidget(SharedThis(this));
}
else
{
return FReply::Unhandled();
}
}
// END SWidget Interface
/** In cursor mode we show an extended, interactive HUD and */
bool bInCursorMode;
/**
* We try to be a good citizen, and restore focus to whoever had focus before we did.
* In this case it is probably the game viewport.
*/
TSharedPtr<SWidget> WidgetFocusedBeforeMe;
};
Let me know if you run into trouble with implementing this.