// Copyright Epic Games, Inc. All Rights Reserved. #include "SlateWidgetElement.h" #include "IApplicationElement.h" #include "Framework/Application/SlateApplication.h" #include "Locators/SlateWidgetLocatorByUniqueTag.h" #include "MetaData/DriverUniqueTagMetaData.h" #include "Framework/MetaData/DriverIdMetaData.h" #include "Widgets/Text/SRichTextBlock.h" #include "Widgets/Input/SMultiLineEditableTextBox.h" #include "Widgets/Text/SMultiLineEditableText.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SEditableText.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/Input/SCheckBox.h" #include "UObject/NameTypes.h" class FSlateWidgetElement : public IApplicationElement { public: virtual ~FSlateWidgetElement() { } virtual FString ToDebugString() const { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; const TArray> AllIdMetaData = Widget->GetAllMetaData(); const TArray> AllTagMetaData = Widget->GetAllMetaData(); //example (STextBlock) 0x00082034 [#Piano|Keyboard] : SlateWidgetElement.cpp(20) FString DebugString; const FString Address = FString::Printf(TEXT("0x%p"), &Widget.Get()); DebugString += TEXT("(") + Widget->GetTypeAsString() + TEXT(") ") + Address; if (AllIdMetaData.Num() + AllTagMetaData.Num() > 0 || !Widget->GetTag().IsNone()) { DebugString += TEXT(" ["); bool bHasPrecedingIdentifier = false; for (const TSharedRef& MetaData : AllIdMetaData) { if (bHasPrecedingIdentifier) { DebugString += TEXT("|"); } DebugString += TEXT("#"); DebugString += MetaData->Id.ToString(); bHasPrecedingIdentifier = true; } if (!Widget->GetTag().IsNone()) { if (bHasPrecedingIdentifier) { DebugString += TEXT("|"); } DebugString += Widget->GetTag().ToString(); } for (const TSharedRef& MetaData : AllTagMetaData) { if (bHasPrecedingIdentifier) { DebugString += TEXT("|"); } DebugString += MetaData->Tag.ToString(); bHasPrecedingIdentifier = true; } DebugString += TEXT("]"); } DebugString += TEXT(" : ") + Widget->GetReadableLocation(); return DebugString; } virtual FVector2D GetAbsolutePosition() const override { return WidgetPath.Widgets.Last().Geometry.LocalToAbsolute(FVector2D::ZeroVector); } virtual FVector2D GetSize() const override { return WidgetPath.Widgets.Last().Geometry.GetDrawSize(); } virtual TSharedPtr GetWindow() const override { const TSharedPtr Window = WidgetPath.GetWindow(); if (!Window.IsValid()) { return nullptr; } return Window->GetNativeWindow(); } virtual bool IsVisible() const override { static FName SWindowType(TEXT("SWindow")); const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; if (Widget->GetVisibility().IsVisible()) { FVector2D FakeCursorPosition = GetAbsolutePosition() + (GetSize() / 2); TArray> Windows; if (WidgetPath.TopLevelWindow.IsValid()) { Windows.Add(WidgetPath.TopLevelWindow.ToSharedRef()); } else if (WidgetPath.Widgets[0].Widget->GetType() == SWindowType) { Windows.Add(StaticCastSharedRef(WidgetPath.Widgets[0].Widget)); } else { return false; } FWidgetPath UnderCursor = FSlateApplication::Get().LocateWindowUnderMouse(FakeCursorPosition, Windows, true); int32 Count = UnderCursor.Widgets.Num(); for (int32 Index = 0; Index < Count; Index++) { TSharedRef FoundWidget = UnderCursor.Widgets[Index].Widget; if (FoundWidget == Widget) { return true; } } } return false; } virtual bool IsInteractable() const override { static FName SWindowType(TEXT("SWindow")); const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; if (Widget->IsEnabled() && Widget->GetVisibility().IsHitTestVisible()) { FVector2D FakeCursorPosition = GetAbsolutePosition() + (GetSize() / 2); TArray> Windows; if (WidgetPath.TopLevelWindow.IsValid()) { Windows.Add(WidgetPath.TopLevelWindow.ToSharedRef()); } else if (WidgetPath.Widgets[0].Widget->GetType() == SWindowType) { Windows.Add(StaticCastSharedRef(WidgetPath.Widgets[0].Widget)); } else { return false; } FWidgetPath UnderCursor = FSlateApplication::Get().LocateWindowUnderMouse(FakeCursorPosition, Windows, false); int32 Count = UnderCursor.Widgets.Num(); for (int32 Index = 0; Index < Count; Index++) { TSharedRef FoundWidget = UnderCursor.Widgets[Index].Widget; if (FoundWidget == Widget) { return true; } } } return false; } virtual bool IsChecked() const override { static FName SCheckBoxType(TEXT("SCheckBox")); const TSharedRef RootWidget = WidgetPath.Widgets.Last().Widget; TArray> Stack; Stack.Push(RootWidget); bool bFoundCheck = false; bool bResult = false; while (Stack.Num() > 0) { const TSharedRef Widget = Stack.Pop(); if (Widget->GetType() == SCheckBoxType) { if (bFoundCheck == true) { return false; } bResult = StaticCastSharedRef(Widget)->IsChecked(); bFoundCheck = true; } else { FChildren* ChildWidgets = Widget->GetChildren(); int32 ChildCount = ChildWidgets->Num(); for (int32 i = 0; i < ChildCount; i++) { Stack.Push(ChildWidgets->GetChildAt(i)); } } } return bResult; } virtual FText GetText() const override { static FName SRichTextBlockType(TEXT("SRichTextBlock")); static FName STextBlockType(TEXT("STextBlock")); static FName SEditableTextType(TEXT("SEditableText")); static FName SEditableTextBoxType(TEXT("SEditableTextBox")); static FName SMultiLineEditableTextType(TEXT("SMultiLineEditableText")); static FName SMultiLineEditableTextBoxType(TEXT("SMultiLineEditableTextBox")); const TSharedRef RootWidget = WidgetPath.Widgets.Last().Widget; TArray> Stack; Stack.Push(RootWidget); bool bFoundText = false; FText Result = FText::GetEmpty(); while(Stack.Num() > 0) { const TSharedRef Widget = Stack.Pop(); if (Widget->GetType() == STextBlockType) { if (bFoundText == true) { return FText::GetEmpty(); } Result = StaticCastSharedRef(Widget)->GetText(); bFoundText = true; } else if (Widget->GetType() == SEditableTextType) { if (bFoundText == true) { return FText::GetEmpty(); } Result = StaticCastSharedRef(Widget)->GetText(); bFoundText = true; } else if (Widget->GetType() == SEditableTextBoxType) { if (bFoundText == true) { return FText::GetEmpty(); } Result = StaticCastSharedRef(Widget)->GetText(); bFoundText = true; } else if (Widget->GetType() == SRichTextBlockType) { if (bFoundText == true) { return FText::GetEmpty(); } Result = StaticCastSharedRef(Widget)->GetText(); bFoundText = true; } else if (Widget->GetType() == SMultiLineEditableTextType) { if (bFoundText == true) { return FText::GetEmpty(); } Result = StaticCastSharedRef(Widget)->GetText(); bFoundText = true; } else if (Widget->GetType() == SMultiLineEditableTextBoxType) { if (bFoundText == true) { return FText::GetEmpty(); } Result = StaticCastSharedRef(Widget)->GetText(); bFoundText = true; } else { FChildren* ChildWidgets = Widget->GetChildren(); int32 ChildCount = ChildWidgets->Num(); for (int32 i = 0; i < ChildCount; i++) { Stack.Push(ChildWidgets->GetChildAt(i)); } } } return Result; } virtual TSharedRef CreateLocator() const override { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; const TSharedRef UniqueMetaData = MakeShareable(new FDriverUniqueTagMetaData()); Widget->AddMetadata(UniqueMetaData); return FSlateWidgetLocatorByUniqueTagFactory::Create(UniqueMetaData); } virtual bool CanFocus() const override { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; return Widget->SupportsKeyboardFocus(); } virtual bool Focus() const override { return FSlateApplication::Get().SetUserFocus(FSlateApplication::Get().GetUserIndexForKeyboard(), WidgetPath, EFocusCause::SetDirectly); } virtual bool Focus(uint32 UserIndex) const override { return FSlateApplication::Get().SetUserFocus(UserIndex, WidgetPath, EFocusCause::SetDirectly); } virtual bool IsFocused() const override { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; return FSlateApplication::Get().GetUserFocusedWidget(FSlateApplication::Get().GetUserIndexForKeyboard()) == Widget; } virtual bool IsFocused(uint32 UserIndex) const override { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; return FSlateApplication::Get().GetUserFocusedWidget(UserIndex) == Widget; } virtual bool HasFocusedDescendants() const override { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; return FSlateApplication::Get().HasFocusedDescendants(Widget); } virtual bool HasFocusedDescendants(uint32 UserIndex) const override { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; return FSlateApplication::Get().HasUserFocusedDescendants(Widget, UserIndex); } virtual bool IsHovered() const override { const TSharedRef Widget = WidgetPath.Widgets.Last().Widget; return Widget->IsHovered(); } static TSharedPtr HasScrollableDescendants(const FWidgetPath& WidgetPath, int32& ErrorCode) { TArray> IgnoreWidgets; return HasScrollableDescendants(WidgetPath, IgnoreWidgets, ErrorCode); } static TSharedPtr HasScrollableDescendants(const FWidgetPath& WidgetPath, const TArray>& IgnoreWidgets, int32& ErrorCode) { static FName SScrollBarType(TEXT("SScrollBar")); TArray> Stack; Stack.Push(WidgetPath.Widgets.Last().Widget); TSharedPtr ScrollBar; while (Stack.Num() > 0) { const TSharedRef Widget = Stack.Pop(); if (Widget->GetType() == SScrollBarType) { TSharedPtr FoundScrollBar = StaticCastSharedRef(Widget); if (FoundScrollBar->IsNeeded()) { if (ScrollBar.IsValid() && ScrollBar != FoundScrollBar) { ErrorCode = 2; return nullptr; } ScrollBar = FoundScrollBar; } } else { FChildren* ChildWidgets = Widget->GetChildren(); int32 ChildCount = ChildWidgets->Num(); for (int32 i = 0; i < ChildCount; i++) { const TSharedRef ChildWidget = ChildWidgets->GetChildAt(i); if (!IgnoreWidgets.Contains(ChildWidget)) { Stack.Push(ChildWidget); } } } } if (!ScrollBar.IsValid()) { ErrorCode = 1; return nullptr; } ErrorCode = 0; return ScrollBar; } virtual bool IsScrollable() const override { EOrientation ScrollOrientation; return IsScrollable(ScrollOrientation); } virtual bool IsScrollable(EOrientation& OutScrollOrientation) const override { int32 ErrorCode; const TSharedPtr ScrollBar = HasScrollableDescendants(WidgetPath, ErrorCode); if (ScrollBar.IsValid()) { OutScrollOrientation = ScrollBar->GetOrientation(); } return ScrollBar.IsValid(); } virtual bool IsScrolledToBeginning() const override { int32 ErrorCode; const TSharedPtr ScrollBar = HasScrollableDescendants(WidgetPath, ErrorCode); if (!ScrollBar.IsValid()) { return true; } return ScrollBar->DistanceFromTop() < KINDA_SMALL_NUMBER; } virtual bool IsScrolledToEnd() const override { int32 ErrorCode; const TSharedPtr ScrollBar = HasScrollableDescendants(WidgetPath, ErrorCode); if (!ScrollBar.IsValid()) { return true; } return ScrollBar->DistanceFromBottom() < KINDA_SMALL_NUMBER; } virtual TSharedPtr GetScrollableParent() const override { FWidgetPath ParentWidgetPath = WidgetPath; ParentWidgetPath.Widgets.Remove(ParentWidgetPath.Widgets.Num() - 1); TArray> IgnoreWidgets; IgnoreWidgets.Add(WidgetPath.Widgets.Last().Widget); bool bFoundScrollableParent = false; for (int Index = WidgetPath.Widgets.Num() - 2; Index >= 0; Index--) { const TSharedRef ParentWidget = WidgetPath.Widgets[Index].Widget; int32 ErrorCode; const TSharedPtr ScrollBar = HasScrollableDescendants(ParentWidgetPath, IgnoreWidgets, ErrorCode); if (ScrollBar.IsValid()) { bFoundScrollableParent = true; break; } else if (ErrorCode == 2) { bFoundScrollableParent = false; break; } IgnoreWidgets.Add(ParentWidget); ParentWidgetPath.Widgets.Remove(ParentWidgetPath.Widgets.Num() - 1); } if (bFoundScrollableParent) { return FSlateWidgetElementFactory::Create(ParentWidgetPath); } return nullptr; } virtual void* GetRawElement() const override { if (!WidgetPath.IsValid()) { return nullptr; } return (void*)(&WidgetPath); } private: FSlateWidgetElement( const FWidgetPath& InWidgetPath) : WidgetPath(InWidgetPath) { } private: const FWidgetPath WidgetPath; friend FSlateWidgetElementFactory; }; TSharedRef FSlateWidgetElementFactory::Create( const FWidgetPath& WidgetPath) { return MakeShareable(new FSlateWidgetElement(WidgetPath)); }