How UE4 UI Handles Input: From Platform Events to UMG

A source-level walkthrough of UE4's input path, from Windows messages and input devices through GenericApplication, Slate focus routing, SWidget, and finally UMG delegates.

On this page

Starting from UE4's Windows platform source code, this article traces the complete path an input event follows from the operating system and input devices into the engine, through GenericApplication, FSlateApplication, focus paths, and event routing, and finally into UMG Blueprint events. Function locations and signatures may differ slightly between UE4 point releases, but the overall layering and processing model remain largely the same.

The pipeline at a glance

UE4's UI input pipeline can be summarized as: platform input → GenericApplicationFSlateApplicationFWidgetPathSWidgetUWidget delegates.

  • Devices such as gamepads are generally polled once per frame to detect state changes. Windows mouse and keyboard input primarily arrives through window messages and is processed later in the engine's frame loop.
  • FSlateApplication is both the platform layer's MessageHandler and the core of Slate input routing.
  • An event first travels down the focus path during tunneling, then travels back up during bubbling if it has not yet been handled.
  • UMG is the UObject/reflection-layer wrapper around Slate. Delegates ultimately carry events from SWidget to UWidget and Blueprint.

How UE4 receives and processes input

Abstracting input devices

In the UE4 source tree, there are two modules whose names begin with Input: InputDevice and InputCore. If we open the former, we find that the InputDevice module defines only a handful of headers. In practice, two of them contain the information relevant to this discussion. The first is:

C++
// In module Runtime/InputDevice, file IInputDevice.h
class IInputDevice
{
public:
	virtual ~IInputDevice() {}

	virtual void Tick( float DeltaTime ) = 0;

	virtual void SendControllerEvents() = 0;

	virtual void SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) = 0;

    virtual bool Exec( UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar ) = 0;

	virtual void SetChannelValue (int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value) = 0;
	virtual void SetChannelValues (int32 ControllerId, const FForceFeedbackValues &values) = 0;
	virtual bool SupportsForceFeedback(int32 ControllerId) { return true; }

	virtual void SetLightColor(int32 ControllerId, FColor Color) { };
	virtual void ResetLightColor(int32 ControllerId) { };

	virtual void SetDeviceProperty(int32 ControllerId, const FInputDeviceProperty* Property) {}

	virtual class IHapticDevice* GetHapticDevice() { return nullptr; }

	virtual bool IsGamepadAttached() const { return false;}
};

And the second is:

C++
// In module Runtime/InputDevice, file IHapticDevice.h
class IHapticDevice
{
public:
	virtual void SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values) = 0;

	virtual void GetHapticFrequencyRange(float& MinFrequency, float& MaxFrequency) const = 0;

	virtual float GetHapticAmplitudeScale() const = 0;
};

These two interface classes, both prefixed with I, define a collection of pure virtual functions. Together, they describe the behavior expected of input devices that can be connected to UE. If we use the IDE to find usages of these interfaces, we can see implementations across several platform modules:

Implementations and call sites of IInputDevice in different platform modules

Focusing on the Windows module, the interface is used in two files:

  • XInputInterface.h
  • WindowsApplication.cpp

Let us begin with the first file. Its main job is to declare XInputInterface, a class derived from IInputDevice. The comments tell us that this input-interface implementation represents an input device for Xbox 360 controllers. In addition to implementing the virtual functions from IInputDevice, it records the following controller state:

C++
// In module Runtime/ApplicationCore/Windows, file XInputInterface.h
/**
 * Interface class for XInput devices (xbox 360 controller)                 
 */
class XInputInterface : public IInputDevice {
public:
	...
	virtual void SendControllerEvents() override;			// 发送按钮事件的函数
private:
	/** In the engine, all controllers map to xbox controllers for consistency */
	uint8 X360ToXboxControllerMapping[MAX_NUM_CONTROLLER_BUTTONS];			// 记录了按键映射

	FGamepadKeyNames::Type Buttons[MAX_NUM_CONTROLLER_BUTTONS];				// 记录了每一个按钮的名字

	TSharedRef<FGenericApplicationMessageHandler> MessageHandler;
};

One especially important method in this class is SendControllerEvents. Let us inspect what it does:

C++
// In module Runtime/ApplicationCore/Windows, file XInputInterface.cpp
void XInputInterface::SendControllerEvents() {
	bool bWereConnected[MAX_NUM_XINPUT_CONTROLLERS];
	XINPUT_STATE XInputStates[MAX_NUM_XINPUT_CONTROLLERS];

	bIsGamepadAttached = false;
	for ( int32 ControllerIndex=0; ControllerIndex < MAX_NUM_XINPUT_CONTROLLERS; ++ControllerIndex ) {
		FControllerState& ControllerState = ControllerStates[ControllerIndex];

		bWereConnected[ControllerIndex] = ControllerState.bIsConnected;

		if( ControllerState.bIsConnected || bNeedsControllerStateUpdate ) {
			XINPUT_STATE& XInputState = XInputStates[ControllerIndex];
			FMemory::Memzero( &XInputState, sizeof(XINPUT_STATE) );

			ControllerState.bIsConnected = ( XInputGetState( ControllerIndex, &XInputState ) == ERROR_SUCCESS ) ? true : false;

			if (ControllerState.bIsConnected) {
				bIsGamepadAttached = true;
			}
		}
	}

	...
}

The first part is straightforward: it checks whether each controller is currently connected to the computer. The second part examines every button state in turn and records the result:

C++
// In module Runtime/ApplicationCore/Windows, file XInputInterface.cpp
void XInputInterface::SendControllerEvents() {
	...

	for ( int32 ControllerIndex = 0; ControllerIndex < MAX_NUM_XINPUT_CONTROLLERS; ++ControllerIndex ) {
		// Set input scope, there doesn't seem to be a reliable way to differentiate 360 vs Xbox one controllers so use generic name
		FInputDeviceScope InputScope(this, XInputInterfaceName, ControllerIndex, XInputControllerIdentifier);
		FControllerState& ControllerState = ControllerStates[ControllerIndex];
		const bool bWasConnected = bWereConnected[ControllerIndex];

		// If the controller is connected send events or if the controller was connected send a final event with default states so that 
		// the game doesn't think that controller buttons are still held down
		if( ControllerState.bIsConnected || bWasConnected ) {
			const XINPUT_STATE& XInputState = XInputStates[ControllerIndex];

			// If the controller is connected now but was not before, refresh the information
			if (!bWasConnected && ControllerState.bIsConnected) {
				FCoreDelegates::OnControllerConnectionChange.Broadcast(true, -1, ControllerState.ControllerId);
			} else if (bWasConnected && !ControllerState.bIsConnected) {
				FCoreDelegates::OnControllerConnectionChange.Broadcast(false, -1, ControllerState.ControllerId);
			}
			
			bool CurrentStates[MAX_NUM_CONTROLLER_BUTTONS] = {0};
		
			// 收集当前输入设备所有按键的状态
			CurrentStates[X360ToXboxControllerMapping[0]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_A);
			CurrentStates[X360ToXboxControllerMapping[1]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_B);
			CurrentStates[X360ToXboxControllerMapping[2]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_X);
			CurrentStates[X360ToXboxControllerMapping[3]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_Y);
			CurrentStates[X360ToXboxControllerMapping[4]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER);
			CurrentStates[X360ToXboxControllerMapping[5]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER);
			CurrentStates[X360ToXboxControllerMapping[6]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_BACK);
			CurrentStates[X360ToXboxControllerMapping[7]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_START);
			CurrentStates[X360ToXboxControllerMapping[8]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB);
			CurrentStates[X360ToXboxControllerMapping[9]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB);
			CurrentStates[X360ToXboxControllerMapping[10]] = !!(XInputState.Gamepad.bLeftTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD);
			CurrentStates[X360ToXboxControllerMapping[11]] = !!(XInputState.Gamepad.bRightTrigger > XINPUT_GAMEPAD_TRIGGER_THRESHOLD);
			CurrentStates[X360ToXboxControllerMapping[12]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP);
			CurrentStates[X360ToXboxControllerMapping[13]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN);
			CurrentStates[X360ToXboxControllerMapping[14]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT);
			CurrentStates[X360ToXboxControllerMapping[15]] = !!(XInputState.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT);
			CurrentStates[X360ToXboxControllerMapping[16]] = !!(XInputState.Gamepad.sThumbLY > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
			CurrentStates[X360ToXboxControllerMapping[17]] = !!(XInputState.Gamepad.sThumbLY < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
			CurrentStates[X360ToXboxControllerMapping[18]] = !!(XInputState.Gamepad.sThumbLX < -XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
			CurrentStates[X360ToXboxControllerMapping[19]] = !!(XInputState.Gamepad.sThumbLX > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE);
			CurrentStates[X360ToXboxControllerMapping[20]] = !!(XInputState.Gamepad.sThumbRY > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
			CurrentStates[X360ToXboxControllerMapping[21]] = !!(XInputState.Gamepad.sThumbRY < -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
			CurrentStates[X360ToXboxControllerMapping[22]] = !!(XInputState.Gamepad.sThumbRX < -XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);
			CurrentStates[X360ToXboxControllerMapping[23]] = !!(XInputState.Gamepad.sThumbRX > XINPUT_GAMEPAD_RIGHT_THUMB_DEADZONE);

			...
		}
	}

	...
}

After collecting the button data, the method chooses which events to emit from the current state. It compares the current button state with the previous state and sends the appropriate event, such as Pressed or Released:

C++
// In module Runtime/ApplicationCore/Windows, file XInputInterface.cpp
void XInputInterface::SendControllerEvents() {
	...
	for ( int32 ControllerIndex = 0; ControllerIndex < MAX_NUM_XINPUT_CONTROLLERS; ++ControllerIndex ) {
		...
		if( ControllerState.bIsConnected || bWasConnected ) {
			...
			// For each button check against the previous state and send the correct message if any
			for (int32 ButtonIndex = 0; ButtonIndex < MAX_NUM_CONTROLLER_BUTTONS; ++ButtonIndex) {
				// 比较状态,如需要则发送按键事件
				if (CurrentStates[ButtonIndex] != ControllerState.ButtonStates[ButtonIndex]) {
					if( CurrentStates[ButtonIndex] ) {
						MessageHandler->OnControllerButtonPressed( Buttons[ButtonIndex], ControllerState.ControllerId, false );
					} else {
						MessageHandler->OnControllerButtonReleased( Buttons[ButtonIndex], ControllerState.ControllerId, false );
					}

					if ( CurrentStates[ButtonIndex] != 0 ) {
						// this button was pressed - set the button's NextRepeatTime to the InitialButtonRepeatDelay
						ControllerState.NextRepeatTime[ButtonIndex] = CurrentTime + InitialButtonRepeatDelay;
					}
				} else if ( CurrentStates[ButtonIndex] != 0 && ControllerState.NextRepeatTime[ButtonIndex] <= CurrentTime ) {
					MessageHandler->OnControllerButtonPressed( Buttons[ButtonIndex], ControllerState.ControllerId, true );

					// set the button's NextRepeatTime to the ButtonRepeatDelay
					ControllerState.NextRepeatTime[ButtonIndex] = CurrentTime + ButtonRepeatDelay;
				}

				// Update the state for next time
				ControllerState.ButtonStates[ButtonIndex] = CurrentStates[ButtonIndex];
			}
			...	// 应用力反馈
		}
	}

	...
}

Now that we have examined the Xbox event-sending function, two questions naturally arise:

  1. Where is SendControllerEvents called?
  2. The class also has a MessageHandler member. SendControllerEvents uses it to send button notifications such as OnControllerButtonPressed. What exactly is this MessageHandler, and where is it assigned?

To answer the first question, return to the second file in the Windows module that uses the IInputDevice interface: WindowsApplication.cpp. The interface appears only in FWindowsApplication::PollGameDeviceState(), whose definition is very short:

C++
// In module Runtime/ApplicationCore/Windows, file WindowsApplication.cpp
void FWindowsApplication::PollGameDeviceState( const float TimeDelta ) {
	if (bForceNoGamepads) { return; }

	// initialize any externally-implemented input devices (we delay load initialize the array so any plugins have had time to load)
	if (!bHasLoadedInputPlugins && GIsRunning ) {
		TArray<IInputDeviceModule*> PluginImplementations = IModularFeatures::Get().GetModularFeatureImplementations<IInputDeviceModule>( IInputDeviceModule::GetModularFeatureName() );
		for( auto InputPluginIt = PluginImplementations.CreateIterator(); InputPluginIt; ++InputPluginIt ) {
			TSharedPtr<IInputDevice> Device = (*InputPluginIt)->CreateInputDevice(MessageHandler);
			AddExternalInputDevice(Device);			
		}

		bHasLoadedInputPlugins = true;
	}

	if (FApp::UseVRFocus() && !FApp::HasVRFocus()) {
		return; // do not proceed if the app uses VR focus but doesn't have it
	}

	// Poll game device states and send new events
	XInput->SendControllerEvents();

	// Poll externally-implemented devices
	for( auto DeviceIt = ExternalInputDevices.CreateIterator(); DeviceIt; ++DeviceIt ) {
		(*DeviceIt)->Tick( TimeDelta );
		(*DeviceIt)->SendControllerEvents();
	}
}

Ignoring the loading code in the first half, notice the two blocks at the bottom. They call SendControllerEvents on both XInput and every entry in the ExternalInputDevices array. This is the same function we just saw in XInputInterface: it detects changes in button state and emits the appropriate button events. The declarations of these two members can be found in FWindowsApplication:

C++
// In module Runtime/ApplicationCore/Windows, file WindowsApplication.h
/**
 * Windows-specific application implementation.
 */
class APPLICATIONCORE_API FWindowsApplication : public GenericApplication , public IForceFeedbackSystem {
	...
private:
	TSharedRef<class XInputInterface> XInput;
	/** List of input devices implemented in external modules. */
	TArray<TSharedPtr<class IInputDevice>> ExternalInputDevices;

	...
};

The code separates input devices into two categories. XInput is the Microsoft API used by Windows applications to process controller interaction, including support for multiple controllers, vibration effects, dead zones, and related features. ExternalInputDevices, on the other hand, handles external input devices that are not managed through XInput.

We now know where SendControllerEvents is called. Next, let us determine how its MessageHandler is assigned and what that object represents. Returning to the XInputInterface declaration, we find an overridden function near the top of the class:

C++
void XInputInterface::SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) {
	MessageHandler = InMessageHandler;
}

An IDE usage search shows two places where MessageHandler can be assigned: the SetMessageHandler method shown above and the constructor. Therefore, if we locate the construction of XInputInterface and every call to its setter, we can identify the handler.

Searching for the constructor and setter leads us back to FWindowsApplication. The constructor is reached from the FWindowsApplication constructor through the static call XInputInterface::Create(MessageHandler). The setter is wrapped once more by FWindowsApplication::SetMessageHandler:

C++
// In module Runtime/ApplicationCore/Windows, file WindowsApplication.cpp
// WindowsApplication构造函数
FWindowsApplication::FWindowsApplication( const HINSTANCE HInstance, const HICON IconHandle )
	: GenericApplication( MakeShareable( new FWindowsCursor() ) )
	...
	, XInput( XInputInterface::Create( MessageHandler ) )			// 设置MessageHandler
	...
	, ClipCursorRect() { ... }

...

void FWindowsApplication::SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) {
	GenericApplication::SetMessageHandler(InMessageHandler);
	XInput->SetMessageHandler( InMessageHandler );				// 设置MessageHandler

	TArray<IInputDeviceModule*> PluginImplementations = IModularFeatures::Get().GetModularFeatureImplementations<IInputDeviceModule>( IInputDeviceModule::GetModularFeatureName() );
	for( auto DeviceIt = ExternalInputDevices.CreateIterator(); DeviceIt; ++DeviceIt ) {
		(*DeviceIt)->SetMessageHandler(InMessageHandler);		// 设置MessageHandler
	}
}

This function installs the same MessageHandler on every input device connected to the current Windows application.

Before following the handler further, let us summarize what we have established so far. The input-device abstraction exposes SendControllerEvents, which emits input events according to the controller's current state. FWindowsApplication calls this function. Inside it, an input device uses a MessageHandler to emit the corresponding input notifications. FWindowsApplication also invokes the setter for that handler, and it centrally owns the references to every input device.

This makes FWindowsApplication look like the object responsible for all input processing. What, then, does FWindowsApplication represent within the engine as a whole?

GenericApplication

The inheritance relationship for FWindowsApplication is declared in WindowsApplication.h:

C++
/**
 * Windows-specific application implementation.
 */
class APPLICATIONCORE_API FWindowsApplication : public GenericApplication, public IForceFeedbackSystem
{ ... };

The comment explains that FWindowsApplication is the Windows-specific implementation of GenericApplication. If we jump to the declaration of GenericApplication, we find many virtual functions with empty default implementations. These functions define an interface for window operations such as creating a window with MakeWindow and querying whether it is minimized with IsMinimized. They also include the two functions that mattered in the previous section, PollGameDeviceState and SetMessageHandler:

C++
/**
 * Generic platform application interface
 */
class GenericApplication 
{
public:

	DECLARE_MULTICAST_DELEGATE_OneParam( FOnConsoleCommandAdded, const FString& /*Command*/ );
	typedef FOnConsoleCommandAdded::FDelegate FOnConsoleCommandListener;

	GenericApplication( const TSharedPtr< ICursor >& InCursor )
		: Cursor( InCursor )
		, MessageHandler( MakeShareable( new FGenericApplicationMessageHandler() ) )
	{  }
	virtual ~GenericApplication() {}

	virtual void SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) { MessageHandler = InMessageHandler; }

	TSharedRef< FGenericApplicationMessageHandler > GetMessageHandler() { return MessageHandler; }

#if WITH_ACCESSIBILITY
	virtual void SetAccessibleMessageHandler(const TSharedRef<FGenericAccessibleMessageHandler>& InAccessibleMessageHandler) { AccessibleMessageHandler = InAccessibleMessageHandler; }
	TSharedRef<FGenericAccessibleMessageHandler> GetAccessibleMessageHandler() const { return AccessibleMessageHandler; }
#endif

	virtual void PollGameDeviceState( const float TimeDelta ) { }
	virtual void PumpMessages( const float TimeDelta ) { }
	virtual void ProcessDeferredEvents( const float TimeDelta ) { }
	virtual void Tick ( const float TimeDelta ) { }
	virtual TSharedRef< FGenericWindow > MakeWindow() { return MakeShareable( new FGenericWindow() ); }
	virtual void InitializeWindow( const TSharedRef< FGenericWindow >& Window, const TSharedRef< FGenericWindowDefinition >& InDefinition, const TSharedPtr< FGenericWindow >& InParent, const bool bShowImmediately ) { }
	virtual void SetCapture( const TSharedPtr< FGenericWindow >& InWindow ) { }
	virtual void* GetCapture( void ) const { return NULL; }
	virtual FModifierKeysState GetModifierKeys() const  { return FModifierKeysState(); }

	/** @return true if the system cursor is currently directly over a slate window. */
	virtual bool IsCursorDirectlyOverSlateWindow() const { return true; }

	/** @return Native window under the mouse cursor. */
	virtual TSharedPtr< FGenericWindow > GetWindowUnderCursor() { return TSharedPtr< FGenericWindow >( nullptr ); }

	virtual bool IsMinimized() const { return false; }
	virtual void SetHighPrecisionMouseMode( const bool Enable, const TSharedPtr< FGenericWindow >& InWindow ) { };
	virtual bool IsUsingHighPrecisionMouseMode() const { return false; }
	virtual bool IsUsingTrackpad() const { return false; }
	virtual bool IsMouseAttached() const { return true; }
	virtual bool IsGamepadAttached() const { return false; }
	virtual void RegisterConsoleCommandListener(const FOnConsoleCommandListener& InListener) {}
	virtual void AddPendingConsoleCommand(const FString& InCommand) {}
	virtual FPlatformRect GetWorkArea( const FPlatformRect& CurrentWindow ) const {
		FPlatformRect OutRect;
		OutRect.Left = 0;
		OutRect.Top = 0;
		OutRect.Right = 0;
		OutRect.Bottom = 0;
		return OutRect;
	}

	virtual bool TryCalculatePopupWindowPosition( const FPlatformRect& InAnchor, const FVector2D& InSize, const FVector2D& ProposedPlacement, const EPopUpOrientation::Type Orientation, /*OUT*/ FVector2D* const CalculatedPopUpPosition ) const { return false; }

	DECLARE_EVENT_OneParam(GenericApplication, FOnDisplayMetricsChanged, const FDisplayMetrics&);
	
	/** Notifies subscribers when any of the display metrics change: e.g. resolution changes or monitor sare re-arranged. */
	FOnDisplayMetricsChanged& OnDisplayMetricsChanged(){ return OnDisplayMetricsChangedEvent; }

	virtual void GetInitialDisplayMetrics( FDisplayMetrics& OutDisplayMetrics ) const { FDisplayMetrics::RebuildDisplayMetrics(OutDisplayMetrics); }

	
	/** Delegate for virtual keyboard being shown/hidden in case UI wants to slide out of the way */
	DECLARE_EVENT_OneParam(FSlateApplication, FVirtualKeyboardShownEvent, FPlatformRect);
	FVirtualKeyboardShownEvent& OnVirtualKeyboardShown()  { return VirtualKeyboardShownEvent; }
	
	DECLARE_EVENT(FSlateApplication, FVirtualKeyboardHiddenEvent);
	FVirtualKeyboardHiddenEvent& OnVirtualKeyboardHidden()  { return VirtualKeyboardHiddenEvent; }

	
	/** Gets the horizontal alignment of the window title bar's title text. */
	virtual EWindowTitleAlignment::Type GetWindowTitleAlignment() const { return EWindowTitleAlignment::Left; }

	virtual EWindowTransparency GetWindowTransparencySupport() const { return EWindowTransparency::None; }

	virtual void DestroyApplication() { }

	virtual IInputInterface* GetInputInterface() { return nullptr; }	

	/** Function to return the current implementation of the Text Input Method System */
	virtual ITextInputMethodSystem *GetTextInputMethodSystem() { return NULL; }
	
	/** Send any analytics captured by the application */
	virtual void SendAnalytics(IAnalyticsProvider* Provider) { }
	virtual bool SupportsSystemHelp() const { return false; }
	virtual void ShowSystemHelp() {}
	virtual bool ApplicationLicenseValid(FPlatformUserId PlatformUser = PLATFORMUSERID_NONE) { return true; }
	virtual bool IsAllowedToRender() const { return true; }
	virtual void FinishedInputThisFrame() {}
public:

	const TSharedPtr< ICursor > Cursor;

protected:

	TSharedRef< class FGenericApplicationMessageHandler > MessageHandler;

	...
};

From these interface declarations, we can form a useful model: GenericApplication is the platform abstraction responsible for creating and managing windows and input devices on different systems. FWindowsApplication is the Windows implementation of that abstraction.

What FGenericApplicationMessageHandler contains

Despite its name, this handler is less a single handler implementation than an interface that defines a complete family of input events. Its declaration contains many virtual functions whose default behavior is simply return false;:

C++
/** Interface that defines how to handle interaction with a user via hardware input and output */
class FGenericApplicationMessageHandler
{
public:

	virtual ~FGenericApplicationMessageHandler() {}

	virtual bool ShouldProcessUserInputMessages( const TSharedPtr< FGenericWindow >& PlatformWindow ) const { return false; }

	virtual bool OnKeyChar( const TCHAR Character, const bool IsRepeat ) { return false; }

	virtual bool OnKeyDown( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat ) { return false; }

	virtual bool OnKeyUp( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat ) { return false; }

	virtual void OnInputLanguageChanged() { }

	virtual bool OnMouseDown( const TSharedPtr< FGenericWindow >& Window, const EMouseButtons::Type Button ) { return false; }

	virtual bool OnMouseDown( const TSharedPtr< FGenericWindow >& Window, const EMouseButtons::Type Button, const FVector2D CursorPos ) { return false; }

	virtual bool OnMouseUp( const EMouseButtons::Type Button ) { return false; }

	virtual bool OnMouseUp( const EMouseButtons::Type Button, const FVector2D CursorPos ) { return false; }

	virtual bool OnMouseDoubleClick( const TSharedPtr< FGenericWindow >& Window, const EMouseButtons::Type Button ) { return false; }

	virtual bool OnMouseDoubleClick( const TSharedPtr< FGenericWindow >& Window, const EMouseButtons::Type Button, const FVector2D CursorPos ) { return false; }

	...
};

These declarations cover keyboard, mouse, and controller events, including OnKeyDown and OnMouseDown.

Returning to XInputInterface::SendControllerEvents, we can now see that the XInput device keeps a reference to the object that receives input events and uses it to forward button notifications.

So far, however, we have only found the definition of the MessageHandler interface. Where are these events actually handled?

How the game installs its MessageHandler (excluding editor-specific paths)

An IDE search reveals that only one engine class actually derives from FGenericApplicationMessageHandler: FSlateApplication. It overrides all of the virtual functions declared by FGenericApplicationMessageHandler.

When is this handler installed? Recall that FWindowsApplication can receive its handler in two ways: through its constructor or through a setter. Searching for usages of the FWindowsApplication constructor and setter gives us the following result:

C++
void FSlateApplication::SetPlatformApplication(const TSharedRef<class GenericApplication>& InPlatformApplication) {
	PlatformApplication->SetMessageHandler(MakeShareable(new FGenericApplicationMessageHandler()));
#if WITH_ACCESSIBILITY
	PlatformApplication->SetAccessibleMessageHandler(MakeShareable(new FGenericAccessibleMessageHandler()));
#endif

	PlatformApplication = InPlatformApplication;
	PlatformApplication->SetMessageHandler(CurrentApplication.ToSharedRef());
#if WITH_ACCESSIBILITY
	PlatformApplication->SetAccessibleMessageHandler(CurrentApplication->GetAccessibleMessageHandler());
#endif
}

And immediately above the constructor, the static CreateWindowsApplication function:

C++
FWindowsApplication* FWindowsApplication::CreateWindowsApplication( const HINSTANCE InstanceHandle, const HICON IconHandle ) {
	WindowsApplication = new FWindowsApplication( InstanceHandle, IconHandle );
	return WindowsApplication;
}

The constructor path does not appear to tell us much about where the game installs its handler. What if we continue tracing callers of the setter instead?

Call sites of SetPlatformApplication

This is even stranger: the caller is not in a Runtime module at all, but in code related to command-line tools. The game itself must therefore install the handler elsewhere.

Where GenericApplication is stored

Since usage searches through the setter and constructor did not give us the answer, let us first solve a related problem: where is the GenericApplication instance that opens, closes, and manages windows stored?

In FSlateApplication::SetPlatformApplication, the call that assigns the MessageHandler uses a variable named PlatformApplication. Jumping to its declaration shows that it is a static variable on FSlateApplicationBase, one of the base classes of FSlateApplication:

C++
// In module Runtime/SlateCore/Application/SlateApplicationBase.cpp
TSharedPtr<FSlateApplicationBase> FSlateApplicationBase::CurrentBaseApplication = nullptr;
TSharedPtr<GenericApplication> FSlateApplicationBase::PlatformApplication = nullptr;

...

Because PlatformApplication is stored by SlateApplication, it is reasonable to suspect that the MessageHandler is installed when SlateApplication itself is created.

Where the game actually installs the MessageHandler

Object creation normally begins in a constructor. In the previous section, however, we discovered that the Slate application is held through static state. Static instances of this kind are commonly initialized through a dedicated static Create function. Following that idea, we find two overloads of FSlateApplication::Create, defined as follows:

C++

void FSlateApplication::Create() {
	GSlateFastWidgetPath = GIsEditor ? false : true;

	Create(MakeShareable(FPlatformApplicationMisc::CreateApplication()));
}

TSharedRef<FSlateApplication> FSlateApplication::Create(const TSharedRef<class GenericApplication>& InPlatformApplication) {
	EKeys::Initialize();

	FCoreStyle::ResetToDefault();

	// Note: Important to establish the static PlatformApplication property first, as the FSlateApplication ctor relies on it
	PlatformApplication = InPlatformApplication;

	CurrentApplication = MakeShareable( new FSlateApplication() );	// 创建SlateApplication
	CurrentBaseApplication = CurrentApplication;

	PlatformApplication->SetMessageHandler( CurrentApplication.ToSharedRef() );	// 将当前的SlateApplication设置为GenericApplication的MessageHandler
#if WITH_ACCESSIBILITY
	PlatformApplication->SetAccessibleMessageHandler(CurrentApplication->GetAccessibleMessageHandler());
#endif

	// The grid needs to know the size and coordinate system of the desktop.
	// Some monitor setups have a primary monitor on the right and below the
	// left one, so the leftmost upper right monitor can be something like (-1280, -200)Synt
	{
		// Get an initial value for the VirtualDesktop geometry
		CurrentApplication->VirtualDesktopRect = []() {
			FDisplayMetrics DisplayMetrics;
			FSlateApplicationBase::Get().GetDisplayMetrics(DisplayMetrics);
			const FPlatformRect& VirtualDisplayRect = DisplayMetrics.VirtualDisplayRect;
			return FSlateRect(VirtualDisplayRect.Left, VirtualDisplayRect.Top, VirtualDisplayRect.Right, VirtualDisplayRect.Bottom);
		}();

		// Sign up for updates from the OS. Polling this every frame is too expensive on at least some OSs.
		PlatformApplication->OnDisplayMetricsChanged().AddSP(CurrentApplication.ToSharedRef(), &FSlateApplication::OnVirtualDesktopSizeChanged);
	}

	FAsyncTaskNotificationFactory::Get().RegisterFactory(TEXT("Slate"), []() -> FAsyncTaskNotificationFactory::FImplPointerType { return new FSlateAsyncTaskNotificationImpl(); });

	return CurrentApplication.ToSharedRef();
}

This is the path we were looking for. The parameterless Create first calls MakeShareable(FPlatformApplicationMisc::CreateApplication()) to create a GenericApplication. It passes that application into the second overload, which constructs a new Slate application through MakeShareable(new FSlateApplication()). Finally, GenericApplication::SetMessageHandler installs the Slate application as the platform application's message handler.

We already know what happens next: GenericApplication propagates the same handler to its input devices.

Who initializes SlateApplication and polls input?

We have now established that calling FSlateApplication::Create() constructs both the SlateApplication and the GenericApplication required by a running game. The call should therefore occur during engine initialization. Searching for references confirms this:

C++
int32 FEngineLoop::PreInitPreStartupScreen(const TCHAR* CmdLine) {
	...
	if (!IsRunningDedicatedServer() && (bHasEditorToken || bIsRegularClient)) {
		// Init platform application
		SCOPED_BOOT_TIMING("FSlateApplication::Create()");
		FSlateApplication::Create();		// 创建SlateApplication和GenericApplication
	}
	...
}

At last, we reach the engine's main loop. PreInitPreStartupScreen creates the application whenever the process is not a dedicated server and is either a normal client or has an editor token.

If these objects are created by the main loop, is input polling also performed there? Return to the FSlateApplication declaration and recall the function responsible for polling: FSlateApplication::PollGameDeviceState(). Searching for its call sites produces a very direct result:

Call site of PollGameDeviceState

The relevant code is short enough that there is little to filter out:

C++
void FEngineLoop::Tick() {
	...
	{
		...
		// process accumulated Slate input
		if (FSlateApplication::IsInitialized() && !bIdleMode) {
			CSV_SCOPED_TIMING_STAT_EXCLUSIVE(Input);
			SCOPE_TIME_GUARD(TEXT("SlateInput"));
			QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_SlateInput);
			LLM_SCOPE(ELLMTag::UI);

			FSlateApplication& SlateApp = FSlateApplication::Get();
			{
				QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_PollGameDeviceState);
				SlateApp.PollGameDeviceState();		// 检测输入
			}
			// Gives widgets a chance to process any accumulated input
			{
				QUICK_SCOPE_CYCLE_COUNTER(STAT_FEngineLoop_Tick_FinishedInputThisFrame);
				SlateApp.FinishedInputThisFrame();
			}
		}
		...
	}
	...
}

We can now draw a firm conclusion. During initialization of the engine's main loop, UE creates the platform window manager and SlateApplication. Later, the engine's tick function polls for input, so device state is checked proactively once per frame. When an input device detects a state change, it emits the corresponding event through its MessageHandler, which means the matching event function on FSlateApplication is invoked.

How UE4 UI receives, processes, and responds to input

The previous section showed what happens when a button changes state. For example, if a button was released in the previous frame but is down in the current frame, the engine should raise a key-down event. That event is forwarded to the corresponding method on FSlateApplication.

Using a key press as an example, pressing a button eventually reaches FSlateApplication::OnKeyDown:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::OnKeyDown( const int32 KeyCode, const uint32 CharacterCode, const bool IsRepeat ) {
	FKey const Key = FInputKeyManager::Get().GetKeyFromCodes( KeyCode, CharacterCode );
	FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(), GetUserIndexForKeyboard(), IsRepeat, CharacterCode, KeyCode);

	return ProcessKeyDownEvent( KeyEvent );
}

This function first resolves the key from its key code and obtains the corresponding FKey. It then constructs an FKeyEvent that describes the input event and passes it to ProcessKeyDownEvent. The definition of ProcessKeyDownEvent is shown below:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::ProcessKeyDownEvent( const FKeyEvent& InKeyEvent ) {
	SCOPE_CYCLE_COUNTER(STAT_ProcessKeyDown);

	TScopeCounter<int32> BeginInput(ProcessingInput);

	TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(InKeyEvent);

	// Analog cursor gets first chance at the input
	if (InputPreProcessors.HandleKeyDownEvent(*this, InKeyEvent)) {
		return true;
	}

	FReply Reply = FReply::Unhandled();

	SetLastUserInteractionTime(this->GetCurrentTime());
	
	
	if (SlateUser->IsDragDropping() && InKeyEvent.GetKey() == EKeys::Escape)
	{
		// Pressing ESC while drag and dropping terminates the drag drop.
		SlateUser->CancelDragDrop();
		Reply = FReply::Handled();
	}
	else
	{
		LastUserInteractionTimeForThrottling = LastUserInteractionTime;

#if SLATE_HAS_WIDGET_REFLECTOR
		// If we are inspecting, pressing ESC exits inspection mode.
		if ( InKeyEvent.GetKey() == EKeys::Escape )
		{
			TSharedPtr<IWidgetReflector> WidgetReflector = WidgetReflectorPtr.Pin();
			const bool bIsWidgetReflectorPicking = WidgetReflector.IsValid() && WidgetReflector->IsInPickingMode();
			if ( bIsWidgetReflectorPicking )
			{
					WidgetReflector->OnWidgetPicked();
					Reply = FReply::Handled();

					return Reply.IsEventHandled();
			}
		}
#endif

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
		// Ctrl+Shift+~ summons the Toolbox.
		if (InKeyEvent.GetKey() == EKeys::Tilde && InKeyEvent.IsControlDown() && InKeyEvent.IsShiftDown())
		{
			IToolboxModule* ToolboxModule = FModuleManager::LoadModulePtr<IToolboxModule>("Toolbox");
			if (ToolboxModule)
			{
				ToolboxModule->SummonToolbox();
			}
		}

#endif //!(UE_BUILD_SHIPPING || UE_BUILD_TEST)

		// Bubble the keyboard event
		TSharedRef<FWidgetPath> EventPathRef = SlateUser->GetFocusPath();
		const FWidgetPath& EventPath = EventPathRef.Get();

		// Switch worlds for widgets inOnPreviewMouseButtonDown the current path
		FScopedSwitchWorldHack SwitchWorld(EventPath);

		// Tunnel the keyboard event
		Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FTunnelPolicy(EventPath), InKeyEvent, [] (const FArrangedWidget& CurrentWidget, const FKeyEvent& Event) {
			if (CurrentWidget.Widget->IsEnabled()) {
				const FReply TempReply = CurrentWidget.Widget->OnPreviewKeyDown(CurrentWidget.Geometry, Event);
				return TempReply;
			}
			return FReply::Unhandled();
		}, ESlateDebuggingInputEvent::PreviewKeyDown);

		// Send out key down events.
		if ( !Reply.IsEventHandled() ) {
			Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InKeyEvent, [] (const FArrangedWidget& SomeWidgetGettingEvent, const FKeyEvent& Event) {
				if (SomeWidgetGettingEvent.Widget->IsEnabled()) {
					const FReply TempReply = SomeWidgetGettingEvent.Widget->OnKeyDown(SomeWidgetGettingEvent.Geometry, Event);
					return TempReply;
				}

				return FReply::Unhandled();
			}, ESlateDebuggingInputEvent::KeyDown);
		}

		// If the key event was not processed by any widget...
		if ( !Reply.IsEventHandled() && UnhandledKeyDownEventHandler.IsBound() )
		{
			Reply = UnhandledKeyDownEventHandler.Execute(InKeyEvent);
		}
	}

	return Reply.IsEventHandled();
}

To understand how the key event continues through the system, we first need to understand what this function does.

It begins with GetOrCreateUser, which returns a Slate user or creates one if the user does not yet exist. An FSlateUser represents a logical input user. A single-player game will usually have only one such object. In this case the user is identified from the user index passed into OnKeyDown. Member functions on FSlateUser expose the logical user's current input state. The first block, for example, checks whether the input occurred during a drag-and-drop operation. If so, the operation is cancelled immediately and the reply is marked as handled:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::ProcessKeyDownEvent( const FKeyEvent& InKeyEvent ) {
	...
	TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(InKeyEvent);		// 获取当前的SlateUser
	if (SlateUser->IsDragDropping() && InKeyEvent.GetKey() == EKeys::Escape) {
		// Pressing ESC while drag and dropping terminates the drag drop.
		SlateUser->CancelDragDrop();
		Reply = FReply::Handled();
	} else {
		...
	}
	...
}

If the user is not dragging, execution moves to the next stage, where the key-down event is treated as input that must be routed. Before examining the code, consider how a complex UMG hierarchy is normally built. Containers such as CanvasPanel position their children through anchors and other layout data. Several widgets can therefore be stacked at the same screen position, for example:

[UButton → UVerticalBox → UCanvasPanel]

How does UE determine which widget should receive the event?

UE uses a data structure called a focus path. In simple terms, the engine applies a set of rules to find the widgets that may participate in focus, then orders them from the root down to the most specific child. ProcessKeyDownEvent obtains that path here:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::ProcessKeyDownEvent( const FKeyEvent& InKeyEvent ) {
	...
	TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(InKeyEvent);		// 获取当前的SlateUser
	if (SlateUser->IsDragDropping() && InKeyEvent.GetKey() == EKeys::Escape) {
		...
	} else {
		...
		// Bubble the keyboard event
		TSharedRef<FWidgetPath> EventPathRef = SlateUser->GetFocusPath();
		const FWidgetPath& EventPath = EventPathRef.Get();
		...
	}
	...
}

How the focus path is built

If we jump to FSlateUser::GetFocusPath(), we find that it directly returns a member named StrongFocusPath. Searching for assignments to that member leads to FSlateApplication::SetUserFocus(). By examining that function block by block, we can see when and how the focus path is constructed.

The first block is:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::SetUserFocus(FSlateUser& User, const FWidgetPath& InFocusPath, const EFocusCause InCause) {
	if (InFocusPath.IsValid()) {
		TSharedRef<SWindow> Window = InFocusPath.GetWindow();
		if (ActiveModalWindows.Num() != 0 && !(Window->IsDescendantOf(GetActiveModalWindow()) || ActiveModalWindows.Top() == Window)) {
			UE_LOG(LogSlate, Warning, TEXT("Ignoring SetUserFocus because it's not an active modal Window (user %i not set to %s."), User.GetUserIndex(), *InFocusPath.GetLastWidget()->ToString());
			return false;
		}
	}

	...
}

It first verifies that the supplied InFocusPath is valid, then obtains the window that owns the path. Next, it checks that an active window exists and that the path belongs to the top-level window. If both conditions are satisfied, the request is valid; otherwise, the function immediately returns false.

The second block is:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::SetUserFocus(FSlateUser& User, const FWidgetPath& InFocusPath, const EFocusCause InCause) {
	...
	// Get the old Widget information
	const FWeakWidgetPath OldFocusedWidgetPath = User.GetWeakFocusPath();
	TSharedPtr<SWidget> OldFocusedWidget = OldFocusedWidgetPath.IsValid() ? OldFocusedWidgetPath.GetLastWidget().Pin() : TSharedPtr< SWidget >();
	
	// Get the new widget information by finding the first widget in the path that supports focus
	FWidgetPath NewFocusedWidgetPath;
	TSharedPtr<SWidget> NewFocusedWidget;

	if (InFocusPath.IsValid()) {
		for (int32 WidgetIndex = InFocusPath.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex) {
			const FArrangedWidget& WidgetToFocus = InFocusPath.Widgets[WidgetIndex];

			// Does this widget support keyboard focus?  If so, then we'll go ahead and set it!
			if (WidgetToFocus.Widget->SupportsKeyboardFocus()) {
				// Is we aren't changing focus then simply return
				if (WidgetToFocus.Widget == OldFocusedWidget) {
					//UE_LOG(LogSlate, Warning, TEXT("--Focus Has Not Changed--"));
					return false;
				}
				NewFocusedWidget = WidgetToFocus.Widget;
				NewFocusedWidgetPath = InFocusPath.GetPathDownTo(NewFocusedWidget.ToSharedRef());
				break;
			}
		}
	}
	...
}

This loop traverses InFocusPath backward, starting at the deepest leaf widget in the UMG hierarchy and moving toward the root. It checks whether each widget supports keyboard focus. When it finds a focusable widget that differs from the leaf widget in the previous focus path, it makes that widget the new focused widget. NewFocusedWidgetPath becomes the portion of InFocusPath from the root through that deepest focusable widget.

Consider a simple example in which InFocusPath is:

[SWindow → SPanel → SButton → STextBlock]

Because the loop walks backward, it visits STextBlock first. STextBlock does not override the SupportsKeyboardFocus virtual function from SWidget, so the default result is false and nothing happens during the first iteration.

The second iteration visits SButton:

C++
bool SButton::SupportsKeyboardFocus() const {
	// Buttons are focusable by default
	return bIsFocusable;
}

Whether a button accepts keyboard focus depends on its configuration. Suppose the button in this example is focusable and the previous focus path differs from the new one. Then:

  1. NewFocusedWidget is set to the SButton.
  2. NewFocusedWidgetPath becomes [SWindow → SPanel → SButton], excluding the trailing STextBlock that cannot receive focus.

The third block is:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::SetUserFocus(FSlateUser& User, const FWidgetPath& InFocusPath, const EFocusCause InCause) {
	...
	User.IncrementFocusVersion();
	int32 CurrentFocusVersion = User.GetFocusVersion();
	FFocusEvent FocusEvent(InCause, User.GetUserIndex());
	FocusChangingDelegate.Broadcast(FocusEvent, OldFocusedWidgetPath, OldFocusedWidget, NewFocusedWidgetPath, NewFocusedWidget);

	// Notify widgets in the old focus path that focus is changing
	if (OldFocusedWidgetPath.IsValid()) {
		FScopedSwitchWorldHack SwitchWorld(OldFocusedWidgetPath.Window.Pin());

		for (int32 ChildIndex = 0; ChildIndex < OldFocusedWidgetPath.Widgets.Num(); ++ChildIndex) {
			TSharedPtr<SWidget> SomeWidget = OldFocusedWidgetPath.Widgets[ChildIndex].Pin();
			if (SomeWidget.IsValid()) {
				SomeWidget->OnFocusChanging(OldFocusedWidgetPath, NewFocusedWidgetPath, FocusEvent);

				// If focus setting is interrupted, stop what we're doing, as someone has already changed the focus path.
				if ( CurrentFocusVersion != User.GetFocusVersion()) {
					return false;
				}
			}
		}
	}

	// Notify widgets in the new focus path that focus is changing
	if (NewFocusedWidgetPath.IsValid()) {
		FScopedSwitchWorldHack SwitchWorld(NewFocusedWidgetPath.GetWindow());

		for (int32 ChildIndex = 0; ChildIndex < NewFocusedWidgetPath.Widgets.Num(); ++ChildIndex) {
			TSharedPtr<SWidget> SomeWidget = NewFocusedWidgetPath.Widgets[ChildIndex].Widget;
			if (SomeWidget.IsValid()) {
				SomeWidget->OnFocusChanging(OldFocusedWidgetPath, NewFocusedWidgetPath, FocusEvent);

				// If focus setting is interrupted, stop what we're doing, as someone has already changed the focus path.
				if ( CurrentFocusVersion != User.GetFocusVersion()) {
					return false;
				}
			}
		}
	}
	...
}

This code broadcasts the focus-path change through delegates. The notification covers the path as a whole and each widget in the old and new paths, including the OnAddedToFocusPath and OnRemovedFromFocusPath events that can be bound in C++ or Blueprint.

The fourth block is:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::SetUserFocus(FSlateUser& User, const FWidgetPath& InFocusPath, const EFocusCause InCause) {
	...
	// Figure out if we should show focus for this focus entry
	bool ShowFocus = false;
	if (NewFocusedWidgetPath.IsValid()) {
		ShowFocus = InCause == EFocusCause::Navigation;
		for (int32 WidgetIndex = NewFocusedWidgetPath.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex) {
			TOptional<bool> QueryShowFocus = NewFocusedWidgetPath.Widgets[WidgetIndex].Widget->OnQueryShowFocus(InCause);
			if ( QueryShowFocus.IsSet()) {
				ShowFocus = QueryShowFocus.GetValue();
				break;
			}
		}
	}
	...
}

Here the path is traversed backward to find a widget that should display a focus effect, such as the background change shown when a mouse hovers over an SButton. For the example [SWindow → SPanel → SButton], if the button is configured to show a focus effect, the first iteration breaks immediately and ShowFocus becomes true.

After determining whether a focus effect should be shown, the function stores the actual focus path on the Slate user:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::SetUserFocus(FSlateUser& User, const FWidgetPath& InFocusPath, const EFocusCause InCause) {
	...
	// Store a weak widget path to the widget that's taking focus
	User.SetFocusPath(NewFocusedWidgetPath, InCause, ShowFocus);
	...
}

It calls SetFocusPath on the supplied Slate user and passes both the new path and the focus-effect state.

The sixth and final block is:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::SetUserFocus(FSlateUser& User, const FWidgetPath& InFocusPath, const EFocusCause InCause) {
	...
	// Let the old widget know that it lost keyboard focus
	if (OldFocusedWidget.IsValid()) {
		// Switch worlds for widgets in the old path
		FScopedSwitchWorldHack SwitchWorld(OldFocusedWidgetPath.Window.Pin());

		// Let previously-focused widget know that it's losing focus
		OldFocusedWidget->OnFocusLost(FocusEvent);
	}

	// Let the new widget know that it's received keyboard focus
	if (NewFocusedWidget.IsValid()) {
		TSharedPtr<SWindow> FocusedWindow = NewFocusedWidgetPath.GetWindow();

		// Switch worlds for widgets in the new path
		FScopedSwitchWorldHack SwitchWorld(FocusedWindow);

		// Set ActiveTopLevelWindow to the newly focused window
		ActiveTopLevelWindow = FocusedWindow;
		
		const FArrangedWidget& WidgetToFocus = NewFocusedWidgetPath.Widgets.Last();

		FReply Reply = NewFocusedWidget->OnFocusReceived(WidgetToFocus.Geometry, FocusEvent);
		if (Reply.IsEventHandled()) {
			ProcessReply(InFocusPath, Reply, nullptr, nullptr, User.GetUserIndex());
		}

		GetRelevantNavConfig(User.GetUserIndex())->OnNavigationChangedFocus(OldFocusedWidget, NewFocusedWidget, FocusEvent);
	}

	return true;
}

This last section invokes the focus-lost event on the previously focused widget and the focus-received event on the newly focused widget. The function then returns true.

We now understand the focus path returned inside ProcessKeyDownEvent.

How is the InFocusPath argument to SetUserFocus constructed?

Searching for callers of SetUserFocus reveals several overloads. The overload that actually constructs a widget path is bool FSlateApplication::SetUserFocus(uint32 UserIndex, const TSharedPtr<SWidget>& WidgetToFocus, EFocusCause ReasonFocusIsChanging). Let us inspect what it does:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::SetUserFocus(uint32 UserIndex, const TSharedPtr<SWidget>& WidgetToFocus, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/) {
	TSharedPtr<FSlateUser> CurrentUser = GetUser(UserIndex);
	if (ensureMsgf(WidgetToFocus.IsValid(), TEXT("Attempting to focus an invalid widget. If your intent is to clear focus use ClearUserFocus()")) && CurrentUser) {
		FWidgetPath PathToWidget;
		const bool bFound = FSlateWindowHelper::FindPathToWidget(SlateWindows, WidgetToFocus.ToSharedRef(), /*OUT*/ PathToWidget);
		if (bFound) {
			return SetUserFocus(*CurrentUser, PathToWidget, ReasonFocusIsChanging);
		} else {
			const bool bFoundVirtual = FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, WidgetToFocus.ToSharedRef(), /*OUT*/ PathToWidget);
			if (bFoundVirtual) {
				return SetUserFocus(*CurrentUser, PathToWidget, ReasonFocusIsChanging);
			}
		}
	}

	return false;
}

FSlateWindowHelper::FindPathToWidget returns a path from the supplied Slate widget and the Slate window that contains it. What happens inside that function?

How Slate widgets are organized

Before answering that question, we need to understand how UE maintains a UI with deeply nested relationships. The SWidget declaration contains the following members:

C++
class SLATECORE_API SWidget : public FSlateControlledConstruction, public TSharedFromThis<SWidget> {
public:
	FORCEINLINE bool IsParentValid() const { return ParentWidgetPtr.IsValid(); }
	FORCEINLINE TSharedPtr<SWidget> GetParentWidget() const { return ParentWidgetPtr.Pin(); }

	/**
	 * Every widget that has children must implement this method. This allows for iteration over the Widget's
	 * children regardless of how they are actually stored.
	 */
	virtual FChildren* GetChildren() = 0;
	virtual FChildren* GetAllChildren() { return GetChildren(); }

private:
	/** Pointer to this widgets parent widget.  If it is null this is a root widget or it is not in the widget tree */
	TWeakPtr<SWidget> ParentWidgetPtr;
};

An SWidget stores a pointer to its parent. If it has children, its implementation of GetChildren also lets callers enumerate them. One parent and multiple children means that Slate manages its widgets as a tree.

Constructing the preliminary focus path

We have seen that the final focus path is produced by trimming another path. The original path is returned by FSlateWindowHelper::FindPathToWidget for the supplied WidgetToFocus. Here is that function:

C++
bool FSlateWindowHelper::FindPathToWidget( const TArray<TSharedRef<SWindow>>& WindowsToSearch, TSharedRef<const SWidget> InWidget, FWidgetPath& OutWidgetPath, EVisibility VisibilityFilter ) {
	SCOPE_CYCLE_COUNTER(STAT_FindPathToWidget);

	if (GSlateFastWidgetPath) {
		TSharedPtr<SWidget> CurWidget = ConstCastSharedRef<SWidget>(InWidget);
		OutWidgetPath.Widgets.SetFilter(VisibilityFilter);
		while (true) {
			EVisibility CurWidgetVisibility = CurWidget->GetVisibility();
			if (OutWidgetPath.Widgets.Accepts(CurWidgetVisibility)) {
				FArrangedWidget ArrangedWidget(CurWidget.ToSharedRef(), CurWidget->GetCachedGeometry());
				OutWidgetPath.Widgets.AddWidget(CurWidgetVisibility, ArrangedWidget);

				TSharedPtr<SWidget> CurWidgetParent = CurWidget->GetParentWidget();		// 获取父母Widget
				if (!CurWidgetParent.IsValid()) {
					if (CurWidget->Advanced_IsWindow()) {
						OutWidgetPath.TopLevelWindow = StaticCastSharedPtr<SWindow>(CurWidget);
						OutWidgetPath.Widgets.Reverse();
						return true;
					}

					OutWidgetPath.Widgets.Empty();
					return false;
				}

				if (!CurWidgetParent->ValidatePathToChild(CurWidget.Get())) {
					OutWidgetPath.Widgets.Empty();
					return false;
				}

				CurWidget = CurWidgetParent;		// 指针往上指一层
			} else {
				OutWidgetPath.Widgets.Empty();
				return false;
			}
		}
	} else {
		bool bFoundWidget = false;

		for (int32 WindowIndex = 0; !bFoundWidget && WindowIndex < WindowsToSearch.Num(); ++WindowIndex) {
			TSharedRef<SWindow> CurWindow = WindowsToSearch[WindowIndex];

			FArrangedChildren JustWindow(VisibilityFilter);
			{
				JustWindow.AddWidget(FArrangedWidget(CurWindow, CurWindow->GetWindowGeometryInScreen()));
			}

			FWidgetPath PathToWidget(CurWindow, JustWindow);

			if ((CurWindow == InWidget) || PathToWidget.ExtendPathTo(FWidgetMatcher(InWidget), VisibilityFilter)) {
				OutWidgetPath = PathToWidget;
				bFoundWidget = true;
			}

			if (!bFoundWidget) {
				bFoundWidget = FindPathToWidget(CurWindow->GetChildWindows(), InWidget, OutWidgetPath, VisibilityFilter);
			}
		}

		return bFoundWidget;
	}
}

Although an if/else statement divides the implementation into two paths, both branches perform the same conceptual task. The distinction is whether global fast widget-path lookup is supported. In the fast path, the function repeatedly calls SWidget::GetParentWidget() to obtain the current widget's parent and then makes that parent the current widget until it reaches the root. Without fast lookup, it recursively descends the hierarchy and assembles the path one level at a time.

In what order do button events travel through widgets in the focus path?

After FSlateApplication::ProcessKeyDownEvent obtains the focus path, it contains two very similar blocks:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::ProcessKeyDownEvent( const FKeyEvent& InKeyEvent ) {
	...
	TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(InKeyEvent);		// 获取当前的SlateUser
	if (SlateUser->IsDragDropping() && InKeyEvent.GetKey() == EKeys::Escape) {
		...
	} else {
		...
		// Tunnel the keyboard event
		Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FTunnelPolicy(EventPath), InKeyEvent, [] (const FArrangedWidget& CurrentWidget, const FKeyEvent& Event) {
			if (CurrentWidget.Widget->IsEnabled()) {
				const FReply TempReply = CurrentWidget.Widget->OnPreviewKeyDown(CurrentWidget.Geometry, Event);
				return TempReply;
			}
			return FReply::Unhandled();
		}, ESlateDebuggingInputEvent::PreviewKeyDown);

		// Send out key down events.
		if ( !Reply.IsEventHandled() ) {
			Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InKeyEvent, [] (const FArrangedWidget& SomeWidgetGettingEvent, const FKeyEvent& Event) {
				if (SomeWidgetGettingEvent.Widget->IsEnabled()) {
					const FReply TempReply = SomeWidgetGettingEvent.Widget->OnKeyDown(SomeWidgetGettingEvent.Geometry, Event);
					return TempReply;
				}
				return FReply::Unhandled();
			}, ESlateDebuggingInputEvent::KeyDown);
		}
	}
	...
}

In essence, these blocks traverse the widgets in the focus path once from front to back and once from back to front, calling the corresponding input-event function on each SWidget.

Continuing the earlier example, suppose the focus path is:

[SWindow → SPanel → SButton]

Tunneling traverses from the root SWindow toward the leaf SButton. Bubbling begins at SButton and walks back toward SWindow.

This design resembles routed events in Microsoft's WPF framework. The root-to-leaf pass can intercept global operations such as shortcuts—for example, Ctrl+S to save. The leaf-to-root pass then gives the visually topmost control the first opportunity to consume the input.

At the end of the function, if no widget has handled the input, the event is passed to UnhandledKeyDownEventHandler, a delegate declared on FSlateApplication. The function finally returns whether the input was handled:

C++
// In module Runtime/Slate/Framework/Application, file SlateApplication.cpp
bool FSlateApplication::ProcessKeyDownEvent( const FKeyEvent& InKeyEvent ) {
	TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(InKeyEvent);		// 获取当前的SlateUser
	if (SlateUser->IsDragDropping() && InKeyEvent.GetKey() == EKeys::Escape) {
		...
	} else {
		...
		// If the key event was not processed by any widget...
		if ( !Reply.IsEventHandled() && UnhandledKeyDownEventHandler.IsBound() ) {
			Reply = UnhandledKeyDownEventHandler.Execute(InKeyEvent);
		} 
	}
	return Reply.IsEventHandled();
}

How Slate handles input

We now know that UE first tunnels and then bubbles an event. In each pass, it visits the Slate widgets in the focus path in a defined order and calls the corresponding input-event function on each widget.

Take the example of clicking a button on screen. FSlateApplication::ProcessMouseButtonDownEvent() eventually calls SButton::OnMouseButtonDown() on the SButton in the focus path. The function handles the mouse click as follows:

C++
FReply SButton::OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) {
	FReply Reply = FReply::Unhandled();
	if (IsEnabled() && (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton || MouseEvent.IsTouchEvent())) {
		Press();
		PressedScreenSpacePosition = MouseEvent.GetScreenSpacePosition();

		EButtonClickMethod::Type InputClickMethod = GetClickMethodFromInputType(MouseEvent);
		
		if(InputClickMethod == EButtonClickMethod::MouseDown) {
			//get the reply from the execute function
			Reply = ExecuteOnClick();

			//You should ALWAYS handle the OnClicked event.
			ensure(Reply.IsEventHandled() == true);
		} else if (InputClickMethod == EButtonClickMethod::PreciseClick) {
			// do not capture the pointer for precise taps or clicks
			// 
			Reply = FReply::Handled();
		} else {
			//we need to capture the mouse for MouseUp events
			Reply = FReply::Handled().CaptureMouse( AsShared() );
		}
	}

	Invalidate(EInvalidateWidget::Layout);

	//return the constructed reply
	return Reply;
}

The function performs four main tasks:

  1. It creates a Reply value to return.
  2. It checks whether the button is enabled. If so, it determines whether the incoming mouse event was triggered by the left mouse button or synthesized from a touch event.
  3. If the checks pass, it triggers the press event. It then consults the configured click method—which controls rules such as MouseDown versus DownAndUp—to decide whether it should continue and execute the click event.
  4. After processing the event, it invalidates the button so that it will be repainted, then returns Reply.

What happens inside Press and ExecuteOnClick?

C++
// In module Runtime/Slate/Widgets/Input, file SButton.h
/**
 * Slate's Buttons are clickable Widgets that can contain arbitrary widgets as its Content().
 */
class SLATE_API SButton : public SBorder {
	...
protected:
	...
	/** The delegate to execute when the button is clicked */
	FOnClicked OnClicked;

	/** The delegate to execute when the button is pressed */
	FSimpleDelegate OnPressed;

	...
};

// In module Runtime/Slate/Widgets/Input, file SButton.cpp
FReply SButton::ExecuteOnClick() {
	if (OnClicked.IsBound()) {
		FReply Reply = OnClicked.Execute();
		return Reply;
	} else {
		return FReply::Handled();
	}
}

void SButton::Press() {
	if ( !bIsPressed ) {
		bIsPressed = true;
		PlayPressedSound();
		OnPressed.ExecuteIfBound();
	}
}

The class uses FOnClicked and FSimpleDelegate. Both are simple delegate types that take no input parameters; the difference is that FOnClicked returns an FReply.

The functions that process press and click events execute the functions bound to these delegates. This raises another question: when are functions bound to delegates such as OnPressed? A usage search shows that the only assignment occurs in SButton::Construct, where the value comes from InArgs:

C++
// In module Runtime/Slate/Widgets/Input, file SButton.cpp
/**
 * Construct this widget
 *
 * @param	InArgs	The declaration data for this widget
 */
void SButton::Construct( const FArguments& InArgs ) {
	bIsPressed = false;

	...

	// 将传入的委托赋值给当前Slate当中的委托成员变量
	OnClicked = InArgs._OnClicked;
	OnPressed = InArgs._OnPressed;
	OnReleased = InArgs._OnReleased;
	OnHovered = InArgs._OnHovered;
	OnUnhovered = InArgs._OnUnhovered;

	ClickMethod = InArgs._ClickMethod;
	TouchMethod = InArgs._TouchMethod;
	PressMethod = InArgs._PressMethod;

	HoveredSound = InArgs._HoveredSoundOverride.Get(Style->HoveredSlateSound);
	PressedSound = InArgs._PressedSoundOverride.Get(Style->PressedSlateSound);
}

Where and when is this Construct method called?

The relationship between Slate and UMG

To answer that question, we first need to describe the relationship between Slate and UUserWidget.

When we create a WidgetBlueprint, its generated widget class ultimately derives from UUserWidget. The comment in the declaration and a typical UMG class make that relationship clear:

C++
// In module Runtime/UMG/Blueprint, file UserWidget.h
/**
 * The user widget is extensible by users through the WidgetBlueprint.
 */
UCLASS(Abstract, editinlinenew, BlueprintType, Blueprintable, meta=( DontUseGenericSpawnObject="True", DisableNativeTick) )
class UMG_API UUserWidget : public UWidget, public INamedSlotInterface
{
	...
};

The controls we drag into UMG—images, buttons, and so on—derive directly from UWidget. For example, UButton derives from UContentWidget, which derives from UPanelWidget, which in turn derives from UWidget:

C++
// In module Runtime/UMG/Components, file Button.h
UCLASS()
class UMG_API UButton : public UContentWidget {
	...
};

UMG therefore has a UButton representing a button, while Slate has an SButton that represents a button and processes button input. There must be a connection between them.

SButton itself contains no references to UWidget or its subclasses. Looking in the opposite direction, however, UWidget and its descendants do contain references to Slate widgets. UButton, for example, contains the following:

C++
// In module Runtime/UMG/Components, file Button.h
UCLASS()
class UMG_API UButton : public UContentWidget {
	...
protected:
	//~ Begin UWidget Interface
	virtual TSharedRef<SWidget> RebuildWidget() override;		// 构建对应`SWidget`的接口函数
	...

protected:
	/** Cached pointer to the underlying slate button owned by this UWidget */
	TSharedPtr<SButton> MyButton;		// 指向SButton的指针
};

// In module Runtime/UMG/Components, file Button.cpp
TSharedRef<SWidget> UButton::RebuildWidget() {
	MyButton = SNew(SButton)
		.OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
		.OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed))
		.OnReleased(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleReleased))
		.OnHovered_UObject( this, &ThisClass::SlateHandleHovered )
		.OnUnhovered_UObject( this, &ThisClass::SlateHandleUnhovered )
		.ButtonStyle(&WidgetStyle)
		.ClickMethod(ClickMethod)
		.TouchMethod(TouchMethod)
		.PressMethod(PressMethod)
		.IsFocusable(IsFocusable);

	if ( GetChildrenCount() > 0 ) {
		Cast<UButtonSlot>(GetContentSlot())->BuildSlot(MyButton.ToSharedRef());
	}
	
	return MyButton.ToSharedRef();
}

The declaration includes a pointer to SButton and a function that creates a button and assigns it to MyButton.

Consider another commonly used control, UImage:

C++
// In module Runtime/UMG/Components, file Image.h
UCLASS()
class UMG_API UImage : public UWidget {
	...
protected:
	TSharedPtr<SImage> MyImage;			// 指向SImage的指针
	...
};

Every control at the UMG layer corresponds to a control at the Slate layer. Slate itself is not integrated with UE's reflection system. In other words, UMG is a reflection-enabled wrapper around the Slate framework that provides UObject and Blueprint functionality.

Creating Slate widgets

Now that we understand the relationship between Slate and UMG, return to the earlier question: where is an SWidget's Construct method called? The UMG implementation provides a clue in RebuildWidget, where this expression appears to construct a new Slate widget:

C++
// In module Runtime/UMG/Components, file Button.cpp
TSharedRef<SWidget> UButton::RebuildWidget() {
	MyButton = SNew(SButton)
		.OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
		.OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed))
		.OnReleased(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleReleased))
		.OnHovered_UObject( this, &ThisClass::SlateHandleHovered )
		.OnUnhovered_UObject( this, &ThisClass::SlateHandleUnhovered )
		.ButtonStyle(&WidgetStyle)
		.ClickMethod(ClickMethod)
		.TouchMethod(TouchMethod)
		.PressMethod(PressMethod)
		.IsFocusable(IsFocusable);

	if ( GetChildrenCount() > 0 ) {
		Cast<UButtonSlot>(GetContentSlot())->BuildSlot(MyButton.ToSharedRef());
	}
	
	return MyButton.ToSharedRef();
}

Let us expand the SNew macro. At the next level, it becomes:

C++
// In module Runtime/SlateCore/Widgets/DeclaraiveSyntaxSupport.h
#define SNew( WidgetType, ... ) \
	MakeTDecl<WidgetType>( #WidgetType, __FILE__, __LINE__, RequiredArgs::MakeRequiredArgs(__VA_ARGS__) ) <<= TYPENAME_OUTSIDE_TEMPLATE WidgetType::FArguments()

Many macros and templates are involved. Expanding the SNew expression used by UButton gives us the following code:

C++
MakeTDecl<SButton>("SButton", "UButton.cpp", 53, RequiredArgs::MakeRequiredArgs()) 
	<<= 
	SButton::FArguments().OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
				.OnClicked(BIND_UOBJECT_DELEGATE(FOnClicked, SlateHandleClicked))
				.OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed))
				.OnReleased(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandleReleased))
				.OnHovered_UObject( this, &ThisClass::SlateHandleHovered )
				.OnUnhovered_UObject( this, &ThisClass::SlateHandleUnhovered )
				.ButtonStyle(&WidgetStyle)
				.ClickMethod(ClickMethod)
				.TouchMethod(TouchMethod)
				.PressMethod(PressMethod)
				.IsFocusable(IsFocusable);

This appears to call a function named MakeTDecl, then apply the <<= operator to its return value. The right-hand operand is a constructed WidgetType::FArguments() object, and the returned type overloads <<=. The definitions of MakeTDecl and its return type are shown here:

C++
// In module Runtime/SlateCore/Widgets/DeclaraiveSyntaxSupport.h
template<typename WidgetType, typename RequiredArgsPayloadType>
TDecl<WidgetType, RequiredArgsPayloadType> MakeTDecl( const ANSICHAR* InType, const ANSICHAR* InFile, int32 OnLine, RequiredArgsPayloadType&& InRequiredArgs ) {
	return TDecl<WidgetType, RequiredArgsPayloadType>(InType, InFile, OnLine, Forward<RequiredArgsPayloadType>(InRequiredArgs));
}

template<class WidgetType, typename RequiredArgsPayloadType>
struct TDecl {
	TDecl( const ANSICHAR* InType, const ANSICHAR* InFile, int32 OnLine, RequiredArgsPayloadType&& InRequiredArgs )
		: _Widget( TWidgetAllocator<WidgetType, TIsDerivedFrom<WidgetType, SUserWidget>::IsDerived >::PrivateAllocateWidget() )
		, _RequiredArgs(InRequiredArgs) {
		_Widget->SetDebugInfo( InType, InFile, OnLine, sizeof(WidgetType) );
	}

	...

	/**
	 * Complete widget construction from InArgs.
	 * @param InArgs  NamedArguments from which to construct the widget.
	 * @return A reference to the widget that we constructed.
	 */
	TSharedRef<WidgetType> operator<<=( const typename WidgetType::FArguments& InArgs ) const {
		...

		_RequiredArgs.CallConstruct(_Widget, InArgs);
		return _Widget;
	}

	const TSharedRef<WidgetType> _Widget;
	RequiredArgsPayloadType& _RequiredArgs;
};

MakeTDecl directly returns a newly constructed TDecl. The TDecl constructor stores the supplied debugging information. Near the end of the structure, we find the <<= overload we were looking for. It takes the already constructed InArgs and passes it to CallConstruct on the object built by RequiredArgs::MakeRequiredArgs(__VA_ARGS__). It then returns the constructed SWidget. Back in UButton::RebuildWidget, that widget is assigned to MyButton.

That gives us the overall flow, but what exactly is the RequiredArgs::MakeRequiredArgs(__VA_ARGS__) value? Jumping to its definition reveals a family of overloads that accept between zero and five arguments:

C++
namespace RequiredArgs {
	...

	FORCEINLINE T0RequiredArgs MakeRequiredArgs() {
		return T0RequiredArgs();
	}

	template<typename Arg0Type>
	T1RequiredArgs<Arg0Type&&> MakeRequiredArgs(Arg0Type&& InArg0) {
		return T1RequiredArgs<Arg0Type&&>(Forward<Arg0Type>(InArg0));
	}

	template<typename Arg0Type, typename Arg1Type>
	T2RequiredArgs<Arg0Type&&, Arg1Type&&> MakeRequiredArgs(Arg0Type&& InArg0, Arg1Type&& InArg1) {
		return T2RequiredArgs<Arg0Type&&, Arg1Type&&>(Forward<Arg0Type>(InArg0), Forward<Arg1Type>(InArg1));
	}

	template<typename Arg0Type, typename Arg1Type, typename Arg2Type>
	T3RequiredArgs<Arg0Type&&, Arg1Type&&, Arg2Type&&> MakeRequiredArgs(Arg0Type&& InArg0, Arg1Type&& InArg1, Arg2Type&& InArg2) {
		return T3RequiredArgs<Arg0Type&&, Arg1Type&&, Arg2Type&&>(Forward<Arg0Type>(InArg0), Forward<Arg1Type>(InArg1), Forward<Arg2Type>(InArg2));
	}

	template<typename Arg0Type, typename Arg1Type, typename Arg2Type, typename Arg3Type>
	T4RequiredArgs<Arg0Type&&, Arg1Type&&, Arg2Type&&, Arg3Type&&> MakeRequiredArgs(Arg0Type&& InArg0, Arg1Type&& InArg1, Arg2Type&& InArg2, Arg3Type&& InArg3) {
		return T4RequiredArgs<Arg0Type&&, Arg1Type&&, Arg2Type&&, Arg3Type&&>(Forward<Arg0Type>(InArg0), Forward<Arg1Type>(InArg1), Forward<Arg2Type>(InArg2), Forward<Arg3Type>(InArg3));
	}

	template<typename Arg0Type, typename Arg1Type, typename Arg2Type, typename Arg3Type, typename Arg4Type>
	T5RequiredArgs<Arg0Type&&, Arg1Type&&, Arg2Type&&, Arg3Type&&, Arg4Type&&> MakeRequiredArgs(Arg0Type&& InArg0, Arg1Type&& InArg1, Arg2Type&& InArg2, Arg3Type&& InArg3, Arg4Type&& InArg4) {
		return T5RequiredArgs<Arg0Type&&, Arg1Type&&, Arg2Type&&, Arg3Type&&, Arg4Type&&>(Forward<Arg0Type>(InArg0), Forward<Arg1Type>(InArg1), Forward<Arg2Type>(InArg2), Forward<Arg3Type>(InArg3), Forward<Arg4Type>(InArg4));
	}
}

Each overload constructs and returns a structure for a particular number of arguments. The definitions from T0RequiredArgs through T5RequiredArgs follow the same pattern, so one example is enough to show what they do:

C++
namespace RequiredArgs {
	...
	struct T0RequiredArgs {
		T0RequiredArgs() { }

		template<class WidgetType>
		void CallConstruct(const TSharedRef<WidgetType>& OnWidget, const typename WidgetType::FArguments& WithNamedArgs) const {
			// YOUR WIDGET MUST IMPLEMENT void Construct(const FArguments& InArgs)
			OnWidget->Construct(WithNamedArgs);
			OnWidget->CacheVolatility();
		}
	};

	...
}

The corresponding CallConstruct accepts a reference to the widget being constructed and the InArgs supplied by the <<= operator. This finally identifies the caller of SWidget::Construct(const FArguments& InArgs) that we were searching for.

We are now close to understanding the entire SNew macro. After expansion, one element remains: WidgetType::FArguments() on the right side of <<=. For a particular SWidget subclass, this names that widget type's own argument structure. We can confirm this in SButton:

C++
class SLATE_API SButton : public SBorder {
	SLATE_BEGIN_ARGS( SButton )
		: _Content()
		, _ButtonStyle( &FCoreStyle::Get().GetWidgetStyle< FButtonStyle >( "Button" ) )
		, _TextStyle( &FCoreStyle::Get().GetWidgetStyle< FTextBlockStyle >("NormalText") )
		, _HAlign( HAlign_Fill )
		, _VAlign( VAlign_Fill )
		, _ContentPadding(FMargin(4.0, 2.0))
		, _Text()
		, _ClickMethod( EButtonClickMethod::DownAndUp )
		, _TouchMethod( EButtonTouchMethod::DownAndUp )
		, _PressMethod( EButtonPressMethod::DownAndUp )
		, _DesiredSizeScale( FVector2D(1,1) )
		, _ContentScale( FVector2D(1,1) )
		, _ButtonColorAndOpacity(FLinearColor::White)
		, _ForegroundColor( FCoreStyle::Get().GetSlateColor( "InvertedForeground" ) )
		, _IsFocusable( true ) { }
		
		...
	
		/** Called when the button is clicked */
		SLATE_EVENT( FOnClicked, OnClicked )

		/** Called when the button is pressed */
		SLATE_EVENT( FSimpleDelegate, OnPressed )

		/** Called when the button is released */
		SLATE_EVENT( FSimpleDelegate, OnReleased )

		SLATE_EVENT( FSimpleDelegate, OnHovered )

		SLATE_EVENT( FSimpleDelegate, OnUnhovered )

		...

	SLATE_END_ARGS()

	...
};

UE uses a dedicated family of macros to make these argument declarations manageable. SButton::FArguments contains many button-specific settings, including alignment, color and opacity, the button's child content—which may be text or an image—and, importantly, the delegates that process input events. Expanding the OnPressed portion of SButton::FArguments produces code like this:

C++
class SLATE_API SButton : public SBorder {
public:
	struct FArguments : public TSlateBaseNamedArgs<SButton> {
		typedef FArguments WidgetArgsType;
		__declspec(noinline) FArguments()
			: _Content()
			, _ButtonStyle( &FCoreStyle::Get().GetWidgetStyle< FButtonStyle >( "Button" ) )
			, _TextStyle( &FCoreStyle::Get().GetWidgetStyle< FTextBlockStyle >("NormalText") )
			, _HAlign( HAlign_Fill )
			, _VAlign( VAlign_Fill )
			, _ContentPadding(FMargin(4.0, 2.0))
			, _Text()
			, _ClickMethod( EButtonClickMethod::DownAndUp )
			, _TouchMethod( EButtonTouchMethod::DownAndUp )
			, _PressMethod( EButtonPressMethod::DownAndUp )
			, _DesiredSizeScale( FVector2D(1,1) )
			, _ContentScale( FVector2D(1,1) )
			, _ButtonColorAndOpacity(FLinearColor::White)
			, _ForegroundColor( FCoreStyle::Get().GetSlateColor( "InvertedForeground" ) )
			, _IsFocusable( true ) { }

		...
		WidgetArgsType& OnPressed(const FSimpleDelegate& InDelegate) {
			_OnPressed = InDelegate;
			return *this;
		}

		WidgetArgsType& OnPressed(FSimpleDelegate&& InDelegate) {
			_OnPressed = MoveTemp(InDelegate);
			return *this;
		}
		...

		FSimpleDelegate _OnPressed;
	};
};

The full SLATE_EVENT macro expands to more than these two functions, but this subset is enough for our purposes. Recall how UButton supplies the pressed handler: it calls .OnPressed(BIND_UOBJECT_DELEGATE(FSimpleDelegate, SlateHandlePressed)) on the corresponding FArguments. Conceptually, that call expands to:

C++
SButton::FArgument::OnPressed(FSimpleDelegate::CreateUObject(this, &ThisClass::SlateHandlePressed));

It invokes the OnPressed overload that accepts an rvalue, assigns the delegate to the matching member of FArguments, and returns itself so that construction can continue in builder style.

We can now summarize how the initialization arguments reach SButton. UE first builds the widget's FArguments, then passes those arguments into void Construct(const FArguments& InArgs). This is how input-event bindings cross into the SWidget layer. The following diagram illustrates the process:

The SWidget construction flow

Passing input events from Slate to UMG

We have established how UMG and Slate are connected and how input delegates are supplied. For the pressed event, UButton binds SlateHandlePressed to SButton. Its implementation is simple:

C++
// In module Runtime/UMG/Conmponents, file Button.h
UCLASS()
class UMG_API UButton : public UContentWidget {
	...

	/** Called when the button is pressed */
	UPROPERTY(BlueprintAssignable, Category="Button|Event")
	FOnButtonPressedEvent OnPressed;

	...
};

// In module Runtime/UMG/Components, file Button.cpp
void UButton::SlateHandlePressed() {
	OnPressed.Broadcast();
}

It broadcasts the event to every function bound to OnPressed, including handlers bound in a UMG Blueprint and handlers bound in UMG C++ code.

Tracing the call stack for a mouse click on a button

Based on the analysis above, we can predict the call order when the left mouse button clicks a button on Windows and eventually invokes its bound OnPressed event. Listed from the engine loop toward the application callback, the path is:

  1. FEngineLoop::Tick() runs once per frame.
  2. FSlateApplication::PollGameDeviceState() attempts to collect the current state of input devices during the frame.
  3. FWindowsApplication::PollGameDeviceState(float TimeDelta) is the platform-specific polling function called by FSlateApplication.
  4. The corresponding device's IInputDevice::SendControllerEvents() checks whether any button state changed and, if so, sends an input notification. For this example, assume it reports that the left mouse button was pressed.
  5. FSlateApplication::OnMouseDown(const TSharedPtr<FGenericWindow>&, const EMouseButtons::Type) receives the input notification through the corresponding FSlateApplication event.
  6. FSlateApplication::OnMouseDown(const TSharedPtr<FGenericWindow>&, const EMouseButtons::Type, const FVector2D) receives the mouse position from the overload above.
  7. FSlateApplication::ProcessMouseButtonDownEvent(const TSharedPtr<FGenericWindow>&, const FPointerEvent&) is called after the overload above has dealt with touch input and determined that this is not a touch event. It traverses every Slate widget in the focus path in the required order and invokes the corresponding widget event.
  8. SButton::OnMouseButtonDown(const FGeometry&, const FPointerEvent&) processes the press and returns a reply.
  9. SButton::Press() executes the handler bound to the OnPressed delegate.
  10. UButton::SlateHandlePressed()—the handler bound to SButton::OnPressed—broadcasts the event to all listeners of UButton::OnPressed.
  11. The function bound to the specific button runs.

Rather than relying only on this predicted sequence, let us inspect the real call stack. First, set a breakpoint in the engine function that handles OnPressed for UButton:

A breakpoint set in UButton::SlateHandlePressed

Click any button in the project. The call stack immediately before UButton::SlateHandlePressed looks like this:

The complete call stack for a button's OnPressed event

Apart from the platform message-receiving portion, the actual path closely matches our prediction. Every frame, the engine actively processes input messages and sends them to the corresponding event on FSlateApplication. FEventRouter::Route then traverses the Slate widgets in the focus path. Finally, a delegate notifies the UWidget layer, which invokes the event handlers we bound.

Let us now focus on the part that differed from the simplified prediction: how Windows processes input messages.

In a conventional Windows application, the window-creation process normally installs a window procedure that receives input messages. The pattern resembles this:

C++
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch(uMsg) {
        case WM_KEYDOWN:
            if (wParam == VK_ESCAPE) PostQuitMessage(0);  // 按下ESC退出程序
            break;
        case WM_LBUTTONDOWN:
            // 处理鼠标左键点击坐标(通过lParam解析)
            break;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

The FWindowsApplication::AppWndProc frame in the call stack is exactly such a procedure:

C++
LRESULT CALLBACK FWindowsApplication::AppWndProc(HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam) {
	ensure( IsInGameThread() );
	return WindowsApplication->ProcessMessage( hwnd, msg, wParam, lParam );
}

The next function down the stack is FWindowsApplication::ProcessMessage. It contains a very large switch statement. The portion relevant to this discussion is:

C++
int32 FWindowsApplication::ProcessMessage( HWND hwnd, uint32 msg, WPARAM wParam, LPARAM lParam ) {
	...
	switch(msg) {
		...
		case WM_KEYDOWN:
		case WM_SYSKEYUP:
		case WM_KEYUP:
		case WM_LBUTTONDBLCLK:
		case WM_LBUTTONDOWN:
		case WM_MBUTTONDBLCLK:
		case WM_MBUTTONDOWN:
		case WM_RBUTTONDBLCLK:
		case WM_RBUTTONDOWN:
		case WM_XBUTTONDBLCLK:
		case WM_XBUTTONDOWN:
		case WM_XBUTTONUP:
		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
		case WM_NCMOUSEMOVE:
		case WM_MOUSEMOVE:
		case WM_MOUSEWHEEL:
#if WINVER >= 0x0601
		case WM_TOUCH:
#endif
			{
				DeferMessage( CurrentNativeEventWindowPtr, hwnd, msg, wParam, lParam );
				// Handled
				return 0;
			}
			break;

		case WM_SETCURSOR:
			...
	};
}

For button presses, mouse movement, mouse-button presses, and many related input events, this code calls DeferMessage. DeferMessage ultimately routes the work to ProcessDeferredMessage, so that is the function we need to examine.

Like ProcessMessage, ProcessDeferredMessage contains a large switch that handles message types such as WM_KEYDOWN and WM_KEYUP differently. Our example is triggered by a mouse click, so the relevant branch is WM_LBUTTONDOWN:

C++
int32 FWindowsApplication::ProcessDeferredMessage( const FDeferredWindowsMessage& DeferredMessage ) {
	switch(msg) {
		...
		// Mouse Button Down
		case WM_LBUTTONDBLCLK:
		case WM_LBUTTONDOWN:
		case WM_MBUTTONDBLCLK:
		case WM_MBUTTONDOWN:
		case WM_RBUTTONDBLCLK:
		case WM_RBUTTONDOWN:
		case WM_XBUTTONDBLCLK:
		case WM_XBUTTONDOWN:
		case WM_LBUTTONUP:
		case WM_MBUTTONUP:
		case WM_RBUTTONUP:
		case WM_XBUTTONUP:
			{
				// 获取鼠标位置
				POINT CursorPoint;
				CursorPoint.x = GET_X_LPARAM(lParam);
				CursorPoint.y = GET_Y_LPARAM(lParam); 

				ClientToScreen(hwnd, &CursorPoint);

				const FVector2D CursorPos(CursorPoint.x, CursorPoint.y);

				// 初始化参数
				EMouseButtons::Type MouseButton = EMouseButtons::Invalid;
				bool bDoubleClick = false;
				bool bMouseUp = false;

				// 根据鼠标事件再次划分赋值
				switch(msg) {
				...
				case WM_LBUTTONDOWN:
					MouseButton = EMouseButtons::Left;
					break;
				};
				...

				// 最后,根据收集到的信息,向MessageHandler发送对应的输入事件
				if (bMouseUp) {
					return MessageHandler->OnMouseUp( MouseButton, CursorPos ) ? 0 : 1;
				} else if (bDoubleClick) {
					MessageHandler->OnMouseDoubleClick( CurrentNativeEventWindowPtr, MouseButton, CursorPos );
				} else {
					MessageHandler->OnMouseDown( CurrentNativeEventWindowPtr, MouseButton, CursorPos );
				}
				return 0;
			}
		...
	}
}

As established earlier, the MessageHandler for a running game is the current SlateApplication instance. From this point onward, execution rejoins the pipeline that we have already traced.

Summary

The complete sequence from device input to a UI response is summarized in the following diagram:

The complete path from platform input to a UMG event response