// 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& WindowToDraw); void VerifyWidgetLayerId(const TSharedRef& 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::min(), std::numeric_limits::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(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::TranslationNeeded()) { const EventType TranslatedEvent = Translate::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 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 { 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( 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 EmptyTouchKeySet; } /** True if we should allow throttling based on mouse movement activity. int32 instead of bool only for console variable system. */ TAutoConsoleVariable 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 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 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 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& 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& 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& 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& InWidgetToMatch ) : WidgetToMatch( InWidgetToMatch ) {} bool operator()(const FArrangedWidget& Candidate) const { return WidgetToMatch == Candidate.Widget; } const TSharedRef& 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& 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 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::Create(const TSharedRef& 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(); }); 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::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()) #if WITH_EDITOR , EditorNavigationConfig(MakeShared()) #endif , SimulateGestures(false, (int32)EGestureEvent::Count) , ProcessingInput(0) , InputManager(MakeShared()) { #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(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 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& 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 PlatformTextField) { SlateTextField = MoveTemp(PlatformTextField); } void FSlateApplication::UsePlatformCursorForCursorUser(bool bUsePlatformCursor) { if (TSharedPtr 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()); 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 NativeWindowUnderMouse = PlatformApplication->GetWindowUnderCursor(); if (NativeWindowUnderMouse.IsValid()) { TSharedPtr 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& 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& WindowToTest) const { for (TSharedPtr 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& 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 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(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 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 > WindowChildren = WindowToDraw->GetChildWindows(); for (int32 ChildIndex=0; ChildIndex < WindowChildren.Num(); ++ChildIndex) { DrawWindowAndChildren( WindowChildren[ChildIndex], DrawWindowArgs ); } } } static bool DoAnyWindowDescendantsNeedPrepass(TSharedRef WindowToPrepass) { TArray>& ChildWindows = WindowToPrepass->GetChildWindows(); if (ChildWindows.IsEmpty()) { return false; } const TSharedRef& ChildWindow = ChildWindows[0]; if (ChildWindow->IsVisible() && !ChildWindow->IsWindowMinimized()) { return true; } else { return DoAnyWindowDescendantsNeedPrepass(ChildWindow); } } static void PrepassWindowAndChildren(TSharedRef WindowToPrepass, const TSharedPtr& DebuggingWindow, TWeakPtr& 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(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, FConcurrentLinearArrayAllocator> ChildWindows(WindowToPrepass->GetChildWindows()); for (const TSharedRef& ChildWindow : ChildWindows) { PrepassWindowAndChildren(ChildWindow, DebuggingWindow, CurrentContext); } } } void FSlateApplication::DrawPrepass( TSharedPtr DrawOnlyThisWindow ) { SCOPED_NAMED_EVENT_TEXT("Slate::Prepass", FColor::Magenta); CSV_SCOPED_TIMING_STAT(Slate, DrawPrePass); TSharedPtr CurrentDebuggingWindowPinned = CurrentDebuggingWindow.Pin(); if (TSharedPtr ActiveModalWindow = GetActiveModalWindow()) { PrepassWindowAndChildren(ActiveModalWindow.ToSharedRef(), CurrentDebuggingWindowPinned, CurrentDebugContextWidget); for (TArray< TSharedRef >::TConstIterator CurrentWindowIt(SlateWindows); CurrentWindowIt; ++CurrentWindowIt) { const TSharedRef& CurrentWindow = *CurrentWindowIt; if (CurrentWindow->IsTopmostWindow()) { PrepassWindowAndChildren(CurrentWindow, CurrentDebuggingWindowPinned, CurrentDebugContextWidget); } } TArray< TSharedRef > 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& CurrentWindow : SlateWindows) { PrepassWindowAndChildren(CurrentWindow, CurrentDebuggingWindowPinned, CurrentDebugContextWidget); } } } TArray GatherAllDescendants(const TArray< TSharedRef >& InWindowList) { TArray GatheredDescendants; GatheredDescendants.Reserve(InWindowList.Num()); for (const TSharedRef& Window : InWindowList) { GatheredDescendants.Add(&Window.Get()); } for (const TSharedRef& SomeWindow : InWindowList) { GatheredDescendants.Append(GatherAllDescendants(SomeWindow->GetChildWindows())); } return GatheredDescendants; } void FSlateApplication::PrivateDrawWindows( TSharedPtr 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 ActiveModalWindow = GetActiveModalWindow(); if (ActiveModalWindow.IsValid()) { DrawWindowAndChildren( ActiveModalWindow.ToSharedRef(), DrawWindowArgs ); for( TArray< TSharedRef >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt ) { const TSharedRef& CurrentWindow = *CurrentWindowIt; if ( CurrentWindow->GetType() == EWindowType::ToolTip ) { DrawWindowAndChildren(CurrentWindow, DrawWindowArgs); } } TArray< TSharedRef > 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 >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt ) { TSharedRef 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 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& Captor : User.GetCaptorWidgets()) { Captor->OnFinishedPointerInput(); } } else { for (const auto& IndexPathPair : User.GetWidgetsUnderPointerLastEventByIndex()) { for (const TWeakPtr& WidgetPtr : IndexPathPair.Value.Widgets) { if (TSharedPtr 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& WidgetPtr : WidgetPath.Widgets ) { if (TSharedPtr 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 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 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& 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 WidgetsAndCursors = Window->GetHittestGrid().GetBubblePath(CursorPosition, GetCursorRadius(), bIgnoreEnabledStatus, UserIndex); return FWidgetPath(MoveTemp(WidgetsAndCursors)); } else { return FWidgetPath(); } } TSharedRef FSlateApplication::AddWindow( TSharedRef 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 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 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 NativeParent = nullptr; TSharedPtr 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> Viewports, const FWeakWidgetPath& FocusPath) { if (Viewports.Num() > 0) { for (const TWeakPtr& FocusWidget : FocusPath.Widgets) { for (const TWeakPtr& Viewport : Viewports) { if (FocusWidget == Viewport) { return true; } } } } return false; } #endif EUINavigation FSlateApplication::GetNavigationDirectionFromKey(const FKeyEvent& InKeyEvent) const { TSharedRef RelevantNavConfig = GetRelevantNavConfig(InKeyEvent.GetUserIndex()); return RelevantNavConfig->GetNavigationDirectionFromKey(InKeyEvent); } EUINavigation FSlateApplication::GetNavigationDirectionFromAnalog(const FAnalogInputEvent& InAnalogEvent) { TSharedRef RelevantNavConfig = GetRelevantNavConfig(InAnalogEvent.GetUserIndex()); return RelevantNavConfig->GetNavigationDirectionFromAnalog(InAnalogEvent); } EUINavigationAction FSlateApplication::GetNavigationActionFromKey(const FKeyEvent& InKeyEvent) const { TSharedRef 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 InSlateWindow, const TSharedPtr 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(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 FSlateApplication::AddWindowAsNativeChild( TSharedRef InSlateWindow, TSharedRef 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 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 FSlateApplication::PushMenu(const TSharedRef& InParentWidget, const FWidgetPath& InOwnerPath, const TSharedRef& InContent, const UE::Slate::FDeprecateVector2DParameter& SummonLocation, const FPopupTransitionEffect& TransitionEffect, const bool bFocusImmediately, const UE::Slate::FDeprecateVector2DParameter& SummonLocationSize, TOptional 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(); } TSharedPtr FSlateApplication::PushMenu(const TSharedPtr& InParentMenu, const TSharedRef& 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 FSlateApplication::PushHostedMenu(const TSharedRef& InParentWidget, const FWidgetPath& InOwnerPath, const TSharedRef& InMenuHost, const TSharedRef& InContent, TSharedPtr& 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(); } TSharedPtr FSlateApplication::PushHostedMenu(const TSharedPtr& InParentMenu, const TSharedRef& InMenuHost, const TSharedRef& InContent, TSharedPtr& OutWrappedContent, const FPopupTransitionEffect& TransitionEffect, EShouldThrottle ShouldThrottle, const bool bIsCollapsedByParent) { return MenuStack.PushHosted(InParentMenu, InMenuHost, InContent, OutWrappedContent, TransitionEffect, ShouldThrottle, bIsCollapsedByParent); } bool FSlateApplication::HasOpenSubMenus(TSharedPtr InMenu) const { return MenuStack.HasOpenSubMenus(InMenu); } bool FSlateApplication::AnyMenusVisible() const { return MenuStack.HasMenus(); } TSharedPtr FSlateApplication::FindMenuInWidgetPath(const FWidgetPath& InWidgetPath) const { return MenuStack.FindMenuInWidgetPath(InWidgetPath); } TSharedPtr FSlateApplication::GetVisibleMenuWindow() const { return MenuStack.GetHostWindow(); } TSharedPtr FSlateApplication::GetMenuHostWidget() const { return MenuStack.GetHostWidget(); } void FSlateApplication::DismissAllMenus() { MenuStack.DismissAll(); } void FSlateApplication::DismissMenu(const TSharedPtr& InFromMenu) { MenuStack.DismissFrom(InFromMenu); } void FSlateApplication::DismissMenuByWidget(const TSharedRef& InWidgetInMenu) { FWidgetPath WidgetPath; if (GeneratePathToWidgetUnchecked(InWidgetInMenu, WidgetPath)) { TSharedPtr Menu = MenuStack.FindMenuInWidgetPath(WidgetPath); if (Menu.IsValid()) { MenuStack.DismissFrom(Menu); } } } void FSlateApplication::RequestDestroyWindow( TSharedRef 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 WindowToDestroy, TArray< TSharedRef >& OutWindowDestroyQueue) { /** @return the list of this window's child windows */ TArray< TSharedRef >& 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 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 >::TIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt ) { TSharedRef 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 >::TIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt ) { TSharedRef CurrentWindow = ( *CurrentWindowIt ); CurrentWindow->EnableWindow( true ); } } } } void FSlateApplication::InvalidateAllViewports() { Renderer->InvalidateAllViewports(); } void FSlateApplication::RegisterGameViewport( TSharedRef InViewport ) { RegisterViewport(InViewport); #if WITH_EDITOR AllGameViewports.Add(InViewport); #endif if (GameViewportWidget != InViewport) { InViewport->SetActive(true); GameViewportWidget = InViewport; } ActivateGameViewport(); } void FSlateApplication::RegisterViewport(TSharedRef InViewport) { TSharedPtr ParentWindow = FindWidgetWindow(InViewport); if (ParentWindow.IsValid()) { TWeakPtr 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 InWindow) { SlateVirtualWindows.AddUnique(InWindow); } void FSlateApplication::UnregisterVirtualWindow(TSharedRef 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 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 FSlateApplication::GetUserIndexForController(int32 ControllerId, FKey InKey) const { return InputManager->GetUserIndexForController(ControllerId, InKey); } int32 FSlateApplication::GetUserIndexForController(int32 ControllerId) const { return InputManager->GetUserIndexForController(ControllerId); } TOptional FSlateApplication::GetUserIndexForInputDevice(FInputDeviceId InputDeviceId) const { return InputManager->GetUserIndexForInputDevice(InputDeviceId); } TOptional FSlateApplication::GetUserIndexForPlatformUser(FPlatformUserId PlatformUser) const { return InputManager->GetUserIndexForPlatformUser(PlatformUser); } void FSlateApplication::SetInputManager(TSharedRef 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 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 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 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 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& WidgetToFocus, EFocusCause ReasonFocusIsChanging /* = EFocusCause::SetDirectly*/) { TSharedPtr 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& 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 FSlateApplication::GetUserFocusedWidget(uint32 UserIndex) const { TSharedPtr User = GetUser(UserIndex); return User ? User->GetFocusedWidget() : TSharedPtr(); } 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 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 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 FSlateApplication::FindBestParentWindowForDialogs(const TSharedPtr& InWidget, const ESlateParentWindowSearchMethod InParentWindowSearchMethod) { TSharedPtr ParentWindow = ( InWidget.IsValid() ) ? FindWidgetWindow(InWidget.ToSharedRef()) : TSharedPtr(); if ( !ParentWindow.IsValid() ) { // First check the active top level window. TSharedPtr 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 SlateWindow : SlateWindows ) { if ( SlateWindow->IsVisible() && SlateWindow->IsRegularWindow() ) { ParentWindow = SlateWindow; break; } } } } return ParentWindow; } const void* FSlateApplication::FindBestParentWindowHandleForDialogs(const TSharedPtr& InWidget, const ESlateParentWindowSearchMethod InParentWindowSearchMethod) { TSharedPtr ParentWindow = FindBestParentWindowForDialogs(InWidget, InParentWindowSearchMethod); const void* ParentWindowWindowHandle = nullptr; if ( ParentWindow.IsValid() && ParentWindow->GetNativeWindow().IsValid() ) { ParentWindowWindowHandle = ParentWindow->GetNativeWindow()->GetOSWindowHandle(); } return ParentWindowWindowHandle; } const TSet& FSlateApplication::GetPressedMouseButtons() const { return PressedMouseButtons; } TSharedPtr FSlateApplication::GetActiveTopLevelWindow() const { return ActiveTopLevelWindow.Pin(); } TSharedPtr FSlateApplication::GetActiveTopLevelRegularWindow() const { TSharedPtr ActiveWindow = ActiveTopLevelWindow.Pin(); while (ActiveWindow && !ActiveWindow->IsRegularWindow()) { ActiveWindow = ActiveWindow->GetParentWindow(); } return ActiveWindow; } TSharedPtr 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 User = GetUser(InUserIndex); return User && SetUserFocus(*User, InFocusPath, InCause); } bool FSlateApplication::SetUserFocusAllowingDescendantFocus(const uint32 InUserIndex, const FWidgetPath& InFocusPath, const EFocusCause InCause) { if (TSharedPtr User = GetUser(InUserIndex)) { const TSharedRef& 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 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 WidgetReflector = WidgetReflectorPtr.Pin(); const bool bReflectorShowingFocus = WidgetReflector.IsValid() && WidgetReflector->IsShowingFocus(); // Get the old Widget information const FWeakWidgetPath OldFocusedWidgetPath = User.GetWeakFocusPath(); TSharedPtr 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 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 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 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 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 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& 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 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 >::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 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 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 FSlateApplication::FindWidgetWindow( TSharedRef InWidget ) const { TSharedPtr TestWidget = ConstCastSharedRef(InWidget); while (TestWidget.IsValid()) { if (TestWidget->Advanced_IsWindow()) { return StaticCastSharedPtr(TestWidget); } TestWidget = TestWidget->GetParentWidget(); }; return nullptr; } TSharedPtr FSlateApplication::FindWidgetWindow( TSharedRef 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 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 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 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(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(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 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 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 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 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 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 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& 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 > FSlateApplication::GetInteractiveTopLevelWindows() { if (ActiveModalWindows.Num() > 0) { // If we have modal windows, only the topmost modal window and its children are interactive. TArray< TSharedRef, 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 CurrentWindow = SlateWindows[WindowIndex]; if (GetCursorUser()->IsWindowHousingInteractiveTooltip(CurrentWindow)) { OutWindows.Add(CurrentWindow); } } return TArray< TSharedRef >(OutWindows); } else { // No modal windows? All windows are interactive. return SlateWindows; } } void FSlateApplication::GetAllVisibleWindowsOrdered(TArray< TSharedRef >& OutWindows) { for( TArray< TSharedRef >::TConstIterator CurrentWindowIt( SlateWindows ); CurrentWindowIt; ++CurrentWindowIt ) { TSharedRef CurrentWindow = *CurrentWindowIt; if ( CurrentWindow->IsVisible() && !CurrentWindow->IsWindowMinimized() ) { GetAllVisibleChildWindows(OutWindows, CurrentWindow); } } } void FSlateApplication::GetAllVisibleChildWindows(TArray< TSharedRef >& OutWindows, TSharedRef CurrentWindow) { if ( CurrentWindow->IsVisible() && !CurrentWindow->IsWindowMinimized() ) { OutWindows.Add(CurrentWindow); const TArray< TSharedRef >& 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 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 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 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()); 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& Reference){ return Reference.Value == Id;}); if (ensure(IndexToRemove != INDEX_NONE)) { FSlateApplication::Get().PreventDebuggingModeStack.RemoveAtSwap(IndexToRemove); } } #endif bool FSlateApplication::IsWindowInDestroyQueue(TSharedRef 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 InputProcessor) { return RegisterInputPreProcessor(InputProcessor, FInputPreprocessorRegistrationKey()); } bool FSlateApplication::RegisterInputPreProcessor(TSharedPtr InputProcessor, const int32 Index) { return RegisterInputPreProcessor(InputProcessor, FInputPreprocessorRegistrationKey{ EInputPreProcessorType::Game, Index }); } bool FSlateApplication::RegisterInputPreProcessor(TSharedPtr InputProcessor, const EInputPreProcessorType Type) { return RegisterInputPreProcessor(InputProcessor, FInputPreprocessorRegistrationKey{ Type, INDEX_NONE }); } bool FSlateApplication::RegisterInputPreProcessor(TSharedPtr InputProcessor, const FInputPreprocessorRegistrationKey& Info) { bool bResult = false; if (InputProcessor.IsValid()) { bResult = InputPreProcessors.Add(FInputPreprocessorRegistration{ Info, InputProcessor.ToSharedRef() }); } return bResult; } void FSlateApplication::UnregisterInputPreProcessor(TSharedPtr InputProcessor) { InputPreProcessors.Remove(InputProcessor); } int32 FSlateApplication::FindInputPreProcessor(TSharedPtr InputProcessor) const { return InputPreProcessors.Find(InputProcessor, EInputPreProcessorType::Game); } int32 FSlateApplication::FindInputPreProcessor(TSharedPtr InputProcessor, const EInputPreProcessorType& Type) const { return InputPreProcessors.Find(InputProcessor, Type); } void FSlateApplication::SetCursorRadius(float NewRadius) { CursorRadius = FMath::Max(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::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 TextEntryWidget ) { SCOPE_CYCLE_COUNTER(STAT_ShowVirtualKeyboard); if (!SlateTextField.IsValid()) { SlateTextField = MakeUnique(); } SlateTextField->ShowVirtualKeyboard(bShow, UserIndex, TextEntryWidget); } bool FSlateApplication::AllowMoveCursor() { if (!SlateTextField.IsValid()) { SlateTextField = MakeUnique(); } return SlateTextField->AllowMoveCursor(); } FSlateRect FSlateApplication::GetPreferredWorkArea() const { if (TSharedPtr 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 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 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& InWindowToDraw) { PrivateDrawWindows( InWindowToDraw ); } bool FSlateApplication::TakeScreenshot(const TSharedRef& Widget, TArray&OutColorData, FIntVector& OutSize) { return TakeScreenshot(Widget, FIntRect(), OutColorData, OutSize); } bool FSlateApplication::TakeHDRScreenshot(const TSharedRef& Widget, TArray& OutColorData, FIntVector& OutSize) { return TakeHDRScreenshot(Widget, FIntRect(), OutColorData, OutSize); } void TakeScreenshotCommon(const TSharedRef& 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& Widget, const FIntRect& InnerWidgetArea, TArray& OutColorData, FIntVector& OutSize) { // We can't screenshot the widget unless there's a valid window handle to draw it in. TSharedPtr WidgetWindow = FSlateApplication::Get().FindWidgetWindow(Widget); if (!WidgetWindow.IsValid()) { return false; } TSharedRef 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& Widget, const FIntRect& InnerWidgetArea, TArray& OutColorData, FIntVector& OutSize) { // We can't screenshot the widget unless there's a valid window handle to draw it in. TSharedPtr WidgetWindow = FSlateApplication::Get().FindWidgetWindow(Widget); if (!WidgetWindow.IsValid()) { return false; } TSharedRef 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 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 FSlateApplication::GetUserFromPlatformUser(FPlatformUserId PlatformUser) { TOptional UserIndex = GetUserIndexForPlatformUser(PlatformUser); if (UserIndex.IsSet()) { return GetUser(UserIndex.GetValue()); } return nullptr; } TSharedPtr FSlateApplication::GetUserFromPlatformUser(FPlatformUserId PlatformUser) const { TOptional UserIndex = GetUserIndexForPlatformUser(PlatformUser); if (UserIndex.IsSet()) { return GetUser(UserIndex.GetValue()); } return nullptr; } TSharedRef 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 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 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 FSlateApplication::GetOrCreateUser(int32 UserIndex) { if (TSharedPtr FoundUser = GetUser(UserIndex)) { return FoundUser.ToSharedRef(); } return RegisterNewUser(UserIndex); } TSharedRef FSlateApplication::GetOrCreateUser(FPlatformUserId PlatformUserId) { if (TSharedPtr FoundUser = GetUser(PlatformUserId)) { return FoundUser.ToSharedRef(); } return RegisterNewUser(PlatformUserId); } TSharedRef FSlateApplication::GetOrCreateUser(FInputDeviceId DeviceId) { // Get a user based on the owning platform user of this input device return GetOrCreateUser(IPlatformInputDeviceMapper::Get().GetUserForInputDevice(DeviceId)); } TSharedRef FSlateApplication::RegisterNewUser(int32 UserIndex, bool bIsVirtual) { return RegisterNewUser(FGenericPlatformMisc::GetPlatformUserForUserIndex(UserIndex), bIsVirtual); } TSharedRef 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 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(); } TSharedRef 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 InPredicate, bool bIncludeVirtualUsers) { for (const TSharedPtr& User : Users) { // Ignore virtual users unless told not to. if (User && (bIncludeVirtualUsers || !User->IsVirtualUser())) { InPredicate(*User); } } } void FSlateApplication::ForEachUser(TFunctionRef 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(X), static_cast(Y) ); } return FVector2f( 1.0f, 1.0f ); } EVisibility FSlateApplication::GetSoftwareCursorVis( ) const { const TSharedPtr& Cursor = PlatformApplication->Cursor; if (bSoftwareCursorAvailable && Cursor.IsValid() && Cursor->GetType() != EMouseCursor::None) { return EVisibility::HitTestInvisible; } return EVisibility::Hidden; } TSharedPtr FSlateApplication::GetKeyboardFocusedWidget() const { TSharedPtr KeyboardUser = GetUser(GetUserIndexForKeyboard()); return KeyboardUser ? KeyboardUser->GetFocusedWidget() : nullptr; } TSharedPtr FSlateApplication::GetMouseCaptorImpl() const { return GetCursorUser()->GetCursorCaptor(); } bool FSlateApplication::HasAnyMouseCaptor() const { for (const TSharedPtr& User : Users) { if (User && User->HasAnyCapture()) { return true; } } return false; } bool FSlateApplication::HasUserMouseCapture(int32 UserIndex) const { TSharedPtr FoundUser = GetUser(UserIndex); return FoundUser && FoundUser->HasAnyCapture(); } FPointerEvent FSlateApplication::TransformPointerEvent(const FPointerEvent& PointerEvent, const TSharedPtr& 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 Widget, int32 UserIndex, TOptional PointerIndex) const { if (TSharedPtr FoundUser = GetUser(UserIndex)) { return PointerIndex.IsSet() ? FoundUser->DoesWidgetHaveCapture(Widget, PointerIndex.GetValue()) : FoundUser->DoesWidgetHaveAnyCapture(Widget); } return false; } bool FSlateApplication::DoesWidgetHaveMouseCapture(const TSharedPtr Widget) const { for (const TSharedPtr& User : Users) { if (User && User->DoesWidgetHaveAnyCapture(Widget)) { return true; } } return false; } TOptional FSlateApplication::HasUserFocus(const TSharedPtr Widget, int32 UserIndex) const { TSharedPtr FoundUser = GetUser(UserIndex); return FoundUser ? FoundUser->HasFocus(Widget) : TOptional(); } TOptional FSlateApplication::HasAnyUserFocus(const TSharedPtr Widget) const { TOptional FocusCause; for (const TSharedPtr& User : Users) { FocusCause = User ? User->HasFocus(Widget) : TOptional(); if (FocusCause.IsSet()) { break; } } return FocusCause; } bool FSlateApplication::IsWidgetDirectlyHovered(const TSharedPtr Widget) const { for (const TSharedPtr& User : Users) { if (User && User->IsWidgetDirectlyUnderAnyPointer(Widget)) { return true; } } return false; } bool FSlateApplication::ShowUserFocus(const TSharedPtr Widget) const { for (const TSharedPtr& User : Users) { if (User && User->ShouldShowFocus(Widget)) { return true; } } return false; } TSharedRef FSlateApplication::GetRelevantNavConfig(int32 UserIndex) const { TSharedPtr RelevantNavConfig = NavigationConfig; if (TSharedPtr 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 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 User = GetUser(UserIndex); return User && User->HasFocusedDescendants(Widget); } bool FSlateApplication::HasFocusedDescendants( const TSharedRef< const SWidget >& Widget ) const { for (const TSharedPtr& User : Users) { if (User && User->HasFocusedDescendants(Widget)) { return true; } } return false; } bool FSlateApplication::IsExternalUIOpened() { return bIsExternalUIOpened; } TSharedRef FSlateApplication::MakeImage( const TAttribute& Image, const TAttribute& Color, const TAttribute& Visibility ) const { return SNew(SImage) .ColorAndOpacity(Color) .Image(Image) .Visibility(Visibility); } TSharedRef FSlateApplication::MakeWindowTitleBar(const FWindowTitleBarArgs& InArgs, TSharedPtr& OutTitleBar) const { TSharedRef TitleBar = SNew(SWindowTitleBar, InArgs.Window, InArgs.CenterContent, InArgs.CenterContentAlignment) .Visibility(EVisibility::SelfHitTestInvisible) .CloseButtonToolTipText(InArgs.CloseButtonToolTipText); OutTitleBar = TitleBar; return TitleBar; } TSharedRef FSlateApplication::MakeToolTip(const TAttribute& ToolTipText) { return SNew(SToolTip) .Text(ToolTipText); } TSharedRef 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 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 User = GetOrCreateUser(InCharacterEvent); TSharedRef 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 BeginInput(ProcessingInput); TSharedRef 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 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 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 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 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 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 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 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 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 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(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(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 BeginInput(ProcessingInput); FPointerEvent TransformedPointerEvent = WidgetsUnderPointer.IsValid() ? TransformPointerEvent(PointerEvent, WidgetsUnderPointer.GetWindow()) : PointerEvent; TSharedRef 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 PreviouslyFocusedWidget = GetKeyboardFocusedWidget(); FReply Reply = FEventRouter::Route( 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( 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 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, TMap> 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 BeginInput(ProcessingInput); FPointerEvent TransformedPointerEvent = WidgetsUnderPointer.IsValid() ? TransformPointerEvent(PointerEvent, WidgetsUnderPointer.GetWindow()) : PointerEvent; FReply Reply = FReply::Unhandled(); TSharedRef SlateUser = GetOrCreateUser(PointerEvent); const bool bIsDragDropping = SlateUser->IsDragDroppingAffected(PointerEvent); TSharedPtr 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( 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(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 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 BeginInput(ProcessingInput); bool bHandled = false; FWeakWidgetPath LastWidgetsUnderPointer; FPointerEvent TransformedPointerEvent = WidgetsUnderPointer.IsValid() ? TransformPointerEvent(PointerEvent, WidgetsUnderPointer.GetWindow()) : PointerEvent; TSharedRef 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(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 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& SomeWidgetPreviouslyUnderCursor = LastWidgetsUnderPointer.Widgets[WidgetIndex].Pin(); if (SomeWidgetPreviouslyUnderCursor.IsValid()) { TOptional 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& 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()); } 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(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(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(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(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(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 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( 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 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 BeginInput(ProcessingInput); FWidgetPath MouseCaptorPath; TSharedRef 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(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& 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 InNavigationSource, const TArray& 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 DestinationWidget = TSharedPtr(); 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 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 Viewport = NavigationSource.GetWindow()->GetViewport(); if (Viewport.IsValid()) { TSharedPtr 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& Menu) { if (MenuBeingDestroyedEvent.IsBound()) { MenuBeingDestroyedEvent.Broadcast(Menu); } } bool FSlateApplication::OnControllerAnalog(FGamepadKeyNames::Type KeyName, FPlatformUserId PlatformUserId, FInputDeviceId InputDeviceId, float AnalogValue) { FKey Key(KeyName); TOptional 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 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 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 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 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 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 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 User = GetOrCreateUser(MotionEvent); if (User->HasValidFocusPath()) { /* Get the controller focus target for this user */ TSharedRef EventPathRef = User->GetFocusPath(); const FWidgetPath& EventPath = EventPathRef.Get(); FScopedSwitchWorldHack SwitchWorld(EventPath); FReply Reply = FEventRouter::Route(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& Window) const { TSharedPtr 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& 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& 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 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 Window = ActivateEvent.GetAffectedWindow(); TSharedPtr Viewport = Window->GetViewport(); if (Viewport.IsValid()) { TSharedPtr ViewportWidgetPtr = Viewport->GetWidget().Pin(); if (ViewportWidgetPtr.IsValid()) { TArray< TSharedRef > 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 Window = ActivateEvent.GetAffectedWindow(); TSharedPtr 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 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& 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& 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 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::InitializeAsStandaloneApplication(const TSharedRef& PlatformRenderer) { return InitializeAsStandaloneApplication(PlatformRenderer, MakeShareable(FPlatformApplicationMisc::CreateApplication())); } TSharedRef FSlateApplication::InitializeAsStandaloneApplication(const TSharedRef< class FSlateRenderer >& PlatformRenderer, const TSharedRef& 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 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& 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 FSlateApplication::GetDragDroppingContent() const { return GetCursorUser()->GetDragDropContent(); } void FSlateApplication::CancelDragDrop() { GetCursorUser()->CancelDragDrop(); } void FSlateApplication::NavigateFromWidgetUnderCursor(const uint32 InUserIndex, EUINavigation InNavigationType, TSharedRef InWindow) { if (InNavigationType != EUINavigation::Invalid) { FWidgetPath PathToLocatedWidget = LocateWidgetInWindow(GetOrCreateUser(InUserIndex)->GetCursorPosition(), InWindow, false, InUserIndex); if (PathToLocatedWidget.IsValid()) { TSharedPtr 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 Cursor) { TGuardValue IteratingGuard(bIsIteratingPreProcessors, true); for (const TSharedPtr& 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& 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 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 InputProcessor, const EInputPreProcessorType& Type) const { const uint32 TypeInt = static_cast(Type); if (InputPreProcessors.IsValidIndex(TypeInt)) { const FProcessorTypeStorage& Storage = InputPreProcessors[TypeInt]; return Storage.Find(InputProcessor); } return INDEX_NONE; } bool FSlateApplication::InputPreProcessorsHelper::PreProcessInput(ESlateDebuggingInputEvent InputEvent, TFunctionRef InputProcessFunc) { TGuardValue IteratingGuard(bIsIteratingPreProcessors, true); bool bShouldExit = false; for (const TSharedPtr& 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& 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& 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& WindowToDraw) { if (WindowToDraw != SNullWidget::NullWidget) { TMap 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& 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