7834 lines
266 KiB
C++
7834 lines
266 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Application/SlateUser.h"
|
|
#include "Rendering/SlateDrawBuffer.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/ScopeLock.h"
|
|
#include "Misc/TimeGuard.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/CoreDelegates.h"
|
|
#include "Misc/App.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "InputCoreModule.h"
|
|
#include "Layout/LayoutUtils.h"
|
|
#include "Sound/ISlateSoundDevice.h"
|
|
#include "Sound/NullSlateSoundDevice.h"
|
|
#include "Framework/Text/PlatformTextField.h"
|
|
#include "Framework/Application/NavigationConfig.h"
|
|
#include "Widgets/SWeakWidget.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "Widgets/SViewport.h"
|
|
#include "Framework/Application/SWindowTitleBar.h"
|
|
#include "Input/Events.h"
|
|
#include "Input/HittestGrid.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "HAL/PlatformStackWalk.h"
|
|
#include "Null/NullPlatformApplicationMisc.h"
|
|
#include "GenericPlatform/GenericPlatformInputDeviceMapper.h"
|
|
|
|
#if WITH_ACCESSIBILITY
|
|
#include "Widgets/Accessibility/SlateAccessibleMessageHandler.h"
|
|
#endif
|
|
|
|
#include "Framework/Application/IWidgetReflector.h"
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Framework/Notifications/SlateAsyncTaskNotificationImpl.h"
|
|
#include "Framework/Application/IInputProcessor.h"
|
|
#include "GenericPlatform/ITextInputMethodSystem.h"
|
|
#include "Framework/Docking/TabCommands.h"
|
|
#include "Math/UnitConversion.h"
|
|
#include "ProfilingDebugging/CsvProfiler.h"
|
|
#include "ProfilingDebugging/StallDetector.h"
|
|
#include "Types/ReflectionMetadata.h"
|
|
#include "Trace/SlateMemoryTags.h"
|
|
#include "Trace/SlateTrace.h"
|
|
#include "Styling/StarshipCoreStyle.h"
|
|
#include "Styling/UMGCoreStyle.h"
|
|
|
|
#ifndef SLATE_HAS_WIDGET_REFLECTOR
|
|
#define SLATE_HAS_WIDGET_REFLECTOR !(UE_BUILD_TEST || UE_BUILD_SHIPPING) && PLATFORM_DESKTOP
|
|
#endif
|
|
|
|
#if PLATFORM_MICROSOFT
|
|
#include "Microsoft/WindowsHWrapper.h"
|
|
#endif
|
|
#include "Debugging/SlateDebugging.h"
|
|
#include "Styling/StyleColors.h"
|
|
|
|
|
|
CSV_DECLARE_CATEGORY_MODULE_EXTERN(CORE_API, Basic);
|
|
|
|
CSV_DECLARE_CATEGORY_MODULE_EXTERN(SLATECORE_API, Slate);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
bool GSlateVerifyParentChildrenRelationship = false;
|
|
static FAutoConsoleVariableRef CVarSlateVerifyParentChildrenRelationship(
|
|
TEXT("Slate.VerifyParentChildrenRelationship"),
|
|
GSlateVerifyParentChildrenRelationship,
|
|
TEXT("Every tick, verify that a widget has only one parent.")
|
|
);
|
|
|
|
bool GSlateVerifyWidgetLayerId = false;
|
|
static FAutoConsoleVariableRef CVarSlateVerifyWidgetLayerId(
|
|
TEXT("Slate.VerifyWidgetLayerId"),
|
|
GSlateVerifyWidgetLayerId,
|
|
TEXT("Every tick, verify that widgets have a LayerId range that fits with their siblings and their parent.")
|
|
);
|
|
namespace UE::Slate::Private
|
|
{
|
|
void VerifyParentChildrenRelationship(const TSharedRef<SWindow>& WindowToDraw);
|
|
void VerifyWidgetLayerId(const TSharedRef<SWindow>& WindowToDraw);
|
|
}
|
|
|
|
bool GSlateTraceNavigationConfig = false;
|
|
static FAutoConsoleVariableRef CVarSlateTraceNavigationConfig(
|
|
TEXT("Slate.Debug.TraceNavigationConfig"),
|
|
GSlateTraceNavigationConfig,
|
|
TEXT("True enables tracing of navigation config & callstack to log.")
|
|
);
|
|
#endif //WITH_SLATE_DEBUGGING
|
|
|
|
bool GSlateInputMotionFiresUserInteractionEvents = true;
|
|
static FAutoConsoleVariableRef CVarSlateInputMotionFiresUserInteractionEvents(
|
|
TEXT("Slate.Input.MotionFiresUserInteractionEvents"),
|
|
GSlateInputMotionFiresUserInteractionEvents,
|
|
TEXT("If this is false, LastUserInteractionTimeUpdateEvent events won't be fired based on motion input, and LastInteractionTime won't be updated\n")
|
|
TEXT("Some motion devices report small tiny changes constantly without filtering, so motion input is unhelpful for determining user activity"));
|
|
|
|
static bool GSlateInputPointerUpFiresPointerMoveForDragDrop = true;
|
|
static FAutoConsoleVariableRef CVarSlateInputPointerUpFiresPointerMoveForDragDrop(
|
|
TEXT("Slate.Input.PointerUpFiresPointerMoveForDragDrop"),
|
|
GSlateInputPointerUpFiresPointerMoveForDragDrop,
|
|
TEXT("When true, a synthetic pointer move event is fired from pointer up to ensure drag events are called if necessary on any widgets before OnDrop."));
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
bool GSlateEnableGamepadEditorNavigation = true;
|
|
static FAutoConsoleVariableRef CVarSlateEnableGamepadEditorNavigation(
|
|
TEXT("Slate.EnableGamepadEditorNavigation"),
|
|
GSlateEnableGamepadEditorNavigation,
|
|
TEXT("True implies we allow gamepad navigation outside of the game viewport.")
|
|
);
|
|
|
|
static bool GSlateUseFixedDeltaTime = false;
|
|
static FAutoConsoleVariableRef CVarSlateUseFixedDeltaTime(
|
|
TEXT("Slate.UseFixedDeltaTime"),
|
|
GSlateUseFixedDeltaTime,
|
|
TEXT("True means we use a constant delta time on every widget tick.")
|
|
);
|
|
|
|
static bool GSlateSkipWidgetDrawingInHeadlessMode = true;
|
|
static FAutoConsoleVariableRef CVarSlateSkipWidgetDrawingInHeadlessMode(
|
|
TEXT("Slate.SkipWidgetDrawingInHeadlessMode"),
|
|
GSlateSkipWidgetDrawingInHeadlessMode,
|
|
TEXT("Skip drawing the widgets when running without rendering (e.g. -nullrhi). On by default, disable if there's non-visual logic in widget's Tick function (as this also skips ticking them).")
|
|
);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
/**
|
|
* The cursor given to any additional input-providing SlateUsers beyond the first one (since the first one owns the platform cursor)
|
|
* Or, if we're on a platform without a cursor, User 0 gets one of these too
|
|
*/
|
|
class FFauxSlateCursor : public ICursor
|
|
{
|
|
public:
|
|
FFauxSlateCursor()
|
|
{
|
|
// We don't support any concept of invalid or unset position for pointer events.
|
|
// To avoid collisions with fullscreen or windows or multi-monitor setups with monitors left of primary,
|
|
// we initialize the faux position to something that shouldn't generate overlap with any widgets.
|
|
CurrentPosition = FVector2D(std::numeric_limits<int32>::min(), std::numeric_limits<int32>::min());
|
|
}
|
|
virtual ~FFauxSlateCursor() {}
|
|
virtual void SetTypeShape(EMouseCursor::Type InCursorType, void* CursorHandle) override {}
|
|
|
|
virtual FVector2D GetPosition() const override { return CurrentPosition; }
|
|
virtual EMouseCursor::Type GetType() const override { return CurrentType; }
|
|
|
|
virtual void SetType(const EMouseCursor::Type InNewCursor) override { CurrentType = InNewCursor; }
|
|
virtual void Show(bool bShow) override { bIsVisible = bShow; }
|
|
|
|
virtual void SetPosition(const int32 X, const int32 Y) override
|
|
{
|
|
FVector2D NewPosition(X, Y);
|
|
UpdateCursorClipping(NewPosition);
|
|
|
|
CurrentPosition = NewPosition;
|
|
}
|
|
|
|
virtual void GetSize(int32& Width, int32& Height) const override
|
|
{
|
|
Width = Height = 32;
|
|
}
|
|
|
|
virtual void Lock(const RECT* const Bounds) override
|
|
{
|
|
if (Bounds)
|
|
{
|
|
CursorClipRect.Min.X = Bounds->left;
|
|
CursorClipRect.Min.Y = Bounds->top;
|
|
CursorClipRect.Max.X = Bounds->right - 1;
|
|
CursorClipRect.Max.Y = Bounds->bottom - 1;
|
|
}
|
|
else
|
|
{
|
|
//@todo DanH: Do we want to enforce a limit to the bounds of the screen like the console cursors?
|
|
CursorClipRect = FIntRect();
|
|
}
|
|
|
|
FVector2D Position = GetPosition();
|
|
if (UpdateCursorClipping(Position))
|
|
{
|
|
SetPosition(Position.X, Position.Y);
|
|
}
|
|
}
|
|
|
|
private:
|
|
bool UpdateCursorClipping(FVector2D& CursorPosition)
|
|
{
|
|
bool bAdjusted = false;
|
|
|
|
if (CursorClipRect.Area() > 0)
|
|
{
|
|
if (CursorPosition.X < CursorClipRect.Min.X)
|
|
{
|
|
CursorPosition.X = CursorClipRect.Min.X;
|
|
bAdjusted = true;
|
|
}
|
|
else if (CursorPosition.X > CursorClipRect.Max.X)
|
|
{
|
|
CursorPosition.X = CursorClipRect.Max.X;
|
|
bAdjusted = true;
|
|
}
|
|
|
|
if (CursorPosition.Y < CursorClipRect.Min.Y)
|
|
{
|
|
CursorPosition.Y = CursorClipRect.Min.Y;
|
|
bAdjusted = true;
|
|
}
|
|
else if (CursorPosition.Y > CursorClipRect.Max.Y)
|
|
{
|
|
CursorPosition.Y = CursorClipRect.Max.Y;
|
|
bAdjusted = true;
|
|
}
|
|
}
|
|
|
|
return bAdjusted;
|
|
}
|
|
|
|
bool bIsVisible = false;
|
|
EMouseCursor::Type CurrentType = EMouseCursor::None;
|
|
FVector2D CurrentPosition = FVector2D::ZeroVector;
|
|
FIntRect CursorClipRect;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
class FEventRouter
|
|
{
|
|
|
|
// @todo slate : Remove remaining [&]-style mass captures.
|
|
// @todo slate : Eliminate all ad-hoc uses of SetEventPath()
|
|
|
|
public:
|
|
|
|
class FDirectPolicy
|
|
{
|
|
public:
|
|
FDirectPolicy( const FWidgetAndPointer& InTarget, const FWidgetPath& InRoutingPath )
|
|
: bEventSent(false)
|
|
, RoutingPath(InRoutingPath)
|
|
, WidgetsUnderCursor(&RoutingPath)
|
|
, Target(InTarget)
|
|
{
|
|
}
|
|
|
|
FDirectPolicy(const FWidgetAndPointer& InTarget, const FWidgetPath& InRoutingPath, const FWidgetPath* InWidgetsUnderCursor)
|
|
: bEventSent(false)
|
|
, RoutingPath(InRoutingPath)
|
|
, WidgetsUnderCursor(InWidgetsUnderCursor)
|
|
, Target(InTarget)
|
|
{
|
|
}
|
|
|
|
static FName Name;
|
|
|
|
bool ShouldKeepGoing() const
|
|
{
|
|
return !bEventSent;
|
|
}
|
|
|
|
void Next()
|
|
{
|
|
bEventSent = true;
|
|
}
|
|
|
|
FWidgetAndPointer GetWidget() const
|
|
{
|
|
return Target;
|
|
}
|
|
|
|
const FWidgetPath& GetRoutingPath() const
|
|
{
|
|
return RoutingPath;
|
|
}
|
|
|
|
const FWidgetPath* GetWidgetsUnderCursor() const
|
|
{
|
|
return WidgetsUnderCursor;
|
|
}
|
|
|
|
private:
|
|
bool bEventSent;
|
|
const FWidgetPath& RoutingPath;
|
|
const FWidgetPath* WidgetsUnderCursor;
|
|
const FWidgetAndPointer& Target;
|
|
};
|
|
|
|
class FToLeafmostPolicy
|
|
{
|
|
public:
|
|
FToLeafmostPolicy( const FWidgetPath& InRoutingPath )
|
|
: bEventSent(false)
|
|
, RoutingPath(InRoutingPath)
|
|
{
|
|
}
|
|
|
|
static FName Name;
|
|
|
|
bool ShouldKeepGoing() const
|
|
{
|
|
return !bEventSent && RoutingPath.Widgets.Num() > 0;
|
|
}
|
|
|
|
void Next()
|
|
{
|
|
bEventSent = true;
|
|
}
|
|
|
|
FWidgetAndPointer GetWidget() const
|
|
{
|
|
const int32 WidgetIndex = RoutingPath.Widgets.Num()-1;
|
|
return FWidgetAndPointer(RoutingPath.Widgets[WidgetIndex], RoutingPath.GetVirtualPointerPosition(WidgetIndex));
|
|
}
|
|
|
|
const FWidgetPath& GetRoutingPath() const
|
|
{
|
|
return RoutingPath;
|
|
}
|
|
|
|
const FWidgetPath* GetWidgetsUnderCursor() const
|
|
{
|
|
return &RoutingPath;
|
|
}
|
|
|
|
private:
|
|
bool bEventSent;
|
|
const FWidgetPath& RoutingPath;
|
|
};
|
|
|
|
class FTunnelPolicy
|
|
{
|
|
public:
|
|
FTunnelPolicy( const FWidgetPath& InRoutingPath )
|
|
: WidgetIndex(0)
|
|
, RoutingPath(InRoutingPath)
|
|
{
|
|
}
|
|
|
|
static FName Name;
|
|
|
|
bool ShouldKeepGoing() const
|
|
{
|
|
return WidgetIndex < RoutingPath.Widgets.Num();
|
|
}
|
|
|
|
void Next()
|
|
{
|
|
++WidgetIndex;
|
|
}
|
|
|
|
FWidgetAndPointer GetWidget() const
|
|
{
|
|
return FWidgetAndPointer(RoutingPath.Widgets[WidgetIndex], RoutingPath.GetVirtualPointerPosition(WidgetIndex));
|
|
}
|
|
|
|
const FWidgetPath& GetRoutingPath() const
|
|
{
|
|
return RoutingPath;
|
|
}
|
|
|
|
const FWidgetPath* GetWidgetsUnderCursor() const
|
|
{
|
|
return &RoutingPath;
|
|
}
|
|
|
|
private:
|
|
int32 WidgetIndex;
|
|
const FWidgetPath& RoutingPath;
|
|
};
|
|
|
|
class FBubblePolicy
|
|
{
|
|
public:
|
|
FBubblePolicy( const FWidgetPath& InRoutingPath )
|
|
: WidgetIndex( InRoutingPath.Widgets.Num()-1 )
|
|
, RoutingPath (InRoutingPath)
|
|
{
|
|
}
|
|
|
|
static FName Name;
|
|
|
|
bool ShouldKeepGoing() const
|
|
{
|
|
return WidgetIndex >= 0;
|
|
}
|
|
|
|
void Next()
|
|
{
|
|
--WidgetIndex;
|
|
}
|
|
|
|
FWidgetAndPointer GetWidget() const
|
|
{
|
|
return FWidgetAndPointer(RoutingPath.Widgets[WidgetIndex], RoutingPath.GetVirtualPointerPosition(WidgetIndex));
|
|
}
|
|
|
|
const FWidgetPath& GetRoutingPath() const
|
|
{
|
|
return RoutingPath;
|
|
}
|
|
|
|
const FWidgetPath* GetWidgetsUnderCursor() const
|
|
{
|
|
return &RoutingPath;
|
|
}
|
|
|
|
private:
|
|
int32 WidgetIndex;
|
|
const FWidgetPath& RoutingPath;
|
|
};
|
|
|
|
/**
|
|
* Route an event along a focus path (as opposed to PointerPath)
|
|
*
|
|
* Focus paths are used focus devices.(e.g. Keyboard or Game Pads)
|
|
* Focus paths change when the user navigates focus (e.g. Tab or
|
|
* Shift Tab, clicks on a focusable widget, or navigation with keyboard/game pad.)
|
|
*/
|
|
template< typename RoutingPolicyType, typename FuncType, typename EventType >
|
|
static FReply RouteAlongFocusPath( FSlateApplication* ThisApplication, RoutingPolicyType RoutingPolicy, EventType KeyEventCopy, const FuncType& Lambda, ESlateDebuggingInputEvent DebuggingInputEvent)
|
|
{
|
|
return Route<FReply>(ThisApplication, RoutingPolicy, KeyEventCopy, Lambda, DebuggingInputEvent);
|
|
}
|
|
|
|
/**
|
|
* Route an event based on the Routing Policy.
|
|
*/
|
|
template< typename ReplyType, typename RoutingPolicyType, typename EventType, typename FuncType >
|
|
static ReplyType Route( FSlateApplication* ThisApplication, RoutingPolicyType RoutingPolicy, EventType EventCopy, const FuncType& Lambda, ESlateDebuggingInputEvent DebuggingInputEvent)
|
|
{
|
|
ReplyType Reply = ReplyType::Unhandled();
|
|
const FWidgetPath& RoutingPath = RoutingPolicy.GetRoutingPath();
|
|
const FWidgetPath* WidgetsUnderCursor = RoutingPolicy.GetWidgetsUnderCursor();
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeRouteInputEvent Scope(DebuggingInputEvent, RoutingPolicyType::Name);
|
|
#endif
|
|
|
|
EventCopy.SetEventPath( RoutingPath );
|
|
|
|
for (; !Reply.IsEventHandled() && RoutingPolicy.ShouldKeepGoing(); RoutingPolicy.Next())
|
|
{
|
|
const FWidgetAndPointer& ArrangedWidget = RoutingPolicy.GetWidget();
|
|
|
|
if constexpr (Translate<EventType>::TranslationNeeded())
|
|
{
|
|
const EventType TranslatedEvent = Translate<EventType>::PointerEvent(ArrangedWidget, EventCopy);
|
|
Reply = Lambda(ArrangedWidget, TranslatedEvent).SetHandler(ArrangedWidget.Widget);
|
|
ProcessReply(ThisApplication, RoutingPath, Reply, WidgetsUnderCursor, &TranslatedEvent);
|
|
}
|
|
else
|
|
{
|
|
Reply = Lambda(ArrangedWidget, EventCopy).SetHandler(ArrangedWidget.Widget);
|
|
ProcessReply(ThisApplication, RoutingPath, Reply, WidgetsUnderCursor, &EventCopy);
|
|
}
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FNoReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FInputEvent* )
|
|
{
|
|
}
|
|
|
|
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FCursorReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FInputEvent* )
|
|
{
|
|
}
|
|
|
|
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FInputEvent* PointerEvent )
|
|
{
|
|
Application->ProcessReply(RoutingPath, Reply, WidgetsUnderCursor, nullptr, PointerEvent->GetUserIndex());
|
|
}
|
|
|
|
static void ProcessReply( FSlateApplication* Application, const FWidgetPath& RoutingPath, const FReply& Reply, const FWidgetPath* WidgetsUnderCursor, const FPointerEvent* PointerEvent )
|
|
{
|
|
Application->ProcessReply(RoutingPath, Reply, WidgetsUnderCursor, PointerEvent, PointerEvent->GetUserIndex());
|
|
}
|
|
|
|
template<typename EventType>
|
|
struct Translate
|
|
{
|
|
static constexpr bool TranslationNeeded() { return false; }
|
|
static EventType PointerEvent( const FWidgetAndPointer& InPosition, const EventType& InEvent )
|
|
{
|
|
// Most events do not do any coordinate translation.
|
|
return InEvent;
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
FName FEventRouter::FDirectPolicy::Name = "Direct";
|
|
FName FEventRouter::FToLeafmostPolicy::Name = "ToLeafmost";
|
|
FName FEventRouter::FTunnelPolicy::Name = "Tunnel";
|
|
FName FEventRouter::FBubblePolicy::Name = "Bubble";
|
|
|
|
template<>
|
|
struct FEventRouter::Translate<FPointerEvent>
|
|
{
|
|
static constexpr bool TranslationNeeded() { return true; }
|
|
static FPointerEvent PointerEvent( const FWidgetAndPointer& InPosition, const FPointerEvent& InEvent )
|
|
{
|
|
// Pointer events are translated into the virtual window space. For 3D Widget Components this means
|
|
if ( !InPosition.GetPointerPosition().IsSet() )
|
|
{
|
|
return InEvent;
|
|
}
|
|
else
|
|
{
|
|
return FPointerEvent::MakeTranslatedEvent<FPointerEvent>( InEvent, InPosition.GetPointerPosition().GetValue() );
|
|
}
|
|
}
|
|
};
|
|
|
|
DECLARE_CYCLE_STAT( TEXT("Message Tick Time"), STAT_SlateMessageTick, STATGROUP_Slate );
|
|
DECLARE_CYCLE_STAT( TEXT("Slate App Input"), STAT_SlateApplicationInput, STATGROUP_Slate );
|
|
DECLARE_CYCLE_STAT( TEXT("Total Slate Tick Time"), STAT_SlateTickTime, STATGROUP_Slate );
|
|
DECLARE_CYCLE_STAT( TEXT("Draw Window And Children Time"), STAT_SlateDrawWindowTime, STATGROUP_Slate );
|
|
DECLARE_CYCLE_STAT( TEXT("TickRegisteredWidgets"), STAT_SlateTickRegisteredWidgets, STATGROUP_Slate );
|
|
DECLARE_CYCLE_STAT( TEXT("PreTickEvent"), STAT_SlatePreTickEvent, STATGROUP_Slate );
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("ShowVirtualKeyboard"), STAT_ShowVirtualKeyboard, STATGROUP_Slate);
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessKeyDown"), STAT_ProcessKeyDown, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessKeyUp"), STAT_ProcessKeyUp, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessKeyChar"), STAT_ProcessKeyChar, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessKeyChar (route focus)"), STAT_ProcessKeyChar_RouteAlongFocusPath, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessKeyChar (call OnKeyChar)"), STAT_ProcessKeyChar_Call_OnKeyChar, STATGROUP_Slate);
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessAnalogInput"), STAT_ProcessAnalogInput, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessMouseButtonDown"), STAT_ProcessMouseButtonDown, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessMouseButtonDoubleClick"), STAT_ProcessMouseButtonDoubleClick, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessMouseButtonUp"), STAT_ProcessMouseButtonUp, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessMouseWheelGesture"), STAT_ProcessMouseWheelGesture, STATGROUP_Slate);
|
|
DECLARE_CYCLE_STAT(TEXT("ProcessMouseMove"), STAT_ProcessMouseMove, STATGROUP_Slate);
|
|
|
|
|
|
namespace SlateDefs
|
|
{
|
|
// How far tool tips should be offset from the mouse cursor position, in pixels
|
|
static const FVector2f ToolTipOffsetFromMouse( 12.0f, 8.0f );
|
|
|
|
// How far tool tips should be pushed out from a force field border, in pixels
|
|
static const FVector2f ToolTipOffsetFromForceField( 4.0f, 3.0f );
|
|
|
|
// Empty set of Touch Key
|
|
static TSet<FKey> EmptyTouchKeySet;
|
|
}
|
|
|
|
|
|
/** True if we should allow throttling based on mouse movement activity. int32 instead of bool only for console variable system. */
|
|
TAutoConsoleVariable<int32> ThrottleWhenMouseIsMoving(
|
|
TEXT( "Slate.ThrottleWhenMouseIsMoving" ),
|
|
false,
|
|
TEXT( "Whether to attempt to increase UI responsiveness based on mouse cursor movement." ) );
|
|
|
|
/** Minimum sustained average frame rate required before we consider the editor to be "responsive" for a smooth UI experience */
|
|
TAutoConsoleVariable<int32> TargetFrameRateForResponsiveness(
|
|
TEXT( "Slate.TargetFrameRateForResponsiveness" ),
|
|
35, // Frames per second
|
|
TEXT( "Minimum sustained average frame rate required before we consider the editor to be \"responsive\" for a smooth UI experience" ) );
|
|
|
|
/** Whether Slate should go to sleep when there are no active timers and the user is idle */
|
|
TAutoConsoleVariable<int32> AllowSlateToSleep(
|
|
TEXT("Slate.AllowSlateToSleep"),
|
|
GIsEditor, // Default to on for editor and standalone programs, off for games
|
|
TEXT("Whether Slate should go to sleep when there are no active timers and the user is idle"));
|
|
|
|
/** The amount of time that must pass without any user action before Slate is put to sleep (provided that there are no active timers). */
|
|
TAutoConsoleVariable<float> SleepBufferPostInput(
|
|
TEXT("Slate.SleepBufferPostInput"),
|
|
0.0f,
|
|
TEXT("The amount of time that must pass without any user action before Slate is put to sleep (provided that there are no active timers)."));
|
|
|
|
static bool bRequireFocusForGamepadInput = false;
|
|
FAutoConsoleVariableRef CVarRequireFocusForGamepadInput(
|
|
TEXT("Slate.RequireFocusForGamepadInput"),
|
|
bRequireFocusForGamepadInput,
|
|
TEXT("Whether gamepad input should be ignored by the engine if the application is not currently active")
|
|
);
|
|
|
|
static bool TransformFullscreenMouseInput = true;
|
|
FAutoConsoleVariableRef CVarSlateTransformFullscreenMouseInput(
|
|
TEXT("Slate.Transform.FullscreenMouseInput"),
|
|
TransformFullscreenMouseInput,
|
|
TEXT("Set true to transform mouse input to account for viewport stretching at fullscreen resolutions not natively supported by the monitor.")
|
|
);
|
|
|
|
#if PLATFORM_UI_NEEDS_TOOLTIPS
|
|
static bool bEnableTooltips = true;
|
|
#else
|
|
static bool bEnableTooltips = false;
|
|
#endif
|
|
FAutoConsoleVariableRef CVarEnableTooltips(
|
|
TEXT("Slate.EnableTooltips"),
|
|
bEnableTooltips,
|
|
TEXT("Whether to allow tooltips to spawn at all."));
|
|
|
|
#if !UE_BUILD_SHIPPING
|
|
|
|
static void HandleGlobalInvalidateCVarTriggered(const TArray<FString>& Args)
|
|
{
|
|
FSlateApplication::Get().InvalidateAllWidgets(true);
|
|
}
|
|
|
|
static FAutoConsoleCommand GlobalInvalidateCommand(
|
|
TEXT("Slate.TriggerInvalidate"),
|
|
TEXT("Triggers a global invalidate of all widgets"),
|
|
FConsoleCommandWithArgsDelegate::CreateStatic(&HandleGlobalInvalidateCVarTriggered)
|
|
);
|
|
#endif
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
FDelegateHandle FPopupSupport::RegisterClickNotification( const TSharedRef<SWidget>& NotifyWhenClickedOutsideMe, const FOnClickedOutside& InNotification )
|
|
{
|
|
// If the subscriber or a zone object is destroyed, the subscription is
|
|
// no longer active. Clean it up here so that consumers of this API have an
|
|
// easy time with resource management.
|
|
struct { void operator()( TArray<FClickSubscriber>& Notifications ) {
|
|
for ( int32 SubscriberIndex=0; SubscriberIndex < Notifications.Num(); )
|
|
{
|
|
if ( !Notifications[SubscriberIndex].ShouldKeep() )
|
|
{
|
|
Notifications.RemoveAtSwap(SubscriberIndex);
|
|
}
|
|
else
|
|
{
|
|
SubscriberIndex++;
|
|
}
|
|
}
|
|
}} ClearOutStaleNotifications;
|
|
|
|
ClearOutStaleNotifications( ClickZoneNotifications );
|
|
|
|
// Add a new notification.
|
|
ClickZoneNotifications.Add( FClickSubscriber( NotifyWhenClickedOutsideMe, InNotification ) );
|
|
|
|
return ClickZoneNotifications.Last().Notification.GetHandle();
|
|
}
|
|
|
|
void FPopupSupport::UnregisterClickNotification( FDelegateHandle Handle )
|
|
{
|
|
for (int32 SubscriptionIndex=0; SubscriptionIndex < ClickZoneNotifications.Num();)
|
|
{
|
|
if (ClickZoneNotifications[SubscriptionIndex].Notification.GetHandle() == Handle)
|
|
{
|
|
ClickZoneNotifications.RemoveAtSwap(SubscriptionIndex);
|
|
}
|
|
else
|
|
{
|
|
SubscriptionIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPopupSupport::SendNotifications( const FWidgetPath& WidgetsUnderCursor )
|
|
{
|
|
struct FArrangedWidgetMatcher
|
|
{
|
|
FArrangedWidgetMatcher( const TSharedRef<SWidget>& InWidgetToMatch )
|
|
: WidgetToMatch( InWidgetToMatch )
|
|
{}
|
|
|
|
bool operator()(const FArrangedWidget& Candidate) const
|
|
{
|
|
return WidgetToMatch == Candidate.Widget;
|
|
}
|
|
|
|
const TSharedRef<SWidget>& WidgetToMatch;
|
|
};
|
|
|
|
// For each subscription, if the widget in question is not being clicked, send the notification.
|
|
// i.e. Notifications are saying "some widget outside you was clicked".
|
|
for (int32 SubscriberIndex=0; SubscriberIndex < ClickZoneNotifications.Num(); ++SubscriberIndex)
|
|
{
|
|
FClickSubscriber& Subscriber = ClickZoneNotifications[SubscriberIndex];
|
|
if (Subscriber.DetectClicksOutsideMe.IsValid())
|
|
{
|
|
// Did we click outside the region in this subscription? If so send the notification.
|
|
FArrangedWidgetMatcher Matcher(Subscriber.DetectClicksOutsideMe.Pin().ToSharedRef());
|
|
const bool bClickedOutsideOfWidget = WidgetsUnderCursor.Widgets.GetInternalArray().IndexOfByPredicate(Matcher) == INDEX_NONE;
|
|
if ( bClickedOutsideOfWidget )
|
|
{
|
|
Subscriber.Notification.ExecuteIfBound();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
void FSlateApplication::OverridePlatformApplication(TSharedPtr<class GenericApplication> InPlatformApplication)
|
|
{
|
|
PlatformApplication = InPlatformApplication;
|
|
|
|
if (PlatformApplication && PlatformApplication->Cursor)
|
|
{
|
|
// Update the Slate user who was using the platform application cursor so
|
|
// they are now using the overridden platform application cursor.
|
|
GetCursorUser()->OverrideCursor(PlatformApplication->Cursor);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("OverridePlatformApplication: Cannot override cursor with invalid value"));
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::Create()
|
|
{
|
|
GSlateFastWidgetPath = GIsEditor ? false : true;
|
|
|
|
Create(MakeShareable(FPlatformApplicationMisc::CreateApplication()));
|
|
}
|
|
|
|
TSharedRef<FSlateApplication> FSlateApplication::Create(const TSharedRef<class GenericApplication>& InPlatformApplication)
|
|
{
|
|
EKeys::Initialize();
|
|
|
|
InitializeCoreStyle();
|
|
|
|
|
|
// Note: Important to establish the static PlatformApplication property first, as the FSlateApplication ctor relies on it
|
|
PlatformApplication = InPlatformApplication;
|
|
|
|
CurrentApplication = MakeShareable( new FSlateApplication() );
|
|
CurrentBaseApplication = CurrentApplication;
|
|
|
|
UE_TRACE_SLATE_APPLICATION_REGISTER_TRACE_EVENTS(*CurrentApplication);
|
|
|
|
PlatformApplication->SetMessageHandler( CurrentApplication.ToSharedRef() );
|
|
#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 MakeShared<FSlateAsyncTaskNotificationImpl>(); });
|
|
|
|
return CurrentApplication.ToSharedRef();
|
|
}
|
|
|
|
void FSlateApplication::Shutdown(bool bShutdownPlatform)
|
|
{
|
|
if (FSlateApplication::IsInitialized())
|
|
{
|
|
CurrentApplication->OnPreShutdown().Broadcast();
|
|
|
|
FAsyncTaskNotificationFactory::Get().UnregisterFactory(TEXT("Slate"));
|
|
|
|
CurrentApplication->OnShutdown();
|
|
CurrentApplication->DestroyRenderer();
|
|
CurrentApplication->Renderer.Reset();
|
|
|
|
if (bShutdownPlatform)
|
|
{
|
|
PlatformApplication->DestroyApplication();
|
|
}
|
|
|
|
PlatformApplication.Reset();
|
|
CurrentApplication.Reset();
|
|
CurrentBaseApplication.Reset();
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FSlateApplication> FSlateApplication::CurrentApplication = nullptr;
|
|
double FSlateApplication::FixedDeltaTime = 1 / 60.0;
|
|
|
|
FSlateApplication::FSlateApplication()
|
|
: bAppIsActive(true)
|
|
, bSlateWindowActive(true)
|
|
, Scale( 1.0f )
|
|
, DragTriggerDistance( 0 )
|
|
, CursorRadius( 0.0f )
|
|
, LastUserInteractionTime( 0.0 )
|
|
, LastUserInteractionTimeForThrottling( 0.0 )
|
|
, LastMouseMoveTime( 0.0 )
|
|
, SlateSoundDevice( MakeShareable(new FNullSlateSoundDevice()) )
|
|
, CurrentTime( FPlatformTime::Seconds() )
|
|
, LastTickTime( 0.0 )
|
|
, AverageDeltaTime( 1.0f / 30.0f ) // Prime the running average with a typical frame rate so it doesn't have to spin up from zero
|
|
, AverageDeltaTimeForResponsiveness( 1.0f / 30.0f )
|
|
, OnExitRequested()
|
|
, NumExternalModalWindowsActive( 0 )
|
|
, RootStyleNode(nullptr)
|
|
, bRequestLeaveDebugMode( false )
|
|
, bLeaveDebugForSingleStep( false )
|
|
, bIsExternalUIOpened( false )
|
|
, bIsFakingTouch(FParse::Param(FCommandLine::Get(), TEXT("simmobile")) || FParse::Param(FCommandLine::Get(), TEXT("faketouches")))
|
|
, bIsGameFakingTouch( false )
|
|
, bIsFakingTouched( false )
|
|
, bAllowFakingTouch( true )
|
|
, bHandleDeviceInputWhenApplicationNotActive(false)
|
|
, bTouchFallbackToMouse( true )
|
|
, bSoftwareCursorAvailable( false )
|
|
, bMenuAnimationsEnabled( false )
|
|
, AppIcon(nullptr)
|
|
, VirtualDesktopRect( 0,0,0,0 )
|
|
, NavigationConfig(MakeShared<FNavigationConfig>())
|
|
#if WITH_EDITOR
|
|
, EditorNavigationConfig(MakeShared<FNavigationConfig>())
|
|
#endif
|
|
, SimulateGestures(false, (int32)EGestureEvent::Count)
|
|
, ProcessingInput(0)
|
|
, InputManager(MakeShared<FSlateDefaultInputMapping>())
|
|
{
|
|
#if WITH_UNREAL_DEVELOPER_TOOLS
|
|
FModuleManager::Get().LoadModule(TEXT("Settings"));
|
|
#endif
|
|
|
|
// If we are embedded inside another app then we never need to be "active"
|
|
bAppIsActive = !GUELibraryOverrideSettings.bIsEmbedded;
|
|
|
|
SetupPhysicalSensitivities();
|
|
|
|
if (GConfig)
|
|
{
|
|
GConfig->GetBool(TEXT("MobileSlateUI"), TEXT("bTouchFallbackToMouse"), bTouchFallbackToMouse, GEngineIni);
|
|
GConfig->GetBool(TEXT("CursorControl"), TEXT("bAllowSoftwareCursor"), bSoftwareCursorAvailable, GEngineIni);
|
|
}
|
|
|
|
bRenderOffScreen = FParse::Param(FCommandLine::Get(), TEXT("RenderOffScreen"));
|
|
|
|
// causes InputCore to initialize, even if statically linked
|
|
FInputCoreModule& InputCore = FModuleManager::LoadModuleChecked<FInputCoreModule>(TEXT("InputCore"));
|
|
|
|
FGenericCommands::Register();
|
|
FTabCommands::Register();
|
|
|
|
NormalExecutionGetter.BindRaw( this, &FSlateApplication::IsNormalExecution );
|
|
|
|
// Add the standard 'default' user (there's always guaranteed to be at least one)
|
|
// The default cursor platform user id the primary platform user
|
|
ensure(SlateAppPrimaryPlatformUser.IsValid() && SlateAppPrimaryPlatformUser == IPlatformInputDeviceMapper::Get().GetPrimaryPlatformUser());
|
|
RegisterNewUser(SlateAppPrimaryPlatformUser);
|
|
|
|
NavigationConfig->OnRegister();
|
|
#if WITH_EDITOR
|
|
EditorNavigationConfig->bIgnoreModifiersForNavigationActions = false;
|
|
EditorNavigationConfig->OnRegister();
|
|
#endif
|
|
|
|
SimulateGestures[(int32)EGestureEvent::LongPress] = true;
|
|
|
|
#if WITH_EDITOR
|
|
FCoreDelegates::OnSafeFrameChangedEvent.AddRaw(this, &FSlateApplication::SwapSafeZoneTypes);
|
|
OnDebugSafeZoneChanged.AddRaw(this, &FSlateApplication::UpdateCustomSafeZone);
|
|
|
|
MenuStack.OnMenuDestroyedEvent().AddRaw(this, &FSlateApplication::OnMenuDestroyed);
|
|
#endif
|
|
|
|
IConsoleVariable* CVarGlobalInvalidation = IConsoleManager::Get().FindConsoleVariable(TEXT("Slate.EnableGlobalInvalidation"));
|
|
if (CVarGlobalInvalidation)
|
|
{
|
|
CVarGlobalInvalidation->SetOnChangedCallback(FConsoleVariableDelegate::CreateLambda([this](IConsoleVariable* Variable)
|
|
{
|
|
UE_TRACE_SLATE_BOOKMARK(TEXT("GlobalInvalidationChanged"));
|
|
OnGlobalInvalidationToggledEvent.Broadcast(GSlateEnableGlobalInvalidation);
|
|
}));
|
|
}
|
|
}
|
|
|
|
FSlateApplication::~FSlateApplication()
|
|
{
|
|
FTabCommands::Unregister();
|
|
FGenericCommands::Unregister();
|
|
|
|
#if WITH_EDITOR
|
|
OnDebugSafeZoneChanged.RemoveAll(this);
|
|
MenuStack.OnMenuDestroyedEvent().RemoveAll(this);
|
|
#endif
|
|
|
|
IConsoleVariable* CVarGlobalInvalidation = IConsoleManager::Get().FindConsoleVariable(TEXT("Slate.EnableGlobalInvalidation"));
|
|
if (CVarGlobalInvalidation)
|
|
{
|
|
CVarGlobalInvalidation->SetOnChangedCallback(FConsoleVariableDelegate());
|
|
}
|
|
|
|
}
|
|
|
|
void FSlateApplication::SetupPhysicalSensitivities()
|
|
{
|
|
const float DragTriggerDistanceInInches = FUnitConversion::Convert(1.0f, EUnit::Millimeters, EUnit::Inches);
|
|
FPlatformApplicationMisc::ConvertInchesToPixels(DragTriggerDistanceInInches, DragTriggerDistance);
|
|
|
|
// TODO Rather than allow people to request the DragTriggerDistance directly, we should
|
|
// probably store a drag trigger distance for touch and mouse, and force users to pass
|
|
// the pointer event they're checking for, and if that pointer event is touch, use touch
|
|
// and if not touch, return mouse.
|
|
#if PLATFORM_DESKTOP
|
|
DragTriggerDistance = FMath::Max(DragTriggerDistance, 5.0f);
|
|
#else
|
|
DragTriggerDistance = FMath::Max(DragTriggerDistance, 10.0f);
|
|
#endif
|
|
|
|
FGestureDetector::LongPressAllowedMovement = DragTriggerDistance;
|
|
}
|
|
|
|
void FSlateApplication::InitHighDPI(const bool bForceEnable)
|
|
{
|
|
IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(TEXT("EnableHighDPIAwareness"));
|
|
|
|
if (CVar)
|
|
{
|
|
bool bRequestEnableHighDPI = true;
|
|
if (GIsEditor)
|
|
{
|
|
GConfig->GetBool(TEXT("HDPI"), TEXT("EnableHighDPIAwareness"), bRequestEnableHighDPI, GEditorSettingsIni);
|
|
}
|
|
else
|
|
{
|
|
GConfig->GetBool(TEXT("/Script/Engine.UserInterfaceSettings"), TEXT("bAllowHighDPIInGameMode"), bRequestEnableHighDPI, GEngineIni);
|
|
}
|
|
|
|
bool bEnableHighDPI = bRequestEnableHighDPI && !FParse::Param(FCommandLine::Get(), TEXT("nohighdpi"));
|
|
bEnableHighDPI |= bForceEnable;
|
|
|
|
// Set the cvar here for other systems that need it.
|
|
CVar->Set(bEnableHighDPI);
|
|
|
|
// High DPI must be enabled before any windows are shown.
|
|
if (bEnableHighDPI)
|
|
{
|
|
FPlatformApplicationMisc::SetHighDPIMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
const FStyleNode* FSlateApplication::GetRootStyle() const
|
|
{
|
|
return RootStyleNode;
|
|
}
|
|
|
|
bool FSlateApplication::InitializeRenderer( TSharedRef<FSlateRenderer> InRenderer, bool bQuietMode )
|
|
{
|
|
Renderer = InRenderer;
|
|
bool bResult = Renderer->Initialize();
|
|
if (!bResult && !bQuietMode)
|
|
{
|
|
FPlatformMisc::MessageBoxExt(EAppMsgType::Ok, *NSLOCTEXT("SlateD3DRenderer", "ProblemWithGraphicsCard", "There is a problem with your graphics card. Please ensure your card meets the minimum system requirements and that you have the latest drivers installed.").ToString(), *NSLOCTEXT("SlateD3DRenderer", "UnsupportedVideoCardErrorTitle", "Unsupported Graphics Card").ToString());
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
void FSlateApplication::InitializeSound( const TSharedRef<ISlateSoundDevice>& InSlateSoundDevice )
|
|
{
|
|
SlateSoundDevice = InSlateSoundDevice;
|
|
}
|
|
|
|
void FSlateApplication::DestroyRenderer()
|
|
{
|
|
if( Renderer.IsValid() )
|
|
{
|
|
Renderer->Destroy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the user closes the outermost frame (i.e. quitting the app). Uses standard UE global variable
|
|
* so normal UE applications work as expected
|
|
*/
|
|
static void OnRequestExit()
|
|
{
|
|
RequestEngineExit(TEXT("Normal Slate Window Closed"));
|
|
}
|
|
|
|
void FSlateApplication::PlaySound( const FSlateSound& SoundToPlay, int32 UserIndex ) const
|
|
{
|
|
SlateSoundDevice->PlaySound(SoundToPlay, UserIndex);
|
|
}
|
|
|
|
float FSlateApplication::GetSoundDuration(const FSlateSound& Sound) const
|
|
{
|
|
return SlateSoundDevice->GetSoundDuration(Sound);
|
|
}
|
|
|
|
UE::Slate::FDeprecateVector2DResult FSlateApplication::GetCursorPos() const
|
|
{
|
|
return GetCursorUser()->GetCursorPosition();
|
|
}
|
|
|
|
UE::Slate::FDeprecateVector2DResult FSlateApplication::GetLastCursorPos() const
|
|
{
|
|
return GetCursorUser()->GetPreviousCursorPosition();
|
|
}
|
|
|
|
void FSlateApplication::SetCursorPos(const FVector2D& MouseCoordinate)
|
|
{
|
|
GetCursorUser()->SetCursorPosition(UE::Slate::CastToVector2f(MouseCoordinate));
|
|
}
|
|
|
|
void FSlateApplication::OverridePlatformTextField(TUniquePtr<IPlatformTextField> PlatformTextField)
|
|
{
|
|
SlateTextField = MoveTemp(PlatformTextField);
|
|
}
|
|
|
|
void FSlateApplication::UsePlatformCursorForCursorUser(bool bUsePlatformCursor)
|
|
{
|
|
if (TSharedPtr<FSlateUser> SlateUser = GetUser(CursorUserIndex))
|
|
{
|
|
const bool bIsUsingPlatformCursor = SlateUser->GetCursor() == PlatformApplication->Cursor;
|
|
|
|
if (bIsUsingPlatformCursor != bUsePlatformCursor)
|
|
{
|
|
if (PlatformApplication && PlatformApplication->Cursor)
|
|
{
|
|
PlatformMouseMovementEvents = 0;
|
|
SlateUser->OverrideCursor(bUsePlatformCursor ? PlatformApplication->Cursor : MakeShared<FFauxSlateCursor>());
|
|
UE_LOG(LogSlate, Log, TEXT("User[%d] UsePlatformCursorForCursorUser(%s)"), SlateUser->GetUserIndex(), bUsePlatformCursor ? TEXT("true") : TEXT("false"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SetPlatformCursorVisibility(bool bNewVisibility)
|
|
{
|
|
if (PlatformApplication && PlatformApplication->Cursor)
|
|
{
|
|
PlatformApplication->Cursor->SetType(bNewVisibility ? EMouseCursor::Default : EMouseCursor::None);
|
|
}
|
|
}
|
|
|
|
FWidgetPath FSlateApplication::LocateWindowUnderMouse( UE::Slate::FDeprecateVector2DParameter ScreenspaceMouseCoordinate, const TArray< TSharedRef< SWindow > >& Windows, bool bIgnoreEnabledStatus, int32 UserIndex)
|
|
{
|
|
// First, give the OS a chance to tell us which window to use, in case a child window is not guaranteed to stay on top of its parent window
|
|
TSharedPtr<FGenericWindow> NativeWindowUnderMouse = PlatformApplication->GetWindowUnderCursor();
|
|
if (NativeWindowUnderMouse.IsValid())
|
|
{
|
|
TSharedPtr<SWindow> Window = FSlateWindowHelper::FindWindowByPlatformWindow(Windows, NativeWindowUnderMouse.ToSharedRef());
|
|
if (Window.IsValid())
|
|
{
|
|
return LocateWidgetInWindow(ScreenspaceMouseCoordinate, Window.ToSharedRef(), bIgnoreEnabledStatus, UserIndex);
|
|
}
|
|
}
|
|
|
|
bool bPrevWindowWasModal = false;
|
|
|
|
for (int32 WindowIndex = Windows.Num() - 1; WindowIndex >= 0; --WindowIndex)
|
|
{
|
|
const TSharedRef<SWindow>& Window = Windows[WindowIndex];
|
|
|
|
if ( !Window->IsVisible() || Window->IsWindowMinimized())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Hittest the window's children first.
|
|
FWidgetPath ResultingPath = LocateWindowUnderMouse(ScreenspaceMouseCoordinate, Window->GetChildWindows(), bIgnoreEnabledStatus, UserIndex);
|
|
if (ResultingPath.IsValid())
|
|
{
|
|
return ResultingPath;
|
|
}
|
|
|
|
// If none of the children were hit, hittest the parent.
|
|
|
|
// Only accept input if the current window accepts input and the current window is not under a modal window or an interactive tooltip
|
|
|
|
if (!bPrevWindowWasModal)
|
|
{
|
|
FWidgetPath PathToLocatedWidget = LocateWidgetInWindow(ScreenspaceMouseCoordinate, Window, bIgnoreEnabledStatus, UserIndex);
|
|
if (PathToLocatedWidget.IsValid())
|
|
{
|
|
return PathToLocatedWidget;
|
|
}
|
|
}
|
|
}
|
|
|
|
return FWidgetPath();
|
|
}
|
|
|
|
bool FSlateApplication::IsWindowHousingInteractiveTooltip(const TSharedRef<const SWindow>& WindowToTest) const
|
|
{
|
|
for (TSharedPtr<const FSlateUser> User : Users)
|
|
{
|
|
if (User && User->IsWindowHousingInteractiveTooltip(WindowToTest))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FSlateApplication::DrawWindows()
|
|
{
|
|
SCOPED_NAMED_EVENT_TEXT("Slate::DrawWindows", FColor::Magenta);
|
|
PrivateDrawWindows();
|
|
}
|
|
|
|
struct FDrawWindowArgs
|
|
{
|
|
FDrawWindowArgs( FSlateDrawBuffer& InDrawBuffer, const FWidgetPath& InWidgetsToVisualizeUnderCursor )
|
|
: OutDrawBuffer( InDrawBuffer )
|
|
, WidgetsToVisualizeUnderCursor(InWidgetsToVisualizeUnderCursor)
|
|
{}
|
|
|
|
FSlateDrawBuffer& OutDrawBuffer;
|
|
const FWidgetPath& WidgetsToVisualizeUnderCursor;
|
|
};
|
|
|
|
|
|
void FSlateApplication::DrawWindowAndChildren( const TSharedRef<SWindow>& WindowToDraw, FDrawWindowArgs& DrawWindowArgs )
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
if (GSlateVerifyParentChildrenRelationship)
|
|
{
|
|
UE::Slate::Private::VerifyParentChildrenRelationship(WindowToDraw);
|
|
}
|
|
#endif
|
|
|
|
// Skip Draw if we are debugging that window.
|
|
if (TSharedPtr<SWindow> CurrentDebuggingWindowPinned = CurrentDebuggingWindow.Pin())
|
|
{
|
|
if (CurrentDebuggingWindowPinned == WindowToDraw)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// On Mac, where child windows can be on screen even if their parent is hidden or minimized, we want to always draw child windows.
|
|
// On other platforms we set bDrawChildWindows to true only if we draw the current window.
|
|
bool bDrawChildWindows = PLATFORM_MAC;
|
|
|
|
// Only draw visible windows or in off-screen rendering mode
|
|
if (bRenderOffScreen || (WindowToDraw->IsVisible() && (!WindowToDraw->IsWindowMinimized() || FApp::UseVRFocus())) )
|
|
{
|
|
TGuardValue TmpContext(CurrentDebugContextWidget, TWeakPtr<SWidget>(WindowToDraw));
|
|
// Switch to the appropriate world for drawing
|
|
FScopedSwitchWorldHack SwitchWorld( WindowToDraw );
|
|
|
|
// Draw Prep
|
|
FSlateWindowElementList& WindowElementList = DrawWindowArgs.OutDrawBuffer.AddWindowElementList(WindowToDraw);
|
|
|
|
// Drawing is done in window space, so null out the positions and keep the size.
|
|
int32 MaxLayerId = 0;
|
|
{
|
|
{
|
|
SCOPED_NAMED_EVENT_TEXT("Slate::DrawWindow", FColor::Magenta);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BeginWindow.Broadcast(WindowElementList);
|
|
#endif
|
|
MaxLayerId = WindowToDraw->PaintWindow(
|
|
GetCurrentTime(),
|
|
GetDeltaTime(),
|
|
WindowElementList,
|
|
FWidgetStyle(),
|
|
WindowToDraw->IsEnabled());
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::EndWindow.Broadcast(WindowElementList);
|
|
#endif
|
|
}
|
|
|
|
// Draw windowless drag drop operations
|
|
ForEachUser([&WindowToDraw, &WindowElementList, &MaxLayerId](FSlateUser& User) {
|
|
User.DrawWindowlessDragDropContent(WindowToDraw, WindowElementList, MaxLayerId);
|
|
});
|
|
|
|
// Draw Software Cursors
|
|
ForEachUser([&WindowToDraw, &WindowElementList, &MaxLayerId](FSlateUser& User) {
|
|
User.DrawCursor(WindowToDraw, WindowElementList, MaxLayerId);
|
|
});
|
|
}
|
|
|
|
#if SLATE_HAS_WIDGET_REFLECTOR
|
|
|
|
// The widget reflector may want to paint some additional stuff as part of the Widget introspection that it performs.
|
|
// For example: it may draw layout rectangles for hovered widgets.
|
|
const bool bVisualizeLayoutUnderCursor = DrawWindowArgs.WidgetsToVisualizeUnderCursor.IsValid();
|
|
const bool bCapturingFromThisWindow = bVisualizeLayoutUnderCursor && DrawWindowArgs.WidgetsToVisualizeUnderCursor.TopLevelWindow == WindowToDraw;
|
|
TSharedPtr<IWidgetReflector> WidgetReflector = WidgetReflectorPtr.Pin();
|
|
if ( bCapturingFromThisWindow || (WidgetReflector.IsValid() && WidgetReflector->ReflectorNeedsToDrawIn(WindowToDraw)) )
|
|
{
|
|
MaxLayerId = WidgetReflector->Visualize( DrawWindowArgs.WidgetsToVisualizeUnderCursor, WindowElementList, MaxLayerId );
|
|
}
|
|
|
|
#endif
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
if (GSlateVerifyWidgetLayerId)
|
|
{
|
|
UE::Slate::Private::VerifyWidgetLayerId(WindowToDraw);
|
|
}
|
|
#endif
|
|
|
|
// This window is visible, so draw its child windows as well
|
|
bDrawChildWindows = true;
|
|
}
|
|
|
|
if (bDrawChildWindows)
|
|
{
|
|
// Draw the child windows
|
|
const TArray< TSharedRef<SWindow> > WindowChildren = WindowToDraw->GetChildWindows();
|
|
for (int32 ChildIndex=0; ChildIndex < WindowChildren.Num(); ++ChildIndex)
|
|
{
|
|
DrawWindowAndChildren( WindowChildren[ChildIndex], DrawWindowArgs );
|
|
}
|
|
}
|
|
}
|
|
|
|
static bool DoAnyWindowDescendantsNeedPrepass(TSharedRef<SWindow> WindowToPrepass)
|
|
{
|
|
TArray<TSharedRef<SWindow>>& ChildWindows = WindowToPrepass->GetChildWindows();
|
|
if (ChildWindows.IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const TSharedRef<SWindow>& ChildWindow = ChildWindows[0];
|
|
if (ChildWindow->IsVisible() && !ChildWindow->IsWindowMinimized())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return DoAnyWindowDescendantsNeedPrepass(ChildWindow);
|
|
}
|
|
}
|
|
|
|
static void PrepassWindowAndChildren(TSharedRef<SWindow> WindowToPrepass, const TSharedPtr<SWindow>& DebuggingWindow, TWeakPtr<SWidget>& CurrentContext)
|
|
{
|
|
// Skip Prepass if we are debugging that window or if we are on the server.
|
|
if (IsRunningDedicatedServer() || WindowToPrepass == DebuggingWindow)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bIsWindowVisible = WindowToPrepass->IsVisible() && !WindowToPrepass->IsWindowMinimized();
|
|
|
|
if (bIsWindowVisible || DoAnyWindowDescendantsNeedPrepass(WindowToPrepass))
|
|
{
|
|
TGuardValue TmpContext(CurrentContext, TWeakPtr<SWidget>(WindowToPrepass));
|
|
FScopedSwitchWorldHack SwitchWorld(WindowToPrepass);
|
|
|
|
{
|
|
WindowToPrepass->ProcessWindowInvalidation();
|
|
WindowToPrepass->SlatePrepass(FSlateApplication::Get().GetApplicationScale() * WindowToPrepass->GetNativeWindow()->GetDPIScaleFactor());
|
|
}
|
|
|
|
if ( bIsWindowVisible && WindowToPrepass->IsAutosized() )
|
|
{
|
|
WindowToPrepass->Resize(WindowToPrepass->GetDesiredSizeDesktopPixels());
|
|
}
|
|
|
|
// Note: Iterate over copy since num children can change during resize above.
|
|
TArray<TSharedRef<SWindow>, FConcurrentLinearArrayAllocator> ChildWindows(WindowToPrepass->GetChildWindows());
|
|
for (const TSharedRef<SWindow>& ChildWindow : ChildWindows)
|
|
{
|
|
PrepassWindowAndChildren(ChildWindow, DebuggingWindow, CurrentContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::DrawPrepass( TSharedPtr<SWindow> DrawOnlyThisWindow )
|
|
{
|
|
SCOPED_NAMED_EVENT_TEXT("Slate::Prepass", FColor::Magenta);
|
|
CSV_SCOPED_TIMING_STAT(Slate, DrawPrePass);
|
|
|
|
TSharedPtr<SWindow> CurrentDebuggingWindowPinned = CurrentDebuggingWindow.Pin();
|
|
|
|
if (TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow())
|
|
{
|
|
PrepassWindowAndChildren(ActiveModalWindow.ToSharedRef(), CurrentDebuggingWindowPinned, CurrentDebugContextWidget);
|
|
|
|
for (TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt(SlateWindows); CurrentWindowIt; ++CurrentWindowIt)
|
|
{
|
|
const TSharedRef<SWindow>& CurrentWindow = *CurrentWindowIt;
|
|
if (CurrentWindow->IsTopmostWindow())
|
|
{
|
|
PrepassWindowAndChildren(CurrentWindow, CurrentDebuggingWindowPinned, CurrentDebugContextWidget);
|
|
}
|
|
}
|
|
|
|
TArray< TSharedRef<SWindow> > NotificationWindows;
|
|
FSlateNotificationManager::Get().GetWindows(NotificationWindows);
|
|
for (auto CurrentWindowIt(NotificationWindows.CreateIterator()); CurrentWindowIt; ++CurrentWindowIt)
|
|
{
|
|
PrepassWindowAndChildren(*CurrentWindowIt, CurrentDebuggingWindowPinned, CurrentDebugContextWidget);
|
|
}
|
|
}
|
|
else if (DrawOnlyThisWindow.IsValid())
|
|
{
|
|
PrepassWindowAndChildren(DrawOnlyThisWindow.ToSharedRef(), CurrentDebuggingWindowPinned, CurrentDebugContextWidget);
|
|
}
|
|
else
|
|
{
|
|
// Draw all windows
|
|
for (const TSharedRef<SWindow>& CurrentWindow : SlateWindows)
|
|
{
|
|
PrepassWindowAndChildren(CurrentWindow, CurrentDebuggingWindowPinned, CurrentDebugContextWidget);
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<SWindow*> GatherAllDescendants(const TArray< TSharedRef<SWindow> >& InWindowList)
|
|
{
|
|
TArray<SWindow*> GatheredDescendants;
|
|
GatheredDescendants.Reserve(InWindowList.Num());
|
|
for (const TSharedRef<SWindow>& Window : InWindowList)
|
|
{
|
|
GatheredDescendants.Add(&Window.Get());
|
|
}
|
|
|
|
for (const TSharedRef<SWindow>& SomeWindow : InWindowList)
|
|
{
|
|
GatheredDescendants.Append(GatherAllDescendants(SomeWindow->GetChildWindows()));
|
|
}
|
|
|
|
return GatheredDescendants;
|
|
}
|
|
|
|
void FSlateApplication::PrivateDrawWindows( TSharedPtr<SWindow> DrawOnlyThisWindow )
|
|
{
|
|
if (GSlateSkipWidgetDrawingInHeadlessMode && !FApp::CanEverRender())
|
|
{
|
|
// early out, as window "drawing" can take 1-2ms of a -nullrhi PC game
|
|
return;
|
|
}
|
|
|
|
check(Renderer.IsValid());
|
|
|
|
// Grab a scope lock around access to the resource proxy map, just to ensure we never cross over
|
|
// with the loading thread.
|
|
FScopeLock ScopeLock(Renderer->GetResourceCriticalSection());
|
|
|
|
FWidgetPath WidgetsToVisualizeUnderCursor;
|
|
|
|
#if SLATE_HAS_WIDGET_REFLECTOR
|
|
// Is user expecting visual feedback from the Widget Reflector?
|
|
if (WidgetReflectorPtr.IsValid() && WidgetReflectorPtr.Pin()->IsVisualizingLayoutUnderCursor())
|
|
{
|
|
WidgetsToVisualizeUnderCursor = GetCursorUser()->GetLastWidgetsUnderCursor().ToWidgetPath();
|
|
}
|
|
#endif
|
|
|
|
// Prepass the window
|
|
DrawPrepass( DrawOnlyThisWindow );
|
|
|
|
{
|
|
FSlateRenderer::FScopedAcquireDrawBuffer ScopedDrawBuffer{ *Renderer };
|
|
FDrawWindowArgs DrawWindowArgs( ScopedDrawBuffer.GetDrawBuffer(), WidgetsToVisualizeUnderCursor);
|
|
ensureMsgf(DrawWindowArgs.OutDrawBuffer.IsLocked(), TEXT("The buffer should be lock by GetDrawBuffer."));
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER( STAT_SlateDrawWindowTime );
|
|
|
|
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
|
|
|
|
if (ActiveModalWindow.IsValid())
|
|
{
|
|
DrawWindowAndChildren( ActiveModalWindow.ToSharedRef(), DrawWindowArgs );
|
|
|
|
for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
|
|
{
|
|
const TSharedRef<SWindow>& CurrentWindow = *CurrentWindowIt;
|
|
if ( CurrentWindow->GetType() == EWindowType::ToolTip )
|
|
{
|
|
DrawWindowAndChildren(CurrentWindow, DrawWindowArgs);
|
|
}
|
|
}
|
|
|
|
TArray< TSharedRef<SWindow> > NotificationWindows;
|
|
FSlateNotificationManager::Get().GetWindows(NotificationWindows);
|
|
for( auto CurrentWindowIt( NotificationWindows.CreateIterator() ); CurrentWindowIt; ++CurrentWindowIt )
|
|
{
|
|
DrawWindowAndChildren(*CurrentWindowIt, DrawWindowArgs);
|
|
}
|
|
}
|
|
else if( DrawOnlyThisWindow.IsValid() )
|
|
{
|
|
DrawWindowAndChildren( DrawOnlyThisWindow.ToSharedRef(), DrawWindowArgs );
|
|
}
|
|
else
|
|
{
|
|
// Draw all windows
|
|
// Use of an old-style iterator is intentional here, as SlateWindows
|
|
// array may be mutated by user logic in draw calls. The iterator
|
|
// prevents us from reading off the end and only keeps an index
|
|
// internally:
|
|
for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
|
|
{
|
|
TSharedRef<SWindow> CurrentWindow = *CurrentWindowIt;
|
|
// Only draw visible windows or in off-screen rendering mode
|
|
if (bRenderOffScreen || CurrentWindow->IsVisible() )
|
|
{
|
|
DrawWindowAndChildren( CurrentWindow, DrawWindowArgs );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is potentially dangerous on the movie playback thread that slate sometimes runs on
|
|
if(!IsInSlateThread())
|
|
{
|
|
// Some windows may have been destroyed/removed.
|
|
// Do not attempt to draw any windows that have been removed.
|
|
TArray<SWindow*> AllWindows = GatherAllDescendants(SlateWindows);
|
|
DrawWindowArgs.OutDrawBuffer.RemoveUnusedWindowElement(AllWindows);
|
|
}
|
|
|
|
Renderer->DrawWindows( DrawWindowArgs.OutDrawBuffer );
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::PollGameDeviceState()
|
|
{
|
|
if( ActiveModalWindows.Num() == 0 && !GIntraFrameDebuggingGameThread && (!bRequireFocusForGamepadInput || IsActive()))
|
|
{
|
|
// Don't poll when a modal window open or intra frame debugging is happening
|
|
PlatformApplication->PollGameDeviceState( GetDeltaTime() );
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::FinishedInputThisFrame()
|
|
{
|
|
const float DeltaTime = GetDeltaTime();
|
|
|
|
PlatformApplication->FinishedInputThisFrame();
|
|
|
|
// Any preprocessors are given a chance to process accumulated values (or do whatever other tick things they want)
|
|
// after we've finished processing all of the input for the frame
|
|
if (PlatformApplication->Cursor.IsValid())
|
|
{
|
|
InputPreProcessors.Tick(DeltaTime, *this, PlatformApplication->Cursor.ToSharedRef());
|
|
}
|
|
|
|
// Any widgets that may have received pointer input events are given a chance to process accumulated values.
|
|
ForEachUser([](FSlateUser& User) {
|
|
if (User.HasAnyCapture())
|
|
{
|
|
for (const TSharedRef<SWidget>& Captor : User.GetCaptorWidgets())
|
|
{
|
|
Captor->OnFinishedPointerInput();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (const auto& IndexPathPair : User.GetWidgetsUnderPointerLastEventByIndex())
|
|
{
|
|
for (const TWeakPtr<SWidget>& WidgetPtr : IndexPathPair.Value.Widgets)
|
|
{
|
|
if (TSharedPtr<SWidget> Widget = WidgetPtr.Pin())
|
|
{
|
|
Widget->OnFinishedPointerInput();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Any widgets that may have received key events are given a chance to process accumulated values.
|
|
ForEachUser([] (FSlateUser& User) {
|
|
const FWeakWidgetPath& WidgetPath = User.GetWeakFocusPath();
|
|
for ( const TWeakPtr<SWidget>& WidgetPtr : WidgetPath.Widgets )
|
|
{
|
|
if (TSharedPtr<SWidget> Widget = WidgetPtr.Pin())
|
|
{
|
|
Widget->OnFinishedKeyInput();
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
ForEachUser([] (FSlateUser& User) { User.FinishFrame(); });
|
|
}
|
|
|
|
static const TCHAR* LexToString(ESlateTickType TickType)
|
|
{
|
|
switch (TickType)
|
|
{
|
|
case ESlateTickType::Time:
|
|
return TEXT("Time");
|
|
case ESlateTickType::PlatformAndInput:
|
|
return TEXT("Platform and Input");
|
|
case ESlateTickType::Widgets:
|
|
return TEXT("Widgets");
|
|
case ESlateTickType::TimeAndWidgets:
|
|
return TEXT("Time and Widgets");
|
|
case ESlateTickType::All:
|
|
default:
|
|
return TEXT("All");
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::Tick(ESlateTickType TickType)
|
|
{
|
|
LLM_SCOPE_BYTAG(UI_Slate);
|
|
|
|
SCOPE_TIME_GUARD(TEXT("FSlateApplication::Tick"));
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(UI);
|
|
|
|
// It is not valid to tick Slate on any other thread but the game thread unless we are only updating time
|
|
check(IsInGameThread() || TickType == ESlateTickType::Time);
|
|
|
|
FScopeLock SlateTickAccess(&SlateTickCriticalSection);
|
|
|
|
TGuardValue<bool> IsTickingGuard(bIsTicking, true);
|
|
|
|
#if WITH_EDITOR
|
|
FScopedPreventDebuggingMode SlatePreventDebugginModeWhileTicking(NSLOCTEXT("EnterDebuggingMode", "WindowTicking", "The window is ticking."));
|
|
#endif
|
|
|
|
SCOPED_NAMED_EVENT_F(TEXT("Slate::Tick (%s)"), FColor::Magenta, LexToString(TickType));
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_SlateTickTime);
|
|
|
|
float DeltaTime = GetDeltaTime();
|
|
|
|
// IMPORTANT
|
|
// Do not add code to these different if-statements, if you need to add additional logic to
|
|
// ticking the platform, do it inside of TickPlatform, for example. These functions are sometimes
|
|
// called directly inside of Slate Application, so unless they're embedded in those calls, they wont
|
|
// get run.
|
|
|
|
if (EnumHasAnyFlags(TickType, ESlateTickType::PlatformAndInput))
|
|
{
|
|
TickPlatform(DeltaTime);
|
|
}
|
|
|
|
if (EnumHasAnyFlags(TickType, ESlateTickType::Time))
|
|
{
|
|
TickTime();
|
|
}
|
|
|
|
if (GSlateUseFixedDeltaTime)
|
|
{
|
|
DeltaTime = GetFixedDeltaTime();
|
|
|
|
}
|
|
|
|
if (EnumHasAnyFlags(TickType, ESlateTickType::Widgets))
|
|
{
|
|
TickAndDrawWidgets(DeltaTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FSlateApplication::IsTicking() const
|
|
{
|
|
return bIsTicking;
|
|
}
|
|
|
|
void FSlateApplication::TickTime()
|
|
{
|
|
LastTickTime = CurrentTime;
|
|
CurrentTime = FPlatformTime::Seconds();
|
|
|
|
// Handle large quantums
|
|
const double MaxQuantumBeforeClamp = 1.0 / 8.0; // 8 FPS
|
|
if (GetDeltaTime() > MaxQuantumBeforeClamp)
|
|
{
|
|
LastTickTime = CurrentTime - MaxQuantumBeforeClamp;
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::TickPlatform(float DeltaTime)
|
|
{
|
|
SCOPED_NAMED_EVENT_TEXT("Slate::TickPlatform", FColor::Magenta);
|
|
CSV_SCOPED_TIMING_STAT(Slate, TickPlatform);
|
|
|
|
#if WITH_ACCESSIBILITY
|
|
{
|
|
// We ensure to only call this in TickType::All to avoid the movie thread also calling this unnecessarily
|
|
GetAccessibleMessageHandler()->ProcessAccessibleTasks();
|
|
}
|
|
#endif
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_SlateMessageTick);
|
|
|
|
// We need to pump messages here so that slate can receive input.
|
|
if ( ( ActiveModalWindows.Num() > 0 ) || GIntraFrameDebuggingGameThread )
|
|
{
|
|
// We only need to pump messages for slate when a modal window or blocking mode is active is up because normally message pumping is handled in FEngineLoop::Tick
|
|
PlatformApplication->PumpMessages(DeltaTime);
|
|
|
|
if ( FCoreDelegates::StarvedGameLoop.IsBound() )
|
|
{
|
|
FCoreDelegates::StarvedGameLoop.Execute();
|
|
}
|
|
}
|
|
|
|
PlatformApplication->Tick(DeltaTime);
|
|
PlatformApplication->ProcessDeferredEvents(DeltaTime);
|
|
}
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_SlateApplicationInput);
|
|
|
|
const bool bCanSpawnNewTooltip = PlatformApplication->IsCursorDirectlyOverSlateWindow();
|
|
ForEachUser([this, bCanSpawnNewTooltip](FSlateUser& User) {
|
|
User.UpdateCursor();
|
|
User.UpdateTooltip(MenuStack, bCanSpawnNewTooltip);
|
|
});
|
|
|
|
bool bSynthesizedCursorMoveThisFrame = false;
|
|
ForEachUser([&bSynthesizedCursorMoveThisFrame](FSlateUser& User) {
|
|
bSynthesizedCursorMoveThisFrame |= User.SynthesizeCursorMoveIfNeeded();
|
|
});
|
|
bSynthesizedCursorMove = bSynthesizedCursorMoveThisFrame;
|
|
|
|
// Generate any simulated gestures that we've detected.
|
|
ForEachUser([this](FSlateUser& User) {
|
|
User.GetGestureDetector().GenerateGestures(*this, SimulateGestures);
|
|
});
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::TickAndDrawWidgets(float DeltaTime)
|
|
{
|
|
if (Renderer.IsValid())
|
|
{
|
|
// Release any temporary material or texture resources we may have cached and are reporting to prevent
|
|
// GC on those resources. We don't need to force it, we just need to let the ones used last frame to
|
|
// be queued up to be released.
|
|
Renderer->ReleaseAccessedResources(/* Flush State */ false);
|
|
}
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_SlatePreTickEvent);
|
|
PreTickEvent.Broadcast(DeltaTime);
|
|
}
|
|
|
|
// Update average time between ticks. This is used to monitor how responsive the application "feels".
|
|
// Note that we calculate this before we apply the max quantum clamping below, because we want to store
|
|
// the actual frame rate, even if it is very low.
|
|
{
|
|
// Scalar percent of new delta time that contributes to running average. Use a lower value to add more smoothing
|
|
// to the average frame rate. A value of 1.0 will disable smoothing.
|
|
const float RunningAverageScale = 0.1f;
|
|
|
|
AverageDeltaTime = AverageDeltaTime * ( 1.0f - RunningAverageScale ) + GetDeltaTime() * RunningAverageScale;
|
|
|
|
// Don't update average delta time if we're in an exceptional situation, such as when throttling mode
|
|
// is active, because the measured tick time will not be representative of the application's performance.
|
|
// In these cases, the cached average delta time from before the throttle activated will be used until
|
|
// throttling has finished.
|
|
if( FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
|
|
{
|
|
// Clamp to avoid including huge hitchy frames in our average
|
|
const float ClampedDeltaTime = FMath::Clamp( GetDeltaTime(), 0.0f, 1.0f );
|
|
AverageDeltaTimeForResponsiveness = AverageDeltaTimeForResponsiveness * ( 1.0f - RunningAverageScale ) + ClampedDeltaTime * RunningAverageScale;
|
|
}
|
|
}
|
|
|
|
{
|
|
// Update auto-throttling based on elapsed time since user interaction
|
|
ThrottleApplicationBasedOnMouseMovement();
|
|
|
|
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
|
|
|
|
const float SleepThreshold = SleepBufferPostInput.GetValueOnGameThread();
|
|
const double TimeSinceInput = LastTickTime - LastUserInteractionTime;
|
|
const double TimeSinceMouseMove = LastTickTime - LastMouseMoveTime;
|
|
|
|
const bool bIsUserIdle = (TimeSinceInput > SleepThreshold) && (TimeSinceMouseMove > SleepThreshold);
|
|
UpdateAnyActiveTimersArePending();
|
|
|
|
// skip tick/draw if we are idle and there are no active timers registered that we need to drive slate for.
|
|
// This effectively means the slate application is totally idle and we don't need to update the UI.
|
|
// This relies on Widgets properly registering for Active timer when they need something to happen even
|
|
// when the user is not providing any input (ie, animations, viewport rendering, async polling, etc).
|
|
bIsSlateAsleep = true;
|
|
if (!AllowSlateToSleep.GetValueOnGameThread() || bAnyActiveTimersPending || !bIsUserIdle || bSynthesizedCursorMove || FApp::UseVRFocus())
|
|
{
|
|
if (!bSynthesizedCursorMove)
|
|
{
|
|
ForEachUser([](FSlateUser& User) { User.QueueSyntheticCursorMove(); });
|
|
}
|
|
|
|
bIsSlateAsleep = false; // if we get here, then Slate is not sleeping
|
|
|
|
// Update any notifications - this needs to be done after windows have updated themselves
|
|
// (so they know their size)
|
|
{
|
|
FSlateNotificationManager::Get().Tick();
|
|
}
|
|
|
|
// Draw all windows
|
|
DrawWindows();
|
|
|
|
#if WITH_ACCESSIBILITY
|
|
AccessibleMessageHandler->Tick();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
{
|
|
// SCOPE_CYCLE_COUNTER(STAT_SlatePostTickEvent);
|
|
PostTickEvent.Broadcast(DeltaTime);
|
|
}
|
|
|
|
#if WITH_ACCESSIBILITY
|
|
{
|
|
// we call this again to improve the responsiveness of accessibility navigation and announcements
|
|
GetAccessibleMessageHandler()->ProcessAccessibleTasks();
|
|
}
|
|
#endif
|
|
|
|
UE_TRACE_SLATE_APPLICATION_TICK_AND_DRAW_WIDGETS(GetDeltaTime());
|
|
}
|
|
|
|
void FSlateApplication::PumpMessages()
|
|
{
|
|
PlatformApplication->PumpMessages( GetDeltaTime() );
|
|
}
|
|
|
|
void FSlateApplication::ThrottleApplicationBasedOnMouseMovement()
|
|
{
|
|
bool bShouldThrottle = false;
|
|
if( ThrottleWhenMouseIsMoving.GetValueOnGameThread() ) // Interpreted as bool here
|
|
{
|
|
// We only want to engage the throttle for a short amount of time after the mouse stops moving
|
|
const float TimeToThrottleAfterMouseStops = 0.1f;
|
|
|
|
// After a key or mouse button is pressed, we'll leave the throttle disengaged for awhile so the
|
|
// user can use the keys to navigate in a viewport, for example.
|
|
const double MinTimeSinceButtonPressToThrottle = 1.0;
|
|
|
|
// Use a small movement threshold to avoid engaging the throttle when the user bumps the mouse
|
|
const float MinMouseMovePixelsBeforeThrottle = 2.0f;
|
|
|
|
const FVector2f& CursorPos = GetCursorPos();
|
|
static FVector2f LastCursorPos = GetCursorPos();
|
|
//static double LastMouseMoveTime = FPlatformTime::Seconds();
|
|
static bool bIsMouseMoving = false;
|
|
if( CursorPos != LastCursorPos )
|
|
{
|
|
// Did the cursor move far enough that we care?
|
|
if( bIsMouseMoving || ( CursorPos - LastCursorPos ).SizeSquared() >= MinMouseMovePixelsBeforeThrottle * MinMouseMovePixelsBeforeThrottle )
|
|
{
|
|
bIsMouseMoving = true;
|
|
LastMouseMoveTime = this->GetCurrentTime();
|
|
LastCursorPos = CursorPos;
|
|
}
|
|
}
|
|
|
|
const double TimeSinceLastUserInteraction = CurrentTime - LastUserInteractionTimeForThrottling;
|
|
const double TimeSinceLastMouseMove = CurrentTime - LastMouseMoveTime;
|
|
if( TimeSinceLastMouseMove < TimeToThrottleAfterMouseStops )
|
|
{
|
|
// Only throttle if a Slate window is currently active.
|
|
if( this->GetActiveTopLevelWindow().IsValid() )
|
|
{
|
|
// Only throttle if the user hasn't pressed a button in awhile
|
|
if( TimeSinceLastUserInteraction > MinTimeSinceButtonPressToThrottle )
|
|
{
|
|
// If a widget has the mouse captured, then we won't bother throttling
|
|
if( !HasAnyMouseCaptor() )
|
|
{
|
|
// If there is no Slate window under the mouse, then we won't engage throttling
|
|
if( LocateWindowUnderMouse( GetCursorPos(), GetInteractiveTopLevelWindows() ).IsValid() )
|
|
{
|
|
bShouldThrottle = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Mouse hasn't moved in a bit, so reset our movement state
|
|
bIsMouseMoving = false;
|
|
LastCursorPos = CursorPos;
|
|
}
|
|
}
|
|
|
|
if( bShouldThrottle )
|
|
{
|
|
if( !UserInteractionResponsivnessThrottle.IsValid() )
|
|
{
|
|
// Engage throttling
|
|
UserInteractionResponsivnessThrottle = FSlateThrottleManager::Get().EnterResponsiveMode();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( UserInteractionResponsivnessThrottle.IsValid() )
|
|
{
|
|
// Disengage throttling
|
|
FSlateThrottleManager::Get().LeaveResponsiveMode( UserInteractionResponsivnessThrottle );
|
|
}
|
|
}
|
|
}
|
|
|
|
FWidgetPath FSlateApplication::LocateWidgetInWindow(UE::Slate::FDeprecateVector2DParameter ScreenspaceMouseCoordinate, const TSharedRef<SWindow>& Window, bool bIgnoreEnabledStatus, int32 UserIndex) const
|
|
{
|
|
const bool bAcceptsInput = Window->IsVisible() && (Window->AcceptsInput() || IsWindowHousingInteractiveTooltip(Window));
|
|
if (bAcceptsInput && Window->IsScreenspaceMouseWithin(ScreenspaceMouseCoordinate))
|
|
{
|
|
FVector2f CursorPosition = ScreenspaceMouseCoordinate;
|
|
|
|
if (TransformFullscreenMouseInput && !GIsEditor && Window->GetWindowMode() == EWindowMode::Fullscreen)
|
|
{
|
|
// Screen space mapping scales everything. When window resolution doesn't match platform resolution,
|
|
// this causes offset cursor hit-tests in fullscreen. Correct in slate since we are first window-aware slate processor.
|
|
FVector2f WindowSize = Window->GetSizeInScreen();
|
|
FVector2f DisplaySize = { (float)CachedDisplayMetrics.PrimaryDisplayWidth, (float)CachedDisplayMetrics.PrimaryDisplayHeight };
|
|
|
|
CursorPosition *= WindowSize / DisplaySize;
|
|
}
|
|
|
|
TArray<FWidgetAndPointer> WidgetsAndCursors = Window->GetHittestGrid().GetBubblePath(CursorPosition, GetCursorRadius(), bIgnoreEnabledStatus, UserIndex);
|
|
return FWidgetPath(MoveTemp(WidgetsAndCursors));
|
|
}
|
|
else
|
|
{
|
|
return FWidgetPath();
|
|
}
|
|
}
|
|
|
|
|
|
TSharedRef<SWindow> FSlateApplication::AddWindow( TSharedRef<SWindow> InSlateWindow, const bool bShowImmediately )
|
|
{
|
|
// Add the Slate window to the Slate application's top-level window array. Note that neither the Slate window
|
|
// or the native window are ready to be used yet, however we need to make sure they're in the Slate window
|
|
// array so that we can properly respond to OS window messages as soon as they're sent. For example, a window
|
|
// activation message may be sent by the OS as soon as the window is shown (in the Init function), and if we
|
|
// don't add the Slate window to our window list, we wouldn't be able to route that message to the window.
|
|
|
|
FSlateWindowHelper::ArrangeWindowToFront(SlateWindows, InSlateWindow);
|
|
TSharedRef<FGenericWindow> NewWindow = MakeWindow( InSlateWindow, bShowImmediately );
|
|
|
|
if( bShowImmediately )
|
|
{
|
|
InSlateWindow->ShowWindow();
|
|
|
|
//@todo Slate: Potentially dangerous and annoying if all slate windows that are created steal focus.
|
|
if( InSlateWindow->SupportsKeyboardFocus() && InSlateWindow->IsFocusedInitially() )
|
|
{
|
|
InSlateWindow->GetNativeWindow()->SetWindowFocus();
|
|
}
|
|
}
|
|
|
|
return InSlateWindow;
|
|
}
|
|
|
|
TSharedRef< FGenericWindow > FSlateApplication::MakeWindow( TSharedRef<SWindow> InSlateWindow, const bool bShowImmediately )
|
|
{
|
|
// When rendering off-screen without the null platform, don't render to screen. Create a dummy generic window instead
|
|
if (bRenderOffScreen && !FNullPlatformApplicationMisc::IsUsingNullApplication())
|
|
{
|
|
TSharedRef< FGenericWindow > NewWindow = MakeShareable(new FGenericWindow());
|
|
InSlateWindow->SetNativeWindow(NewWindow);
|
|
FSlateApplicationBase::Get().GetRenderer()->CreateViewport(InSlateWindow);
|
|
return NewWindow;
|
|
}
|
|
|
|
TSharedPtr<FGenericWindow> NativeParent = nullptr;
|
|
TSharedPtr<SWindow> ParentWindow = InSlateWindow->GetParentWindow();
|
|
if ( ParentWindow.IsValid() )
|
|
{
|
|
NativeParent = ParentWindow->GetNativeWindow();
|
|
}
|
|
|
|
TSharedRef< FGenericWindowDefinition > Definition = MakeShareable( new FGenericWindowDefinition() );
|
|
|
|
Definition->Type = InSlateWindow->GetType();
|
|
|
|
const FVector2f Size = InSlateWindow->GetInitialDesiredSizeInScreen();
|
|
Definition->WidthDesiredOnScreen = Size.X;
|
|
Definition->HeightDesiredOnScreen = Size.Y;
|
|
|
|
const FVector2f Position = InSlateWindow->GetInitialDesiredPositionInScreen();
|
|
Definition->XDesiredPositionOnScreen = Position.X;
|
|
Definition->YDesiredPositionOnScreen = Position.Y;
|
|
|
|
Definition->HasOSWindowBorder = InSlateWindow->HasOSWindowBorder();
|
|
Definition->TransparencySupport = InSlateWindow->GetTransparencySupport();
|
|
Definition->AppearsInTaskbar = InSlateWindow->AppearsInTaskbar();
|
|
Definition->IsTopmostWindow = InSlateWindow->IsTopmostWindow();
|
|
Definition->AcceptsInput = InSlateWindow->AcceptsInput();
|
|
Definition->ActivationPolicy = InSlateWindow->ActivationPolicy();
|
|
Definition->FocusWhenFirstShown = InSlateWindow->IsFocusedInitially();
|
|
|
|
Definition->HasCloseButton = InSlateWindow->HasCloseBox();
|
|
Definition->SupportsMinimize = InSlateWindow->HasMinimizeBox();
|
|
Definition->SupportsMaximize = InSlateWindow->HasMaximizeBox();
|
|
|
|
Definition->IsModalWindow = InSlateWindow->IsModalWindow();
|
|
Definition->IsRegularWindow = InSlateWindow->IsRegularWindow();
|
|
Definition->HasSizingFrame = InSlateWindow->HasSizingFrame();
|
|
Definition->SizeWillChangeOften = InSlateWindow->SizeWillChangeOften();
|
|
Definition->ShouldPreserveAspectRatio = InSlateWindow->ShouldPreserveAspectRatio();
|
|
Definition->ExpectedMaxWidth = InSlateWindow->GetExpectedMaxWidth();
|
|
Definition->ExpectedMaxHeight = InSlateWindow->GetExpectedMaxHeight();
|
|
|
|
Definition->Title = InSlateWindow->GetTitle().ToString();
|
|
Definition->Opacity = InSlateWindow->GetOpacity();
|
|
Definition->CornerRadius = InSlateWindow->GetCornerRadius();
|
|
|
|
Definition->SizeLimits = InSlateWindow->GetSizeLimits();
|
|
|
|
Definition->bManualDPI = InSlateWindow->IsManualManageDPIChanges();
|
|
|
|
TSharedRef< FGenericWindow > NewWindow = PlatformApplication->MakeWindow();
|
|
|
|
if (LIKELY(FApp::CanEverRender()))
|
|
{
|
|
InSlateWindow->SetNativeWindow(NewWindow);
|
|
|
|
InSlateWindow->SetCachedScreenPosition( Position );
|
|
InSlateWindow->SetCachedSize( Size );
|
|
|
|
PlatformApplication->InitializeWindow( NewWindow, Definition, NativeParent, bShowImmediately );
|
|
|
|
ITextInputMethodSystem* const TextInputMethodSystem = PlatformApplication->GetTextInputMethodSystem();
|
|
if ( TextInputMethodSystem )
|
|
{
|
|
TextInputMethodSystem->ApplyDefaults( NewWindow );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
InSlateWindow->SetNativeWindow(MakeShareable(new FGenericWindow()));
|
|
}
|
|
|
|
return NewWindow;
|
|
}
|
|
|
|
bool FSlateApplication::CanAddModalWindow() const
|
|
{
|
|
// A modal window cannot be opened until the renderer has been created.
|
|
return CanDisplayWindows();
|
|
}
|
|
|
|
bool FSlateApplication::CanDisplayWindows() const
|
|
{
|
|
// The renderer must be created and global shaders be available
|
|
return Renderer.IsValid() && Renderer->AreShadersInitialized();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
static bool IsFocusInViewport(const TSet<TWeakPtr<SViewport>> Viewports, const FWeakWidgetPath& FocusPath)
|
|
{
|
|
if (Viewports.Num() > 0)
|
|
{
|
|
for (const TWeakPtr<SWidget>& FocusWidget : FocusPath.Widgets)
|
|
{
|
|
for (const TWeakPtr<SViewport>& Viewport : Viewports)
|
|
{
|
|
if (FocusWidget == Viewport)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
EUINavigation FSlateApplication::GetNavigationDirectionFromKey(const FKeyEvent& InKeyEvent) const
|
|
{
|
|
TSharedRef<FNavigationConfig> RelevantNavConfig = GetRelevantNavConfig(InKeyEvent.GetUserIndex());
|
|
return RelevantNavConfig->GetNavigationDirectionFromKey(InKeyEvent);
|
|
}
|
|
|
|
EUINavigation FSlateApplication::GetNavigationDirectionFromAnalog(const FAnalogInputEvent& InAnalogEvent)
|
|
{
|
|
TSharedRef<FNavigationConfig> RelevantNavConfig = GetRelevantNavConfig(InAnalogEvent.GetUserIndex());
|
|
return RelevantNavConfig->GetNavigationDirectionFromAnalog(InAnalogEvent);
|
|
}
|
|
|
|
EUINavigationAction FSlateApplication::GetNavigationActionFromKey(const FKeyEvent& InKeyEvent) const
|
|
{
|
|
TSharedRef<FNavigationConfig> RelevantNavConfig = GetRelevantNavConfig(InKeyEvent.GetUserIndex());
|
|
return RelevantNavConfig->GetNavigationActionFromKey(InKeyEvent);
|
|
}
|
|
|
|
EUINavigationAction FSlateApplication::GetNavigationActionForKey(const FKey& InKey) const
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
// Not enough info to pick the best config, so this can only use the default
|
|
return NavigationConfig->GetNavigationActionForKey(InKey);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
void FSlateApplication::AddModalWindow( TSharedRef<SWindow> InSlateWindow, const TSharedPtr<const SWidget> InParentWidget, bool bSlowTaskWindow )
|
|
{
|
|
if( !CanAddModalWindow() )
|
|
{
|
|
// Bail out. The incoming window will never be added, and no native window will be created.
|
|
return;
|
|
}
|
|
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FSlateApplication::AddModalWindow);
|
|
|
|
if( GIsRunningUnattendedScript && !bSlowTaskWindow )
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("A modal window tried to take control while running in unattended script mode. The window was canceled."));
|
|
if (FPlatformMisc::IsDebuggerPresent())
|
|
{
|
|
UE_DEBUG_BREAK();
|
|
}
|
|
else
|
|
{
|
|
FDebug::DumpStackTraceToLog(ELogVerbosity::Error);
|
|
}
|
|
return;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FCoreDelegates::PreSlateModal.Broadcast();
|
|
#endif
|
|
// Push the active modal window onto the stack.
|
|
ActiveModalWindows.AddUnique( InSlateWindow );
|
|
|
|
// Close all the open tooltips when a new window is opened.
|
|
// Tooltips from non-modal windows can be dangerous and re-enter into code that shouldn't execute in a modal state.
|
|
ForEachUser([](FSlateUser& User) {
|
|
User.CloseTooltip();
|
|
});
|
|
|
|
// Set the modal flag on the window
|
|
InSlateWindow->SetAsModalWindow();
|
|
|
|
// Make sure we aren't in the middle of using a slate draw buffer
|
|
Renderer->FlushCommands();
|
|
|
|
// In slow task windows, depending on the frequency with which the window is updated, it could be quite some time
|
|
// before the window is ticked (and drawn) so we hide the window by default and the slow task window will show it when needed
|
|
const bool bShowWindow = !bSlowTaskWindow;
|
|
|
|
// Create the new window
|
|
// Note: generally a modal window should not be added without a parent but
|
|
// due to this being called from wxWidget editors, this is not always possible
|
|
if( InParentWidget.IsValid() )
|
|
{
|
|
// Find the window of the parent widget
|
|
FWidgetPath WidgetPath;
|
|
if (GeneratePathToWidgetUnchecked( InParentWidget.ToSharedRef(), WidgetPath ))
|
|
{
|
|
AddWindowAsNativeChild( InSlateWindow, WidgetPath.GetWindow(), bShowWindow );
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("Modal Window fail to open as a native child. The path to the parent widget (%s) could not be found"), *InParentWidget->ToString());
|
|
AddWindow( InSlateWindow, bShowWindow );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AddWindow( InSlateWindow, bShowWindow );
|
|
}
|
|
|
|
if ( ActiveModalWindows.Num() == 1 )
|
|
{
|
|
// Signal that a slate modal window has opened so external windows may be disabled as well
|
|
ModalWindowStackStartedDelegate.ExecuteIfBound();
|
|
}
|
|
|
|
// Release mouse capture here in case the new modal window has been added in one of the mouse button
|
|
// event callbacks. Otherwise it will be unresponsive until the next mouse up event.
|
|
ReleaseAllPointerCapture();
|
|
|
|
// Clear the cached pressed mouse buttons, in case a new modal window has been added between the mouse down and mouse up of another window.
|
|
PressedMouseButtons.Empty();
|
|
|
|
// Also force the platform capture off as the call to ReleaseMouseCapture() above still relies on mouse up messages to clear the capture
|
|
PlatformApplication->SetCapture( nullptr );
|
|
|
|
// Disable high precision mouse mode when a modal window is added. On some OS'es even when a window is diabled, raw input is sent to it.
|
|
PlatformApplication->SetHighPrecisionMouseMode( false, nullptr );
|
|
|
|
// Block on all modal windows unless its a slow task. In that case the game thread is allowed to run.
|
|
if( !bSlowTaskWindow )
|
|
{
|
|
// Time blocked in this scope shouldn't count against detection of stalls
|
|
SCOPE_STALL_DETECTOR_PAUSE();
|
|
|
|
// Show the cursor if it was previously hidden so users can interact with the window
|
|
if ( PlatformApplication->Cursor.IsValid() )
|
|
{
|
|
PlatformApplication->Cursor->Show( true );
|
|
}
|
|
|
|
// Since the engine tick is paused here we have to end any outstanding frames.
|
|
// That will put us in a clean state for the Slate Tick loop below.
|
|
Renderer->EndFrame();
|
|
|
|
//Throttle loop data
|
|
double LastLoopTime = FPlatformTime::Seconds();
|
|
const double MinThrottlePeriod = (1.0 / 60.0); //Throttle the loop to a maximum of 60Hz
|
|
|
|
// Tick slate from here in the event that we should not return until the modal window is closed.
|
|
while( InSlateWindow == GetActiveModalWindow() )
|
|
{
|
|
//Throttle the loop
|
|
const double CurrentLoopTime = FPlatformTime::Seconds();
|
|
const float SleepTime = static_cast<float>(MinThrottlePeriod - (CurrentLoopTime-LastLoopTime));
|
|
LastLoopTime = CurrentLoopTime;
|
|
if (SleepTime > 0.0f)
|
|
{
|
|
// Sleep a bit to not eat up all CPU time
|
|
FPlatformProcess::Sleep(SleepTime);
|
|
}
|
|
|
|
const float DeltaTime = GetDeltaTime();
|
|
|
|
// Tick any other systems that need to update during modal dialogs
|
|
ModalLoopTickEvent.Broadcast(DeltaTime);
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_SlateTickTime);
|
|
|
|
// Tick and pump messages for the platform.
|
|
TickPlatform(DeltaTime);
|
|
|
|
// It's possible that during ticking the platform we'll find out the modal dialog was closed.
|
|
// in which case we need to abort the current flow.
|
|
if ( InSlateWindow != GetActiveModalWindow() )
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Slate's Tick does not issue Begin/EndFrame so we'll do it ourselves.
|
|
Renderer->BeginFrame();
|
|
|
|
// Advance time for the application
|
|
TickTime();
|
|
|
|
// Tick and render Slate
|
|
TickAndDrawWidgets(DeltaTime);
|
|
|
|
Renderer->EndFrame();
|
|
}
|
|
|
|
// Synchronize the game thread and the render thread so that the render thread doesn't get too far behind.
|
|
Renderer->Sync();
|
|
}
|
|
|
|
// When we let the engine Tick as normal we need to restore the state as before.
|
|
// So we'll start a frame.
|
|
Renderer->BeginFrame();
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SetModalWindowStackStartedDelegate(FModalWindowStackStarted StackStartedDelegate)
|
|
{
|
|
ModalWindowStackStartedDelegate = StackStartedDelegate;
|
|
}
|
|
|
|
void FSlateApplication::SetModalWindowStackEndedDelegate(FModalWindowStackEnded StackEndedDelegate)
|
|
{
|
|
ModalWindowStackEndedDelegate = StackEndedDelegate;
|
|
}
|
|
|
|
TSharedRef<SWindow> FSlateApplication::AddWindowAsNativeChild( TSharedRef<SWindow> InSlateWindow, TSharedRef<SWindow> InParentWindow, const bool bShowImmediately )
|
|
{
|
|
// @VREDITOR HACK
|
|
// Parent window must already have been added
|
|
//checkSlow(FSlateWindowHelper::ContainsWindow(SlateWindows, InParentWindow));
|
|
|
|
// Add the Slate window to the Slate application's top-level window array. Note that neither the Slate window
|
|
// or the native window are ready to be used yet, however we need to make sure they're in the Slate window
|
|
// array so that we can properly respond to OS window messages as soon as they're sent. For example, a window
|
|
// activation message may be sent by the OS as soon as the window is shown (in the Init function), and if we
|
|
// don't add the Slate window to our window list, we wouldn't be able to route that message to the window.
|
|
InParentWindow->AddChildWindow( InSlateWindow );
|
|
|
|
// Only make native generic windows if the parent has one. Nullrhi makes only generic windows, whose handles are always null
|
|
if ( InParentWindow->GetNativeWindow()->GetOSWindowHandle() || !FApp::CanEverRender() || bRenderOffScreen )
|
|
{
|
|
TSharedRef<FGenericWindow> NewWindow = MakeWindow(InSlateWindow, bShowImmediately);
|
|
|
|
if ( bShowImmediately )
|
|
{
|
|
InSlateWindow->ShowWindow();
|
|
|
|
//@todo Slate: Potentially dangerous and annoying if all slate windows that are created steal focus.
|
|
if ( InSlateWindow->SupportsKeyboardFocus() && InSlateWindow->IsFocusedInitially() )
|
|
{
|
|
InSlateWindow->GetNativeWindow()->SetWindowFocus();
|
|
}
|
|
}
|
|
}
|
|
|
|
return InSlateWindow;
|
|
}
|
|
|
|
TSharedPtr<IMenu> FSlateApplication::PushMenu(const TSharedRef<SWidget>& InParentWidget, const FWidgetPath& InOwnerPath, const TSharedRef<SWidget>& InContent, const UE::Slate::FDeprecateVector2DParameter& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const UE::Slate::FDeprecateVector2DParameter& SummonLocationSize, TOptional<EPopupMethod> Method, const bool bIsCollapsedByParent, const int32 FocusUserIndex)
|
|
{
|
|
constexpr bool bEnablePerPixelTransparency = false;
|
|
// Caller supplied a valid path? Pass it to the menu stack.
|
|
if (InOwnerPath.IsValid())
|
|
{
|
|
return MenuStack.Push(InOwnerPath, InContent, SummonLocation, TransitionEffect, bFocusImmediately, SummonLocationSize, Method, bIsCollapsedByParent, bEnablePerPixelTransparency, FocusUserIndex);
|
|
}
|
|
|
|
// If the caller doesn't specify a valid event path we'll generate one from InParentWidget
|
|
FWidgetPath WidgetPath;
|
|
if (GeneratePathToWidgetUnchecked(InParentWidget, WidgetPath))
|
|
{
|
|
return MenuStack.Push(WidgetPath, InContent, SummonLocation, TransitionEffect, bFocusImmediately, SummonLocationSize, Method, bIsCollapsedByParent, bEnablePerPixelTransparency, FocusUserIndex);
|
|
}
|
|
|
|
UE_LOG(LogSlate, Warning, TEXT("Menu could not be pushed. A path to the parent widget(%s) could not be found"), *InParentWidget->ToString());
|
|
return TSharedPtr<IMenu>();
|
|
}
|
|
|
|
TSharedPtr<IMenu> FSlateApplication::PushMenu(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<SWidget>& InContent, const UE::Slate::FDeprecateVector2DParameter& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const UE::Slate::FDeprecateVector2DParameter& SummonLocationSize, const bool bIsCollapsedByParent, const int32 FocusUserIndex)
|
|
{
|
|
constexpr bool bEnablePerPixelTransparency = false;
|
|
return MenuStack.Push(InParentMenu, InContent, SummonLocation, TransitionEffect, bFocusImmediately, SummonLocationSize, bIsCollapsedByParent, bEnablePerPixelTransparency, FocusUserIndex);
|
|
}
|
|
|
|
TSharedPtr<IMenu> FSlateApplication::PushHostedMenu(const TSharedRef<SWidget>& InParentWidget, const FWidgetPath& InOwnerPath, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent)
|
|
{
|
|
// Caller supplied a valid path? Pass it to the menu stack.
|
|
if (InOwnerPath.IsValid())
|
|
{
|
|
return MenuStack.PushHosted(InOwnerPath, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle, bIsCollapsedByParent);
|
|
}
|
|
|
|
// If the caller doesn't specify a valid event path we'll generate one from InParentWidget
|
|
FWidgetPath WidgetPath;
|
|
if (GeneratePathToWidgetUnchecked(InParentWidget, WidgetPath))
|
|
{
|
|
return MenuStack.PushHosted(WidgetPath, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle, bIsCollapsedByParent);
|
|
}
|
|
|
|
return TSharedPtr<IMenu>();
|
|
}
|
|
|
|
TSharedPtr<IMenu> FSlateApplication::PushHostedMenu(const TSharedPtr<IMenu>& InParentMenu, const TSharedRef<IMenuHost>& InMenuHost, const TSharedRef<SWidget>& InContent, TSharedPtr<SWidget>& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent)
|
|
{
|
|
return MenuStack.PushHosted(InParentMenu, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle, bIsCollapsedByParent);
|
|
}
|
|
|
|
bool FSlateApplication::HasOpenSubMenus(TSharedPtr<IMenu> InMenu) const
|
|
{
|
|
return MenuStack.HasOpenSubMenus(InMenu);
|
|
}
|
|
|
|
bool FSlateApplication::AnyMenusVisible() const
|
|
{
|
|
return MenuStack.HasMenus();
|
|
}
|
|
|
|
TSharedPtr<IMenu> FSlateApplication::FindMenuInWidgetPath(const FWidgetPath& InWidgetPath) const
|
|
{
|
|
return MenuStack.FindMenuInWidgetPath(InWidgetPath);
|
|
}
|
|
|
|
TSharedPtr<SWindow> FSlateApplication::GetVisibleMenuWindow() const
|
|
{
|
|
return MenuStack.GetHostWindow();
|
|
}
|
|
|
|
TSharedPtr<SWidget> FSlateApplication::GetMenuHostWidget() const
|
|
{
|
|
return MenuStack.GetHostWidget();
|
|
}
|
|
|
|
void FSlateApplication::DismissAllMenus()
|
|
{
|
|
MenuStack.DismissAll();
|
|
}
|
|
|
|
void FSlateApplication::DismissMenu(const TSharedPtr<IMenu>& InFromMenu)
|
|
{
|
|
MenuStack.DismissFrom(InFromMenu);
|
|
}
|
|
|
|
void FSlateApplication::DismissMenuByWidget(const TSharedRef<SWidget>& InWidgetInMenu)
|
|
{
|
|
FWidgetPath WidgetPath;
|
|
if (GeneratePathToWidgetUnchecked(InWidgetInMenu, WidgetPath))
|
|
{
|
|
TSharedPtr<IMenu> Menu = MenuStack.FindMenuInWidgetPath(WidgetPath);
|
|
if (Menu.IsValid())
|
|
{
|
|
MenuStack.DismissFrom(Menu);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::RequestDestroyWindow( TSharedRef<SWindow> InWindowToDestroy )
|
|
{
|
|
ForEachUser([&InWindowToDestroy] (FSlateUser& User) {
|
|
User.NotifyWindowDestroyed(InWindowToDestroy);
|
|
});
|
|
|
|
// Logging to track down window shutdown issues with movie loading threads. Too spammy in editor builds with all the windows
|
|
#if !WITH_EDITOR
|
|
UE_LOG(LogSlate, Log, TEXT("Request Window '%s' being destroyed"), *InWindowToDestroy->GetTitle().ToString() );
|
|
#endif
|
|
struct local
|
|
{
|
|
static void Helper( const TSharedRef<SWindow> WindowToDestroy, TArray< TSharedRef<SWindow> >& OutWindowDestroyQueue)
|
|
{
|
|
/** @return the list of this window's child windows */
|
|
TArray< TSharedRef<SWindow> >& ChildWindows = WindowToDestroy->GetChildWindows();
|
|
|
|
// Children need to be destroyed first.
|
|
if( ChildWindows.Num() > 0 )
|
|
{
|
|
for( int32 ChildIndex = 0; ChildIndex < ChildWindows.Num(); ++ChildIndex )
|
|
{
|
|
// Recursively request that the window is destroyed which will also queue any children of children etc...
|
|
Helper( ChildWindows[ ChildIndex ], OutWindowDestroyQueue );
|
|
}
|
|
}
|
|
|
|
OutWindowDestroyQueue.AddUnique( WindowToDestroy );
|
|
}
|
|
};
|
|
|
|
local::Helper( InWindowToDestroy, WindowDestroyQueue );
|
|
|
|
DestroyWindowsImmediately();
|
|
}
|
|
|
|
void FSlateApplication::DestroyWindowImmediately( TSharedRef<SWindow> WindowToDestroy )
|
|
{
|
|
// Request that the window and its children are destroyed
|
|
RequestDestroyWindow( WindowToDestroy );
|
|
|
|
DestroyWindowsImmediately();
|
|
}
|
|
|
|
|
|
void FSlateApplication::ExternalModalStart()
|
|
{
|
|
if( NumExternalModalWindowsActive++ == 0 )
|
|
{
|
|
// Close all open menus.
|
|
DismissAllMenus();
|
|
|
|
// Close tool-tips
|
|
ForEachUser([](FSlateUser& User) {
|
|
User.CloseTooltip();
|
|
});
|
|
|
|
// Tick and render Slate so that it can destroy any menu windows if necessary before we disable.
|
|
Tick();
|
|
Renderer->Sync();
|
|
|
|
if( ActiveModalWindows.Num() > 0 )
|
|
{
|
|
// There are still modal windows so only enable the new active modal window.
|
|
GetActiveModalWindow()->EnableWindow( false );
|
|
}
|
|
else
|
|
{
|
|
// We are creating a modal window so all other windows need to be disabled.
|
|
for( TArray< TSharedRef<SWindow> >::TIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
|
|
{
|
|
TSharedRef<SWindow> CurrentWindow = ( *CurrentWindowIt );
|
|
CurrentWindow->EnableWindow( false );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void FSlateApplication::ExternalModalStop()
|
|
{
|
|
check(NumExternalModalWindowsActive > 0);
|
|
if( --NumExternalModalWindowsActive == 0 )
|
|
{
|
|
if( ActiveModalWindows.Num() > 0 )
|
|
{
|
|
// There are still modal windows so only enable the new active modal window.
|
|
GetActiveModalWindow()->EnableWindow( true );
|
|
}
|
|
else
|
|
{
|
|
// We are creating a modal window so all other windows need to be disabled.
|
|
for( TArray< TSharedRef<SWindow> >::TIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
|
|
{
|
|
TSharedRef<SWindow> CurrentWindow = ( *CurrentWindowIt );
|
|
CurrentWindow->EnableWindow( true );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::InvalidateAllViewports()
|
|
{
|
|
Renderer->InvalidateAllViewports();
|
|
}
|
|
|
|
void FSlateApplication::RegisterGameViewport( TSharedRef<SViewport> InViewport )
|
|
{
|
|
RegisterViewport(InViewport);
|
|
|
|
#if WITH_EDITOR
|
|
AllGameViewports.Add(InViewport);
|
|
#endif
|
|
|
|
if (GameViewportWidget != InViewport)
|
|
{
|
|
InViewport->SetActive(true);
|
|
GameViewportWidget = InViewport;
|
|
}
|
|
|
|
ActivateGameViewport();
|
|
}
|
|
|
|
void FSlateApplication::RegisterViewport(TSharedRef<SViewport> InViewport)
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow = FindWidgetWindow(InViewport);
|
|
if (ParentWindow.IsValid())
|
|
{
|
|
TWeakPtr<ISlateViewport> SlateViewport = InViewport->GetViewportInterface();
|
|
if (ensure(SlateViewport.IsValid()))
|
|
{
|
|
ParentWindow->SetViewport(SlateViewport.Pin().ToSharedRef());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::UnregisterGameViewport()
|
|
{
|
|
ResetToDefaultPointerInputSettings();
|
|
|
|
bIsFakingTouched = false;
|
|
bIsGameFakingTouch = false;
|
|
|
|
#if WITH_EDITOR
|
|
AllGameViewports.Empty();
|
|
#endif
|
|
|
|
if (GameViewportWidget.IsValid())
|
|
{
|
|
GameViewportWidget.Pin()->SetActive(false);
|
|
}
|
|
GameViewportWidget.Reset();
|
|
}
|
|
|
|
void FSlateApplication::RegisterVirtualWindow(TSharedRef<SWindow> InWindow)
|
|
{
|
|
SlateVirtualWindows.AddUnique(InWindow);
|
|
}
|
|
|
|
void FSlateApplication::UnregisterVirtualWindow(TSharedRef<SWindow> InWindow)
|
|
{
|
|
SlateVirtualWindows.Remove(InWindow);
|
|
}
|
|
|
|
void FSlateApplication::FlushRenderState()
|
|
{
|
|
InvalidateAllWidgets(true);
|
|
|
|
if ( Renderer.IsValid() )
|
|
{
|
|
// Release any temporary material or texture resources we may have cached and are reporting to prevent
|
|
// GC on those resources. If the game viewport is being unregistered, we need to flush these resources
|
|
// to allow for them to be GC'ed.
|
|
Renderer->ReleaseAccessedResources(/* Flush State */ true);
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SViewport> FSlateApplication::GetGameViewport() const
|
|
{
|
|
return GameViewportWidget.Pin();
|
|
}
|
|
|
|
int32 FSlateApplication::GetUserIndexForMouse() const
|
|
{
|
|
return InputManager->GetUserIndexForMouse();
|
|
}
|
|
|
|
int32 FSlateApplication::GetUserIndexForKeyboard() const
|
|
{
|
|
return InputManager->GetUserIndexForKeyboard();
|
|
}
|
|
|
|
FInputDeviceId FSlateApplication::GetInputDeviceIdForMouse() const
|
|
{
|
|
return InputManager->GetInputDeviceIdForMouse();
|
|
}
|
|
|
|
FInputDeviceId FSlateApplication::GetInputDeviceIdForKeyboard() const
|
|
{
|
|
return InputManager->GetInputDeviceIdForKeyboard();
|
|
}
|
|
|
|
TOptional<int32> FSlateApplication::GetUserIndexForController(int32 ControllerId, FKey InKey) const
|
|
{
|
|
return InputManager->GetUserIndexForController(ControllerId, InKey);
|
|
}
|
|
|
|
int32 FSlateApplication::GetUserIndexForController(int32 ControllerId) const
|
|
{
|
|
return InputManager->GetUserIndexForController(ControllerId);
|
|
}
|
|
|
|
TOptional<int32> FSlateApplication::GetUserIndexForInputDevice(FInputDeviceId InputDeviceId) const
|
|
{
|
|
return InputManager->GetUserIndexForInputDevice(InputDeviceId);
|
|
}
|
|
|
|
TOptional<int32> FSlateApplication::GetUserIndexForPlatformUser(FPlatformUserId PlatformUser) const
|
|
{
|
|
return InputManager->GetUserIndexForPlatformUser(PlatformUser);
|
|
}
|
|
|
|
void FSlateApplication::SetInputManager(TSharedRef<ISlateInputManager> InInputManager)
|
|
{
|
|
//@todo DanH/NickD: Should we be worried at all about potential invalidation of SlateUsers we've created but that no longer occupy an index that can be referenced?
|
|
InputManager = InInputManager;
|
|
}
|
|
|
|
void FSlateApplication::SetUserFocusToGameViewport(uint32 UserIndex, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
|
|
{
|
|
TSharedPtr<SViewport> CurrentGameViewportWidget = GameViewportWidget.Pin();
|
|
if (CurrentGameViewportWidget.IsValid())
|
|
{
|
|
SetUserFocus(UserIndex, CurrentGameViewportWidget, ReasonFocusIsChanging);
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SetAllUserFocusToGameViewport(EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
|
|
{
|
|
TSharedPtr< SViewport > CurrentGameViewportWidget = GameViewportWidget.Pin();
|
|
|
|
if (CurrentGameViewportWidget.IsValid())
|
|
{
|
|
FWidgetPath PathToWidget;
|
|
FSlateWindowHelper::FindPathToWidget(SlateWindows, CurrentGameViewportWidget.ToSharedRef(), /*OUT*/ PathToWidget);
|
|
|
|
SetAllUserFocus(PathToWidget, ReasonFocusIsChanging);
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::ActivateGameViewport()
|
|
{
|
|
// Only focus the window if the application is active, if not the application activation sequence will take care of it.
|
|
if (bAppIsActive && GameViewportWidget.IsValid())
|
|
{
|
|
TSharedRef<SViewport> GameViewportWidgetRef = GameViewportWidget.Pin().ToSharedRef();
|
|
|
|
FWidgetPath PathToViewport;
|
|
// If we cannot find the window it could have been destroyed.
|
|
if (FSlateWindowHelper::FindPathToWidget(SlateWindows, GameViewportWidgetRef, PathToViewport, EVisibility::All))
|
|
{
|
|
TSharedRef<SWindow> Window = PathToViewport.GetWindow();
|
|
|
|
// Set keyboard focus on the actual OS window for the top level Slate window in the viewport path
|
|
// This is needed because some OS messages are only sent to the window with keyboard focus
|
|
// Slate will translate the message and send it to the actual widget with focus.
|
|
// Without this we don't get WM_KEYDOWN or WM_CHAR messages in play in viewport sessions.
|
|
Window->GetNativeWindow()->SetWindowFocus();
|
|
|
|
// Activate the viewport and process the reply
|
|
FWindowActivateEvent ActivateEvent(FWindowActivateEvent::EA_Activate, Window);
|
|
FReply ViewportActivatedReply = GameViewportWidgetRef->OnViewportActivated(ActivateEvent);
|
|
if (ViewportActivatedReply.IsEventHandled())
|
|
{
|
|
ProcessReply(PathToViewport, ViewportActivatedReply, nullptr, nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FSlateApplication::GetTransformFullscreenMouseInput() const
|
|
{
|
|
return TransformFullscreenMouseInput;
|
|
}
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
void FSlateApplication::TryDumpNavigationConfig(TSharedPtr<FNavigationConfig> InNavigationConfig) const
|
|
{
|
|
if (GSlateTraceNavigationConfig && InNavigationConfig)
|
|
{
|
|
UE_LOG(LogSlate, Log, TEXT("Navigation Config Change:\n%s"), *InNavigationConfig->ToString());
|
|
|
|
const uint32 DumpCallstackSize = 65535;
|
|
ANSICHAR DumpCallstack[DumpCallstackSize] = { 0 };
|
|
FString ScriptStack = FFrame::GetScriptCallstack(true /* bReturnEmpty */);
|
|
FPlatformStackWalk::StackWalkAndDump(DumpCallstack, DumpCallstackSize, 0);
|
|
UE_LOG(LogSlate, Log, TEXT("--- Navigation Config Changing Callstack ---"));
|
|
UE_LOG(LogSlate, Log, TEXT("Script Stack:\n%s"), *ScriptStack);
|
|
UE_LOG(LogSlate, Log, TEXT("Callstack:\n%s"), ANSI_TO_TCHAR(DumpCallstack));
|
|
}
|
|
}
|
|
#endif // WITH_SLATE_DEBUGGING
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastWarning(NSLOCTEXT("SlateDebugging", "SetUserFocusFailed", "Attempting to focus a widget that isn't in the tree and visible. If your intent is to clear focus use ClearUserFocus()"), WidgetToFocus);
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
void FSlateApplication::SetAllUserFocus(const TSharedPtr<SWidget>& WidgetToFocus, EFocusCause ReasonFocusIsChanging /*= EFocusCause::SetDirectly*/)
|
|
{
|
|
const bool bValidWidget = WidgetToFocus.IsValid();
|
|
ensureMsgf(bValidWidget, TEXT("Attempting to focus an invalid widget. If your intent is to clear focus use ClearAllUserFocus()"));
|
|
if (bValidWidget)
|
|
{
|
|
FWidgetPath PathToWidget;
|
|
const bool bFound = FSlateWindowHelper::FindPathToWidget(SlateWindows, WidgetToFocus.ToSharedRef(), /*OUT*/ PathToWidget);
|
|
if (bFound)
|
|
{
|
|
SetAllUserFocus(PathToWidget, ReasonFocusIsChanging);
|
|
}
|
|
else
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastWarning(NSLOCTEXT("SlateDebugging", "SetUserFocusFailedAll", "Attempting to focus a widget that isn't in the tree and visible. If your intent is to clear focus use ClearUserFocus()"), WidgetToFocus);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SWidget> FSlateApplication::GetUserFocusedWidget(uint32 UserIndex) const
|
|
{
|
|
TSharedPtr<const FSlateUser> User = GetUser(UserIndex);
|
|
return User ? User->GetFocusedWidget() : TSharedPtr<SWidget>();
|
|
}
|
|
|
|
void FSlateApplication::ClearUserFocus(uint32 UserIndex, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
|
|
{
|
|
SetUserFocus(UserIndex, FWidgetPath(), ReasonFocusIsChanging);
|
|
}
|
|
|
|
void FSlateApplication::ClearAllUserFocus(EFocusCause ReasonFocusIsChanging /*= EFocusCause::SetDirectly*/)
|
|
{
|
|
SetAllUserFocus(FWidgetPath(), ReasonFocusIsChanging);
|
|
}
|
|
|
|
bool FSlateApplication::SetKeyboardFocus(const TSharedPtr< SWidget >& OptionalWidgetToFocus, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/)
|
|
{
|
|
return SetUserFocus(GetUserIndexForKeyboard(), OptionalWidgetToFocus, ReasonFocusIsChanging);
|
|
}
|
|
|
|
void FSlateApplication::ClearKeyboardFocus(const EFocusCause ReasonFocusIsChanging)
|
|
{
|
|
SetUserFocus(GetUserIndexForKeyboard(), FWidgetPath(), ReasonFocusIsChanging);
|
|
}
|
|
|
|
TSharedPtr<SWidget> FSlateApplication::GetCurrentDebugContextWidget() const
|
|
{
|
|
return CurrentDebugContextWidget.Pin();
|
|
}
|
|
|
|
void FSlateApplication::ResetToDefaultInputSettings()
|
|
{
|
|
ProcessReply(FWidgetPath(), FReply::Handled().ClearUserFocus(true), nullptr, nullptr);
|
|
ResetToDefaultPointerInputSettings();
|
|
}
|
|
|
|
void FSlateApplication::ResetToDefaultPointerInputSettings()
|
|
{
|
|
//@todo DanH: Leaving unchanged during the FSlateUser updates, but this seems to be overkill to loop through every single captor
|
|
// and process a ReleaseCapture reply for each. Can't we just ReleaseAllCapture() on every user? (We'd still need to update each user's cursor type)
|
|
//ReleaseMouseCapture();
|
|
ForEachUser([this](FSlateUser& User) {
|
|
for (const FWidgetPath& CaptorPath : User.GetCaptorPaths())
|
|
{
|
|
ProcessReply(CaptorPath, FReply::Handled().ReleaseMouseCapture(), nullptr, nullptr);
|
|
}
|
|
User.GetCursor()->SetType(EMouseCursor::Default);
|
|
});
|
|
|
|
ProcessReply(FWidgetPath(), FReply::Handled().ReleaseMouseLock(), nullptr, nullptr);
|
|
}
|
|
|
|
void* FSlateApplication::GetMouseCaptureWindow() const
|
|
{
|
|
return PlatformApplication->GetCapture();
|
|
}
|
|
|
|
void FSlateApplication::ReleaseMouseCapture()
|
|
{
|
|
ReleaseAllPointerCapture();
|
|
}
|
|
|
|
void FSlateApplication::ReleaseAllPointerCapture()
|
|
{
|
|
ForEachUser([](FSlateUser& User) { User.ReleaseAllCapture(); });
|
|
}
|
|
|
|
void FSlateApplication::ReleaseAllPointerCapture(int32 UserIndex)
|
|
{
|
|
if (TSharedPtr<FSlateUser> User = GetUser(UserIndex))
|
|
{
|
|
User->ReleaseAllCapture();
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::ReleaseMouseCaptureForUser(int32 UserIndex)
|
|
{
|
|
ReleaseAllPointerCapture(UserIndex);
|
|
}
|
|
|
|
FDelegateHandle FSlateApplication::RegisterOnWindowActionNotification(const FOnWindowAction& Notification)
|
|
{
|
|
OnWindowActionNotifications.Add(Notification);
|
|
return OnWindowActionNotifications.Last().GetHandle();
|
|
}
|
|
|
|
void FSlateApplication::UnregisterOnWindowActionNotification(FDelegateHandle Handle)
|
|
{
|
|
for (int32 Index = 0; Index < OnWindowActionNotifications.Num();)
|
|
{
|
|
if (OnWindowActionNotifications[Index].GetHandle() == Handle)
|
|
{
|
|
OnWindowActionNotifications.RemoveAtSwap(Index);
|
|
}
|
|
else
|
|
{
|
|
Index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SWindow> FSlateApplication::FindBestParentWindowForDialogs(const TSharedPtr<SWidget>& InWidget, const ESlateParentWindowSearchMethod InParentWindowSearchMethod)
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow = ( InWidget.IsValid() ) ? FindWidgetWindow(InWidget.ToSharedRef()) : TSharedPtr<SWindow>();
|
|
|
|
if ( !ParentWindow.IsValid() )
|
|
{
|
|
// First check the active top level window.
|
|
TSharedPtr<SWindow> ActiveTopWindow = GetActiveTopLevelWindow();
|
|
if ( ActiveTopWindow.IsValid() && ActiveTopWindow->IsRegularWindow() && InParentWindowSearchMethod == ESlateParentWindowSearchMethod::ActiveWindow )
|
|
{
|
|
ParentWindow = ActiveTopWindow;
|
|
}
|
|
else
|
|
{
|
|
// If the active top level window isn't a good host, lets just try and find the first
|
|
// reasonable window we can host new dialogs off of.
|
|
for ( TSharedPtr<SWindow> SlateWindow : SlateWindows )
|
|
{
|
|
if ( SlateWindow->IsVisible() && SlateWindow->IsRegularWindow() )
|
|
{
|
|
ParentWindow = SlateWindow;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ParentWindow;
|
|
}
|
|
|
|
const void* FSlateApplication::FindBestParentWindowHandleForDialogs(const TSharedPtr<SWidget>& InWidget, const ESlateParentWindowSearchMethod InParentWindowSearchMethod)
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow = FindBestParentWindowForDialogs(InWidget, InParentWindowSearchMethod);
|
|
|
|
const void* ParentWindowWindowHandle = nullptr;
|
|
if ( ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid() )
|
|
{
|
|
ParentWindowWindowHandle = ParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
return ParentWindowWindowHandle;
|
|
}
|
|
|
|
const TSet<FKey>& FSlateApplication::GetPressedMouseButtons() const
|
|
{
|
|
return PressedMouseButtons;
|
|
}
|
|
|
|
TSharedPtr<SWindow> FSlateApplication::GetActiveTopLevelWindow() const
|
|
{
|
|
return ActiveTopLevelWindow.Pin();
|
|
}
|
|
|
|
TSharedPtr<SWindow> FSlateApplication::GetActiveTopLevelRegularWindow() const
|
|
{
|
|
TSharedPtr<SWindow> ActiveWindow = ActiveTopLevelWindow.Pin();
|
|
while (ActiveWindow && !ActiveWindow->IsRegularWindow())
|
|
{
|
|
ActiveWindow = ActiveWindow->GetParentWindow();
|
|
}
|
|
|
|
return ActiveWindow;
|
|
}
|
|
|
|
TSharedPtr<SWindow> FSlateApplication::GetActiveModalWindow() const
|
|
{
|
|
return (ActiveModalWindows.Num() > 0) ? ActiveModalWindows.Last() : nullptr;
|
|
}
|
|
|
|
bool FSlateApplication::SetKeyboardFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause /*= EFocusCause::SetDirectly*/)
|
|
{
|
|
return SetUserFocus(GetUserIndexForKeyboard(), InFocusPath, InCause);
|
|
}
|
|
|
|
bool FSlateApplication::SetUserFocus(const uint32 InUserIndex, const FWidgetPath& InFocusPath, const EFocusCause InCause)
|
|
{
|
|
TSharedPtr<FSlateUser> User = GetUser(InUserIndex);
|
|
return User && SetUserFocus(*User, InFocusPath, InCause);
|
|
}
|
|
|
|
bool FSlateApplication::SetUserFocusAllowingDescendantFocus(const uint32 InUserIndex, const FWidgetPath& InFocusPath, const EFocusCause InCause)
|
|
{
|
|
if (TSharedPtr<FSlateUser> User = GetUser(InUserIndex))
|
|
{
|
|
const TSharedRef<SWidget>& FocusWidget = InFocusPath.Widgets.Last().Widget;
|
|
if (User->GetWeakFocusPath().ContainsWidget(&FocusWidget.Get()))
|
|
{
|
|
// A descendant is already focused
|
|
return true;
|
|
}
|
|
|
|
return SetUserFocus(*User, InFocusPath, InCause);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::SetUserFocus(FSlateUser& User, const FWidgetPath& InFocusPath, const EFocusCause InCause)
|
|
{
|
|
if (InFocusPath.IsValid())
|
|
{
|
|
TSharedRef<SWindow> Window = InFocusPath.GetWindow();
|
|
|
|
// Prevent interactions with tooltips from disrupting the current focus state and closing open menus.
|
|
if (IsWindowHousingInteractiveTooltip(Window))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ActiveModalWindows.Num() != 0 && !(Window->IsDescendantOf(GetActiveModalWindow()) || ActiveModalWindows.Top() == Window))
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastWarning(NSLOCTEXT("SlateDebugging", "SetUserFocusInvalidWindowFailed", "Ignoring SetUserFocus because it's not an active modal Window"), Window);
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
|
|
TSharedPtr<IWidgetReflector> WidgetReflector = WidgetReflectorPtr.Pin();
|
|
const bool bReflectorShowingFocus = WidgetReflector.IsValid() && WidgetReflector->IsShowingFocus();
|
|
|
|
// 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())
|
|
{
|
|
//UE_LOG(LogSlate, Warning, TEXT("Focus for user %i seeking focus path:\n%s"), InUserIndex, *InFocusPath.ToString());
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
User.IncrementFocusVersion();
|
|
int32 CurrentFocusVersion = User.GetFocusVersion();
|
|
|
|
FFocusEvent FocusEvent(InCause, User.GetUserIndex());
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastFocusChanging(FocusEvent, OldFocusedWidgetPath, OldFocusedWidget, NewFocusedWidgetPath, NewFocusedWidget);
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//UE_LOG(LogSlate, Warning, TEXT("Focus for user %i set to %s."), User.GetUserIndex(), NewFocusedWidget.IsValid() ? *NewFocusedWidget->ToString() : TEXT("Invalid"));
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Store a weak widget path to the widget that's taking focus
|
|
User.SetFocusPath(NewFocusedWidgetPath, InCause, ShowFocus);
|
|
|
|
// 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());
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastFocusLost(FocusEvent, OldFocusedWidgetPath, OldFocusedWidget, NewFocusedWidgetPath, NewFocusedWidget);
|
|
#endif
|
|
|
|
// Let previously-focused widget know that it's losing focus
|
|
OldFocusedWidget->OnFocusLost(FocusEvent);
|
|
#if WITH_ACCESSIBILITY
|
|
GetAccessibleMessageHandler()->OnWidgetEventRaised(FSlateAccessibleMessageHandler::FSlateWidgetAccessibleEventArgs(OldFocusedWidget.ToSharedRef(), EAccessibleEvent::FocusChange, true, false, User.GetUserIndex()));
|
|
#endif
|
|
}
|
|
|
|
#if SLATE_HAS_WIDGET_REFLECTOR
|
|
if (bReflectorShowingFocus)
|
|
{
|
|
WidgetReflector->SetWidgetsToVisualize(NewFocusedWidgetPath);
|
|
}
|
|
#endif
|
|
|
|
// 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();
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastFocusReceived(FocusEvent, OldFocusedWidgetPath, OldFocusedWidget, NewFocusedWidgetPath, NewFocusedWidget);
|
|
#endif
|
|
|
|
FReply Reply = NewFocusedWidget->OnFocusReceived(WidgetToFocus.Geometry, FocusEvent);
|
|
if (Reply.IsEventHandled())
|
|
{
|
|
ProcessReply(InFocusPath, Reply, nullptr, nullptr, User.GetUserIndex());
|
|
}
|
|
|
|
GetRelevantNavConfig(User.GetUserIndex())->OnNavigationChangedFocus(OldFocusedWidget, NewFocusedWidget, FocusEvent);
|
|
|
|
#if WITH_ACCESSIBILITY
|
|
GetAccessibleMessageHandler()->OnWidgetEventRaised(FSlateAccessibleMessageHandler::FSlateWidgetAccessibleEventArgs(NewFocusedWidget.ToSharedRef(), EAccessibleEvent::FocusChange, false, true, User.GetUserIndex()));
|
|
#endif
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void FSlateApplication::SetAllUserFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause)
|
|
{
|
|
ForEachUser([&] (FSlateUser& User) {
|
|
SetUserFocus(User, InFocusPath, InCause);
|
|
});
|
|
|
|
// cache the focus path so it can be applied to any new users
|
|
if (InFocusPath.IsValid())
|
|
{
|
|
LastAllUsersFocusWidget = InFocusPath.GetLastWidget();
|
|
}
|
|
else
|
|
{
|
|
LastAllUsersFocusWidget.Reset();
|
|
}
|
|
LastAllUsersFocusCause = InCause;
|
|
}
|
|
|
|
void FSlateApplication::SetAllUserFocusAllowingDescendantFocus(const FWidgetPath& InFocusPath, const EFocusCause InCause)
|
|
{
|
|
const TSharedRef<SWidget>& FocusWidget = InFocusPath.Widgets.Last().Widget;
|
|
|
|
ForEachUser([&] (FSlateUser& User) {
|
|
if (!User.GetWeakFocusPath().ContainsWidget(&FocusWidget.Get()))
|
|
{
|
|
SetUserFocus(User, InFocusPath, InCause);
|
|
}
|
|
});
|
|
|
|
// cache the focus path so it can be applied to any new users
|
|
LastAllUsersFocusWidget = FocusWidget;
|
|
LastAllUsersFocusCause = InCause;
|
|
}
|
|
|
|
FModifierKeysState FSlateApplication::GetModifierKeys() const
|
|
{
|
|
return PlatformApplication->GetModifierKeys();
|
|
}
|
|
|
|
|
|
void FSlateApplication::OnShutdown()
|
|
{
|
|
CloseAllWindowsImmediately();
|
|
}
|
|
|
|
void FSlateApplication::CloseAllWindowsImmediately()
|
|
{
|
|
ForEachUser([](FSlateUser& User) {
|
|
User.ResetDragDropContent();
|
|
User.ResetTooltipWindow();
|
|
});
|
|
|
|
// Destroy all top level windows.
|
|
// ::RequestDestroyWindow will remove the window from the TArray SlateWindows so we
|
|
// should iterate over it backwards to make sure that the WindowIndex is still correct.
|
|
for (int32 WindowIndex = SlateWindows.Num() -1; WindowIndex >= 0; --WindowIndex)
|
|
{
|
|
// This will also request that all children of each window be destroyed
|
|
RequestDestroyWindow(SlateWindows[WindowIndex]);
|
|
}
|
|
|
|
DestroyWindowsImmediately();
|
|
}
|
|
|
|
void FSlateApplication::DestroyWindowsImmediately()
|
|
{
|
|
// Destroy any windows that were queued for deletion.
|
|
|
|
// Thomas.Sarkanen: I've changed this from a for() to a while() loop so that it is now valid to call RequestDestroyWindow()
|
|
// in the callstack of another call to RequestDestroyWindow(). Previously this would cause a stack overflow, as the
|
|
// WindowDestroyQueue would be continually added to each time the for() loop ran.
|
|
while ( WindowDestroyQueue.Num() > 0 )
|
|
{
|
|
TSharedRef<SWindow> CurrentWindow = WindowDestroyQueue[0];
|
|
WindowDestroyQueue.Remove(CurrentWindow);
|
|
if( ActiveModalWindows.Num() > 0 && ActiveModalWindows.Contains( CurrentWindow ) )
|
|
{
|
|
ActiveModalWindows.Remove( CurrentWindow );
|
|
|
|
if( ActiveModalWindows.Num() > 0 )
|
|
{
|
|
// There are still modal windows so only enable the new active modal window.
|
|
GetActiveModalWindow()->EnableWindow( true );
|
|
}
|
|
else
|
|
{
|
|
// There are no modal windows so renable all slate windows
|
|
for ( TArray< TSharedRef<SWindow> >::TConstIterator SlateWindowIter( SlateWindows ); SlateWindowIter; ++SlateWindowIter )
|
|
{
|
|
// All other windows need to be re-enabled BEFORE a modal window is destroyed or focus will not be set correctly
|
|
(*SlateWindowIter)->EnableWindow( true );
|
|
}
|
|
|
|
// Signal that all slate modal windows are closed
|
|
ModalWindowStackEndedDelegate.ExecuteIfBound();
|
|
}
|
|
}
|
|
|
|
// Any window being destroyed should be removed from the menu stack if its in it
|
|
MenuStack.OnWindowDestroyed(CurrentWindow);
|
|
|
|
// Perform actual cleanup of the window
|
|
PrivateDestroyWindow( CurrentWindow );
|
|
}
|
|
|
|
WindowDestroyQueue.Empty();
|
|
}
|
|
|
|
|
|
void FSlateApplication::SetExitRequestedHandler( const FSimpleDelegate& OnExitRequestedHandler )
|
|
{
|
|
OnExitRequested = OnExitRequestedHandler;
|
|
}
|
|
|
|
|
|
bool FSlateApplication::GeneratePathToWidgetUnchecked( TSharedRef<const SWidget> InWidget, FWidgetPath& OutWidgetPath, EVisibility VisibilityFilter ) const
|
|
{
|
|
if ( !FSlateWindowHelper::FindPathToWidget(SlateWindows, InWidget, OutWidgetPath, VisibilityFilter) )
|
|
{
|
|
return FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, InWidget, OutWidgetPath, VisibilityFilter);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void FSlateApplication::GeneratePathToWidgetChecked( TSharedRef<const SWidget> InWidget, FWidgetPath& OutWidgetPath, EVisibility VisibilityFilter ) const
|
|
{
|
|
if ( !FSlateWindowHelper::FindPathToWidget(SlateWindows, InWidget, OutWidgetPath, VisibilityFilter) )
|
|
{
|
|
const bool bWasFound = FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, InWidget, OutWidgetPath, VisibilityFilter);
|
|
check(bWasFound);
|
|
}
|
|
}
|
|
|
|
|
|
TSharedPtr<SWindow> FSlateApplication::FindWidgetWindow( TSharedRef<const SWidget> InWidget ) const
|
|
{
|
|
TSharedPtr<SWidget> TestWidget = ConstCastSharedRef<SWidget>(InWidget);
|
|
while (TestWidget.IsValid())
|
|
{
|
|
if (TestWidget->Advanced_IsWindow())
|
|
{
|
|
return StaticCastSharedPtr<SWindow>(TestWidget);
|
|
}
|
|
|
|
TestWidget = TestWidget->GetParentWidget();
|
|
};
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
TSharedPtr<SWindow> FSlateApplication::FindWidgetWindow( TSharedRef<const SWidget> InWidget, FWidgetPath& OutWidgetPath ) const
|
|
{
|
|
// If the user wants a widget path back populate it instead
|
|
if ( !FSlateWindowHelper::FindPathToWidget(SlateWindows, InWidget, OutWidgetPath, EVisibility::All) )
|
|
{
|
|
if ( !FSlateWindowHelper::FindPathToWidget(SlateVirtualWindows, InWidget, OutWidgetPath, EVisibility::All) )
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return OutWidgetPath.TopLevelWindow;
|
|
}
|
|
|
|
void FSlateApplication::ProcessExternalReply(const FWidgetPath& CurrentEventPath, const FReply TheReply, const int32 UserIndex, const int32 PointerIndex)
|
|
{
|
|
const int32 ValidatedUserIndex = (UserIndex >= 0) ? UserIndex : 0;
|
|
if (PointerIndex == CursorPointerIndex)
|
|
{
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(ValidatedUserIndex);
|
|
const bool bIsPrimaryUser = ValidatedUserIndex == CursorUserIndex;
|
|
|
|
FPointerEvent MouseEvent(
|
|
ValidatedUserIndex,
|
|
PointerIndex,
|
|
SlateUser->GetCursorPosition(),
|
|
SlateUser->GetPreviousCursorPosition(),
|
|
bIsPrimaryUser ? PressedMouseButtons : SlateDefs::EmptyTouchKeySet,
|
|
EKeys::Invalid,
|
|
0,
|
|
bIsPrimaryUser ? PlatformApplication->GetModifierKeys() : FModifierKeysState()
|
|
);
|
|
|
|
FWidgetPath PathToWidget;
|
|
const FWidgetPath* PathToWidgetPtr = nullptr;
|
|
FWeakWidgetPath LastWidgetsUnderCursor = SlateUser->GetLastWidgetsUnderCursor();
|
|
if (LastWidgetsUnderCursor.IsValid())
|
|
{
|
|
PathToWidget = LastWidgetsUnderCursor.ToWidgetPath();
|
|
PathToWidgetPtr = &PathToWidget;
|
|
}
|
|
|
|
ProcessReply(CurrentEventPath, TheReply, PathToWidgetPtr, &MouseEvent, ValidatedUserIndex);
|
|
}
|
|
else
|
|
{
|
|
ProcessReply(CurrentEventPath, TheReply, nullptr, nullptr, ValidatedUserIndex);
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::ProcessReply( const FWidgetPath& CurrentEventPath, const FReply& TheReply, const FWidgetPath* WidgetsUnderMouse, const FPointerEvent* InMouseEvent, const uint32 UserIndex )
|
|
{
|
|
const TSharedPtr<FDragDropOperation> ReplyDragDropContent = TheReply.GetDragDropContent();
|
|
const bool bStartingDragDrop = ReplyDragDropContent.IsValid() && WidgetsUnderMouse && WidgetsUnderMouse->IsValid();
|
|
const bool bIsVirtualInteraction = CurrentEventPath.IsValid() ? CurrentEventPath.GetWindow()->IsVirtualWindow() : false;
|
|
|
|
// Release mouse capture if requested or if we are starting a drag and drop.
|
|
// Make sure to only clobber WidgetsUnderCursor if we actually had a mouse capture.
|
|
uint32 PointerIndex = InMouseEvent != nullptr ? InMouseEvent->GetPointerIndex() : CursorPointerIndex;
|
|
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(UserIndex);
|
|
if (SlateUser->HasCapture(PointerIndex) && (TheReply.ShouldReleaseMouse() || bStartingDragDrop))
|
|
{
|
|
SlateUser->ReleaseCapture(PointerIndex);
|
|
}
|
|
|
|
// Clear focus is requested.
|
|
if (TheReply.ShouldReleaseUserFocus())
|
|
{
|
|
if (TheReply.AffectsAllUsers())
|
|
{
|
|
ClearAllUserFocus(TheReply.GetFocusCause());
|
|
}
|
|
else
|
|
{
|
|
SlateUser->ClearFocus(TheReply.GetFocusCause());
|
|
}
|
|
}
|
|
|
|
if (TheReply.ShouldEndDragDrop())
|
|
{
|
|
SlateUser->CancelDragDrop();
|
|
}
|
|
|
|
if (bStartingDragDrop)
|
|
{
|
|
checkf(!SlateUser->IsDragDropping(), TEXT("Drag and Drop already in progress!"));
|
|
check(TheReply.IsEventHandled());
|
|
check(WidgetsUnderMouse);
|
|
check(InMouseEvent);
|
|
|
|
FPointerEvent TransformedPointerEvent = TransformPointerEvent(*InMouseEvent, WidgetsUnderMouse->GetWindow());
|
|
|
|
SlateUser->SetDragDropContent(ReplyDragDropContent.ToSharedRef());
|
|
|
|
const FWeakWidgetPath LastWidgetsUnderCursor = SlateUser->GetLastWidgetsUnderPointer(PointerIndex);
|
|
|
|
// We have entered drag and drop mode.
|
|
// LastWidgetsUnderCursor.ToWidgetPath(), CurrentEventPath, and
|
|
// *WidgetsUnderMouse should all be the same except during mouse move/
|
|
// drag detected events, in which case, the differences are as
|
|
// follows:
|
|
//
|
|
// A) LastWidgetsUnderCursor.ToWidgetPath() - The path to the widget
|
|
// that the mouse cursor was over on the previous frame/mouse
|
|
// event.
|
|
// B) CurrentEventPath - For the DragDetected event, this is the path
|
|
// to the widget on which the drag was started. Since the drag
|
|
// operation does not activate until the mouse has been dragged
|
|
// a short distance, this means that this can be different from
|
|
// both the widget path that the cursor is currently on and the
|
|
// widget path that the cursor was on for the previous event.
|
|
// C) *WidgetsUnderMouse - The widget that the mouse is currently
|
|
// over.
|
|
//
|
|
// To process the beginning of the drag operation, widgets previously
|
|
// under the mouse cursor receive the OnMouseLeave notification,
|
|
// regardless of whether the cursor is still over them or not.
|
|
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(LastWidgetsUnderCursor.ToWidgetPath()), TransformedPointerEvent, [](const FArrangedWidget& SomeWidget, const FPointerEvent& PointerEvent)
|
|
{
|
|
SomeWidget.Widget->OnMouseLeave( PointerEvent );
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastNoReplyInputEvent(ESlateDebuggingInputEvent::MouseLeave, &PointerEvent, SomeWidget.Widget);
|
|
#endif
|
|
return FNoReply();
|
|
}, ESlateDebuggingInputEvent::MouseLeave);
|
|
|
|
// Then, the original widget started the drag receives OnDragEnter.
|
|
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(CurrentEventPath), FDragDropEvent( TransformedPointerEvent, ReplyDragDropContent ), [](const FArrangedWidget& SomeWidget, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
SomeWidget.Widget->OnDragEnter( SomeWidget.Geometry, DragDropEvent );
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastNoReplyInputEvent(ESlateDebuggingInputEvent::DragEnter, &DragDropEvent, SomeWidget.Widget);
|
|
#endif
|
|
return FNoReply();
|
|
}, ESlateDebuggingInputEvent::DragEnter);
|
|
|
|
// If the cursor is not currently over the widget on which the drag
|
|
// operation started (which should only be the case due to cursor
|
|
// movement), the remainder of events are handled in
|
|
// RoutePointerMoveEvent(), which will immediately call OnDragLeave()
|
|
// on the widget that started the drag followed by OnDragEnter() on
|
|
// the current widget. Thus, using the letters above, and assuming
|
|
// all of the widgets are different, the sequence should end up being:
|
|
//
|
|
// 1. B - OnDragDetected (processing reply with drag content)
|
|
// 1.1. A - OnMouseLeave
|
|
// 1.2. B - OnDragEnter
|
|
// 2. B - OnDragLeave
|
|
// 3. C - OnDragEnter
|
|
// 4. C - OnDragOver
|
|
}
|
|
|
|
// Setting mouse capture, mouse position, and locking the mouse
|
|
// are all operations that we shouldn't do if our application isn't Active (The OS ignores half of it, and we'd be in a half state)
|
|
// We do allow the release of capture and lock when deactivated, this is innocuous of some platforms but required on others when
|
|
// the Application deactivated before the window. (Mac is an example of this)
|
|
if (bHandleDeviceInputWhenApplicationNotActive || bAppIsActive || bIsVirtualInteraction)
|
|
{
|
|
TSharedPtr<SWidget> RequestedMouseCaptor = TheReply.GetMouseCaptor();
|
|
|
|
// Do not capture the mouse if we are also starting a drag and drop.
|
|
if (RequestedMouseCaptor.IsValid() && !bStartingDragDrop)
|
|
{
|
|
if (SlateUser->SetPointerCaptor(PointerIndex, RequestedMouseCaptor.ToSharedRef(), CurrentEventPath))
|
|
{
|
|
const FWeakWidgetPath LastWidgetsUnderCursor = SlateUser->GetLastWidgetsUnderPointer(PointerIndex);
|
|
if (LastWidgetsUnderCursor.IsValid())
|
|
{
|
|
for (int32 WidgetIndex = LastWidgetsUnderCursor.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
|
|
{
|
|
TSharedPtr<SWidget> WidgetPreviouslyUnderCursor = LastWidgetsUnderCursor.Widgets[WidgetIndex].Pin();
|
|
|
|
if (WidgetPreviouslyUnderCursor.IsValid())
|
|
{
|
|
if (WidgetPreviouslyUnderCursor != RequestedMouseCaptor)
|
|
{
|
|
// It's possible for mouse event to be null if we end up here from a keyboard event. If so, we should synthesize an event.
|
|
// WidgetsUnderMouse can also be invalid if the mouse is not over a Slate widget.
|
|
if (InMouseEvent && WidgetsUnderMouse && WidgetsUnderMouse->IsValid())
|
|
{
|
|
FPointerEvent TransformedPointerEvent = TransformPointerEvent(*InMouseEvent, WidgetsUnderMouse->GetWindow());
|
|
|
|
// Note that the event's pointer position is not translated.
|
|
WidgetPreviouslyUnderCursor->OnMouseLeave(TransformedPointerEvent);
|
|
}
|
|
else
|
|
{
|
|
const FPointerEvent& SimulatedPointer = FPointerEvent();
|
|
WidgetPreviouslyUnderCursor->OnMouseLeave(SimulatedPointer);
|
|
}
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseLeave, InMouseEvent, WidgetPreviouslyUnderCursor);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Done routing mouse leave
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Need to handle the case where the mouse has moved onto a new widget before the drag was detected by also calling MouseLeave on the newly hovered widgets
|
|
if (WidgetsUnderMouse && WidgetsUnderMouse->IsValid() && LastWidgetsUnderCursor.Widgets.Last().Pin() != WidgetsUnderMouse->Widgets.Last().Widget)
|
|
{
|
|
for (int32 WidgetIndex = WidgetsUnderMouse->Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
|
|
{
|
|
TSharedPtr<SWidget> WidgetNowUnderCursor = WidgetsUnderMouse->Widgets[WidgetIndex].Widget;
|
|
|
|
if (WidgetNowUnderCursor.IsValid())
|
|
{
|
|
if (WidgetNowUnderCursor != RequestedMouseCaptor && !LastWidgetsUnderCursor.ContainsWidget(WidgetNowUnderCursor.Get()))
|
|
{
|
|
// It's possible for mouse event to be null if we end up here from a keyboard event. If so, we should synthesize an event.
|
|
if (InMouseEvent)
|
|
{
|
|
FPointerEvent TransformedPointerEvent = TransformPointerEvent(*InMouseEvent, WidgetsUnderMouse->GetWindow());
|
|
|
|
// Note that the event's pointer position is not translated.
|
|
WidgetNowUnderCursor->OnMouseLeave(TransformedPointerEvent);
|
|
}
|
|
else
|
|
{
|
|
const FPointerEvent& SimulatedPointer = FPointerEvent();
|
|
WidgetNowUnderCursor->OnMouseLeave(SimulatedPointer);
|
|
}
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseLeave, InMouseEvent, WidgetNowUnderCursor);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Done routing mouse leave
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastWarning(NSLOCTEXT("SlateDebugging", "FailedToCaptureMouse", "Failed To Mouse Capture"), RequestedMouseCaptor);
|
|
#endif
|
|
}
|
|
|
|
// When the cursor capture state changes we need to refresh cursor state.
|
|
SlateUser->RequestCursorQuery();
|
|
}
|
|
|
|
if ( !bIsVirtualInteraction && CurrentEventPath.IsValid() && RequestedMouseCaptor.IsValid())
|
|
{
|
|
// If the mouse is being captured or released, toggle high precision raw input if requested by the reply.
|
|
// Raw input is only used with mouse capture
|
|
if (TheReply.ShouldUseHighPrecisionMouse())
|
|
{
|
|
const TSharedRef< SWindow> Window = CurrentEventPath.GetWindow();
|
|
PlatformApplication->SetCapture(Window->GetNativeWindow());
|
|
PlatformApplication->SetHighPrecisionMouseMode(true, Window->GetNativeWindow());
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
//TODO Capture
|
|
#endif
|
|
|
|
// When the cursor capture state changes we need to refresh cursor state.
|
|
SlateUser->RequestCursorQuery();
|
|
}
|
|
}
|
|
|
|
TOptional<FIntPoint> RequestedMousePos = TheReply.GetRequestedMousePos();
|
|
if (RequestedMousePos.IsSet())
|
|
{
|
|
SlateUser->SetCursorPosition(RequestedMousePos.GetValue());
|
|
}
|
|
|
|
if (TheReply.GetMouseLockWidget())
|
|
{
|
|
// The reply requested mouse lock so tell the native application to lock the mouse to the widget receiving the event
|
|
SlateUser->LockCursor(TheReply.GetMouseLockWidget().ToSharedRef());
|
|
}
|
|
}
|
|
|
|
// Releasing high precision mode. @HACKISH We can only support high precision mode on true mouse hardware cursors
|
|
// but if the user index isn't 0, there's no way it's the real mouse so we should ignore this if it's not user 0,
|
|
// because that means it's a virtual controller.
|
|
if ( UserIndex == CursorUserIndex && !bIsVirtualInteraction )
|
|
{
|
|
if ( CurrentEventPath.IsValid() && TheReply.ShouldReleaseMouse() && !TheReply.ShouldUseHighPrecisionMouse() )
|
|
{
|
|
if ( PlatformApplication->IsUsingHighPrecisionMouseMode() )
|
|
{
|
|
const TSharedRef< SWindow> Window = CurrentEventPath.GetWindow();
|
|
PlatformApplication->SetHighPrecisionMouseMode(false, Window->GetNativeWindow());
|
|
PlatformApplication->SetCapture(nullptr);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
//TODO Release Capture
|
|
#endif
|
|
|
|
// When the cursor capture state changes we need to refresh cursor state.
|
|
SlateUser->RequestCursorQuery();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Releasing Mouse Lock
|
|
if (TheReply.ShouldReleaseMouseLock())
|
|
{
|
|
SlateUser->UnlockCursor();
|
|
}
|
|
|
|
// If we have a valid Navigation request attempt the navigation.
|
|
if (TheReply.GetNavigationDestination().IsValid() || TheReply.GetNavigationType() != EUINavigation::Invalid)
|
|
{
|
|
FWidgetPath NavigationSource;
|
|
if (TheReply.GetNavigationSource() == ENavigationSource::WidgetUnderCursor)
|
|
{
|
|
NavigationSource = *WidgetsUnderMouse;
|
|
}
|
|
else
|
|
{
|
|
NavigationSource = SlateUser->GetFocusPath().Get();
|
|
}
|
|
|
|
if (NavigationSource.IsValid())
|
|
{
|
|
if (!GSlateEnableGamepadEditorNavigation && TheReply.GetNavigationGenesis() == ENavigationGenesis::Controller && !NavigationSource.GetLastWidget()->GetPersistentState().bIsInGameLayer)
|
|
{
|
|
// Gamepad navigation while not in a game layer, do nothing as specified by GSlateEnableGamepadEditorNavigation
|
|
}
|
|
else if (TheReply.GetNavigationDestination().IsValid())
|
|
{
|
|
const bool bAlwaysHandleNavigationAttempt = false;
|
|
ExecuteNavigation(NavigationSource, TheReply.GetNavigationDestination(), UserIndex, bAlwaysHandleNavigationAttempt);
|
|
}
|
|
else
|
|
{
|
|
TSharedRef<SWindow> NavigationWindow = NavigationSource.GetDeepestWindow();
|
|
|
|
FNavigationEvent NavigationEvent(PlatformApplication->GetModifierKeys(), UserIndex, TheReply.GetNavigationType(), TheReply.GetNavigationGenesis());
|
|
|
|
FNavigationReply NavigationReply = FNavigationReply::Escape();
|
|
|
|
for (int32 WidgetIndex = NavigationSource.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
|
|
{
|
|
FArrangedWidget& SomeWidgetGettingEvent = NavigationSource.Widgets[WidgetIndex];
|
|
if (SomeWidgetGettingEvent.Widget->IsEnabled())
|
|
{
|
|
NavigationReply = SomeWidgetGettingEvent.Widget->OnNavigation(SomeWidgetGettingEvent.Geometry, NavigationEvent).SetHandler(SomeWidgetGettingEvent.Widget);
|
|
if (NavigationReply.GetBoundaryRule() != EUINavigationRule::Escape || SomeWidgetGettingEvent.Widget == NavigationWindow || WidgetIndex == 0)
|
|
{
|
|
AttemptNavigation(NavigationSource, NavigationEvent, NavigationReply, SomeWidgetGettingEvent);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( TheReply.GetDetectDragRequest().IsValid() )
|
|
{
|
|
checkSlow(InMouseEvent);
|
|
|
|
FPointerEvent TransformedPointerEvent = TransformPointerEvent(*InMouseEvent, WidgetsUnderMouse->GetWindow());
|
|
|
|
SlateUser->StartDragDetection(
|
|
WidgetsUnderMouse->GetPathDownTo(TheReply.GetDetectDragRequest().ToSharedRef()),
|
|
TransformedPointerEvent.GetPointerIndex(),
|
|
TheReply.GetDetectDragRequestButton(),
|
|
TransformedPointerEvent.GetScreenSpacePosition());
|
|
}
|
|
|
|
// Set focus if requested.
|
|
TSharedPtr<SWidget> RequestedFocusRecepient = TheReply.GetUserFocusRecepient();
|
|
if (TheReply.ShouldSetUserFocus() && RequestedFocusRecepient.IsValid())
|
|
{
|
|
if (TheReply.AffectsAllUsers())
|
|
{
|
|
SetAllUserFocus(RequestedFocusRecepient, TheReply.GetFocusCause());
|
|
}
|
|
else
|
|
{
|
|
SlateUser->SetFocus(RequestedFocusRecepient.ToSharedRef(), TheReply.GetFocusCause());
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SetLastUserInteractionTime(double InCurrentTime)
|
|
{
|
|
if (LastUserInteractionTime != InCurrentTime)
|
|
{
|
|
LastUserInteractionTime = InCurrentTime;
|
|
LastUserInteractionTimeUpdateEvent.Broadcast(LastUserInteractionTime);
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::QueryCursor()
|
|
{
|
|
GetCursorUser()->QueryCursor();
|
|
}
|
|
|
|
void FSlateApplication::ProcessCursorReply(const FCursorReply& CursorReply)
|
|
{
|
|
GetCursorUser()->ProcessCursorReply(CursorReply);
|
|
}
|
|
|
|
void FSlateApplication::SpawnToolTip(const TSharedRef<IToolTip>& InToolTip, const UE::Slate::FDeprecateVector2DParameter& InSpawnLocation)
|
|
{
|
|
GetCursorUser()->ShowTooltip(InToolTip, InSpawnLocation);
|
|
}
|
|
|
|
void FSlateApplication::CloseToolTip()
|
|
{
|
|
GetCursorUser()->CloseTooltip();
|
|
}
|
|
|
|
void FSlateApplication::UpdateToolTip(bool bAllowSpawningOfNewToolTips)
|
|
{
|
|
GetCursorUser()->UpdateTooltip(MenuStack, bAllowSpawningOfNewToolTips);
|
|
}
|
|
|
|
TArray< TSharedRef<SWindow> > FSlateApplication::GetInteractiveTopLevelWindows()
|
|
{
|
|
if (ActiveModalWindows.Num() > 0)
|
|
{
|
|
// If we have modal windows, only the topmost modal window and its children are interactive.
|
|
TArray< TSharedRef<SWindow>, TInlineAllocator<1> > OutWindows;
|
|
OutWindows.Add(ActiveModalWindows.Last().ToSharedRef());
|
|
|
|
// If there is an interactive tooltip open from a modal window, include it too.
|
|
for (int32 WindowIndex = SlateWindows.Num() - 1; WindowIndex >= 0; WindowIndex--)
|
|
{
|
|
TSharedRef<SWindow> CurrentWindow = SlateWindows[WindowIndex];
|
|
if (GetCursorUser()->IsWindowHousingInteractiveTooltip(CurrentWindow))
|
|
{
|
|
OutWindows.Add(CurrentWindow);
|
|
}
|
|
}
|
|
|
|
return TArray< TSharedRef<SWindow> >(OutWindows);
|
|
}
|
|
else
|
|
{
|
|
// No modal windows? All windows are interactive.
|
|
return SlateWindows;
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::GetAllVisibleWindowsOrdered(TArray< TSharedRef<SWindow> >& OutWindows)
|
|
{
|
|
for( TArray< TSharedRef<SWindow> >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt )
|
|
{
|
|
TSharedRef<SWindow> CurrentWindow = *CurrentWindowIt;
|
|
if ( CurrentWindow->IsVisible() && !CurrentWindow->IsWindowMinimized() )
|
|
{
|
|
GetAllVisibleChildWindows(OutWindows, CurrentWindow);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::GetAllVisibleChildWindows(TArray< TSharedRef<SWindow> >& OutWindows, TSharedRef<SWindow> CurrentWindow)
|
|
{
|
|
if ( CurrentWindow->IsVisible() && !CurrentWindow->IsWindowMinimized() )
|
|
{
|
|
OutWindows.Add(CurrentWindow);
|
|
|
|
const TArray< TSharedRef<SWindow> >& WindowChildren = CurrentWindow->GetChildWindows();
|
|
for (int32 ChildIndex=0; ChildIndex < WindowChildren.Num(); ++ChildIndex)
|
|
{
|
|
GetAllVisibleChildWindows( OutWindows, WindowChildren[ChildIndex] );
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::EnterDebuggingMode()
|
|
{
|
|
if (!IsInGameThread())
|
|
{
|
|
ensureMsgf(false, TEXT("Can only enter Debugging Mode while on the game thread."));
|
|
return;
|
|
}
|
|
|
|
auto AddNotification = [Self=this](const FText& SubText)
|
|
{
|
|
if (TSharedPtr<SNotificationItem> MessagePinned = Self->DebuggingModeNotificationMessage.Pin())
|
|
{
|
|
static float DefaultDuration = FNotificationInfo{FText::GetEmpty()}.ExpireDuration;
|
|
MessagePinned->SetSubText(SubText);
|
|
}
|
|
else
|
|
{
|
|
FNotificationInfo Info(NSLOCTEXT("EnterDebuggingMode", "FailTitle", "Debugging Mode Fail"));
|
|
Info.SubText = SubText;
|
|
Self->DebuggingModeNotificationMessage = FSlateNotificationManager::Get().AddNotification(Info);
|
|
}
|
|
UE_LOG(LogSlate, Warning, TEXT("Enter Debugging Mode failed."));
|
|
|
|
static volatile bool bDoDebugBreak = true;
|
|
if (bDoDebugBreak)
|
|
{
|
|
UE_DEBUG_BREAK();
|
|
}
|
|
};
|
|
|
|
if (GetActiveModalWindow().IsValid())
|
|
{
|
|
AddNotification(NSLOCTEXT("EnterDebuggingMode", "Fail_ModalWindow", "A modal window is open."));
|
|
return;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (PreventDebuggingModeStack.Num() > 0)
|
|
{
|
|
AddNotification(PreventDebuggingModeStack.Last().Key);
|
|
return;
|
|
}
|
|
FScopedPreventDebuggingMode Scope(NSLOCTEXT("EnterDebuggingMode", "AlreadyInDebuggingMode", "Already in debug mode."));
|
|
#endif
|
|
|
|
bRequestLeaveDebugMode = false;
|
|
|
|
// Note it is ok to hold a reference here as the game viewport should not be destroyed while in debugging mode
|
|
TSharedPtr<SViewport> PreviousGameViewport;
|
|
|
|
// Disable any game viewports while we are in debug mode so that mouse capture is released and the cursor is visible
|
|
// We need to retain the keyboard input for debugging purposes, so this is called directly rather than calling UnregisterGameViewport which resets input.
|
|
if (GameViewportWidget.IsValid())
|
|
{
|
|
PreviousGameViewport = GameViewportWidget.Pin();
|
|
PreviousGameViewport->SetActive(false);
|
|
GameViewportWidget.Reset();
|
|
}
|
|
|
|
// Find the SWindow that we should not tick while in DebuggingMode.
|
|
ensureMsgf(!CurrentDebuggingWindow.IsValid(), TEXT("Reentry of EnterDebuggingMode with a valid Debugging Window is not supported"));
|
|
CurrentDebuggingWindow.Reset();
|
|
if (TSharedPtr<SWidget> CurrentDebugContextWidgetPinned = CurrentDebugContextWidget.Pin())
|
|
{
|
|
// Only prevent Paint if there is more than one window.
|
|
//That is to prevent the user from getting stuck in the editor.
|
|
if (SlateWindows.Num() > 0)
|
|
{
|
|
CurrentDebuggingWindow = FindWidgetWindow(CurrentDebugContextWidgetPinned.ToSharedRef());
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("EnterDebuggingMode without blocking the window Paint. That may start a new Paint on the same Window while the previous Paint is not completed."));
|
|
}
|
|
}
|
|
TGuardValue TmpContext(CurrentDebugContextWidget, TWeakPtr<SWidget>());
|
|
|
|
Renderer->EndFrame();
|
|
|
|
Renderer->FlushCommands();
|
|
|
|
// We are about to start an in stack tick. Make sure the rendering thread isn't already behind
|
|
Renderer->Sync();
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// Flag that we're about to enter the first frame of intra-frame debugging.
|
|
GFirstFrameIntraFrameDebugging = true;
|
|
#endif //WITH_EDITORONLY_DATA
|
|
|
|
// Tick slate from here in the event that we should not return until the modal window is closed.
|
|
while (!bRequestLeaveDebugMode)
|
|
{
|
|
Renderer->BeginFrame();
|
|
|
|
// Tick and render Slate
|
|
Tick();
|
|
|
|
Renderer->EndFrame();
|
|
|
|
Renderer->FlushCommands();
|
|
|
|
// Synchronize the game thread and the render thread so that the render thread doesn't get too far behind.
|
|
Renderer->Sync();
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
// We are done with the first frame
|
|
GFirstFrameIntraFrameDebugging = false;
|
|
|
|
// If we are requesting leaving debugging mode, leave it now.
|
|
GIntraFrameDebuggingGameThread = !bRequestLeaveDebugMode;
|
|
#endif //WITH_EDITORONLY_DATA
|
|
}
|
|
|
|
Renderer->BeginFrame();
|
|
bRequestLeaveDebugMode = false;
|
|
|
|
CurrentDebuggingWindow.Reset();
|
|
if ( PreviousGameViewport.IsValid() )
|
|
{
|
|
check(!GameViewportWidget.IsValid());
|
|
|
|
// When in single step mode, register the game viewport so we can unregister it later
|
|
// but do not do any of the other stuff like locking or capturing the mouse.
|
|
if( bLeaveDebugForSingleStep )
|
|
{
|
|
GameViewportWidget = PreviousGameViewport;
|
|
}
|
|
else
|
|
{
|
|
// If we had a game viewport before debugging, re-register it now to capture the mouse and lock the cursor
|
|
RegisterGameViewport( PreviousGameViewport.ToSharedRef() );
|
|
}
|
|
}
|
|
|
|
bLeaveDebugForSingleStep = false;
|
|
}
|
|
|
|
void FSlateApplication::LeaveDebuggingMode( bool bLeavingForSingleStep )
|
|
{
|
|
bRequestLeaveDebugMode = true;
|
|
bLeaveDebugForSingleStep = bLeavingForSingleStep;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
FSlateApplication::FScopedPreventDebuggingMode::FScopedPreventDebuggingMode(FText InReason)
|
|
{
|
|
static int32 IdGenerator = 0;
|
|
Id = ++IdGenerator;
|
|
FSlateApplication::Get().PreventDebuggingModeStack.Emplace(MoveTemp(InReason), Id);
|
|
}
|
|
|
|
FSlateApplication::FScopedPreventDebuggingMode::~FScopedPreventDebuggingMode()
|
|
{
|
|
int32 IndexToRemove = FSlateApplication::Get().PreventDebuggingModeStack.IndexOfByPredicate([this](const TPair<FText, int32>& Reference){ return Reference.Value == Id;});
|
|
if (ensure(IndexToRemove != INDEX_NONE))
|
|
{
|
|
FSlateApplication::Get().PreventDebuggingModeStack.RemoveAtSwap(IndexToRemove);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool FSlateApplication::IsWindowInDestroyQueue(TSharedRef<SWindow> Window) const
|
|
{
|
|
return WindowDestroyQueue.Contains(Window);
|
|
}
|
|
|
|
void FSlateApplication::SetUnhandledKeyDownEventHandler( const FOnKeyEvent& NewHandler )
|
|
{
|
|
UnhandledKeyDownEventHandler = NewHandler;
|
|
}
|
|
|
|
void FSlateApplication::SetUnhandledKeyUpEventHandler(const FOnKeyEvent& NewHandler)
|
|
{
|
|
UnhandledKeyUpEventHandler = NewHandler;
|
|
}
|
|
|
|
float FSlateApplication::GetDragTriggerDistance() const
|
|
{
|
|
return DragTriggerDistance;
|
|
}
|
|
|
|
float FSlateApplication::GetDragTriggerDistanceSquared() const
|
|
{
|
|
return DragTriggerDistance * DragTriggerDistance;
|
|
}
|
|
|
|
bool FSlateApplication::HasTraveledFarEnoughToTriggerDrag(const FPointerEvent& PointerEvent, const UE::Slate::FDeprecateVector2DParameter ScreenSpaceOrigin) const
|
|
{
|
|
return ( PointerEvent.GetScreenSpacePosition() - ScreenSpaceOrigin ).SizeSquared() >= ( DragTriggerDistance * DragTriggerDistance );
|
|
}
|
|
|
|
bool FSlateApplication::HasTraveledFarEnoughToTriggerDrag(const FPointerEvent& PointerEvent, const UE::Slate::FDeprecateVector2DParameter ScreenSpaceOrigin, EOrientation Orientation) const
|
|
{
|
|
if (Orientation == Orient_Horizontal)
|
|
{
|
|
return FMath::Abs(PointerEvent.GetScreenSpacePosition().X - ScreenSpaceOrigin.X) >= DragTriggerDistance;
|
|
}
|
|
else // Orientation == Orient_Vertical
|
|
{
|
|
return FMath::Abs(PointerEvent.GetScreenSpacePosition().Y - ScreenSpaceOrigin.Y) >= DragTriggerDistance;
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SetDragTriggerDistance( float ScreenPixels )
|
|
{
|
|
DragTriggerDistance = ScreenPixels;
|
|
}
|
|
|
|
bool FSlateApplication::RegisterInputPreProcessor(TSharedPtr<IInputProcessor> InputProcessor)
|
|
{
|
|
return RegisterInputPreProcessor(InputProcessor, FInputPreprocessorRegistrationKey());
|
|
}
|
|
|
|
bool FSlateApplication::RegisterInputPreProcessor(TSharedPtr<IInputProcessor> InputProcessor, const int32 Index)
|
|
{
|
|
return RegisterInputPreProcessor(InputProcessor, FInputPreprocessorRegistrationKey{ EInputPreProcessorType::Game, Index });
|
|
}
|
|
|
|
bool FSlateApplication::RegisterInputPreProcessor(TSharedPtr<IInputProcessor> InputProcessor, const EInputPreProcessorType Type)
|
|
{
|
|
return RegisterInputPreProcessor(InputProcessor, FInputPreprocessorRegistrationKey{ Type, INDEX_NONE });
|
|
}
|
|
|
|
bool FSlateApplication::RegisterInputPreProcessor(TSharedPtr<IInputProcessor> InputProcessor, const FInputPreprocessorRegistrationKey& Info)
|
|
{
|
|
bool bResult = false;
|
|
if (InputProcessor.IsValid())
|
|
{
|
|
bResult = InputPreProcessors.Add(FInputPreprocessorRegistration{ Info, InputProcessor.ToSharedRef() });
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
void FSlateApplication::UnregisterInputPreProcessor(TSharedPtr<IInputProcessor> InputProcessor)
|
|
{
|
|
InputPreProcessors.Remove(InputProcessor);
|
|
}
|
|
|
|
int32 FSlateApplication::FindInputPreProcessor(TSharedPtr<IInputProcessor> InputProcessor) const
|
|
{
|
|
return InputPreProcessors.Find(InputProcessor, EInputPreProcessorType::Game);
|
|
}
|
|
|
|
int32 FSlateApplication::FindInputPreProcessor(TSharedPtr<IInputProcessor> InputProcessor, const EInputPreProcessorType& Type) const
|
|
{
|
|
return InputPreProcessors.Find(InputProcessor, Type);
|
|
}
|
|
|
|
void FSlateApplication::SetCursorRadius(float NewRadius)
|
|
{
|
|
CursorRadius = FMath::Max<float>(0.0f, NewRadius);
|
|
}
|
|
|
|
float FSlateApplication::GetCursorRadius() const
|
|
{
|
|
return CursorRadius;
|
|
}
|
|
|
|
void FSlateApplication::SetAllowTooltips(bool bCanShow)
|
|
{
|
|
bEnableTooltips = bCanShow;
|
|
}
|
|
|
|
bool FSlateApplication::GetAllowTooltips() const
|
|
{
|
|
return bEnableTooltips;
|
|
}
|
|
|
|
UE::Slate::FDeprecateVector2DResult FSlateApplication::CalculateTooltipWindowPosition( const FSlateRect& InAnchorRect, const UE::Slate::FDeprecateVector2DParameter& InSize, bool bAutoAdjustForDPIScale, EPopupCursorOverlapMode CursorOverlapMode) const
|
|
{
|
|
// first use the CalculatePopupWindowPosition and if cursor is not inside it, proceed with it to avoid behavior change.
|
|
FVector2f PopupPosition = CalculatePopupWindowPosition(InAnchorRect, InSize, bAutoAdjustForDPIScale, UE::Math::TVector2<float>::ZeroVector, Orient_Vertical, EPopupLayoutMode::ToolTip);
|
|
FVector2f Cursor = GetCursorPos();
|
|
if (CursorOverlapMode == EPopupCursorOverlapMode::AllowOverlap || (PopupPosition.X > Cursor.X || PopupPosition.X + InSize.X < Cursor.X ||
|
|
PopupPosition.Y > Cursor.Y || PopupPosition.Y + InSize.Y < Cursor.Y))
|
|
{
|
|
return PopupPosition;
|
|
}
|
|
|
|
const FPlatformRect WorkAreaFinderRect (Cursor.X, Cursor.Y, Cursor.X + 1.0f, Cursor.Y + 1.0f);
|
|
const FPlatformRect PlatformWorkArea = PlatformApplication->GetWorkArea(WorkAreaFinderRect);
|
|
|
|
const FSlateRect WorkAreaRect(
|
|
PlatformWorkArea.Left,
|
|
PlatformWorkArea.Top,
|
|
PlatformWorkArea.Right,
|
|
PlatformWorkArea.Bottom);
|
|
|
|
float DPIScale = 1.0f;
|
|
|
|
if (bAutoAdjustForDPIScale)
|
|
{
|
|
DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(Cursor.X, Cursor.Y);
|
|
}
|
|
|
|
// We want the Tooltip to appear in a 'comfortable' distance. The following vector: 'TooltipCursorOffset'
|
|
// is used to move away from the cursor tip position. If we wouldn't do this the Tooltip would directly
|
|
// appear at the tip of the cursor. The coefficients 16 and 12 are estimated empirical.
|
|
const FVector2f TooltipCursorOffset(16 * DPIScale, 12 * DPIScale);
|
|
|
|
// Calculate the new position of the Tooltip by starting at the Top/Left corner.
|
|
FVector2f ToolTipLocation = Cursor - TooltipCursorOffset - InSize;
|
|
|
|
// Adjust the horizontal position so that it will be inside the work area.
|
|
if ( ToolTipLocation.X < WorkAreaRect.Left )
|
|
{
|
|
ToolTipLocation.X += (InSize.X + 2.0 * TooltipCursorOffset.X);
|
|
}
|
|
|
|
// Adjust the vertical position so that it will be inside the work area.
|
|
if ( ToolTipLocation.Y < WorkAreaRect.Top )
|
|
{
|
|
ToolTipLocation.Y += (InSize.Y + 2.0 * TooltipCursorOffset.Y);
|
|
}
|
|
|
|
return ToolTipLocation;
|
|
}
|
|
|
|
UE::Slate::FDeprecateVector2DResult FSlateApplication::CalculatePopupWindowPosition(const FSlateRect& InAnchor, const UE::Slate::FDeprecateVector2DParameter& InSize, bool bAutoAdjustForDPIScale, const UE::Slate::FDeprecateVector2DParameter& InProposedPlacement, const EOrientation Orientation, const EPopupLayoutMode LayoutMode) const
|
|
{
|
|
FVector2D CalculatedPopUpWindowPosition(0.f, 0.f);
|
|
|
|
float DPIScale = 1.0f;
|
|
|
|
if (bAutoAdjustForDPIScale)
|
|
{
|
|
DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(InAnchor.Left, InAnchor.Top);
|
|
}
|
|
|
|
FVector2f AdjustedSize = InSize * DPIScale;
|
|
|
|
FPlatformRect AnchorRect;
|
|
AnchorRect.Left = InAnchor.Left;
|
|
AnchorRect.Top = InAnchor.Top;
|
|
AnchorRect.Right = InAnchor.Right;
|
|
AnchorRect.Bottom = InAnchor.Bottom;
|
|
|
|
EPopUpOrientation::Type PopUpOrientation = EPopUpOrientation::Horizontal;
|
|
|
|
if (Orientation == EOrientation::Orient_Vertical)
|
|
{
|
|
PopUpOrientation = EPopUpOrientation::Vertical;
|
|
}
|
|
|
|
if (PlatformApplication->TryCalculatePopupWindowPosition(AnchorRect, FVector2D(AdjustedSize), FVector2D(InProposedPlacement), PopUpOrientation, /*OUT*/&CalculatedPopUpWindowPosition))
|
|
{
|
|
return UE::Slate::CastToVector2f(CalculatedPopUpWindowPosition / DPIScale);
|
|
}
|
|
else
|
|
{
|
|
// Calculate the rectangle around our work area
|
|
// Use our own rect. This window as probably doesn't have a size or position yet.
|
|
// Use a size of 1 to get the closest monitor to the start point
|
|
FPlatformRect WorkAreaFinderRect(AnchorRect);
|
|
WorkAreaFinderRect.Right = AnchorRect.Left + 1;
|
|
WorkAreaFinderRect.Bottom = AnchorRect.Top + 1;
|
|
const FPlatformRect PlatformWorkArea = PlatformApplication->GetWorkArea(WorkAreaFinderRect);
|
|
|
|
const FSlateRect WorkAreaRect(
|
|
PlatformWorkArea.Left,
|
|
PlatformWorkArea.Top,
|
|
PlatformWorkArea.Right,
|
|
PlatformWorkArea.Bottom);
|
|
|
|
FVector2f ProposedPlacement = InProposedPlacement;
|
|
|
|
if (ProposedPlacement.IsZero())
|
|
{
|
|
// Assume natural left-to-right, top-to-bottom flow; position popup below and to the right.
|
|
ProposedPlacement = FVector2f(
|
|
Orientation == Orient_Horizontal ? AnchorRect.Right : AnchorRect.Left,
|
|
Orientation == Orient_Horizontal ? AnchorRect.Top : AnchorRect.Bottom);
|
|
}
|
|
|
|
return ComputePopupFitInRect(InAnchor, FSlateRect(ProposedPlacement, ProposedPlacement + AdjustedSize), Orientation, WorkAreaRect, /*bAllowFlip*/LayoutMode == EPopupLayoutMode::Menu) / DPIScale;
|
|
}
|
|
}
|
|
|
|
bool FSlateApplication::IsRunningAtTargetFrameRate() const
|
|
{
|
|
const float MinimumDeltaTime = 1.0f / TargetFrameRateForResponsiveness.GetValueOnGameThread();
|
|
return ( AverageDeltaTimeForResponsiveness <= MinimumDeltaTime ) || !IsNormalExecution();
|
|
}
|
|
|
|
|
|
bool FSlateApplication::AreMenuAnimationsEnabled() const
|
|
{
|
|
return bMenuAnimationsEnabled;
|
|
}
|
|
|
|
|
|
void FSlateApplication::EnableMenuAnimations( const bool bEnableAnimations )
|
|
{
|
|
bMenuAnimationsEnabled = bEnableAnimations;
|
|
}
|
|
|
|
|
|
void FSlateApplication::SetAppIcon(const FSlateBrush* const InAppIcon)
|
|
{
|
|
check(InAppIcon);
|
|
AppIcon = InAppIcon;
|
|
}
|
|
|
|
|
|
const FSlateBrush* FSlateApplication::GetAppIcon() const
|
|
{
|
|
static FName AppIconName("AppIcon");
|
|
return FAppStyle::Get().GetBrush(AppIconName);
|
|
}
|
|
|
|
const FSlateBrush* FSlateApplication::GetAppIconSmall() const
|
|
{
|
|
static FName AppIconName("AppIcon.Small");
|
|
return FAppStyle::Get().GetBrush(AppIconName);
|
|
}
|
|
|
|
void FSlateApplication::ShowVirtualKeyboard( bool bShow, int32 UserIndex, TSharedPtr<IVirtualKeyboardEntry> TextEntryWidget )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ShowVirtualKeyboard);
|
|
|
|
if (!SlateTextField.IsValid())
|
|
{
|
|
SlateTextField = MakeUnique<FPlatformTextField>();
|
|
}
|
|
|
|
SlateTextField->ShowVirtualKeyboard(bShow, UserIndex, TextEntryWidget);
|
|
}
|
|
|
|
bool FSlateApplication::AllowMoveCursor()
|
|
{
|
|
if (!SlateTextField.IsValid())
|
|
{
|
|
SlateTextField = MakeUnique<FPlatformTextField>();
|
|
}
|
|
|
|
return SlateTextField->AllowMoveCursor();
|
|
}
|
|
|
|
FSlateRect FSlateApplication::GetPreferredWorkArea() const
|
|
{
|
|
if (TSharedPtr<const FSlateUser> KeyboardUser = GetUser(GetUserIndexForKeyboard()))
|
|
{
|
|
const FWeakWidgetPath& FocusedWidgetPath = KeyboardUser->GetWeakFocusPath();
|
|
|
|
// First see if we have a focused widget
|
|
if (FocusedWidgetPath.IsValid() && FocusedWidgetPath.Window.IsValid())
|
|
{
|
|
const FVector2f WindowPos = FocusedWidgetPath.Window.Pin()->GetPositionInScreen();
|
|
const FVector2f WindowSize = FocusedWidgetPath.Window.Pin()->GetSizeInScreen();
|
|
return GetWorkArea(FSlateRect(WindowPos.X, WindowPos.Y, WindowPos.X + WindowSize.X, WindowPos.Y + WindowSize.Y));
|
|
}
|
|
|
|
// no focus widget, so use cursor position if there are windows present in the work area
|
|
const FVector2f CursorPos = KeyboardUser->GetCursorPosition();
|
|
const FSlateRect WorkArea = GetWorkArea(FSlateRect(CursorPos.X, CursorPos.Y, CursorPos.X + 1.0f, CursorPos.Y + 1.0f));
|
|
|
|
if (FSlateWindowHelper::CheckWorkAreaForWindows(SlateWindows, WorkArea))
|
|
{
|
|
return WorkArea;
|
|
}
|
|
|
|
// If we can't find a window where the cursor is at, try finding a main window.
|
|
if (TSharedPtr<SWindow> ActiveTop = GetActiveTopLevelWindow())
|
|
{
|
|
// Use the current top level windows rect
|
|
return GetWorkArea(ActiveTop->GetRectInScreen());
|
|
}
|
|
|
|
// If we can't find a top level window check for an active modal window
|
|
if (TSharedPtr<SWindow> ActiveModal = GetActiveModalWindow())
|
|
{
|
|
// Use the current active modal windows rect
|
|
return GetWorkArea(ActiveModal->GetRectInScreen());
|
|
}
|
|
}
|
|
|
|
// Either there is no keyboard user or there are no windows on work area - fall back to primary display
|
|
FDisplayMetrics DisplayMetrics;
|
|
GetCachedDisplayMetrics(DisplayMetrics);
|
|
|
|
const FPlatformRect& DisplayRect = DisplayMetrics.PrimaryDisplayWorkAreaRect;
|
|
return FSlateRect((float)DisplayRect.Left, (float)DisplayRect.Top, (float)DisplayRect.Right, (float)DisplayRect.Bottom);
|
|
}
|
|
|
|
FSlateRect FSlateApplication::GetWorkArea( const FSlateRect& InRect ) const
|
|
{
|
|
FPlatformRect InPlatformRect;
|
|
InPlatformRect.Left = FMath::TruncToInt(InRect.Left);
|
|
InPlatformRect.Top = FMath::TruncToInt(InRect.Top);
|
|
InPlatformRect.Right = FMath::TruncToInt(InRect.Right);
|
|
InPlatformRect.Bottom = FMath::TruncToInt(InRect.Bottom);
|
|
|
|
const FPlatformRect OutPlatformRect = PlatformApplication->GetWorkArea( InPlatformRect );
|
|
return FSlateRect( OutPlatformRect.Left, OutPlatformRect.Top, OutPlatformRect.Right, OutPlatformRect.Bottom );
|
|
}
|
|
|
|
bool FSlateApplication::SupportsSourceAccess() const
|
|
{
|
|
if(QuerySourceCodeAccessDelegate.IsBound())
|
|
{
|
|
return QuerySourceCodeAccessDelegate.Execute();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FSlateApplication::GotoLineInSource(const FString& FileName, int32 LineNumber) const
|
|
{
|
|
if ( SupportsSourceAccess() )
|
|
{
|
|
if(SourceCodeAccessDelegate.IsBound())
|
|
{
|
|
SourceCodeAccessDelegate.Execute(FileName, LineNumber, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::ForceRedrawWindow(const TSharedRef<SWindow>& InWindowToDraw)
|
|
{
|
|
PrivateDrawWindows( InWindowToDraw );
|
|
}
|
|
|
|
bool FSlateApplication::TakeScreenshot(const TSharedRef<SWidget>& Widget, TArray<FColor>&OutColorData, FIntVector& OutSize)
|
|
{
|
|
return TakeScreenshot(Widget, FIntRect(), OutColorData, OutSize);
|
|
}
|
|
|
|
bool FSlateApplication::TakeHDRScreenshot(const TSharedRef<SWidget>& Widget, TArray<FLinearColor>& OutColorData, FIntVector& OutSize)
|
|
{
|
|
return TakeHDRScreenshot(Widget, FIntRect(), OutColorData, OutSize);
|
|
}
|
|
|
|
void TakeScreenshotCommon(const TSharedRef<SWidget>& Widget, const FIntRect& InnerWidgetArea, FIntRect& ScreenshotRect, SWindow* WidgetWindow)
|
|
{
|
|
FWidgetPath WidgetPath;
|
|
FSlateApplication::Get().GeneratePathToWidgetChecked(Widget, WidgetPath);
|
|
|
|
FArrangedWidget ArrangedWidget = WidgetPath.FindArrangedWidget(Widget).Get(FArrangedWidget::GetNullWidget());
|
|
FVector2f Position = FVector2f(ArrangedWidget.Geometry.AbsolutePosition);
|
|
FVector2f Size = ArrangedWidget.Geometry.GetDrawSize();
|
|
FVector2f WindowPosition = WidgetWindow->GetPositionInScreen();
|
|
|
|
ScreenshotRect = InnerWidgetArea.IsEmpty() ? FIntRect(0, 0, (int32)Size.X, (int32)Size.Y) : InnerWidgetArea;
|
|
|
|
ScreenshotRect.Min.X += ( Position.X - WindowPosition.X );
|
|
ScreenshotRect.Min.Y += ( Position.Y - WindowPosition.Y );
|
|
ScreenshotRect.Max.X += ( Position.X - WindowPosition.X );
|
|
ScreenshotRect.Max.Y += ( Position.Y - WindowPosition.Y );
|
|
}
|
|
|
|
bool FSlateApplication::TakeScreenshot(const TSharedRef<SWidget>& Widget, const FIntRect& InnerWidgetArea, TArray<FColor>& OutColorData, FIntVector& OutSize)
|
|
{
|
|
// We can't screenshot the widget unless there's a valid window handle to draw it in.
|
|
TSharedPtr<SWindow> WidgetWindow = FSlateApplication::Get().FindWidgetWindow(Widget);
|
|
if (!WidgetWindow.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<SWindow> CurrentWindowRef = WidgetWindow.ToSharedRef();
|
|
|
|
FIntRect ScreenshotRect;
|
|
TakeScreenshotCommon(Widget, InnerWidgetArea, ScreenshotRect, WidgetWindow.Get());
|
|
|
|
Renderer->PrepareToTakeScreenshot(ScreenshotRect, &OutColorData, WidgetWindow.Get());
|
|
PrivateDrawWindows(WidgetWindow);
|
|
|
|
OutSize.X = ScreenshotRect.Size().X;
|
|
OutSize.Y = ScreenshotRect.Size().Y;
|
|
|
|
return (OutSize.X != 0 && OutSize.Y != 0 && OutColorData.Num() >= OutSize.X * OutSize.Y);
|
|
}
|
|
|
|
bool FSlateApplication::TakeHDRScreenshot(const TSharedRef<SWidget>& Widget, const FIntRect& InnerWidgetArea, TArray<FLinearColor>& OutColorData, FIntVector& OutSize)
|
|
{
|
|
// We can't screenshot the widget unless there's a valid window handle to draw it in.
|
|
TSharedPtr<SWindow> WidgetWindow = FSlateApplication::Get().FindWidgetWindow(Widget);
|
|
if (!WidgetWindow.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<SWindow> CurrentWindowRef = WidgetWindow.ToSharedRef();
|
|
|
|
FIntRect ScreenshotRect;
|
|
TakeScreenshotCommon(Widget, InnerWidgetArea, ScreenshotRect, WidgetWindow.Get());
|
|
|
|
Renderer->PrepareToTakeHDRScreenshot(ScreenshotRect, &OutColorData, WidgetWindow.Get());
|
|
PrivateDrawWindows(WidgetWindow);
|
|
|
|
OutSize.X = ScreenshotRect.Size().X;
|
|
OutSize.Y = ScreenshotRect.Size().Y;
|
|
|
|
return (OutSize.X != 0 && OutSize.Y != 0 && OutColorData.Num() >= OutSize.X * OutSize.Y);
|
|
}
|
|
|
|
TSharedPtr<FSlateUser> FSlateApplication::GetUser(FPlatformUserId PlatformUser)
|
|
{
|
|
int32 InternalId = 0;
|
|
if (PlatformUser.IsValid())
|
|
{
|
|
InternalId = PlatformUser.GetInternalId();
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("SlateApplication::GetUser called with an invalid platform user! Defaulting to 0"));
|
|
}
|
|
return Users.IsValidIndex(InternalId) ? Users[InternalId] : nullptr;
|
|
}
|
|
|
|
TSharedPtr<FSlateUser> FSlateApplication::GetUserFromPlatformUser(FPlatformUserId PlatformUser)
|
|
{
|
|
TOptional<int32> UserIndex = GetUserIndexForPlatformUser(PlatformUser);
|
|
if (UserIndex.IsSet())
|
|
{
|
|
return GetUser(UserIndex.GetValue());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedPtr<const FSlateUser> FSlateApplication::GetUserFromPlatformUser(FPlatformUserId PlatformUser) const
|
|
{
|
|
TOptional<int32> UserIndex = GetUserIndexForPlatformUser(PlatformUser);
|
|
if (UserIndex.IsSet())
|
|
{
|
|
return GetUser(UserIndex.GetValue());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
TSharedRef<FSlateVirtualUserHandle> FSlateApplication::FindOrCreateVirtualUser(int32 VirtualUserIndex)
|
|
{
|
|
// Ensure we have a large enough array to add the new virtual user
|
|
if ( VirtualUserIndex >= VirtualUsers.Num() )
|
|
{
|
|
VirtualUsers.SetNum(VirtualUserIndex + 1);
|
|
}
|
|
|
|
TSharedPtr<FSlateVirtualUserHandle> VirtualUserHandle = VirtualUsers[VirtualUserIndex].Pin();
|
|
if (!VirtualUserHandle.IsValid())
|
|
{
|
|
// Register at the next available user index (beyond those potentially occupied by real users)
|
|
int32 NextVirtualUserIndex = SlateApplicationDefs::MaxHardwareUsers;
|
|
while (GetUser(NextVirtualUserIndex))
|
|
{
|
|
NextVirtualUserIndex++;
|
|
}
|
|
|
|
TSharedRef<FSlateUser> NewUser = RegisterNewUser(NextVirtualUserIndex, true);
|
|
|
|
// Make a virtual user handle that will unregister the virtual user upon destruction
|
|
VirtualUserHandle = MakeShareable(new FSlateVirtualUserHandle(NewUser->GetUserIndex(), VirtualUserIndex));
|
|
|
|
// Update the virtual user array, so we can get this user back later.
|
|
VirtualUsers[VirtualUserIndex] = VirtualUserHandle;
|
|
}
|
|
|
|
return VirtualUserHandle.ToSharedRef();
|
|
}
|
|
|
|
TSharedRef<FSlateUser> FSlateApplication::GetOrCreateUser(int32 UserIndex)
|
|
{
|
|
if (TSharedPtr<FSlateUser> FoundUser = GetUser(UserIndex))
|
|
{
|
|
return FoundUser.ToSharedRef();
|
|
}
|
|
return RegisterNewUser(UserIndex);
|
|
}
|
|
|
|
TSharedRef<FSlateUser> FSlateApplication::GetOrCreateUser(FPlatformUserId PlatformUserId)
|
|
{
|
|
if (TSharedPtr<FSlateUser> FoundUser = GetUser(PlatformUserId))
|
|
{
|
|
return FoundUser.ToSharedRef();
|
|
}
|
|
return RegisterNewUser(PlatformUserId);
|
|
}
|
|
|
|
TSharedRef<FSlateUser> FSlateApplication::GetOrCreateUser(FInputDeviceId DeviceId)
|
|
{
|
|
// Get a user based on the owning platform user of this input device
|
|
return GetOrCreateUser(IPlatformInputDeviceMapper::Get().GetUserForInputDevice(DeviceId));
|
|
}
|
|
|
|
TSharedRef<FSlateUser> FSlateApplication::RegisterNewUser(int32 UserIndex, bool bIsVirtual)
|
|
{
|
|
return RegisterNewUser(FGenericPlatformMisc::GetPlatformUserForUserIndex(UserIndex), bIsVirtual);
|
|
}
|
|
|
|
TSharedRef<FSlateUser> FSlateApplication::RegisterNewUser(FPlatformUserId PlatformUserId, bool bIsVirtual)
|
|
{
|
|
int32 UserIndex = PlatformUserId.GetInternalId();
|
|
|
|
// We tolerate no shenanigans with inappropriate arguments here
|
|
// New users must be registered at a valid non-negative index that is not already occupied by another user
|
|
check(UserIndex >= 0);
|
|
check(!Users.IsValidIndex(UserIndex) || !Users[UserIndex]);
|
|
|
|
TSharedPtr<ICursor> UserCursor;
|
|
if (UserIndex == CursorUserIndex && PlatformApplication && PlatformApplication->Cursor)
|
|
{
|
|
check(!bIsVirtual);
|
|
UserCursor = PlatformApplication->Cursor;
|
|
}
|
|
else if (!bIsVirtual)
|
|
{
|
|
// Real users all control a cursor, but there's only one platform cursor (and it *always* belongs to the 0th user)
|
|
// Everyone else gets a faux cursor instead
|
|
UserCursor = MakeShared<FFauxSlateCursor>();
|
|
}
|
|
|
|
TSharedRef<FSlateUser> NewUser = FSlateUser::Create(UserIndex, UserCursor);
|
|
|
|
// Ensure we have a large enough array to add the new User
|
|
if (UserIndex >= Users.Num())
|
|
{
|
|
Users.SetNum(UserIndex + 1);
|
|
}
|
|
|
|
Users[UserIndex] = NewUser;
|
|
|
|
// Apply the last known "all users" focus widget path to this new user if they do not have a specific one
|
|
if (!NewUser->HasValidFocusPath() && LastAllUsersFocusWidget.IsValid())
|
|
{
|
|
SetUserFocus(NewUser->GetUserIndex(), LastAllUsersFocusWidget.Pin(), LastAllUsersFocusCause);
|
|
}
|
|
|
|
UE_LOG(LogSlate, Log, TEXT("Slate User Registered. User Index %d, Is Virtual User: %d"), UserIndex, bIsVirtual);
|
|
UserRegisteredEvent.Broadcast(UserIndex);
|
|
|
|
return NewUser;
|
|
}
|
|
|
|
void FSlateApplication::UnregisterUser(int32 UserIndex)
|
|
{
|
|
if ( UserIndex < Users.Num() )
|
|
{
|
|
UE_LOG(LogSlate, Log, TEXT("Slate User Unregistered. User Index %d"), UserIndex);
|
|
|
|
ClearUserFocus(UserIndex, EFocusCause::SetDirectly);
|
|
Users[UserIndex].Reset();
|
|
|
|
NavigationConfig->OnUserRemoved(UserIndex);
|
|
#if WITH_EDITOR
|
|
EditorNavigationConfig->OnUserRemoved(UserIndex);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::ForEachUser(TFunctionRef<void(FSlateUser&)> InPredicate, bool bIncludeVirtualUsers)
|
|
{
|
|
for (const TSharedPtr<FSlateUser>& User : Users)
|
|
{
|
|
// Ignore virtual users unless told not to.
|
|
if (User && (bIncludeVirtualUsers || !User->IsVirtualUser()))
|
|
{
|
|
InPredicate(*User);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::ForEachUser(TFunctionRef<void(FSlateUser*)> InPredicate, bool bIncludeVirtualUsers /*= false*/)
|
|
{
|
|
ForEachUser([&InPredicate](FSlateUser& User)
|
|
{
|
|
InPredicate(&User);
|
|
}, bIncludeVirtualUsers);
|
|
}
|
|
|
|
void FSlateApplication::SetFixedDeltaTime(double InSeconds)
|
|
{
|
|
FixedDeltaTime = InSeconds;
|
|
}
|
|
/* FSlateApplicationBase interface
|
|
*****************************************************************************/
|
|
|
|
UE::Slate::FDeprecateVector2DResult FSlateApplication::GetCursorSize( ) const
|
|
{
|
|
if ( PlatformApplication->Cursor.IsValid() )
|
|
{
|
|
int32 X;
|
|
int32 Y;
|
|
PlatformApplication->Cursor->GetSize( X, Y );
|
|
return FVector2f( static_cast<float>(X), static_cast<float>(Y) );
|
|
}
|
|
|
|
return FVector2f( 1.0f, 1.0f );
|
|
}
|
|
|
|
EVisibility FSlateApplication::GetSoftwareCursorVis( ) const
|
|
{
|
|
const TSharedPtr<ICursor>& Cursor = PlatformApplication->Cursor;
|
|
if (bSoftwareCursorAvailable && Cursor.IsValid() && Cursor->GetType() != EMouseCursor::None)
|
|
{
|
|
return EVisibility::HitTestInvisible;
|
|
}
|
|
return EVisibility::Hidden;
|
|
}
|
|
|
|
TSharedPtr<SWidget> FSlateApplication::GetKeyboardFocusedWidget() const
|
|
{
|
|
TSharedPtr<const FSlateUser> KeyboardUser = GetUser(GetUserIndexForKeyboard());
|
|
return KeyboardUser ? KeyboardUser->GetFocusedWidget() : nullptr;
|
|
}
|
|
|
|
TSharedPtr<SWidget> FSlateApplication::GetMouseCaptorImpl() const
|
|
{
|
|
return GetCursorUser()->GetCursorCaptor();
|
|
}
|
|
|
|
bool FSlateApplication::HasAnyMouseCaptor() const
|
|
{
|
|
for (const TSharedPtr<FSlateUser>& User : Users)
|
|
{
|
|
if (User && User->HasAnyCapture())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::HasUserMouseCapture(int32 UserIndex) const
|
|
{
|
|
TSharedPtr<const FSlateUser> FoundUser = GetUser(UserIndex);
|
|
return FoundUser && FoundUser->HasAnyCapture();
|
|
}
|
|
|
|
FPointerEvent FSlateApplication::TransformPointerEvent(const FPointerEvent& PointerEvent, const TSharedPtr<SWindow>& Window) const
|
|
{
|
|
FPointerEvent TransformedPointerEvent = PointerEvent;
|
|
if (Window)
|
|
{
|
|
if (TransformFullscreenMouseInput && !GIsEditor && Window->GetWindowMode() == EWindowMode::Fullscreen)
|
|
{
|
|
// Screen space mapping scales everything. When window resolution doesn't match platform resolution,
|
|
// this causes offset cursor hit-tests in fullscreen. Correct in slate since we are first window-aware slate processor.
|
|
FVector2f WindowSize = Window->GetSizeInScreen();
|
|
FVector2f DisplaySize = { (float)CachedDisplayMetrics.PrimaryDisplayWidth, (float)CachedDisplayMetrics.PrimaryDisplayHeight };
|
|
|
|
TransformedPointerEvent = FPointerEvent(PointerEvent, PointerEvent.GetScreenSpacePosition() * WindowSize / DisplaySize, PointerEvent.GetLastScreenSpacePosition() * WindowSize / DisplaySize);
|
|
}
|
|
}
|
|
|
|
return TransformedPointerEvent;
|
|
}
|
|
|
|
bool FSlateApplication::DoesWidgetHaveMouseCaptureByUser(const TSharedPtr<const SWidget> Widget, int32 UserIndex, TOptional<int32> PointerIndex) const
|
|
{
|
|
if (TSharedPtr<const FSlateUser> FoundUser = GetUser(UserIndex))
|
|
{
|
|
return PointerIndex.IsSet() ? FoundUser->DoesWidgetHaveCapture(Widget, PointerIndex.GetValue()) : FoundUser->DoesWidgetHaveAnyCapture(Widget);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::DoesWidgetHaveMouseCapture(const TSharedPtr<const SWidget> Widget) const
|
|
{
|
|
for (const TSharedPtr<FSlateUser>& User : Users)
|
|
{
|
|
if (User && User->DoesWidgetHaveAnyCapture(Widget))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TOptional<EFocusCause> FSlateApplication::HasUserFocus(const TSharedPtr<const SWidget> Widget, int32 UserIndex) const
|
|
{
|
|
TSharedPtr<const FSlateUser> FoundUser = GetUser(UserIndex);
|
|
return FoundUser ? FoundUser->HasFocus(Widget) : TOptional<EFocusCause>();
|
|
}
|
|
|
|
TOptional<EFocusCause> FSlateApplication::HasAnyUserFocus(const TSharedPtr<const SWidget> Widget) const
|
|
{
|
|
TOptional<EFocusCause> FocusCause;
|
|
for (const TSharedPtr<FSlateUser>& User : Users)
|
|
{
|
|
FocusCause = User ? User->HasFocus(Widget) : TOptional<EFocusCause>();
|
|
if (FocusCause.IsSet())
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return FocusCause;
|
|
}
|
|
|
|
bool FSlateApplication::IsWidgetDirectlyHovered(const TSharedPtr<const SWidget> Widget) const
|
|
{
|
|
for (const TSharedPtr<FSlateUser>& User : Users)
|
|
{
|
|
if (User && User->IsWidgetDirectlyUnderAnyPointer(Widget))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::ShowUserFocus(const TSharedPtr<const SWidget> Widget) const
|
|
{
|
|
for (const TSharedPtr<FSlateUser>& User : Users)
|
|
{
|
|
if (User && User->ShouldShowFocus(Widget))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<FNavigationConfig> FSlateApplication::GetRelevantNavConfig(int32 UserIndex) const
|
|
{
|
|
TSharedPtr<FNavigationConfig> RelevantNavConfig = NavigationConfig;
|
|
if (TSharedPtr<const FSlateUser> User = GetUser(UserIndex))
|
|
{
|
|
#if WITH_EDITOR
|
|
// Check if the focused widget is an editor widget or a PIE widget so we know which config to use.
|
|
if (UserIndex == GetUserIndexForKeyboard() && !IsFocusInViewport(AllGameViewports, User->GetWeakFocusPath()))
|
|
{
|
|
RelevantNavConfig = EditorNavigationConfig;
|
|
}
|
|
else
|
|
#endif
|
|
if (TSharedPtr<FNavigationConfig> UserNavConfig = User->GetUserNavigationConfig())
|
|
{
|
|
// Use the user's personal config if it has one assigned
|
|
RelevantNavConfig = UserNavConfig;
|
|
}
|
|
}
|
|
|
|
return RelevantNavConfig.ToSharedRef();
|
|
}
|
|
|
|
bool FSlateApplication::HasUserFocusedDescendants(const TSharedRef< const SWidget >& Widget, int32 UserIndex) const
|
|
{
|
|
TSharedPtr<const FSlateUser> User = GetUser(UserIndex);
|
|
return User && User->HasFocusedDescendants(Widget);
|
|
}
|
|
|
|
bool FSlateApplication::HasFocusedDescendants( const TSharedRef< const SWidget >& Widget ) const
|
|
{
|
|
for (const TSharedPtr<FSlateUser>& User : Users)
|
|
{
|
|
if (User && User->HasFocusedDescendants(Widget))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::IsExternalUIOpened()
|
|
{
|
|
return bIsExternalUIOpened;
|
|
}
|
|
|
|
|
|
TSharedRef<SImage> FSlateApplication::MakeImage( const TAttribute<const FSlateBrush*>& Image, const TAttribute<FSlateColor>& Color, const TAttribute<EVisibility>& Visibility ) const
|
|
{
|
|
return SNew(SImage)
|
|
.ColorAndOpacity(Color)
|
|
.Image(Image)
|
|
.Visibility(Visibility);
|
|
}
|
|
|
|
|
|
TSharedRef<SWidget> FSlateApplication::MakeWindowTitleBar(const FWindowTitleBarArgs& InArgs, TSharedPtr<IWindowTitleBar>& OutTitleBar) const
|
|
{
|
|
TSharedRef<SWindowTitleBar> TitleBar = SNew(SWindowTitleBar, InArgs.Window, InArgs.CenterContent, InArgs.CenterContentAlignment)
|
|
.Visibility(EVisibility::SelfHitTestInvisible)
|
|
.CloseButtonToolTipText(InArgs.CloseButtonToolTipText);
|
|
|
|
OutTitleBar = TitleBar;
|
|
|
|
return TitleBar;
|
|
}
|
|
|
|
|
|
TSharedRef<IToolTip> FSlateApplication::MakeToolTip(const TAttribute<FText>& ToolTipText)
|
|
{
|
|
return SNew(SToolTip)
|
|
.Text(ToolTipText);
|
|
}
|
|
|
|
|
|
TSharedRef<IToolTip> FSlateApplication::MakeToolTip( const FText& ToolTipText )
|
|
{
|
|
return SNew(SToolTip)
|
|
.Text(ToolTipText);
|
|
}
|
|
|
|
/* FGenericApplicationMessageHandler interface
|
|
*****************************************************************************/
|
|
|
|
bool FSlateApplication::ShouldProcessUserInputMessages( const TSharedPtr< FGenericWindow >& PlatformWindow ) const
|
|
{
|
|
TSharedPtr< SWindow > Window;
|
|
if ( PlatformWindow.IsValid() )
|
|
{
|
|
Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow.ToSharedRef() );
|
|
}
|
|
|
|
if (ActiveModalWindows.Num() == 0 ||
|
|
(Window.IsValid() &&
|
|
(Window->IsDescendantOf(GetActiveModalWindow()) || ActiveModalWindows.Top() == Window || IsWindowHousingInteractiveTooltip(Window.ToSharedRef()))))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::OnKeyChar( const TCHAR Character, const bool IsRepeat )
|
|
{
|
|
FCharacterEvent CharacterEvent( Character, PlatformApplication->GetModifierKeys(), 0, IsRepeat );
|
|
return ProcessKeyCharEvent( CharacterEvent );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessKeyCharEvent( const FCharacterEvent& InCharacterEvent )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyChar);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::KeyChar, InCharacterEvent);
|
|
#endif
|
|
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
// NOTE: We intentionally don't reset LastUserInteractionTimeForThrottling here so that the UI can be responsive while typing
|
|
|
|
// Bubble the key event
|
|
TSharedRef<FSlateUser> User = GetOrCreateUser(InCharacterEvent);
|
|
TSharedRef<FWidgetPath> EventPathRef = User->GetFocusPath();
|
|
const FWidgetPath& EventPath = EventPathRef.Get();
|
|
|
|
// Switch worlds for widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(EventPath);
|
|
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyChar_RouteAlongFocusPath);
|
|
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InCharacterEvent, [](const FArrangedWidget& SomeWidgetGettingEvent, const FCharacterEvent& Event)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyChar_Call_OnKeyChar);
|
|
|
|
if (SomeWidgetGettingEvent.Widget->IsEnabled())
|
|
{
|
|
const FReply TempReply = SomeWidgetGettingEvent.Widget->OnKeyChar(SomeWidgetGettingEvent.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::KeyChar, &Event, TempReply, SomeWidgetGettingEvent.Widget, Event.GetCharacter());
|
|
#endif
|
|
return TempReply;
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}, ESlateDebuggingInputEvent::KeyChar);
|
|
}
|
|
|
|
return Reply.IsEventHandled();
|
|
}
|
|
|
|
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 );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessKeyDownEvent( const FKeyEvent& InKeyEvent )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyDown);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::KeyDown, InKeyEvent);
|
|
#endif
|
|
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(InKeyEvent);
|
|
|
|
#if WITH_EDITOR
|
|
//Send the key input to all pre input key down listener function
|
|
if (OnApplicationPreInputKeyDownListenerEvent.IsBound())
|
|
{
|
|
OnApplicationPreInputKeyDownListenerEvent.Broadcast(InKeyEvent);
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
// 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
|
|
// Bubble the keyboard event
|
|
TSharedRef<FWidgetPath> EventPathRef = SlateUser->GetFocusPath();
|
|
const FWidgetPath& EventPath = EventPathRef.Get();
|
|
|
|
// Switch worlds for widgets in 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);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::PreviewKeyDown, &Event, TempReply, CurrentWidget.Widget, Event.GetKey().GetFName());
|
|
#endif
|
|
return TempReply;
|
|
}
|
|
else
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastNoReplyInputEvent(ESlateDebuggingInputEvent::PreviewKeyDown, &Event, CurrentWidget.Widget);
|
|
#endif
|
|
}
|
|
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);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::KeyDown, &Event, TempReply, SomeWidgetGettingEvent.Widget, Event.GetKey().GetFName());
|
|
#endif
|
|
return TempReply;
|
|
}
|
|
else
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastNoReplyInputEvent(ESlateDebuggingInputEvent::KeyDown, &Event, SomeWidgetGettingEvent.Widget);
|
|
#endif
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
bool FSlateApplication::OnKeyUp( 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 ProcessKeyUpEvent( KeyEvent );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessKeyUpEvent( const FKeyEvent& InKeyEvent )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessKeyUp);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::KeyUp, InKeyEvent);
|
|
#endif
|
|
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
// Analog cursor gets first chance at the input
|
|
if (InputPreProcessors.HandleKeyUpEvent(*this, InKeyEvent))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
|
|
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
|
|
|
|
// Bubble the key event
|
|
TSharedRef<FWidgetPath> EventPathRef = GetOrCreateUser(InKeyEvent)->GetFocusPath();
|
|
const FWidgetPath& EventPath = EventPathRef.Get();
|
|
|
|
// Switch worlds for widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(EventPath);
|
|
|
|
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), InKeyEvent, [](const FArrangedWidget& SomeWidgetGettingEvent, const FKeyEvent& Event)
|
|
{
|
|
if (SomeWidgetGettingEvent.Widget->IsEnabled())
|
|
{
|
|
const FReply TempReply = SomeWidgetGettingEvent.Widget->OnKeyUp(SomeWidgetGettingEvent.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::KeyUp, &Event, TempReply, SomeWidgetGettingEvent.Widget, Event.GetKey().ToString());
|
|
#endif
|
|
return TempReply;
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}, ESlateDebuggingInputEvent::KeyUp);
|
|
|
|
// If the key event was not processed by any widget...
|
|
if (!Reply.IsEventHandled() && UnhandledKeyUpEventHandler.IsBound())
|
|
{
|
|
Reply = UnhandledKeyUpEventHandler.Execute(InKeyEvent);
|
|
}
|
|
|
|
return Reply.IsEventHandled();
|
|
}
|
|
|
|
void FSlateApplication::OnInputLanguageChanged()
|
|
{
|
|
FInputKeyManager::Get().InitKeyMappings();
|
|
}
|
|
|
|
bool FSlateApplication::ProcessAnalogInputEvent(const FAnalogInputEvent& InAnalogInputEvent)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessAnalogInput);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::AnalogInput, InAnalogInputEvent);
|
|
#endif
|
|
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
// Analog cursor gets first chance at the input
|
|
if (InputPreProcessors.HandleAnalogInputEvent(*this, InAnalogInputEvent))
|
|
{
|
|
Reply = FReply::Handled();
|
|
}
|
|
|
|
if (!Reply.IsEventHandled())
|
|
{
|
|
TSharedRef<FWidgetPath> EventPathRef = GetOrCreateUser(InAnalogInputEvent)->GetFocusPath();
|
|
const FWidgetPath& EventPath = EventPathRef.Get();
|
|
|
|
FAnalogInputEvent ModifiedEvent(InAnalogInputEvent);
|
|
ModifiedEvent.SetEventPath(EventPath);
|
|
|
|
// Switch worlds for widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(EventPath);
|
|
|
|
Reply = FEventRouter::RouteAlongFocusPath(this, FEventRouter::FBubblePolicy(EventPath), ModifiedEvent, [](const FArrangedWidget& SomeWidgetGettingEvent, const FAnalogInputEvent& Event)
|
|
{
|
|
if (SomeWidgetGettingEvent.Widget->IsEnabled())
|
|
{
|
|
const FReply TempReply = SomeWidgetGettingEvent.Widget->OnAnalogValueChanged(SomeWidgetGettingEvent.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::AnalogInput, &Event, TempReply, SomeWidgetGettingEvent.Widget, Event.GetKey().ToString());
|
|
#endif
|
|
return TempReply;
|
|
}
|
|
|
|
return FReply::Unhandled();
|
|
}, ESlateDebuggingInputEvent::AnalogInput);
|
|
}
|
|
|
|
// Ensure the analog input event exceeds the thresholds set in the navigation config before considering as interaction.
|
|
const TSharedRef<FNavigationConfig> RelevantNavConfig = GetRelevantNavConfig(InAnalogInputEvent.GetUserIndex());
|
|
if (RelevantNavConfig->IsAnalogEventBeyondNavigationThreshold(InAnalogInputEvent))
|
|
{
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
|
|
}
|
|
|
|
return Reply.IsEventHandled();
|
|
}
|
|
|
|
FKey TranslateMouseButtonToKey( const EMouseButtons::Type Button )
|
|
{
|
|
FKey Key = EKeys::Invalid;
|
|
|
|
switch( Button )
|
|
{
|
|
case EMouseButtons::Left:
|
|
Key = EKeys::LeftMouseButton;
|
|
break;
|
|
case EMouseButtons::Middle:
|
|
Key = EKeys::MiddleMouseButton;
|
|
break;
|
|
case EMouseButtons::Right:
|
|
Key = EKeys::RightMouseButton;
|
|
break;
|
|
case EMouseButtons::Thumb01:
|
|
Key = EKeys::ThumbMouseButton;
|
|
break;
|
|
case EMouseButtons::Thumb02:
|
|
Key = EKeys::ThumbMouseButton2;
|
|
break;
|
|
}
|
|
|
|
return Key;
|
|
}
|
|
|
|
void FSlateApplication::SetGameIsFakingTouchEvents(const bool bIsFaking, FVector2D* CursorLocation)
|
|
{
|
|
// note, this is usually guarded by FPlatformMisc::DesktopTouchScreen()
|
|
// the only place this is not guarded is in FPIEPreviewDeviceModule::OnWindowReady()
|
|
if ( bIsGameFakingTouch != bIsFaking )
|
|
{
|
|
if (bAllowFakingTouch && bIsFakingTouched && !bIsFaking && bIsGameFakingTouch && !bIsFakingTouch)
|
|
{
|
|
OnTouchEnded((CursorLocation ? *CursorLocation : PlatformApplication->Cursor->GetPosition()), 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice());
|
|
}
|
|
|
|
bIsGameFakingTouch = bIsFaking;
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SetGameAllowsFakingTouchEvents(const bool bAllowFaking)
|
|
{
|
|
bAllowFakingTouch = bAllowFaking;
|
|
if(!bAllowFaking && IsFakingTouchEvents())
|
|
{
|
|
SetGameIsFakingTouchEvents(bAllowFaking);
|
|
}
|
|
}
|
|
|
|
bool FSlateApplication::IsFakingTouchEvents() const
|
|
{
|
|
return bAllowFakingTouch && (bIsFakingTouch || bIsGameFakingTouch);
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseDown(const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button)
|
|
{
|
|
return OnMouseDown(PlatformWindow, Button, GetCursorPos());
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseDown( const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button, const FVector2D CursorPos )
|
|
{
|
|
// convert a left mouse button click to touch event if we are faking it
|
|
if (IsFakingTouchEvents() && Button == EMouseButtons::Left)
|
|
{
|
|
bIsFakingTouched = true;
|
|
return OnTouchStarted( PlatformWindow, PlatformApplication->Cursor->GetPosition(), 1.0f, /* touch index */ 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice() );
|
|
}
|
|
|
|
FKey Key = TranslateMouseButtonToKey( Button );
|
|
|
|
FPointerEvent MouseEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
CursorPos,
|
|
GetLastCursorPos(),
|
|
PressedMouseButtons,
|
|
Key,
|
|
0,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
return ProcessMouseButtonDownEvent( PlatformWindow, MouseEvent );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessMouseButtonDownEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& MouseEvent )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseButtonDown);
|
|
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::MouseButtonDown, MouseEvent);
|
|
#endif
|
|
|
|
#if WITH_EDITOR
|
|
//Send the key input to all pre input key down listener function
|
|
if (OnApplicationMousePreInputButtonDownListenerEvent.IsBound())
|
|
{
|
|
OnApplicationMousePreInputButtonDownListenerEvent.Broadcast(MouseEvent);
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
|
|
|
|
if (PlatformWindow.IsValid())
|
|
{
|
|
PlatformApplication->SetCapture(PlatformWindow);
|
|
}
|
|
|
|
if (MouseEvent.GetUserIndex() == CursorUserIndex)
|
|
{
|
|
PressedMouseButtons.Add(MouseEvent.GetEffectingButton());
|
|
}
|
|
|
|
// Input preprocessors get the first chance at the input
|
|
if (InputPreProcessors.HandleMouseButtonDownEvent(*this, MouseEvent))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool bInGame = false;
|
|
|
|
// Only process mouse down messages if we are not drag/dropping
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(MouseEvent);
|
|
if (!SlateUser->IsDragDropping())
|
|
{
|
|
FReply Reply = FReply::Unhandled();
|
|
if (SlateUser->HasCapture(MouseEvent.GetPointerIndex()))
|
|
{
|
|
FWidgetPath MouseCaptorPath = SlateUser->GetCaptorPath(MouseEvent.GetPointerIndex(), FWeakWidgetPath::EInterruptedPathHandling::Truncate, &MouseEvent);
|
|
FArrangedWidget& MouseCaptorWidget = MouseCaptorPath.Widgets.Last();
|
|
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(MouseCaptorPath);
|
|
bInGame = FApp::IsGame();
|
|
|
|
FPointerEvent TransformedPointerEvent = TransformPointerEvent(MouseEvent, MouseCaptorPath.GetWindow());
|
|
|
|
Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), TransformedPointerEvent, [] (const FArrangedWidget& InMouseCaptorWidget, const FPointerEvent& Event)
|
|
{
|
|
const FReply TempReply = InMouseCaptorWidget.Widget->OnPreviewMouseButtonDown(InMouseCaptorWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::PreviewMouseButtonDown, &Event, TempReply, InMouseCaptorWidget.Widget);
|
|
#endif
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::PreviewMouseButtonDown);
|
|
|
|
if ( !Reply.IsEventHandled() )
|
|
{
|
|
Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), TransformedPointerEvent,
|
|
[this] (const FArrangedWidget& InMouseCaptorWidget, const FPointerEvent& Event)
|
|
{
|
|
FReply TempReply = FReply::Unhandled();
|
|
if ( Event.IsTouchEvent() )
|
|
{
|
|
TempReply = InMouseCaptorWidget.Widget->OnTouchStarted(InMouseCaptorWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchStart, &Event, TempReply, InMouseCaptorWidget.Widget);
|
|
#endif
|
|
}
|
|
if ( !Event.IsTouchEvent() || ( !TempReply.IsEventHandled() && this->bTouchFallbackToMouse ) )
|
|
{
|
|
TempReply = InMouseCaptorWidget.Widget->OnMouseButtonDown(InMouseCaptorWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseButtonDown, &Event, TempReply, InMouseCaptorWidget.Widget);
|
|
#endif
|
|
}
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseButtonDown);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FWidgetPath WidgetsUnderCursor = LocateWindowUnderMouse( MouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, SlateUser->GetUserIndex());
|
|
|
|
PopupSupport.SendNotifications( WidgetsUnderCursor );
|
|
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(WidgetsUnderCursor);
|
|
bInGame = FApp::IsGame();
|
|
|
|
//@todo NickD: Route API should be private; update Process methods to accept an FWidgetPath
|
|
Reply = RoutePointerDownEvent(WidgetsUnderCursor, MouseEvent);
|
|
}
|
|
|
|
// See if expensive tasks should be throttled. By default on mouse down expensive tasks are throttled
|
|
// to ensure Slate responsiveness in low FPS situations
|
|
if (Reply.IsEventHandled() && !bInGame && !MouseEvent.IsTouchEvent())
|
|
{
|
|
// Enter responsive mode if throttling should occur and its not already happening
|
|
if( Reply.ShouldThrottle() && !MouseButtonDownResponsivnessThrottle.IsValid() )
|
|
{
|
|
MouseButtonDownResponsivnessThrottle = FSlateThrottleManager::Get().EnterResponsiveMode();
|
|
}
|
|
else if( !Reply.ShouldThrottle() && MouseButtonDownResponsivnessThrottle.IsValid() )
|
|
{
|
|
// Leave responsive mode if a widget chose not to throttle
|
|
FSlateThrottleManager::Get().LeaveResponsiveMode( MouseButtonDownResponsivnessThrottle );
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FReply FSlateApplication::RoutePointerDownEvent(const FWidgetPath& WidgetsUnderPointer, const FPointerEvent& PointerEvent)
|
|
{
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
FPointerEvent TransformedPointerEvent = WidgetsUnderPointer.IsValid() ? TransformPointerEvent(PointerEvent, WidgetsUnderPointer.GetWindow()) : PointerEvent;
|
|
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(PointerEvent);
|
|
SlateUser->UpdatePointerPosition(PointerEvent);
|
|
|
|
#if PLATFORM_MAC
|
|
NSWindow* ActiveWindow = [ NSApp keyWindow ];
|
|
const bool bNeedToActivateWindow = ( ActiveWindow == nullptr );
|
|
#else
|
|
const bool bNeedToActivateWindow = false;
|
|
#endif
|
|
|
|
const TSharedPtr<SWidget> PreviouslyFocusedWidget = GetKeyboardFocusedWidget();
|
|
|
|
FReply Reply = FEventRouter::Route<FReply>( this, FEventRouter::FTunnelPolicy( WidgetsUnderPointer ), TransformedPointerEvent, []( const FArrangedWidget TargetWidget, const FPointerEvent& Event )
|
|
{
|
|
const FReply TempReply = TargetWidget.Widget->OnPreviewMouseButtonDown(TargetWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::PreviewMouseButtonDown, &Event, TempReply, TargetWidget.Widget);
|
|
#endif
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::PreviewMouseButtonDown);
|
|
|
|
if( !Reply.IsEventHandled() )
|
|
{
|
|
Reply = FEventRouter::Route<FReply>( this, FEventRouter::FBubblePolicy( WidgetsUnderPointer ), TransformedPointerEvent, [this]( const FArrangedWidget TargetWidget, const FPointerEvent& Event )
|
|
{
|
|
FReply TempReply = FReply::Unhandled();
|
|
if( !TempReply.IsEventHandled() )
|
|
{
|
|
if( Event.IsTouchEvent() )
|
|
{
|
|
TempReply = TargetWidget.Widget->OnTouchStarted( TargetWidget.Geometry, Event );
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchStart, &Event, TempReply, TargetWidget.Widget);
|
|
#endif
|
|
}
|
|
if( !Event.IsTouchEvent() || ( !TempReply.IsEventHandled() && this->bTouchFallbackToMouse ) )
|
|
{
|
|
TempReply = TargetWidget.Widget->OnMouseButtonDown( TargetWidget.Geometry, Event );
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseButtonDown, &Event, TempReply, TargetWidget.Widget);
|
|
#endif
|
|
}
|
|
}
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseButtonDown);
|
|
|
|
// When we perform a touch begin, we need to also send a mouse enter as if it were a cursor.
|
|
if (PointerEvent.IsTouchEvent() && !IsFakingTouchEvents())
|
|
{
|
|
for (int32 WidgetIndex = WidgetsUnderPointer.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
|
|
{
|
|
const FArrangedWidget& TargetWidget = WidgetsUnderPointer.Widgets[WidgetIndex];
|
|
|
|
TargetWidget.Widget->OnMouseEnter(TargetWidget.Geometry, TransformedPointerEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseEnter, &TransformedPointerEvent, TargetWidget.Widget);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
#if PLATFORM_MAC
|
|
const bool bIsDetectingLMBDrag = PointerEvent.GetEffectingButton() == EKeys::LeftMouseButton && SlateUser->IsDetectingDrag(PointerEvent.GetPointerIndex());
|
|
#endif
|
|
|
|
// If none of the widgets requested keyboard focus to be set (or set the keyboard focus explicitly), set it to the leaf-most widget under the mouse.
|
|
// On Mac we prevent the OS from activating the window on mouse down, so we have full control and can activate only if there's nothing draggable under the mouse cursor.
|
|
const bool bFocusChangedByEventHandler = PreviouslyFocusedWidget != GetKeyboardFocusedWidget();
|
|
if( ( !bFocusChangedByEventHandler || bNeedToActivateWindow ) &&
|
|
( !Reply.GetUserFocusRecepient().IsValid()
|
|
#if PLATFORM_MAC
|
|
|| bIsDetectingLMBDrag
|
|
#endif
|
|
)
|
|
)
|
|
{
|
|
for ( int32 WidgetIndex = WidgetsUnderPointer.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex )
|
|
{
|
|
const FArrangedWidget& CurWidget = WidgetsUnderPointer.Widgets[WidgetIndex];
|
|
if ( CurWidget.Widget->SupportsKeyboardFocus() )
|
|
{
|
|
FWidgetPath NewFocusedWidgetPath = WidgetsUnderPointer.GetPathDownTo(CurWidget.Widget);
|
|
SetUserFocus(PointerEvent.GetUserIndex(), NewFocusedWidgetPath, EFocusCause::Mouse);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if PLATFORM_MAC
|
|
const bool bIsVirtualInteraction = WidgetsUnderPointer.TopLevelWindow.IsValid() ? WidgetsUnderPointer.TopLevelWindow->IsVirtualWindow() : false;
|
|
if ( !bIsVirtualInteraction )
|
|
{
|
|
TSharedPtr<SWindow> TopLevelWindow = WidgetsUnderPointer.TopLevelWindow;
|
|
if ( bNeedToActivateWindow || ( TopLevelWindow.IsValid() && TopLevelWindow->GetNativeWindow()->GetOSWindowHandle() != ActiveWindow ) )
|
|
{
|
|
// Clicking on a context menu should not activate anything
|
|
// @todo: This needs to be updated when we have window type in SWindow and we no longer have to guess if WidgetsUnderCursor.TopLevelWindow is a menu
|
|
const bool bIsContextMenu = TopLevelWindow.IsValid() && !TopLevelWindow->IsRegularWindow() && TopLevelWindow->HasMinimizeBox() && TopLevelWindow->HasMaximizeBox();
|
|
if ( !bIsContextMenu && bIsDetectingLMBDrag && ActiveWindow == [NSApp keyWindow] )
|
|
{
|
|
TMap<TSharedRef<FSlateUser>, TMap<uint32, FWeakWidgetPath>> AllPointerCaptors;
|
|
ForEachUser([&AllPointerCaptors](FSlateUser& User) { AllPointerCaptors.Add(User.AsShared(), User.GetCaptorPathsByIndex()); });
|
|
FPlatformApplicationMisc::ActivateApplication();
|
|
if ( TopLevelWindow.IsValid() )
|
|
{
|
|
TopLevelWindow->BringToFront(true);
|
|
}
|
|
ForEachUser([&AllPointerCaptors](FSlateUser& User) { User.RestoreCaptorPathsByIndex(AllPointerCaptors.FindChecked(User.AsShared())); });
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return Reply;
|
|
}
|
|
|
|
|
|
FReply FSlateApplication::RoutePointerUpEvent(const FWidgetPath& WidgetsUnderPointer, const FPointerEvent& PointerEvent)
|
|
{
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
FPointerEvent TransformedPointerEvent = WidgetsUnderPointer.IsValid() ? TransformPointerEvent(PointerEvent, WidgetsUnderPointer.GetWindow()) : PointerEvent;
|
|
|
|
FReply Reply = FReply::Unhandled();
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(PointerEvent);
|
|
const bool bIsDragDropping = SlateUser->IsDragDroppingAffected(PointerEvent);
|
|
TSharedPtr<FDragDropOperation> LocalDragDropContent;
|
|
|
|
bool bDropWasHandled = false;
|
|
FWidgetPath LocalWidgetsUnderPointer = WidgetsUnderPointer;
|
|
|
|
if (SlateUser->HasCapture(PointerEvent.GetPointerIndex()))
|
|
{
|
|
FWidgetPath MouseCaptorPath = SlateUser->GetCaptorPath(PointerEvent.GetPointerIndex(), FWeakWidgetPath::EInterruptedPathHandling::Truncate, &PointerEvent);
|
|
#if PLATFORM_MAC
|
|
// Because of Deferred event on Mac, by the time this event is processed, the window with the mouse capture might have been dismissed before the mouse up is processed
|
|
if ( MouseCaptorPath.Widgets.Num() > 0 )
|
|
#else
|
|
if ( ensureMsgf(MouseCaptorPath.Widgets.Num() > 0, TEXT("A window had a widget with mouse capture. That entire window has been dismissed before the mouse up could be processed.")) )
|
|
#endif
|
|
{
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld( MouseCaptorPath );
|
|
|
|
Reply =
|
|
FEventRouter::Route<FReply>( this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), TransformedPointerEvent, [this]( const FArrangedWidget& TargetWidget, const FPointerEvent& Event )
|
|
{
|
|
FReply TempReply = FReply::Unhandled();
|
|
if (Event.IsTouchEvent())
|
|
{
|
|
TempReply = TargetWidget.Widget->OnTouchEnded(TargetWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchEnd, &Event, TempReply, TargetWidget.Widget);
|
|
#endif
|
|
}
|
|
|
|
if (!Event.IsTouchEvent() || (!TempReply.IsEventHandled() && this->bTouchFallbackToMouse))
|
|
{
|
|
TempReply = TargetWidget.Widget->OnMouseButtonUp( TargetWidget.Geometry, Event );
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseButtonUp, &Event, TempReply, TargetWidget.Widget);
|
|
#endif
|
|
}
|
|
|
|
if ( Event.IsTouchEvent() && !IsFakingTouchEvents() )
|
|
{
|
|
// Generate a Leave event when a touch ends as well, since a touch can enter a widget and then end inside it
|
|
TargetWidget.Widget->OnMouseLeave(Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseLeave, &Event, TargetWidget.Widget);
|
|
#endif
|
|
}
|
|
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseButtonUp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!LocalWidgetsUnderPointer.IsValid())
|
|
{
|
|
LocalWidgetsUnderPointer = LocateWindowUnderMouse(PointerEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, SlateUser->GetUserIndex());
|
|
}
|
|
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(LocalWidgetsUnderPointer);
|
|
|
|
if (bIsDragDropping)
|
|
{
|
|
// Route a synthetic pointer move event to ensure drag events ( e.g. OnDragLeave ) are called if necessary on any widgets before OnDrop
|
|
if (GSlateInputPointerUpFiresPointerMoveForDragDrop)
|
|
{
|
|
const bool bIsSynthetic = true;
|
|
RoutePointerMoveEvent(LocalWidgetsUnderPointer, PointerEvent, bIsSynthetic);
|
|
}
|
|
|
|
// Cache the drag drop content and reset the pointer in case OnMouseButtonUpMessage re-enters as a result of OnDrop
|
|
// In such a case, we want the re-entrant call to skip any drag-drop stuff (otherwise we'd execute the drop action twice)
|
|
LocalDragDropContent = SlateUser->GetDragDropContent();
|
|
SlateUser->ResetDragDropContent();
|
|
}
|
|
|
|
Reply = FEventRouter::Route<FReply>(this, FEventRouter::FBubblePolicy(LocalWidgetsUnderPointer), TransformedPointerEvent, [&](const FArrangedWidget& CurWidget, const FPointerEvent& Event)
|
|
{
|
|
if (bIsDragDropping)
|
|
{
|
|
FDragDropEvent LocalDropEvent(Event, LocalDragDropContent);
|
|
const FReply TempDropReply = CurWidget.Widget->OnDrop(CurWidget.Geometry, LocalDropEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::DragDrop, &LocalDropEvent, TempDropReply, CurWidget.Widget);
|
|
#endif
|
|
return TempDropReply;
|
|
}
|
|
|
|
FReply TempReply = FReply::Unhandled();
|
|
|
|
if (Event.IsTouchEvent())
|
|
{
|
|
TempReply = CurWidget.Widget->OnTouchEnded(CurWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchEnd, &Event, TempReply, CurWidget.Widget);
|
|
#endif
|
|
}
|
|
|
|
if (!Event.IsTouchEvent() || (!TempReply.IsEventHandled() && bTouchFallbackToMouse))
|
|
{
|
|
TempReply = CurWidget.Widget->OnMouseButtonUp(CurWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseButtonUp, &Event, TempReply, CurWidget.Widget);
|
|
#endif
|
|
}
|
|
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseButtonUp);
|
|
}
|
|
|
|
SlateUser->NotifyPointerReleased(PointerEvent, LocalWidgetsUnderPointer, LocalDragDropContent, Reply.IsEventHandled());
|
|
|
|
#if PLATFORM_MAC
|
|
// Make sure the application and its front window are activated if user wasn't drag & dropping between windows
|
|
if (PointerEvent.GetEffectingButton() == EKeys::LeftMouseButton && !bIsDragDropping)
|
|
{
|
|
TSharedPtr<SWindow> ActiveWindow = GetActiveTopLevelWindow();
|
|
if (ActiveWindow.IsValid() && !ActiveWindow->GetNativeWindow()->IsForegroundWindow() && !ActiveWindow->GetNativeWindow()->IsMinimized())
|
|
{
|
|
FPlatformApplicationMisc::ActivateApplication();
|
|
|
|
if (!ActiveWindow->IsVirtualWindow())
|
|
{
|
|
ActiveWindow->BringToFront(true);
|
|
}
|
|
}
|
|
else if ([NSApp keyWindow] == nullptr)
|
|
{
|
|
FPlatformApplicationMisc::ActivateApplication();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return Reply;
|
|
}
|
|
|
|
bool FSlateApplication::RoutePointerMoveEvent(const FWidgetPath& WidgetsUnderPointer, const FPointerEvent& PointerEvent, bool bIsSynthetic)
|
|
{
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
bool bHandled = false;
|
|
FWeakWidgetPath LastWidgetsUnderPointer;
|
|
|
|
FPointerEvent TransformedPointerEvent = WidgetsUnderPointer.IsValid() ? TransformPointerEvent(PointerEvent, WidgetsUnderPointer.GetWindow()) : PointerEvent;
|
|
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(PointerEvent);
|
|
SlateUser->NotifyPointerMoveBegin(PointerEvent);
|
|
|
|
// Currently we support only one dragged widget at a time per user
|
|
bool bShouldStartDetectingDrag = !SlateUser->IsDragDropping();
|
|
|
|
#if WITH_EDITOR
|
|
//@TODO VREDITOR - Remove and move to interaction component
|
|
if (bShouldStartDetectingDrag && OnDragDropCheckOverride.IsBound())
|
|
{
|
|
bShouldStartDetectingDrag = OnDragDropCheckOverride.Execute();
|
|
}
|
|
#endif
|
|
|
|
if (!bIsSynthetic && bShouldStartDetectingDrag)
|
|
{
|
|
FWidgetPath DragDetectPath = SlateUser->DetectDrag(PointerEvent, GetDragTriggerDistance());
|
|
if (DragDetectPath.IsValid())
|
|
{
|
|
FWidgetAndPointer DetectDragForMe = DragDetectPath.FindArrangedWidgetAndCursor(DragDetectPath.GetLastWidget()).Get(FWidgetAndPointer());
|
|
|
|
// A drag has been triggered. The cursor exited some widgets as a result.
|
|
// This assignment ensures that we will send OnLeave notifications to those widgets.
|
|
LastWidgetsUnderPointer = DragDetectPath;
|
|
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(DragDetectPath);
|
|
|
|
// Send an OnDragDetected to the widget that requested drag detection.
|
|
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FDirectPolicy(DetectDragForMe, DragDetectPath, &WidgetsUnderPointer), PointerEvent, [](const FArrangedWidget& InDetectDragForMe, const FPointerEvent& TranslatedMouseEvent)
|
|
{
|
|
const FReply TempReply = InDetectDragForMe.Widget->OnDragDetected(InDetectDragForMe.Geometry, TranslatedMouseEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::DragDetected, &TranslatedMouseEvent, TempReply, InDetectDragForMe.Widget);
|
|
#endif
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::DragDetected);
|
|
}
|
|
}
|
|
|
|
// A drag was detected if the user is now executing a drag-drop action
|
|
if (bShouldStartDetectingDrag && SlateUser->IsDragDropping())
|
|
{
|
|
// When a drag was detected, we pretend that the widgets under the mouse last time around.
|
|
// We have set LastWidgetsUnderCursor accordingly when the drag was detected above.
|
|
}
|
|
else
|
|
{
|
|
LastWidgetsUnderPointer = SlateUser->GetWidgetsUnderPointerLastEventByIndex().FindRef(PointerEvent.GetPointerIndex());
|
|
}
|
|
|
|
FWidgetPath MouseCaptorPath = SlateUser->GetCaptorPath(PointerEvent.GetPointerIndex(), FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid, &PointerEvent);
|
|
|
|
// Send out mouse leave events
|
|
// If we are doing a drag and drop, we will send this event instead.
|
|
TSharedPtr<FDragDropOperation> DragDropContent = SlateUser->GetDragDropContent();
|
|
{
|
|
FDragDropEvent DragDropEvent(PointerEvent, DragDropContent);
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(LastWidgetsUnderPointer.Window.Pin());
|
|
|
|
for (int32 WidgetIndex = LastWidgetsUnderPointer.Widgets.Num()-1; WidgetIndex >=0; --WidgetIndex)
|
|
{
|
|
// Guards for cases where WidgetIndex can become invalid due to MouseMove being re-entrant.
|
|
if (WidgetIndex >= LastWidgetsUnderPointer.Widgets.Num())
|
|
{
|
|
WidgetIndex = LastWidgetsUnderPointer.Widgets.Num() - 1;
|
|
}
|
|
|
|
if (WidgetIndex >= 0)
|
|
{
|
|
const TSharedPtr<SWidget>& SomeWidgetPreviouslyUnderCursor = LastWidgetsUnderPointer.Widgets[WidgetIndex].Pin();
|
|
if (SomeWidgetPreviouslyUnderCursor.IsValid())
|
|
{
|
|
TOptional<FArrangedWidget> FoundWidget = WidgetsUnderPointer.FindArrangedWidget(SomeWidgetPreviouslyUnderCursor.ToSharedRef());
|
|
const bool bWidgetNoLongerUnderMouse = !FoundWidget.IsSet();
|
|
|
|
bool bWidgetUnderOtherUsersPointer = false;
|
|
|
|
// Verify if the Widget is under another user pointer
|
|
if (bWidgetNoLongerUnderMouse)
|
|
{
|
|
for (const TSharedPtr<FSlateUser>& user : Users)
|
|
{
|
|
if (user != nullptr && user->GetUserIndex() != PointerEvent.GetUserIndex())
|
|
{
|
|
if (user->IsWidgetDirectlyUnderAnyPointer(SomeWidgetPreviouslyUnderCursor))
|
|
{
|
|
bWidgetUnderOtherUsersPointer = true;
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We consider the Widget is nolonger under a pointer if it's no longer under any pointers.
|
|
if (bWidgetNoLongerUnderMouse && !bWidgetUnderOtherUsersPointer)
|
|
{
|
|
// Widget is no longer under cursor, so send a MouseLeave.
|
|
// The widget might not even be in the hierarchy any more!
|
|
// Thus, we cannot translate the PointerPosition into the appropriate space for this event.
|
|
if (SlateUser->IsDragDroppingAffected(PointerEvent))
|
|
{
|
|
// Note that the event's pointer position is not translated.
|
|
SomeWidgetPreviouslyUnderCursor->OnDragLeave(DragDropEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::DragLeave, &DragDropEvent, SomeWidgetPreviouslyUnderCursor);
|
|
#endif
|
|
// Reset the cursor override
|
|
DragDropEvent.GetOperation()->SetCursorOverride(TOptional<EMouseCursor::Type>());
|
|
}
|
|
else
|
|
{
|
|
// Only fire mouse leave events for widgets inside the captor path, or whoever if there is no captor path.
|
|
if (MouseCaptorPath.IsValid() == false || MouseCaptorPath.ContainsWidget(SomeWidgetPreviouslyUnderCursor.Get()))
|
|
{
|
|
// Note that the event's pointer position is not translated.
|
|
SomeWidgetPreviouslyUnderCursor->OnMouseLeave(PointerEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseLeave, &PointerEvent, SomeWidgetPreviouslyUnderCursor);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (MouseCaptorPath.IsValid())
|
|
{
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(MouseCaptorPath);
|
|
|
|
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), TransformedPointerEvent, [&MouseCaptorPath, &LastWidgetsUnderPointer](const FArrangedWidget& WidgetUnderCursor, const FPointerEvent& Event)
|
|
{
|
|
if (!LastWidgetsUnderPointer.ContainsWidget(WidgetUnderCursor.GetWidgetPtr()))
|
|
{
|
|
if (MouseCaptorPath.ContainsWidget(WidgetUnderCursor.GetWidgetPtr()))
|
|
{
|
|
WidgetUnderCursor.Widget->OnMouseEnter(WidgetUnderCursor.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastNoReplyInputEvent(ESlateDebuggingInputEvent::MouseEnter, &Event, WidgetUnderCursor.Widget);
|
|
#endif
|
|
}
|
|
}
|
|
return FNoReply();
|
|
}, ESlateDebuggingInputEvent::MouseEnter);
|
|
|
|
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), TransformedPointerEvent, [this, bIsSynthetic](const FArrangedWidget& MouseCaptorWidget, const FPointerEvent& Event)
|
|
{
|
|
FReply TempReply = FReply::Unhandled();
|
|
|
|
bool bAllowMouseFallback = true;
|
|
if (Event.IsTouchEvent())
|
|
{
|
|
if (Event.IsTouchForceChangedEvent())
|
|
{
|
|
TempReply = MouseCaptorWidget.Widget->OnTouchForceChanged(MouseCaptorWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchForceChanged, &Event, TempReply, MouseCaptorWidget.Widget);
|
|
#endif
|
|
bAllowMouseFallback = false;
|
|
}
|
|
else if (Event.IsTouchFirstMoveEvent())
|
|
{
|
|
TempReply = MouseCaptorWidget.Widget->OnTouchFirstMove(MouseCaptorWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchFirstMove, &Event, TempReply, MouseCaptorWidget.Widget);
|
|
#endif
|
|
bAllowMouseFallback = false;
|
|
}
|
|
else
|
|
{
|
|
TempReply = MouseCaptorWidget.Widget->OnTouchMoved(MouseCaptorWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchMoved, &Event, TempReply, MouseCaptorWidget.Widget);
|
|
#endif
|
|
}
|
|
}
|
|
if ((!Event.IsTouchEvent() && bAllowMouseFallback) || (!TempReply.IsEventHandled() && this->bTouchFallbackToMouse))
|
|
{
|
|
// Only handle if not synthetic, else widgets with mouse capture can cause the mouse to move at app start.
|
|
if (!bIsSynthetic)
|
|
{
|
|
TempReply = MouseCaptorWidget.Widget->OnMouseMove(MouseCaptorWidget.Geometry, Event);
|
|
}
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseMove, &Event, TempReply, MouseCaptorWidget.Widget);
|
|
#endif
|
|
}
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseEnter);
|
|
bHandled = Reply.IsEventHandled();
|
|
}
|
|
else
|
|
{
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(WidgetsUnderPointer);
|
|
|
|
const bool bIsDragDroppingAffected = SlateUser->IsDragDroppingAffected(PointerEvent);
|
|
|
|
// Send out mouse enter events.
|
|
if (bIsDragDroppingAffected)
|
|
{
|
|
FDragDropEvent DragDropEvent(PointerEvent, DragDropContent);
|
|
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), DragDropEvent, [&LastWidgetsUnderPointer](const FArrangedWidget& WidgetUnderCursor, const FDragDropEvent& InDragDropEvent)
|
|
{
|
|
if (!LastWidgetsUnderPointer.ContainsWidget(WidgetUnderCursor.GetWidgetPtr()))
|
|
{
|
|
WidgetUnderCursor.Widget->OnDragEnter(WidgetUnderCursor.Geometry, InDragDropEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastNoReplyInputEvent(ESlateDebuggingInputEvent::DragEnter, &InDragDropEvent, WidgetUnderCursor.Widget);
|
|
#endif
|
|
}
|
|
return FNoReply();
|
|
}, ESlateDebuggingInputEvent::DragEnter);
|
|
}
|
|
else
|
|
{
|
|
FEventRouter::Route<FNoReply>(this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), TransformedPointerEvent, [&LastWidgetsUnderPointer](const FArrangedWidget& WidgetUnderCursor, const FPointerEvent& Event)
|
|
{
|
|
if (!LastWidgetsUnderPointer.ContainsWidget(WidgetUnderCursor.GetWidgetPtr()))
|
|
{
|
|
WidgetUnderCursor.Widget->OnMouseEnter(WidgetUnderCursor.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastNoReplyInputEvent(ESlateDebuggingInputEvent::MouseEnter, &Event, WidgetUnderCursor.Widget);
|
|
#endif
|
|
}
|
|
return FNoReply();
|
|
}, ESlateDebuggingInputEvent::MouseEnter);
|
|
}
|
|
|
|
// Bubble the MouseMove event.
|
|
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FBubblePolicy(WidgetsUnderPointer), TransformedPointerEvent, [&](const FArrangedWidget& CurWidget, const FPointerEvent& Event)
|
|
{
|
|
FReply TempReply = FReply::Unhandled();
|
|
|
|
if (bIsDragDroppingAffected)
|
|
{
|
|
FDragDropEvent DragDropEvent(Event, DragDropContent);
|
|
TempReply = CurWidget.Widget->OnDragOver(CurWidget.Geometry, DragDropEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::DragOver, &DragDropEvent, TempReply, CurWidget.Widget);
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
bool bAllowMouseFallback = true;
|
|
if (Event.IsTouchEvent())
|
|
{
|
|
if (Event.IsTouchForceChangedEvent())
|
|
{
|
|
TempReply = CurWidget.Widget->OnTouchForceChanged(CurWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchForceChanged, &Event, TempReply, CurWidget.Widget);
|
|
#endif
|
|
bAllowMouseFallback = false;
|
|
}
|
|
else if (Event.IsTouchFirstMoveEvent())
|
|
{
|
|
TempReply = CurWidget.Widget->OnTouchFirstMove(CurWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchFirstMove, &Event, TempReply, CurWidget.Widget);
|
|
#endif
|
|
bAllowMouseFallback = false;
|
|
}
|
|
else
|
|
{
|
|
TempReply = CurWidget.Widget->OnTouchMoved(CurWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchMoved, &Event, TempReply, CurWidget.Widget);
|
|
#endif
|
|
}
|
|
}
|
|
if (!TempReply.IsEventHandled() && bAllowMouseFallback)
|
|
{
|
|
TempReply = CurWidget.Widget->OnMouseMove(CurWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseMove, &Event, TempReply, CurWidget.Widget);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseMove);
|
|
|
|
bHandled = Reply.IsEventHandled();
|
|
}
|
|
|
|
SlateUser->NotifyPointerMoveComplete(PointerEvent, WidgetsUnderPointer);
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseDoubleClick( const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button )
|
|
{
|
|
return OnMouseDoubleClick(PlatformWindow, Button, GetCursorPos());
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseDoubleClick( const TSharedPtr< FGenericWindow >& PlatformWindow, const EMouseButtons::Type Button, const FVector2D CursorPos )
|
|
{
|
|
if (IsFakingTouchEvents())
|
|
{
|
|
bIsFakingTouched = true;
|
|
return OnTouchStarted(PlatformWindow, PlatformApplication->Cursor->GetPosition(), 1.0f, /* touch index */ 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice());
|
|
}
|
|
|
|
FKey Key = TranslateMouseButtonToKey( Button );
|
|
|
|
FPointerEvent MouseEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
CursorPos,
|
|
GetLastCursorPos(),
|
|
PressedMouseButtons,
|
|
Key,
|
|
0,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
return ProcessMouseButtonDoubleClickEvent( PlatformWindow, MouseEvent );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessMouseButtonDoubleClickEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& InMouseEvent )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseButtonDoubleClick);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::MouseButtonDoubleClick, InMouseEvent);
|
|
#endif
|
|
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
|
|
|
|
PlatformApplication->SetCapture( PlatformWindow );
|
|
|
|
if (InMouseEvent.GetUserIndex() == CursorUserIndex)
|
|
{
|
|
PressedMouseButtons.Add( InMouseEvent.GetEffectingButton() );
|
|
}
|
|
|
|
// Input preprocessors get the first chance at the input
|
|
if (InputPreProcessors.HandleMouseButtonDoubleClickEvent(*this, InMouseEvent))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (GetOrCreateUser(InMouseEvent)->HasCapture(InMouseEvent.GetPointerIndex()))
|
|
{
|
|
// If a widget has mouse capture, we've opted to simply treat this event as a mouse down
|
|
return ProcessMouseButtonDownEvent(PlatformWindow, InMouseEvent);
|
|
}
|
|
|
|
FWidgetPath WidgetsUnderCursor = LocateWindowUnderMouse(InMouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, InMouseEvent.GetUserIndex());
|
|
|
|
FReply Reply = RoutePointerDoubleClickEvent( WidgetsUnderCursor, InMouseEvent );
|
|
|
|
return Reply.IsEventHandled();
|
|
}
|
|
|
|
|
|
FReply FSlateApplication::RoutePointerDoubleClickEvent(const FWidgetPath& WidgetsUnderPointer, const FPointerEvent& PointerEvent)
|
|
{
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
FReply Reply = FReply::Unhandled();
|
|
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld( WidgetsUnderPointer );
|
|
|
|
FPointerEvent TransformedPointerEvent = WidgetsUnderPointer.IsValid() ? TransformPointerEvent(PointerEvent, WidgetsUnderPointer.GetWindow()) : PointerEvent;
|
|
|
|
Reply = FEventRouter::Route<FReply>( this, FEventRouter::FBubblePolicy( WidgetsUnderPointer ), TransformedPointerEvent, []( const FArrangedWidget& TargetWidget, const FPointerEvent& Event )
|
|
{
|
|
const FReply TempReply = TargetWidget.Widget->OnMouseButtonDoubleClick(TargetWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseButtonDoubleClick, &Event, TempReply, TargetWidget.Widget);
|
|
#endif
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseButtonDoubleClick);
|
|
|
|
return Reply;
|
|
}
|
|
|
|
|
|
bool FSlateApplication::OnMouseUp( const EMouseButtons::Type Button )
|
|
{
|
|
return OnMouseUp(Button, GetCursorPos());
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseUp( const EMouseButtons::Type Button, const FVector2D CursorPos )
|
|
{
|
|
// convert left mouse click to touch event if we are faking it
|
|
if (IsFakingTouchEvents() && Button == EMouseButtons::Left)
|
|
{
|
|
bIsFakingTouched = false;
|
|
return OnTouchEnded(PlatformApplication->Cursor->GetPosition(), 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice());
|
|
}
|
|
|
|
FKey Key = TranslateMouseButtonToKey( Button );
|
|
|
|
FPointerEvent MouseEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
CursorPos,
|
|
GetLastCursorPos(),
|
|
PressedMouseButtons,
|
|
Key,
|
|
0,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
return ProcessMouseButtonUpEvent( MouseEvent );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessMouseButtonUpEvent( const FPointerEvent& MouseEvent )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseButtonUp);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::MouseButtonUp, MouseEvent);
|
|
#endif
|
|
|
|
// If in responsive mode throttle, leave it on mouse up. Release this before dispatching the event to prevent being stuck in this mode
|
|
// until the next click if a modal dialog is opened.
|
|
if (MouseButtonDownResponsivnessThrottle.IsValid())
|
|
{
|
|
FSlateThrottleManager::Get().LeaveResponsiveMode(MouseButtonDownResponsivnessThrottle);
|
|
}
|
|
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
LastUserInteractionTimeForThrottling = LastUserInteractionTime;
|
|
|
|
const bool bIsCursorUser = MouseEvent.GetUserIndex() == CursorUserIndex;
|
|
if (bIsCursorUser)
|
|
{
|
|
PressedMouseButtons.Remove(MouseEvent.GetEffectingButton());
|
|
}
|
|
|
|
// Input preprocessors get the first chance at the input
|
|
if (InputPreProcessors.HandleMouseButtonUpEvent(*this, MouseEvent))
|
|
{
|
|
// If mouse up event is consumed by a preprocessor, associated mouse down event needs to be cleared as well. Otherwise, subsequent mouse down events get ignored until the first one is cleared.
|
|
// This was only affecting the swipe detection on touch, we can remove this condition if we want the same fix for other platforms, but reducing the scope for now
|
|
if (MouseEvent.IsTouchEvent())
|
|
{
|
|
TSharedRef<FSlateUser> SlateUser = GetOrCreateUser(MouseEvent);
|
|
FWidgetPath WidgetsUnderPointer = LocateWindowUnderMouse(MouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, SlateUser->GetUserIndex());
|
|
|
|
SlateUser->NotifyPointerReleased(MouseEvent, WidgetsUnderPointer, nullptr, true);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// An empty widget path is passed in. As an optimization, one will be generated only if a captured mouse event isn't routed
|
|
FWidgetPath EmptyPath;
|
|
const bool bHandled = RoutePointerUpEvent( EmptyPath, MouseEvent ).IsEventHandled();
|
|
|
|
if ( bIsCursorUser && PressedMouseButtons.Num() == 0 )
|
|
{
|
|
PlatformApplication->SetCapture( nullptr );
|
|
}
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseWheel( const float Delta )
|
|
{
|
|
return OnMouseWheel(Delta, GetCursorPos());
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseWheel( const float Delta, const FVector2D CursorPos )
|
|
{
|
|
FPointerEvent MouseWheelEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
CursorPos,
|
|
CursorPos,
|
|
PressedMouseButtons,
|
|
EKeys::Invalid,
|
|
Delta,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
return ProcessMouseWheelOrGestureEvent( MouseWheelEvent, nullptr );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessMouseWheelOrGestureEvent( const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseWheelGesture);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::MouseWheel, InWheelEvent);
|
|
#endif
|
|
|
|
bool bShouldProcessEvent = false;
|
|
|
|
if ( InGestureEvent )
|
|
{
|
|
switch ( InGestureEvent->GetGestureType() )
|
|
{
|
|
case EGestureEvent::LongPress:
|
|
bShouldProcessEvent = true;
|
|
break;
|
|
default:
|
|
bShouldProcessEvent = InGestureEvent->GetGestureDelta() != FVector2f::ZeroVector;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bShouldProcessEvent = InWheelEvent.GetWheelDelta() != 0;
|
|
}
|
|
|
|
if ( !bShouldProcessEvent )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
|
|
// Input preprocessors get the first chance at the input
|
|
if (InputPreProcessors.HandleMouseWheelOrGestureEvent(*this, InWheelEvent, InGestureEvent))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// NOTE: We intentionally don't reset LastUserInteractionTimeForThrottling here so that the UI can be responsive while scrolling
|
|
|
|
FWidgetPath EventPath = LocateWindowUnderMouse(InWheelEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, InWheelEvent.GetUserIndex());
|
|
|
|
return RouteMouseWheelOrGestureEvent(EventPath, InWheelEvent, InGestureEvent).IsEventHandled();
|
|
}
|
|
|
|
FReply FSlateApplication::RouteMouseWheelOrGestureEvent(const FWidgetPath& WidgetsUnderPointer, const FPointerEvent& InWheelEvent, const FPointerEvent* InGestureEvent)
|
|
{
|
|
TScopeCounter<int32> BeginInput(ProcessingInput);
|
|
|
|
FWidgetPath MouseCaptorPath;
|
|
|
|
TSharedRef<FSlateUser> User = GetOrCreateUser(InWheelEvent);
|
|
if (User->HasCapture(InWheelEvent.GetPointerIndex()))
|
|
{
|
|
MouseCaptorPath = User->GetCaptorPath(InWheelEvent.GetPointerIndex(), FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid, &InWheelEvent);
|
|
}
|
|
|
|
const FWidgetPath& EventPath = MouseCaptorPath.IsValid() ? MouseCaptorPath : WidgetsUnderPointer;
|
|
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(EventPath);
|
|
|
|
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FBubblePolicy(EventPath), InWheelEvent, [&InGestureEvent] (const FArrangedWidget& CurWidget, const FPointerEvent& Event)
|
|
{
|
|
FReply TempReply = FReply::Unhandled();
|
|
// Gesture event gets first shot, if slate doesn't respond to it, we'll try the wheel event.
|
|
if ( InGestureEvent != nullptr )
|
|
{
|
|
TempReply = CurWidget.Widget->OnTouchGesture(CurWidget.Geometry, *InGestureEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::TouchGesture, InGestureEvent, TempReply, CurWidget.Widget);
|
|
#endif
|
|
}
|
|
|
|
// Send the mouse wheel event if we haven't already handled the gesture version of this event.
|
|
if ( !TempReply.IsEventHandled() && Event.GetWheelDelta() != 0 )
|
|
{
|
|
TempReply = CurWidget.Widget->OnMouseWheel(CurWidget.Geometry, Event);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MouseWheel, &Event, TempReply, CurWidget.Widget);
|
|
#endif
|
|
}
|
|
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MouseWheel);
|
|
|
|
return Reply;
|
|
}
|
|
|
|
bool FSlateApplication::OnMouseMove()
|
|
{
|
|
if (!PlatformApplication->Cursor.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// If the left button is pressed we fake
|
|
if (IsFakingTouchEvents() && (GetPressedMouseButtons().Num() == 0 || GetPressedMouseButtons().Contains(EKeys::LeftMouseButton)))
|
|
{
|
|
// convert to touch event if we are faking it
|
|
if (bIsFakingTouched)
|
|
{
|
|
return OnTouchMoved(PlatformApplication->Cursor->GetPosition(), 1.0f, 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice());
|
|
}
|
|
|
|
// Throw out the mouse move event if we're faking touch events but the mouse button isn't down.
|
|
return false;
|
|
}
|
|
|
|
bool Result = true;
|
|
const FVector2f CurrentCursorPosition = UE::Slate::CastToVector2f(PlatformApplication->Cursor->GetPosition());
|
|
if (LastPlatformCursorPosition != CurrentCursorPosition)
|
|
{
|
|
// Force the cursor user index to use the platform cursor since we've been notified that the platform
|
|
// cursor position has changed. This is done intentionally after getting the positions in order to avoid
|
|
// false positives.
|
|
|
|
// NOTE: When we swap out the real OS cursor for the faux slate cursor ie. UsePlatformCursorForCursorUser(false)
|
|
// we reset this event count to 0. This occurs typically when a gamepad is being used and you don't want to manipulate the real
|
|
// OS cursor, instead move around a fake cursor so that you can still do development stuff outside the game window. Anyway,
|
|
// when this occurs, in the future the OS will send you a long delayed mouse movement. I'm not exactly sure what's triggering it,
|
|
// it's not a movement from the application, I think it's something more subtle, like swapping true cursor visibility for using
|
|
// a None cursor, it could also be some combination.
|
|
//
|
|
// In any event, we track the number of events, and if we get more than 3, then we start trying to swap back to the OS cursor.
|
|
// the 3 should give us any buffer needed for either a last frame mouse movement, or a weird condition like noted above.
|
|
PlatformMouseMovementEvents++;
|
|
|
|
if (PlatformMouseMovementEvents > 3)
|
|
{
|
|
UsePlatformCursorForCursorUser(true);
|
|
}
|
|
|
|
LastMouseMoveTime = GetCurrentTime();
|
|
|
|
FPointerEvent MouseEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
CurrentCursorPosition,
|
|
LastPlatformCursorPosition,
|
|
PressedMouseButtons,
|
|
EKeys::Invalid,
|
|
0,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
Result = ProcessMouseMoveEvent( MouseEvent );
|
|
LastPlatformCursorPosition = CurrentCursorPosition;
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FSlateApplication::OnRawMouseMove( const int32 X, const int32 Y )
|
|
{
|
|
// We fake a move only if the left mous button is down
|
|
if (IsFakingTouchEvents() && (GetPressedMouseButtons().Num() == 0 || GetPressedMouseButtons().Contains(EKeys::LeftMouseButton)))
|
|
{
|
|
// convert to touch event if we are faking it
|
|
if (bIsFakingTouched)
|
|
{
|
|
return OnTouchMoved(GetCursorPos(), 1.0f, 0, FSlateApplicationBase::SlateAppPrimaryPlatformUser, IPlatformInputDeviceMapper::Get().GetDefaultInputDevice());
|
|
}
|
|
|
|
// Throw out the mouse move event if we're faking touch events but the mouse button isn't down.
|
|
return false;
|
|
}
|
|
|
|
if ( X != 0 || Y != 0 )
|
|
{
|
|
FPointerEvent MouseEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
GetCursorPos(),
|
|
GetLastCursorPos(),
|
|
FVector2f( X, Y ),
|
|
PressedMouseButtons,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
ProcessMouseMoveEvent(MouseEvent);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FSlateApplication::ProcessMouseMoveEvent( const FPointerEvent& MouseEvent, bool bIsSynthetic )
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_ProcessMouseMove);
|
|
|
|
if (IsFakingTouchEvents() && !MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
|
|
{
|
|
// If we're faking touch events and the left mouse button is not down, do not process the mouse move event
|
|
return false;
|
|
}
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::MouseMove, MouseEvent);
|
|
#endif
|
|
|
|
if ( !bIsSynthetic )
|
|
{
|
|
if (InputPreProcessors.HandleMouseMoveEvent(*this, MouseEvent))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Guard against synthesized mouse moves and only track user interaction if the cursor pos changed
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
}
|
|
|
|
// When the event came from the OS, we are guaranteed to be over a slate window.
|
|
// Otherwise, we are synthesizing a MouseMove ourselves, and must verify that the
|
|
// cursor is indeed over a Slate window. Synthesized device (gamepad) input while
|
|
// the application is inactive also needs to populate the widget path.
|
|
const bool bOverSlateWindow = !bIsSynthetic || IsActive() || PlatformApplication->IsCursorDirectlyOverSlateWindow() || GetHandleDeviceInputWhenApplicationNotActive();
|
|
|
|
FWidgetPath WidgetsUnderCursor = bOverSlateWindow
|
|
? LocateWindowUnderMouse(MouseEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, MouseEvent.GetUserIndex())
|
|
: FWidgetPath();
|
|
|
|
bool bResult;
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_ProcessMouseMove_RoutePointerMoveEvent);
|
|
bResult = RoutePointerMoveEvent(WidgetsUnderCursor, MouseEvent, bIsSynthetic);
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
bool FSlateApplication::OnCursorSet()
|
|
{
|
|
GetCursorUser()->RequestCursorQuery();
|
|
return true;
|
|
}
|
|
|
|
void FSlateApplication::NavigateToWidget(const uint32 UserIndex, const TSharedPtr<SWidget>& NavigationDestination, ENavigationSource NavigationSource)
|
|
{
|
|
if (NavigationDestination.IsValid())
|
|
{
|
|
FWidgetPath NavigationSourceWP;
|
|
if (NavigationSource == ENavigationSource::WidgetUnderCursor)
|
|
{
|
|
NavigationSourceWP = LocateWindowUnderMouse(GetOrCreateUser(UserIndex)->GetCursorPosition(), GetInteractiveTopLevelWindows(), false, UserIndex);
|
|
}
|
|
else
|
|
{
|
|
NavigationSourceWP = *GetOrCreateUser(UserIndex)->GetFocusPath();
|
|
}
|
|
|
|
if (NavigationSourceWP.IsValid())
|
|
{
|
|
bool bAlwaysHandleNavigationAttempt = false;
|
|
ExecuteNavigation(NavigationSourceWP, NavigationDestination, UserIndex, bAlwaysHandleNavigationAttempt);
|
|
}
|
|
}
|
|
}
|
|
|
|
EUINavigation FSlateApplication::NavigateFromWidget(const uint32 InUserIndex, TSharedPtr<SWidget> InNavigationSource, const TArray<EUINavigation>& InNavigationTypes)
|
|
{
|
|
FWidgetPath NavigationSourcePath;
|
|
if (InNavigationSource.IsValid())
|
|
{
|
|
FindPathToWidget(InNavigationSource.ToSharedRef(), NavigationSourcePath, EVisibility::All);
|
|
}
|
|
else
|
|
{
|
|
NavigationSourcePath = Users[InUserIndex]->GetFocusPath().Get();
|
|
}
|
|
|
|
if (!NavigationSourcePath.IsValid())
|
|
{
|
|
UE_LOG(LogSlate, Log, TEXT("Failed to navigate from widget %s. No widget currently in Focus"), *(InNavigationSource->ToString()));
|
|
return EUINavigation::Invalid;
|
|
}
|
|
|
|
// Traverse up the widget tree and attempt to navigate to next available widget by searching neighbor space
|
|
for (int32 WidgetIndex = NavigationSourcePath.Widgets.Num() - 1; WidgetIndex >= 0; --WidgetIndex)
|
|
{
|
|
const FArrangedWidget& BoundaryWidget = NavigationSourcePath.Widgets[WidgetIndex];
|
|
|
|
for (EUINavigation Direction : InNavigationTypes)
|
|
{
|
|
// Try to advance to next widget
|
|
const FNavigationEvent NavigationEventNext(PlatformApplication->GetModifierKeys(), InUserIndex, Direction, ENavigationGenesis::User);
|
|
if (AttemptNavigation(NavigationSourcePath, NavigationEventNext, FNavigationReply::Escape(), BoundaryWidget))
|
|
{
|
|
return Direction;
|
|
}
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogSlate, Log, TEXT("Failed to navigate from widget %s. No neighboring widgets could be reached."), *(InNavigationSource->ToString()));
|
|
return EUINavigation::Invalid;
|
|
}
|
|
|
|
bool FSlateApplication::AttemptNavigation(const FWidgetPath& NavigationSource, const FNavigationEvent& NavigationEvent, const FNavigationReply& NavigationReply, const FArrangedWidget& BoundaryWidget)
|
|
{
|
|
if ( !NavigationSource.IsValid() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedPtr<SWidget> DestinationWidget = TSharedPtr<SWidget>();
|
|
bool bAlwaysHandleNavigationAttempt = false;
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
ESlateDebuggingNavigationMethod NavigationMethod = ESlateDebuggingNavigationMethod::Unknown;
|
|
#endif
|
|
|
|
EUINavigation NavigationType = NavigationEvent.GetNavigationType();
|
|
if ( NavigationReply.GetBoundaryRule() == EUINavigationRule::Explicit )
|
|
{
|
|
const SWidget* FocusRecipient = NavigationReply.GetFocusRecipient().Get();
|
|
if ( FocusRecipient && FocusRecipient->IsEnabled() && FocusRecipient->SupportsKeyboardFocus() )
|
|
{
|
|
DestinationWidget = NavigationReply.GetFocusRecipient();
|
|
bAlwaysHandleNavigationAttempt = true;
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
NavigationMethod = ESlateDebuggingNavigationMethod::Explicit;
|
|
#endif
|
|
}
|
|
#if WITH_SLATE_DEBUGGING
|
|
else
|
|
{
|
|
const TCHAR* Reason = TEXT("Unknown");
|
|
if (!FocusRecipient)
|
|
{
|
|
Reason = TEXT("Widget is a nullptr");
|
|
}
|
|
else if (!FocusRecipient->IsEnabled())
|
|
{
|
|
Reason = TEXT("Widget disabled");
|
|
}
|
|
else
|
|
{
|
|
ensure(!FocusRecipient->SupportsKeyboardFocus());
|
|
Reason = TEXT("Widget does not support keyboard focus");
|
|
}
|
|
|
|
UE_LOG(LogSlate, VeryVerbose, TEXT("Could not Explicitly navigate to widget '%s' because '%s'"), *FReflectionMetaData::GetWidgetDebugInfo(FocusRecipient), Reason);
|
|
}
|
|
#endif
|
|
}
|
|
else if ( NavigationReply.GetBoundaryRule() == EUINavigationRule::Custom )
|
|
{
|
|
const FNavigationDelegate& FocusDelegate = NavigationReply.GetFocusDelegate();
|
|
if ( FocusDelegate.IsBound() )
|
|
{
|
|
// Switch worlds for widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(NavigationSource);
|
|
|
|
DestinationWidget = FocusDelegate.Execute(NavigationType);
|
|
bAlwaysHandleNavigationAttempt = true;
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
NavigationMethod = ESlateDebuggingNavigationMethod::CustomDelegateBound;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
NavigationMethod = ESlateDebuggingNavigationMethod::CustomDelegateUnbound;
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Find the next widget
|
|
if (NavigationType == EUINavigation::Next || NavigationType == EUINavigation::Previous)
|
|
{
|
|
// Fond the next widget
|
|
FWeakWidgetPath WeakNavigationSource(NavigationSource);
|
|
FWidgetPath NewFocusedWidgetPath = WeakNavigationSource.ToNextFocusedPath(NavigationType, NavigationReply, BoundaryWidget);
|
|
|
|
// Resolve the Widget Path
|
|
FArrangedWidget& NewFocusedArrangedWidget = NewFocusedWidgetPath.Widgets.Last();
|
|
DestinationWidget = NewFocusedArrangedWidget.Widget;
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
NavigationMethod = ESlateDebuggingNavigationMethod::NextOrPrevious;
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Resolve the Widget Path
|
|
const FArrangedWidget& FocusedArrangedWidget = NavigationSource.Widgets.Last();
|
|
|
|
// Switch worlds for widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld(NavigationSource);
|
|
|
|
DestinationWidget = NavigationSource.GetDeepestWindow()->GetHittestGrid().FindNextFocusableWidget(FocusedArrangedWidget, NavigationType, NavigationReply, BoundaryWidget, NavigationEvent.GetUserIndex());
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
NavigationMethod = ESlateDebuggingNavigationMethod::HitTestGrid;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastAttemptNavigation(NavigationEvent, NavigationReply, NavigationSource, DestinationWidget, NavigationMethod);
|
|
#endif
|
|
|
|
return ExecuteNavigation(NavigationSource, DestinationWidget, NavigationEvent.GetUserIndex(), bAlwaysHandleNavigationAttempt);
|
|
}
|
|
|
|
bool FSlateApplication::ExecuteNavigation(const FWidgetPath& NavigationSource, TSharedPtr<SWidget> DestinationWidget, const uint32 UserIndex, bool bAlwaysHandleNavigationAttempt)
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
// TODO Execute Navigation
|
|
#endif
|
|
|
|
bool bHandled = false;
|
|
|
|
// Give the custom viewport navigation event handler a chance to handle the navigation if the NavigationSource is contained within it.
|
|
TSharedPtr<ISlateViewport> Viewport = NavigationSource.GetWindow()->GetViewport();
|
|
if (Viewport.IsValid())
|
|
{
|
|
TSharedPtr<SWidget> ViewportWidget = Viewport->GetWidget().Pin();
|
|
if (ViewportWidget.IsValid())
|
|
{
|
|
if (NavigationSource.ContainsWidget(ViewportWidget.Get()))
|
|
{
|
|
bHandled = Viewport->HandleNavigation(UserIndex, DestinationWidget);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set controller focus if the navigation hasn't been handled have a valid widget
|
|
if (!bHandled)
|
|
{
|
|
if (DestinationWidget.IsValid())
|
|
{
|
|
SetUserFocus(UserIndex, DestinationWidget, EFocusCause::Navigation);
|
|
bHandled = true;
|
|
}
|
|
else if (bAlwaysHandleNavigationAttempt)
|
|
{
|
|
bHandled = true;
|
|
}
|
|
}
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastExecuteNavigation();
|
|
#endif
|
|
|
|
return bHandled;
|
|
}
|
|
|
|
|
|
void FSlateApplication::OnMenuDestroyed(const TSharedRef<IMenu>& Menu)
|
|
{
|
|
if (MenuBeingDestroyedEvent.IsBound())
|
|
{
|
|
MenuBeingDestroyedEvent.Broadcast(Menu);
|
|
}
|
|
}
|
|
|
|
bool FSlateApplication::OnControllerAnalog(FGamepadKeyNames::Type KeyName, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId, float AnalogValue)
|
|
{
|
|
FKey Key(KeyName);
|
|
TOptional<int32> UserIndex = GetUserIndexForInputDevice(InputDeviceId);
|
|
if (UserIndex.IsSet() && ensureMsgf(Key.IsValid(), TEXT("OnControllerAnalog(KeyName=%s,InputDeviceId=%d,AnalogValue=%f) key is invalid"), *KeyName.ToString(), InputDeviceId.GetId(), AnalogValue))
|
|
{
|
|
FAnalogInputEvent AnalogInputEvent(Key, PlatformApplication->GetModifierKeys(), InputDeviceId, false, 0, 0, AnalogValue, UserIndex);
|
|
|
|
return ProcessAnalogInputEvent(AnalogInputEvent);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::OnControllerButtonPressed(FGamepadKeyNames::Type KeyName, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId, bool IsRepeat)
|
|
{
|
|
FKey Key(KeyName);
|
|
TOptional<int32> UserIndex = GetUserIndexForInputDevice(InputDeviceId);
|
|
if (UserIndex.IsSet() && ensureMsgf(Key.IsValid(), TEXT("OnControllerButtonPressed(KeyName=%s,InputDeviceId=%d,IsRepeat=%u) key is invalid"), *KeyName.ToString(), InputDeviceId.GetId(), IsRepeat))
|
|
{
|
|
FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(), InputDeviceId, IsRepeat, 0, 0, UserIndex);
|
|
|
|
return ProcessKeyDownEvent(KeyEvent);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::OnControllerButtonReleased(FGamepadKeyNames::Type KeyName, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId, bool IsRepeat)
|
|
{
|
|
FKey Key(KeyName);
|
|
TOptional<int32> UserIndex = GetUserIndexForInputDevice(InputDeviceId);
|
|
if (UserIndex.IsSet() && ensureMsgf(Key.IsValid(), TEXT("OnControllerButtonReleased(KeyName=%s,InputDeviceId=%d,IsRepeat=%u) key is invalid"), *KeyName.ToString(), InputDeviceId.GetId(), IsRepeat))
|
|
{
|
|
FKeyEvent KeyEvent(Key, PlatformApplication->GetModifierKeys(), InputDeviceId, IsRepeat, 0, 0, UserIndex);
|
|
|
|
return ProcessKeyUpEvent(KeyEvent);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::OnTouchGesture( EGestureEvent GestureType, const FVector2D &Delta, const float MouseWheelDelta, bool bIsDirectionInvertedFromDevice )
|
|
{
|
|
const FVector2f CurrentCursorPosition = GetCursorPos();
|
|
|
|
FPointerEvent GestureEvent(
|
|
CurrentCursorPosition,
|
|
CurrentCursorPosition,
|
|
PressedMouseButtons,
|
|
PlatformApplication->GetModifierKeys(),
|
|
GestureType,
|
|
Delta,
|
|
bIsDirectionInvertedFromDevice
|
|
);
|
|
|
|
FPointerEvent MouseWheelEvent(
|
|
CursorPointerIndex,
|
|
CurrentCursorPosition,
|
|
CurrentCursorPosition,
|
|
PressedMouseButtons,
|
|
EKeys::Invalid,
|
|
MouseWheelDelta,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
return ProcessMouseWheelOrGestureEvent( MouseWheelEvent, &GestureEvent );
|
|
}
|
|
|
|
bool ValidateTouchIndex(int32 TouchIndex)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool FSlateApplication::OnTouchStarted( const TSharedPtr< FGenericWindow >& PlatformWindow, const FVector2D& Location, float Force, int32 TouchIndex, FPlatformUserId PlatformUserId, FInputDeviceId DeviceId )
|
|
{
|
|
// Don't process touches that overlap or surpass with the cursor pointer index.
|
|
if (TouchIndex >= (int32)ETouchIndex::CursorPointerIndex)
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
// Only log when the touch starts, we don't want to spam the logs.
|
|
UE_LOG(LogSlate, Warning, TEXT("Maximum Touch Index Exceeded, %d, the maximum index allowed is %d"), TouchIndex, (((int32)ETouchIndex::CursorPointerIndex) - 1));
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
FPointerEvent PointerEvent(
|
|
DeviceId,
|
|
TouchIndex,
|
|
Location,
|
|
Location,
|
|
Force,
|
|
true);
|
|
ProcessTouchStartedEvent( PlatformWindow, PointerEvent );
|
|
|
|
return true;
|
|
}
|
|
|
|
void FSlateApplication::ProcessTouchStartedEvent( const TSharedPtr< FGenericWindow >& PlatformWindow, const FPointerEvent& InTouchEvent )
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::TouchStart, InTouchEvent);
|
|
#endif
|
|
|
|
GetOrCreateUser(InTouchEvent)->NotifyTouchStarted(InTouchEvent);
|
|
ProcessMouseButtonDownEvent(PlatformWindow, InTouchEvent);
|
|
}
|
|
|
|
bool FSlateApplication::OnTouchMoved( const FVector2D& Location, float Force, int32 TouchIndex, FPlatformUserId PlatformUserId, FInputDeviceId DeviceID )
|
|
{
|
|
TSharedRef<FSlateUser> User = GetOrCreateUser(DeviceID);
|
|
if (User->IsTouchPointerActive(TouchIndex))
|
|
{
|
|
FPointerEvent PointerEvent(
|
|
DeviceID,
|
|
TouchIndex,
|
|
Location,
|
|
User->GetPreviousPointerPosition(TouchIndex),
|
|
Force,
|
|
true);
|
|
ProcessTouchMovedEvent(PointerEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FSlateApplication::ProcessTouchMovedEvent( const FPointerEvent& PointerEvent )
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::TouchMoved, PointerEvent);
|
|
#endif
|
|
|
|
ProcessMouseMoveEvent(PointerEvent);
|
|
}
|
|
|
|
bool FSlateApplication::OnTouchEnded( const FVector2D& Location, int32 TouchIndex, FPlatformUserId PlatformUserId, FInputDeviceId DeviceID )
|
|
{
|
|
TSharedRef<FSlateUser> User = GetOrCreateUser(DeviceID);
|
|
if (User->IsTouchPointerActive(TouchIndex))
|
|
{
|
|
FPointerEvent PointerEvent(
|
|
DeviceID,
|
|
TouchIndex,
|
|
Location,
|
|
Location,
|
|
0.0f,
|
|
true);
|
|
ProcessTouchEndedEvent(PointerEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FSlateApplication::ProcessTouchEndedEvent(const FPointerEvent& PointerEvent)
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::TouchEnd, PointerEvent);
|
|
#endif
|
|
|
|
ProcessMouseButtonUpEvent(PointerEvent);
|
|
}
|
|
|
|
bool FSlateApplication::OnTouchForceChanged(const FVector2D& Location, float Force, int32 TouchIndex, FPlatformUserId PlatformUserId, FInputDeviceId DeviceID)
|
|
{
|
|
TSharedRef<FSlateUser> User = GetOrCreateUser(DeviceID);
|
|
if (User->IsTouchPointerActive(TouchIndex))
|
|
{
|
|
FPointerEvent PointerEvent(
|
|
DeviceID,
|
|
TouchIndex,
|
|
Location,
|
|
Location,
|
|
Force,
|
|
true,
|
|
true,
|
|
false);
|
|
ProcessTouchMovedEvent(PointerEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FSlateApplication::OnTouchFirstMove(const FVector2D& Location, float Force, int32 TouchIndex, FPlatformUserId PlatformUserId, FInputDeviceId DeviceID)
|
|
{
|
|
TSharedRef<FSlateUser> User = GetOrCreateUser(DeviceID);
|
|
if (User->IsTouchPointerActive(TouchIndex))
|
|
{
|
|
FPointerEvent PointerEvent(
|
|
DeviceID,
|
|
TouchIndex,
|
|
Location,
|
|
User->GetPreviousPointerPosition(TouchIndex),
|
|
Force,
|
|
true,
|
|
false,
|
|
true);
|
|
ProcessTouchMovedEvent(PointerEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FSlateApplication::ShouldSimulateGesture(EGestureEvent Gesture, bool bEnable)
|
|
{
|
|
check(FGestureDetector::IsGestureSupported(Gesture));
|
|
|
|
SimulateGestures[(uint8)Gesture] = bEnable;
|
|
}
|
|
|
|
bool FSlateApplication::OnMotionDetected(const FVector& Tilt, const FVector& RotationRate, const FVector& Gravity, const FVector& Acceleration, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId)
|
|
{
|
|
FMotionEvent MotionEvent(
|
|
InputDeviceId,
|
|
Tilt,
|
|
RotationRate,
|
|
Gravity,
|
|
Acceleration
|
|
);
|
|
ProcessMotionDetectedEvent(MotionEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
void FSlateApplication::ProcessMotionDetectedEvent( const FMotionEvent& MotionEvent )
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::MotionDetected, MotionEvent);
|
|
#endif
|
|
|
|
if (GSlateInputMotionFiresUserInteractionEvents)
|
|
{
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
}
|
|
|
|
if (!InputPreProcessors.HandleMotionDetectedEvent(*this, MotionEvent))
|
|
{
|
|
TSharedRef<FSlateUser> User = GetOrCreateUser(MotionEvent);
|
|
if (User->HasValidFocusPath())
|
|
{
|
|
/* Get the controller focus target for this user */
|
|
TSharedRef<FWidgetPath> EventPathRef = User->GetFocusPath();
|
|
const FWidgetPath& EventPath = EventPathRef.Get();
|
|
|
|
FScopedSwitchWorldHack SwitchWorld(EventPath);
|
|
|
|
FReply Reply = FEventRouter::Route<FReply>(this, FEventRouter::FBubblePolicy(EventPath), MotionEvent, [](const FArrangedWidget& SomeWidget, const FMotionEvent& InMotionEvent)
|
|
{
|
|
const FReply TempReply = SomeWidget.Widget->OnMotionDetected(SomeWidget.Geometry, InMotionEvent);
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastInputEvent(ESlateDebuggingInputEvent::MotionDetected, &InMotionEvent, TempReply, SomeWidget.Widget);
|
|
#endif
|
|
return TempReply;
|
|
}, ESlateDebuggingInputEvent::MotionDetected);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FSlateApplication::OnSizeChanged( const TSharedRef< FGenericWindow >& PlatformWindow, const int32 Width, const int32 Height, bool bWasMinimized )
|
|
{
|
|
LLM_SCOPE(ELLMTag::UI);
|
|
|
|
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
|
|
|
|
if ( Window.IsValid() )
|
|
{
|
|
Window->SetCachedSize( FVector2f( Width, Height ) );
|
|
|
|
Renderer->RequestResize( Window, Width, Height );
|
|
|
|
if (FPlatformProperties::HasFixedResolution())
|
|
{
|
|
Renderer->SetSystemResolution(Width, Height);
|
|
}
|
|
|
|
if ( !bWasMinimized && Window->IsRegularWindow() && !Window->HasOSWindowBorder() && Window->IsVisible() && Window->IsDrawingEnabled() )
|
|
{
|
|
PrivateDrawWindows( Window );
|
|
}
|
|
|
|
if( !bWasMinimized && Window->IsVisible() && Window->IsRegularWindow() && Window->IsAutosized() )
|
|
{
|
|
// Reduces flickering due to one frame lag when windows are resized automatically
|
|
Renderer->FlushCommands();
|
|
}
|
|
|
|
// Inform the notification manager we have activated a window - it may want to force notifications
|
|
// back to the front of the z-order
|
|
FSlateNotificationManager::Get().ForceNotificationsInFront( Window.ToSharedRef() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FSlateApplication::OnOSPaint( const TSharedRef< FGenericWindow >& PlatformWindow )
|
|
{
|
|
// This is only called in a modal move loop and in cooked build, the back buffer already
|
|
// has UI composited so don't do anything to prevent drawing UI over existing UI
|
|
if (GIsEditor)
|
|
{
|
|
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow(SlateWindows, PlatformWindow);
|
|
PrivateDrawWindows(Window);
|
|
Renderer->FlushCommands();
|
|
}
|
|
}
|
|
|
|
FWindowSizeLimits FSlateApplication::GetSizeLimitsForWindow(const TSharedRef<FGenericWindow>& Window) const
|
|
{
|
|
TSharedPtr<SWindow> SlateWindow = FSlateWindowHelper::FindWindowByPlatformWindow(SlateWindows, Window);
|
|
if (SlateWindow.IsValid())
|
|
{
|
|
return SlateWindow->GetSizeLimits();
|
|
}
|
|
else
|
|
{
|
|
return FWindowSizeLimits();
|
|
}
|
|
|
|
}
|
|
|
|
void FSlateApplication::OnResizingWindow( const TSharedRef< FGenericWindow >& PlatformWindow )
|
|
{
|
|
// Flush the rendering command queue to ensure that there aren't pending viewport draw commands for the old viewport size.
|
|
Renderer->FlushCommands();
|
|
}
|
|
|
|
bool FSlateApplication::BeginReshapingWindow( const TSharedRef< FGenericWindow >& PlatformWindow )
|
|
{
|
|
if(!IsExternalUIOpened())
|
|
{
|
|
if (!ThrottleHandle.IsValid())
|
|
{
|
|
ThrottleHandle = FSlateThrottleManager::Get().EnterResponsiveMode();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FSlateApplication::FinishedReshapingWindow(const TSharedRef< FGenericWindow >& PlatformWindow)
|
|
{
|
|
#if WITH_EDITOR
|
|
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow(SlateWindows, PlatformWindow);
|
|
|
|
if (Window.IsValid())
|
|
{
|
|
Renderer->OnWindowFinishReshaped(Window);
|
|
}
|
|
#endif
|
|
|
|
if (ThrottleHandle.IsValid())
|
|
{
|
|
FSlateThrottleManager::Get().LeaveResponsiveMode(ThrottleHandle);
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SignalSystemDPIChanged(const TSharedRef<FGenericWindow>& PlatformWindow)
|
|
{
|
|
#if WITH_EDITOR
|
|
TSharedPtr< SWindow > SlateWindow = FSlateWindowHelper::FindWindowByPlatformWindow(SlateWindows, PlatformWindow);
|
|
|
|
if (SlateWindow.IsValid() && SlateWindow->IsRegularWindow())
|
|
{
|
|
OnSignalSystemDPIChangedEvent.Broadcast(SlateWindow.ToSharedRef());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FSlateApplication::HandleDPIScaleChanged(const TSharedRef<FGenericWindow>& PlatformWindow)
|
|
{
|
|
#if WITH_EDITOR
|
|
TSharedPtr< SWindow > SlateWindow = FSlateWindowHelper::FindWindowByPlatformWindow(SlateWindows, PlatformWindow);
|
|
|
|
if (SlateWindow.IsValid() && SlateWindow->IsRegularWindow())
|
|
{
|
|
OnWindowDPIScaleChangedEvent.Broadcast(SlateWindow.ToSharedRef());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FSlateApplication::OnMovedWindow( const TSharedRef< FGenericWindow >& PlatformWindow, const int32 X, const int32 Y )
|
|
{
|
|
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
|
|
|
|
if ( Window.IsValid() )
|
|
{
|
|
Window->SetCachedScreenPosition( FVector2f( X, Y ) );
|
|
}
|
|
}
|
|
|
|
FWindowActivateEvent::EActivationType TranslationWindowActivationMessage( const EWindowActivation ActivationType )
|
|
{
|
|
FWindowActivateEvent::EActivationType Result = FWindowActivateEvent::EA_Activate;
|
|
|
|
switch( ActivationType )
|
|
{
|
|
case EWindowActivation::Activate:
|
|
Result = FWindowActivateEvent::EA_Activate;
|
|
break;
|
|
case EWindowActivation::ActivateByMouse:
|
|
Result = FWindowActivateEvent::EA_ActivateByMouse;
|
|
break;
|
|
case EWindowActivation::Deactivate:
|
|
Result = FWindowActivateEvent::EA_Deactivate;
|
|
break;
|
|
default:
|
|
check( false );
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FSlateApplication::OnWindowActivationChanged( const TSharedRef< FGenericWindow >& PlatformWindow, const EWindowActivation ActivationType )
|
|
{
|
|
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
|
|
|
|
if ( !Window.IsValid() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FWindowActivateEvent::EActivationType TranslatedActivationType = TranslationWindowActivationMessage( ActivationType );
|
|
FWindowActivateEvent WindowActivateEvent( TranslatedActivationType, Window.ToSharedRef() );
|
|
|
|
return ProcessWindowActivatedEvent( WindowActivateEvent );
|
|
}
|
|
|
|
bool FSlateApplication::ProcessWindowActivatedEvent( const FWindowActivateEvent& ActivateEvent )
|
|
{
|
|
//UE_LOG(LogSlate, Warning, TEXT("Window being %s: %p"), ActivateEvent.GetActivationType() == FWindowActivateEvent::EA_Deactivate ? TEXT("Deactivated") : TEXT("Activated"), &(ActivateEvent.GetAffectedWindow().Get()));
|
|
|
|
TSharedPtr<SWindow> ActiveModalWindow = GetActiveModalWindow();
|
|
|
|
if ( ActivateEvent.GetActivationType() != FWindowActivateEvent::EA_Deactivate )
|
|
{
|
|
ReleaseAllPointerCapture();
|
|
|
|
const bool bActivatedByMouse = ActivateEvent.GetActivationType() == FWindowActivateEvent::EA_ActivateByMouse;
|
|
|
|
// Only window activate by mouse is considered a user interaction
|
|
if (bActivatedByMouse)
|
|
{
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
}
|
|
|
|
// NOTE: The window is brought to front even when a modal window is active and this is not the modal window one of its children
|
|
// The reason for this is so that the Slate window order is in sync with the OS window order when a modal window is open. This is important so that when the modal window closes the proper window receives input from Slate.
|
|
// If you change this be sure to test windows are activated properly and receive input when they are opened when a modal dialog is open.
|
|
FSlateWindowHelper::BringWindowToFront(SlateWindows, ActivateEvent.GetAffectedWindow());
|
|
|
|
// Do not process activation messages unless we have no modal windows or the current window is modal or we are over an interactive tooltip
|
|
if ( !ActiveModalWindow.IsValid() || ActivateEvent.GetAffectedWindow() == ActiveModalWindow || ActivateEvent.GetAffectedWindow()->IsDescendantOf(ActiveModalWindow)
|
|
|| IsWindowHousingInteractiveTooltip(ActivateEvent.GetAffectedWindow()) )
|
|
{
|
|
// Window being ACTIVATED
|
|
{
|
|
// Switch worlds widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld( ActivateEvent.GetAffectedWindow() );
|
|
ActivateEvent.GetAffectedWindow()->OnIsActiveChanged( ActivateEvent );
|
|
}
|
|
|
|
if ( ActivateEvent.GetAffectedWindow()->IsRegularWindow() )
|
|
{
|
|
ActiveTopLevelWindow = ActivateEvent.GetAffectedWindow();
|
|
}
|
|
|
|
// A Slate window was activated
|
|
bSlateWindowActive = true;
|
|
|
|
{
|
|
FScopedSwitchWorldHack SwitchWorld( ActivateEvent.GetAffectedWindow() );
|
|
// let the menu stack know of new window being activated. We may need to close menus as a result
|
|
MenuStack.OnWindowActivated( ActivateEvent.GetAffectedWindow() );
|
|
}
|
|
|
|
// Inform the notification manager we have activated a window - it may want to force notifications
|
|
// back to the front of the z-order
|
|
FSlateNotificationManager::Get().ForceNotificationsInFront( ActivateEvent.GetAffectedWindow() );
|
|
|
|
// As we've just been activated, attempt to restore the resolution that the engine previously cached.
|
|
// This allows us to force ourselves back to the correct resolution after alt-tabbing out of a fullscreen
|
|
// window and then going back in again.
|
|
Renderer->RestoreSystemResolution(ActivateEvent.GetAffectedWindow());
|
|
}
|
|
else
|
|
{
|
|
// An attempt is being made to activate another window when a modal window is running
|
|
ActiveModalWindow->BringToFront();
|
|
ActiveModalWindow->FlashWindow();
|
|
}
|
|
|
|
|
|
TSharedRef<SWindow> Window = ActivateEvent.GetAffectedWindow();
|
|
TSharedPtr<ISlateViewport> Viewport = Window->GetViewport();
|
|
if (Viewport.IsValid())
|
|
{
|
|
TSharedPtr<SWidget> ViewportWidgetPtr = Viewport->GetWidget().Pin();
|
|
if (ViewportWidgetPtr.IsValid())
|
|
{
|
|
TArray< TSharedRef<SWindow> > JustThisWindow;
|
|
JustThisWindow.Add(Window);
|
|
|
|
FWidgetPath PathToViewport;
|
|
if (FSlateWindowHelper::FindPathToWidget(JustThisWindow, ViewportWidgetPtr.ToSharedRef(), PathToViewport, EVisibility::All))
|
|
{
|
|
// Activate the viewport and process the reply
|
|
FReply ViewportActivatedReply = Viewport->OnViewportActivated(ActivateEvent);
|
|
if (ViewportActivatedReply.IsEventHandled())
|
|
{
|
|
ProcessReply(PathToViewport, ViewportActivatedReply, nullptr, nullptr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Window being DEACTIVATED
|
|
|
|
// If our currently-active top level window was deactivated, take note of that
|
|
if ( ActivateEvent.GetAffectedWindow()->IsRegularWindow() &&
|
|
ActivateEvent.GetAffectedWindow() == ActiveTopLevelWindow.Pin() )
|
|
{
|
|
ActiveTopLevelWindow.Reset();
|
|
}
|
|
|
|
// A Slate window was deactivated. Currently there is no active Slate window
|
|
bSlateWindowActive = false;
|
|
|
|
// Switch worlds for the activated window
|
|
FScopedSwitchWorldHack SwitchWorld( ActivateEvent.GetAffectedWindow() );
|
|
ActivateEvent.GetAffectedWindow()->OnIsActiveChanged( ActivateEvent );
|
|
|
|
TSharedRef<SWindow> Window = ActivateEvent.GetAffectedWindow();
|
|
TSharedPtr<ISlateViewport> Viewport = Window->GetViewport();
|
|
if (Viewport.IsValid())
|
|
{
|
|
Viewport->OnViewportDeactivated(ActivateEvent);
|
|
}
|
|
|
|
// A window was deactivated; mouse capture should be cleared
|
|
ResetToDefaultPointerInputSettings();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FSlateApplication::OnApplicationActivationChanged( const bool IsActive )
|
|
{
|
|
ProcessApplicationActivationEvent( IsActive );
|
|
return true;
|
|
}
|
|
|
|
void FSlateApplication::ProcessApplicationActivationEvent(bool InAppActivated)
|
|
{
|
|
if (GUELibraryOverrideSettings.bIsEmbedded)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool UserSwitchedAway = bAppIsActive && !InAppActivated;
|
|
const bool StateChanged = bAppIsActive != InAppActivated;
|
|
|
|
bAppIsActive = InAppActivated;
|
|
|
|
// If the user switched to a different application then we should dismiss our pop-ups. In the case
|
|
// where a user clicked on a different Slate window, OnWindowActivatedMessage() will be call MenuStack.OnWindowActivated()
|
|
// to destroy any windows in our stack that are no longer appropriate to be displayed.
|
|
if (UserSwitchedAway)
|
|
{
|
|
// Close pop-up menus
|
|
DismissAllMenus();
|
|
|
|
// Close tool-tips
|
|
ForEachUser([](FSlateUser& User) {
|
|
User.CloseTooltip();
|
|
});
|
|
|
|
// No slate window is active when our entire app becomes inactive
|
|
bSlateWindowActive = false;
|
|
|
|
// Stop all slate-only drag-drop operations
|
|
ForEachUser([](FSlateUser& User) {
|
|
if (User.IsDragDropping() && User.GetDragDropContent()->IsExternalOperation())
|
|
{
|
|
User.CancelDragDrop();
|
|
}
|
|
});
|
|
|
|
// Clear the pressed buttons when we deactivate the application, the button state can no longer be trusted.
|
|
PressedMouseButtons.Reset();
|
|
}
|
|
|
|
// Only broadcast when state has changed
|
|
if (StateChanged)
|
|
{
|
|
OnApplicationActivationStateChanged().Broadcast(InAppActivated);
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::SetNavigationConfig(TSharedRef<FNavigationConfig> InNavigationConfig)
|
|
{
|
|
NavigationConfig->OnUnregister();
|
|
NavigationConfig = InNavigationConfig;
|
|
NavigationConfig->OnRegister();
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
TryDumpNavigationConfig(NavigationConfig);
|
|
#endif // WITH_SLATE_DEBUGGING
|
|
}
|
|
|
|
bool FSlateApplication::OnConvertibleLaptopModeChanged()
|
|
{
|
|
EConvertibleLaptopMode NewMode = FPlatformMisc::GetConvertibleLaptopMode();
|
|
|
|
// Notify that we want the mobile experience when in tablet mode, otherwise use mouse and keyboard
|
|
if (!(FParse::Param(FCommandLine::Get(), TEXT("simmobile")) || FParse::Param(FCommandLine::Get(), TEXT("faketouches"))))
|
|
{
|
|
// Not sure what the correct long-term strategy is. Use bIsFakingTouch for now to get things going.
|
|
if (NewMode == EConvertibleLaptopMode::Tablet)
|
|
{
|
|
bIsFakingTouch = true;
|
|
}
|
|
else
|
|
{
|
|
bIsFakingTouch = false;
|
|
}
|
|
}
|
|
|
|
FCoreDelegates::PlatformChangedLaptopMode.Broadcast(NewMode);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
EWindowZone::Type FSlateApplication::GetWindowZoneForPoint( const TSharedRef< FGenericWindow >& PlatformWindow, const int32 X, const int32 Y )
|
|
{
|
|
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
|
|
|
|
if ( Window.IsValid() )
|
|
{
|
|
return Window->GetCurrentWindowZone( FVector2f( X, Y ) );
|
|
}
|
|
|
|
return EWindowZone::NotInWindow;
|
|
}
|
|
|
|
|
|
void FSlateApplication::PrivateDestroyWindow( const TSharedRef<SWindow>& DestroyedWindow )
|
|
{
|
|
WindowBeingDestroyedEvent.Broadcast(*DestroyedWindow);
|
|
|
|
// Notify the window that it is going to be destroyed. The window must be completely intact when this is called
|
|
// because delegates are allowed to leave Slate here
|
|
DestroyedWindow->NotifyWindowBeingDestroyed();
|
|
|
|
// Release rendering resources.
|
|
// This MUST be done before destroying the native window as the native window is required to be valid before releasing rendering resources with some API's
|
|
Renderer->OnWindowDestroyed( DestroyedWindow );
|
|
|
|
// Destroy the native window
|
|
DestroyedWindow->DestroyWindowImmediately();
|
|
|
|
// Remove the window and all its children from the Slate window list
|
|
FSlateWindowHelper::RemoveWindowFromList(SlateWindows, DestroyedWindow);
|
|
|
|
// Shutdown the application if there are no more windows
|
|
{
|
|
bool bAnyRegularWindows = false;
|
|
for( auto WindowIter( SlateWindows.CreateConstIterator() ); WindowIter; ++WindowIter )
|
|
{
|
|
auto Window = *WindowIter;
|
|
if( Window->IsRegularWindow() )
|
|
{
|
|
bAnyRegularWindows = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bAnyRegularWindows)
|
|
{
|
|
OnExitRequested.ExecuteIfBound();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::OnWindowClose( const TSharedRef< FGenericWindow >& PlatformWindow )
|
|
{
|
|
TSharedPtr< SWindow > Window = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, PlatformWindow );
|
|
|
|
if ( Window.IsValid() )
|
|
{
|
|
bool bCanCloseWindow = true;
|
|
TSharedPtr< SViewport > CurrentGameViewportWidget = GameViewportWidget.Pin();
|
|
if (CurrentGameViewportWidget.IsValid())
|
|
{
|
|
TSharedPtr< ISlateViewport > SlateViewport = CurrentGameViewportWidget->GetViewportInterface().Pin();
|
|
if (SlateViewport.IsValid())
|
|
{
|
|
bCanCloseWindow = !SlateViewport->OnRequestWindowClose().bIsHandled;
|
|
}
|
|
}
|
|
|
|
if (bCanCloseWindow)
|
|
{
|
|
Window->RequestDestroyWindow();
|
|
}
|
|
}
|
|
}
|
|
|
|
EDropEffect::Type FSlateApplication::OnDragEnterText( const TSharedRef< FGenericWindow >& Window, const FString& Text )
|
|
{
|
|
const TSharedPtr< FExternalDragOperation > DragDropOperation = FExternalDragOperation::NewText( Text );
|
|
const TSharedPtr< SWindow > EffectingWindow = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, Window );
|
|
|
|
EDropEffect::Type Result = EDropEffect::None;
|
|
if ( DragDropOperation.IsValid() && EffectingWindow.IsValid() )
|
|
{
|
|
Result = OnDragEnter( EffectingWindow.ToSharedRef(), DragDropOperation.ToSharedRef() );
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
EDropEffect::Type FSlateApplication::OnDragEnterFiles( const TSharedRef< FGenericWindow >& Window, const TArray< FString >& Files )
|
|
{
|
|
const TSharedPtr< FExternalDragOperation > DragDropOperation = FExternalDragOperation::NewFiles( Files );
|
|
const TSharedPtr< SWindow > EffectingWindow = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, Window );
|
|
|
|
EDropEffect::Type Result = EDropEffect::None;
|
|
if ( DragDropOperation.IsValid() && EffectingWindow.IsValid() )
|
|
{
|
|
Result = OnDragEnter( EffectingWindow.ToSharedRef(), DragDropOperation.ToSharedRef() );
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
EDropEffect::Type FSlateApplication::OnDragEnterExternal( const TSharedRef< FGenericWindow >& Window, const FString& Text, const TArray< FString >& Files )
|
|
{
|
|
const TSharedPtr< FExternalDragOperation > DragDropOperation = FExternalDragOperation::NewOperation( Text, Files );
|
|
const TSharedPtr< SWindow > EffectingWindow = FSlateWindowHelper::FindWindowByPlatformWindow( SlateWindows, Window );
|
|
|
|
EDropEffect::Type Result = EDropEffect::None;
|
|
if ( DragDropOperation.IsValid() && EffectingWindow.IsValid() )
|
|
{
|
|
Result = OnDragEnter( EffectingWindow.ToSharedRef(), DragDropOperation.ToSharedRef() );
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
EDropEffect::Type FSlateApplication::OnDragEnter( const TSharedRef< SWindow >& Window, const TSharedRef<FExternalDragOperation>& DragDropOperation )
|
|
{
|
|
// We are encountering a new drag and drop operation.
|
|
// Assume we cannot handle it.
|
|
DragIsHandled = false;
|
|
|
|
const FVector2f CurrentCursorPosition = GetCursorPos();
|
|
const FVector2f LastCursorPosition = GetLastCursorPos();
|
|
|
|
// Tell slate to enter drag and drop mode.
|
|
// Make a faux mouse event for slate, so we can initiate a drag and drop.
|
|
FDragDropEvent DragDropEvent(
|
|
FPointerEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
CurrentCursorPosition,
|
|
LastCursorPosition,
|
|
PressedMouseButtons,
|
|
EKeys::Invalid,
|
|
0,
|
|
PlatformApplication->GetModifierKeys() ),
|
|
DragDropOperation
|
|
);
|
|
|
|
ProcessDragEnterEvent( Window, DragDropEvent );
|
|
return EDropEffect::None;
|
|
}
|
|
|
|
bool FSlateApplication::ProcessDragEnterEvent( TSharedRef<SWindow> WindowEntered, const FDragDropEvent& DragDropEvent )
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::FScopeProcessInputEvent Scope(ESlateDebuggingInputEvent::DragDrop, DragDropEvent);
|
|
#endif
|
|
|
|
SetLastUserInteractionTime(this->GetCurrentTime());
|
|
|
|
FWidgetPath WidgetsUnderCursor = LocateWindowUnderMouse( DragDropEvent.GetScreenSpacePosition(), GetInteractiveTopLevelWindows(), false, DragDropEvent.GetUserIndex());
|
|
// There are no "interactable" widget under the cursor.
|
|
if (!WidgetsUnderCursor.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Switch worlds for widgets in the current path
|
|
FScopedSwitchWorldHack SwitchWorld( WidgetsUnderCursor );
|
|
|
|
FReply TriggerDragDropReply = FReply::Handled().BeginDragDrop( DragDropEvent.GetOperation().ToSharedRef() );
|
|
ProcessReply( WidgetsUnderCursor, TriggerDragDropReply, &WidgetsUnderCursor, &DragDropEvent );
|
|
|
|
GetOrCreateUser(DragDropEvent)->UpdatePointerPosition(DragDropEvent);
|
|
|
|
return true;
|
|
}
|
|
|
|
EDropEffect::Type FSlateApplication::OnDragOver( const TSharedPtr< FGenericWindow >& Window )
|
|
{
|
|
EDropEffect::Type Result = EDropEffect::None;
|
|
|
|
if (GetCursorUser()->IsDragDropping())
|
|
{
|
|
bool MouseMoveHandled = true;
|
|
FVector2f CursorMovementDelta( 0, 0 );
|
|
const FVector2f CurrentCursorPosition = GetCursorPos();
|
|
const FVector2f LastCursorPosition = GetLastCursorPos();
|
|
|
|
if ( LastCursorPosition != CurrentCursorPosition )
|
|
{
|
|
FPointerEvent MouseEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
CurrentCursorPosition,
|
|
LastCursorPosition,
|
|
PressedMouseButtons,
|
|
EKeys::Invalid,
|
|
0,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
MouseMoveHandled = ProcessMouseMoveEvent( MouseEvent );
|
|
CursorMovementDelta = MouseEvent.GetCursorDelta();
|
|
}
|
|
|
|
// Slate is now in DragAndDrop mode. It is tracking the payload.
|
|
// We just need to convey mouse movement.
|
|
if ( CursorMovementDelta.SizeSquared() > 0 )
|
|
{
|
|
DragIsHandled = MouseMoveHandled;
|
|
}
|
|
|
|
if ( DragIsHandled )
|
|
{
|
|
Result = EDropEffect::Copy;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void FSlateApplication::OnDragLeave( const TSharedPtr< FGenericWindow >& Window )
|
|
{
|
|
GetCursorUser()->ResetDragDropContent();
|
|
}
|
|
|
|
EDropEffect::Type FSlateApplication::OnDragDrop(const TSharedPtr< FGenericWindow >& Window)
|
|
{
|
|
EDropEffect::Type Result = EDropEffect::None;
|
|
|
|
if (GetCursorUser()->IsDragDropping())
|
|
{
|
|
FPointerEvent MouseEvent(
|
|
GetUserIndexForMouse(),
|
|
CursorPointerIndex,
|
|
GetCursorPos(),
|
|
GetLastCursorPos(),
|
|
PressedMouseButtons,
|
|
EKeys::LeftMouseButton,
|
|
0,
|
|
PlatformApplication->GetModifierKeys()
|
|
);
|
|
|
|
// User dropped into a Slate window. Slate is already in drag and drop mode.
|
|
// It knows what to do based on a mouse up.
|
|
if (ProcessMouseButtonUpEvent(MouseEvent))
|
|
{
|
|
Result = EDropEffect::Copy;
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool FSlateApplication::OnWindowAction( const TSharedRef< FGenericWindow >& PlatformWindow, const EWindowAction::Type InActionType)
|
|
{
|
|
// Return false to tell the OS layer that it should ignore the action
|
|
|
|
if (IsExternalUIOpened())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bResult = true;
|
|
|
|
for (int32 Index = 0; Index < OnWindowActionNotifications.Num(); Index++)
|
|
{
|
|
if (OnWindowActionNotifications[Index].IsBound())
|
|
{
|
|
if (OnWindowActionNotifications[Index].Execute(PlatformWindow, InActionType))
|
|
{
|
|
// If the delegate returned true, it means that it wants the OS layer to stop processing the action
|
|
bResult = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (InActionType == EWindowAction::ClickedNonClientArea)
|
|
{
|
|
#if WITH_SLATE_DEBUGGING
|
|
if (!CVarSlateDismissMenuStacksOnFocusLost->GetBool())
|
|
{
|
|
return bResult;
|
|
}
|
|
#endif
|
|
|
|
DismissAllMenus();
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void FSlateApplication::OnVirtualDesktopSizeChanged(const FDisplayMetrics& NewDisplayMetric)
|
|
{
|
|
CachedDisplayMetrics = NewDisplayMetric;
|
|
CachedDebugTitleSafeRatio = FDisplayMetrics::GetDebugTitleSafeZoneRatio();
|
|
const FPlatformRect& VirtualDisplayRect = NewDisplayMetric.VirtualDisplayRect;
|
|
VirtualDesktopRect = FSlateRect(
|
|
VirtualDisplayRect.Left,
|
|
VirtualDisplayRect.Top,
|
|
VirtualDisplayRect.Right,
|
|
VirtualDisplayRect.Bottom);
|
|
|
|
if (Renderer.IsValid())
|
|
{
|
|
Renderer->OnVirtualDesktopSizeChanged(NewDisplayMetric);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
*****************************************************************************/
|
|
|
|
TSharedRef<FSlateApplication> FSlateApplication::InitializeAsStandaloneApplication(const TSharedRef<FSlateRenderer>& PlatformRenderer)
|
|
{
|
|
return InitializeAsStandaloneApplication(PlatformRenderer, MakeShareable(FPlatformApplicationMisc::CreateApplication()));
|
|
}
|
|
|
|
|
|
TSharedRef<FSlateApplication> FSlateApplication::InitializeAsStandaloneApplication(const TSharedRef< class FSlateRenderer >& PlatformRenderer, const TSharedRef<class GenericApplication>& InPlatformApplication)
|
|
{
|
|
// Initialise High DPI mode. This must be called before any window (including the splash screen is created).
|
|
// We don't need to use the force flag which was added for PIE.
|
|
const bool bForceEnable = false;
|
|
FSlateApplication::InitHighDPI(bForceEnable);
|
|
|
|
// create the platform slate application (what FSlateApplication::Get() returns)
|
|
TSharedRef<FSlateApplication> Slate = FSlateApplication::Create(InPlatformApplication);
|
|
|
|
// initialize renderer
|
|
FSlateApplication::Get().InitializeRenderer(PlatformRenderer);
|
|
|
|
// set the normal UE IsEngineExitRequested() when outer frame is closed
|
|
FSlateApplication::Get().SetExitRequestedHandler(FSimpleDelegate::CreateStatic(&OnRequestExit));
|
|
|
|
return Slate;
|
|
}
|
|
|
|
void FSlateApplication::InitializeCoreStyle()
|
|
{
|
|
if (!FStarshipCoreStyle::IsInitialized())
|
|
{
|
|
#if ALLOW_THEMES
|
|
USlateThemeManager::Get().LoadThemes();
|
|
#endif
|
|
FStarshipCoreStyle::ResetToDefault();
|
|
FAppStyle::SetAppStyleSet(FStarshipCoreStyle::GetCoreStyle());
|
|
}
|
|
|
|
FUMGCoreStyle::ResetToDefault();
|
|
}
|
|
|
|
void FSlateApplication::SetWidgetReflector(const TSharedRef<IWidgetReflector>& WidgetReflector)
|
|
{
|
|
if ( SourceCodeAccessDelegate.IsBound() )
|
|
{
|
|
WidgetReflector->SetSourceAccessDelegate(SourceCodeAccessDelegate);
|
|
}
|
|
|
|
if ( AssetAccessDelegate.IsBound() )
|
|
{
|
|
WidgetReflector->SetAssetAccessDelegate(AssetAccessDelegate);
|
|
}
|
|
|
|
WidgetReflectorPtr = WidgetReflector;
|
|
}
|
|
|
|
bool FSlateApplication::IsDragDropping() const
|
|
{
|
|
return GetCursorUser()->IsDragDropping();
|
|
}
|
|
|
|
bool FSlateApplication::IsDragDroppingAffected(const FPointerEvent& InPointerEvent) const
|
|
{
|
|
return GetCursorUser()->IsDragDroppingAffected(InPointerEvent);
|
|
}
|
|
|
|
TSharedPtr<FDragDropOperation> FSlateApplication::GetDragDroppingContent() const
|
|
{
|
|
return GetCursorUser()->GetDragDropContent();
|
|
}
|
|
|
|
void FSlateApplication::CancelDragDrop()
|
|
{
|
|
GetCursorUser()->CancelDragDrop();
|
|
}
|
|
|
|
void FSlateApplication::NavigateFromWidgetUnderCursor(const uint32 InUserIndex, EUINavigation InNavigationType, TSharedRef<SWindow> InWindow)
|
|
{
|
|
if (InNavigationType != EUINavigation::Invalid)
|
|
{
|
|
FWidgetPath PathToLocatedWidget = LocateWidgetInWindow(GetOrCreateUser(InUserIndex)->GetCursorPosition(), InWindow, false, InUserIndex);
|
|
if (PathToLocatedWidget.IsValid())
|
|
{
|
|
TSharedPtr<SWidget> WidgetToNavFrom = PathToLocatedWidget.Widgets.Last().Widget;
|
|
|
|
if (WidgetToNavFrom.IsValid())
|
|
{
|
|
ProcessReply(PathToLocatedWidget, FReply::Handled().SetNavigation(InNavigationType, ENavigationGenesis::User, ENavigationSource::WidgetUnderCursor), &PathToLocatedWidget, nullptr, InUserIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::InputPreProcessorsHelper::Tick(const float DeltaTime, FSlateApplication& SlateApp, TSharedRef<ICursor> Cursor)
|
|
{
|
|
TGuardValue<bool> IteratingGuard(bIsIteratingPreProcessors, true);
|
|
|
|
for (const TSharedPtr<IInputProcessor>& Processor : InputPreProcessorsIteratorList)
|
|
{
|
|
Processor->Tick(DeltaTime, SlateApp, Cursor);
|
|
}
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleKeyDownEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::KeyDown
|
|
, [&SlateApp, &InKeyEvent](IInputProcessor& Processor) { return Processor.HandleKeyDownEvent(SlateApp, InKeyEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleKeyUpEvent(FSlateApplication& SlateApp, const FKeyEvent& InKeyEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::KeyUp
|
|
, [&SlateApp, &InKeyEvent](IInputProcessor& Processor) { return Processor.HandleKeyUpEvent(SlateApp, InKeyEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleAnalogInputEvent(FSlateApplication& SlateApp, const FAnalogInputEvent& InAnalogInputEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::AnalogInput
|
|
, [&SlateApp, &InAnalogInputEvent](IInputProcessor& Processor) { return Processor.HandleAnalogInputEvent(SlateApp, InAnalogInputEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleMouseMoveEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::MouseMove
|
|
, [&SlateApp, &MouseEvent](IInputProcessor& Processor) { return Processor.HandleMouseMoveEvent(SlateApp, MouseEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleMouseButtonDownEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::MouseButtonDown
|
|
, [&SlateApp, &MouseEvent](IInputProcessor& Processor) { return Processor.HandleMouseButtonDownEvent(SlateApp, MouseEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleMouseButtonUpEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::MouseButtonUp
|
|
, [&SlateApp, &MouseEvent](IInputProcessor& Processor) { return Processor.HandleMouseButtonUpEvent(SlateApp, MouseEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleMouseButtonDoubleClickEvent(FSlateApplication& SlateApp, const FPointerEvent& MouseEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::MouseButtonDoubleClick
|
|
, [&SlateApp, &MouseEvent](IInputProcessor& Processor) { return Processor.HandleMouseButtonDoubleClickEvent(SlateApp, MouseEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleMouseWheelOrGestureEvent(FSlateApplication& SlateApp, const FPointerEvent& WheelEvent, const FPointerEvent* GestureEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::MouseWheel
|
|
, [&SlateApp, &WheelEvent, &GestureEvent](IInputProcessor& Processor) { return Processor.HandleMouseWheelOrGestureEvent(SlateApp, WheelEvent, GestureEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::HandleMotionDetectedEvent(FSlateApplication& SlateApp, const FMotionEvent& MotionEvent)
|
|
{
|
|
return PreProcessInput(ESlateDebuggingInputEvent::MotionDetected
|
|
, [&SlateApp, &MotionEvent](IInputProcessor& Processor) { return Processor.HandleMotionDetectedEvent(SlateApp, MotionEvent); });
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::Add(const FInputPreprocessorRegistration& Registration)
|
|
{
|
|
// We check if the processor attempting registration is already registered
|
|
bool bAlreadyRegistered = false;
|
|
for (const FProcessorTypeStorage& Storage : InputPreProcessors)
|
|
{
|
|
if (Storage.Contains(Registration.InputProcessor))
|
|
{
|
|
bAlreadyRegistered = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!bAlreadyRegistered)
|
|
{
|
|
if (!bIsIteratingPreProcessors)
|
|
{
|
|
AddInternal(Registration);
|
|
}
|
|
else
|
|
{
|
|
ProcessorsPendingAddition.Add(Registration);
|
|
}
|
|
}
|
|
|
|
ProcessorsPendingRemoval.Remove(Registration.InputProcessor);
|
|
|
|
return !bAlreadyRegistered;
|
|
}
|
|
|
|
void FSlateApplication::InputPreProcessorsHelper::AddInternal(const FInputPreprocessorRegistration& Registration)
|
|
{
|
|
if (!InputPreProcessors.IsValidIndex((uint32)Registration.Info.Type))
|
|
{
|
|
InputPreProcessors.EmplaceAt((int32)Registration.Info.Type, FProcessorTypeStorage());
|
|
}
|
|
|
|
FProcessorTypeStorage& Storage = InputPreProcessors[(uint32)Registration.Info.Type];
|
|
|
|
if (Registration.Info.Priority == INDEX_NONE)
|
|
{
|
|
Storage.Add(Registration.InputProcessor);
|
|
}
|
|
else
|
|
{
|
|
if (Registration.Info.Priority >= Storage.Num())
|
|
{
|
|
Storage.SetNum(Registration.Info.Priority); // No need for +1, insertion at the Num position doesn't cause an error. +1 would add unneeded empty spaces.
|
|
}
|
|
Storage.Insert(Registration.InputProcessor, Registration.Info.Priority);
|
|
}
|
|
|
|
// We rebuild the iterator list
|
|
InputPreProcessorsIteratorList.Reset();
|
|
for (const FProcessorTypeStorage& TypeStorage : InputPreProcessors)
|
|
{
|
|
for (const TSharedPtr<IInputProcessor>& Processor : TypeStorage)
|
|
{
|
|
// We won't add the empty spaces in the map to the list
|
|
if (Processor)
|
|
{
|
|
InputPreProcessorsIteratorList.Add(Processor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FSlateApplication::InputPreProcessorsHelper::Remove(TSharedPtr<IInputProcessor> InputProcessor)
|
|
{
|
|
if (bIsIteratingPreProcessors)
|
|
{
|
|
ProcessorsPendingRemoval.Add(InputProcessor);
|
|
}
|
|
else
|
|
{
|
|
for (FProcessorTypeStorage& Storage : InputPreProcessors)
|
|
{
|
|
Storage.Remove(InputProcessor);
|
|
}
|
|
|
|
InputPreProcessorsIteratorList.Remove(InputProcessor);
|
|
}
|
|
|
|
ProcessorsPendingAddition.RemoveAllSwap([InputProcessor](const FInputPreprocessorRegistration& Registration)
|
|
{
|
|
return Registration.InputProcessor == InputProcessor;
|
|
});
|
|
}
|
|
|
|
void FSlateApplication::InputPreProcessorsHelper::RemoveAll()
|
|
{
|
|
if (bIsIteratingPreProcessors)
|
|
{
|
|
for (const FProcessorTypeStorage& Storage : InputPreProcessors)
|
|
{
|
|
ProcessorsPendingRemoval.Append(Storage);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (FProcessorTypeStorage& Storage : InputPreProcessors)
|
|
{
|
|
Storage.Reset();
|
|
}
|
|
|
|
InputPreProcessorsIteratorList.Reset();
|
|
}
|
|
|
|
ProcessorsPendingAddition.Reset();
|
|
}
|
|
|
|
int32 FSlateApplication::InputPreProcessorsHelper::Find(TSharedPtr<IInputProcessor> InputProcessor, const EInputPreProcessorType& Type) const
|
|
{
|
|
const uint32 TypeInt = static_cast<uint32>(Type);
|
|
if (InputPreProcessors.IsValidIndex(TypeInt))
|
|
{
|
|
const FProcessorTypeStorage& Storage = InputPreProcessors[TypeInt];
|
|
return Storage.Find(InputProcessor);
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
bool FSlateApplication::InputPreProcessorsHelper::PreProcessInput(ESlateDebuggingInputEvent InputEvent, TFunctionRef<bool(IInputProcessor&)> InputProcessFunc)
|
|
{
|
|
TGuardValue<bool> IteratingGuard(bIsIteratingPreProcessors, true);
|
|
|
|
bool bShouldExit = false;
|
|
for (const TSharedPtr<IInputProcessor>& Processor : InputPreProcessorsIteratorList)
|
|
{
|
|
bShouldExit = InputProcessFunc(*Processor);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
FSlateDebugging::BroadcastPreProcessInputEvent(InputEvent, Processor->GetDebugName(), bShouldExit);
|
|
#endif
|
|
if (bShouldExit)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int32 Index = ProcessorsPendingRemoval.Num() - 1; Index >= 0; --Index)
|
|
{
|
|
TSharedPtr<IInputProcessor>& Processor = ProcessorsPendingRemoval[Index];
|
|
|
|
for (FProcessorTypeStorage& Storage : InputPreProcessors)
|
|
{
|
|
Storage.Remove(Processor);
|
|
InputPreProcessorsIteratorList.Remove(Processor);
|
|
}
|
|
}
|
|
ProcessorsPendingRemoval.Reset();
|
|
|
|
for (const FInputPreprocessorRegistration& Registration : ProcessorsPendingAddition)
|
|
{
|
|
AddInternal(Registration);
|
|
}
|
|
ProcessorsPendingAddition.Reset();
|
|
|
|
return bShouldExit;
|
|
}
|
|
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
namespace UE::Slate::Private
|
|
{
|
|
|
|
bool VerifyParentChildrenRelationship_Recursive(SWidget* Parent, TMap<const SWidget*, const SWidget*>& AllWidgets)
|
|
{
|
|
bool bResult = true;
|
|
Parent->GetAllChildren()->ForEachWidget([&bResult, Parent, &AllWidgets](SWidget& ChildWidget)
|
|
{
|
|
if (&ChildWidget != &SNullWidget::NullWidget.Get())
|
|
{
|
|
if (AllWidgets.Find(&ChildWidget))
|
|
{
|
|
bResult = false;
|
|
UE_LOG(LogSlate, Warning, TEXT("The widget '%s' is owned by more than one parent. 1:'%s' 2:'%s'.")
|
|
, *FReflectionMetaData::GetWidgetDebugInfo(ChildWidget)
|
|
, *FReflectionMetaData::GetWidgetDebugInfo(AllWidgets[&ChildWidget])
|
|
, *FReflectionMetaData::GetWidgetDebugInfo(Parent));
|
|
}
|
|
else
|
|
{
|
|
AllWidgets.Add(&ChildWidget, Parent);
|
|
}
|
|
|
|
if (ChildWidget.GetParentWidget().Get() != Parent)
|
|
{
|
|
bResult = false;
|
|
UE_LOG(LogSlate, Warning, TEXT("The widget '%s' has the wrong parent."), *FReflectionMetaData::GetWidgetDebugInfo(ChildWidget));
|
|
}
|
|
|
|
bResult = bResult && VerifyParentChildrenRelationship_Recursive(&ChildWidget, AllWidgets);
|
|
}
|
|
});
|
|
|
|
return bResult;
|
|
}
|
|
|
|
bool VerifyWidgetLayerId_Recursive(SWidget& Widget, bool bInsideInvalidationRoot)
|
|
{
|
|
bool bResult = true;
|
|
|
|
if (!bInsideInvalidationRoot)
|
|
{
|
|
if (const FSlateInvalidationRoot* AsInvalidationRoot = Widget.Advanced_AsInvalidationRoot())
|
|
{
|
|
bInsideInvalidationRoot = AsInvalidationRoot->GetLastPaintType() == ESlateInvalidationPaintType::Fast;
|
|
}
|
|
}
|
|
|
|
const uint32 LastPaintFrame = Widget.Debug_GetLastPaintFrame();
|
|
const bool bIsDeferredPaint = Widget.GetPersistentState().bDeferredPainting;
|
|
const int32 InLayerId = Widget.GetPersistentState().LayerId;
|
|
const int32 OutLayerId = Widget.GetPersistentState().OutgoingLayerId;
|
|
|
|
int32 PreviousInLayerId = InLayerId;
|
|
int32 PreviousOutLayerId = InLayerId;
|
|
|
|
Widget.GetAllChildren()->ForEachWidget([&bResult, bInsideInvalidationRoot, LastPaintFrame, bIsDeferredPaint, InLayerId, OutLayerId](SWidget& ChildWidget)
|
|
{
|
|
if (&ChildWidget != &SNullWidget::NullWidget.Get())
|
|
{
|
|
|
|
if (ChildWidget.GetVisibility().IsVisible() && (bInsideInvalidationRoot || ChildWidget.Debug_GetLastPaintFrame() == LastPaintFrame))
|
|
{
|
|
const bool bIsChildDeferredPaint = ChildWidget.GetPersistentState().bDeferredPainting;
|
|
if (bIsChildDeferredPaint == bIsDeferredPaint)
|
|
{
|
|
const int32 ChildInLayerId = ChildWidget.GetPersistentState().LayerId;
|
|
const int32 ChildOutLayerId = ChildWidget.GetPersistentState().OutgoingLayerId;
|
|
|
|
if (ChildInLayerId== 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bLayerInIsValid = InLayerId <= ChildInLayerId && InLayerId <= ChildOutLayerId;
|
|
const bool bLayerOutIsValid = OutLayerId >= ChildInLayerId && OutLayerId >= ChildOutLayerId;
|
|
if (!bLayerInIsValid || !bLayerOutIsValid)
|
|
{
|
|
SWidget* ParentWidget = ChildWidget.GetParentWidget().Get();
|
|
check(ParentWidget);
|
|
// The parent may just have tick and will be painted on the next frame (this is not desired but possible.
|
|
if (!ParentWidget->GetProxyHandle().HasAnyInvalidationReason(ParentWidget, EInvalidateWidgetReason::Paint))
|
|
{
|
|
bResult = false;
|
|
UE_LOG(LogSlate, Warning, TEXT("The widget '%s' LayerId is invalid. Parent: [%d,%d] Child: [%d,%d].")
|
|
, *FReflectionMetaData::GetWidgetDebugInfo(ChildWidget)
|
|
, InLayerId
|
|
, OutLayerId
|
|
, ChildInLayerId
|
|
, ChildOutLayerId);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
bResult = bResult && VerifyWidgetLayerId_Recursive(ChildWidget, bInsideInvalidationRoot);
|
|
}
|
|
}
|
|
});
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void VerifyParentChildrenRelationship(const TSharedRef<SWindow>& WindowToDraw)
|
|
{
|
|
if (WindowToDraw != SNullWidget::NullWidget)
|
|
{
|
|
TMap<const SWidget*, const SWidget*> AllWidgets;
|
|
AllWidgets.Add(&WindowToDraw.Get(), nullptr);
|
|
if (!Private::VerifyParentChildrenRelationship_Recursive(&WindowToDraw.Get(), AllWidgets))
|
|
{
|
|
CVarSlateVerifyParentChildrenRelationship->Set(false, CVarSlateVerifyParentChildrenRelationship->GetFlags());
|
|
ensureAlwaysMsgf(false, TEXT("VerifyParentChildrenRelationship failed. See log for more info."));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void VerifyWidgetLayerId(const TSharedRef<SWindow>& WindowToDraw)
|
|
{
|
|
if (WindowToDraw != SNullWidget::NullWidget)
|
|
{
|
|
if (!Private::VerifyWidgetLayerId_Recursive(WindowToDraw.Get(), false))
|
|
{
|
|
CVarSlateVerifyWidgetLayerId->Set(false, CVarSlateVerifyWidgetLayerId->GetFlags());
|
|
ensureAlwaysMsgf(false, TEXT("VerifyWidgetLayerId failed. See log for more info."));
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
} //namespace
|
|
#endif //WITH_SLATE_DEBUGGING
|