2344 lines
78 KiB
C++
2344 lines
78 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Widgets/SWidgetReflector.h"
|
|
|
|
#include "ISlateReflectorModule.h"
|
|
#include "SlateReflectorModule.h"
|
|
#include "Rendering/DrawElements.h"
|
|
#include "Misc/App.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/Parse.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Docking/TabManager.h"
|
|
#include "Framework/Commands/UICommandList.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "InputEventVisualizer.h"
|
|
#include "Styling/WidgetReflectorStyle.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "Widgets/Layout/SSpacer.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Widgets/Layout/SBox.h"
|
|
#include "Widgets/Layout/SSeparator.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "Widgets/Layout/SSplitter.h"
|
|
#include "Widgets/Views/SHeaderRow.h"
|
|
#include "Widgets/Views/STableViewBase.h"
|
|
#include "Widgets/Views/STableRow.h"
|
|
#include "Widgets/Views/SListView.h"
|
|
#include "Widgets/Views/STreeView.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Widgets/Input/SSpinBox.h"
|
|
#include "Widgets/SSlateOptions.h"
|
|
#include "Widgets/SWidgetReflectorTreeWidgetItem.h"
|
|
#include "Widgets/SWidgetReflectorToolTipWidget.h"
|
|
#include "Widgets/SWidgetEventLog.h"
|
|
#include "Widgets/SWidgetHittestGrid.h"
|
|
#include "Widgets/SWidgetList.h"
|
|
#include "Widgets/SInvalidationPanel.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Widgets/Navigation/SBreadcrumbTrail.h"
|
|
#include "Widgets/Input/SComboBox.h"
|
|
#include "Widgets/SWidgetSnapshotVisualizer.h"
|
|
#include "WidgetSnapshotService.h"
|
|
#include "Types/ReflectionMetadata.h"
|
|
#include "Debugging/SlateDebugging.h"
|
|
#include "VisualTreeCapture.h"
|
|
#include "Models/WidgetReflectorNode.h"
|
|
#include "Fonts/FontCache.h"
|
|
#include "Fonts/FontTypes.h"
|
|
#include "IImageWrapperModule.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Textures/SlateShaderResource.h"
|
|
|
|
#if SLATE_REFLECTOR_HAS_DESKTOP_PLATFORM
|
|
#include "DesktopPlatformModule.h"
|
|
#endif // SLATE_REFLECTOR_HAS_DESKTOP_PLATFORM
|
|
|
|
#if SLATE_REFLECTOR_HAS_SESSION_SERVICES
|
|
#include "ISessionManager.h"
|
|
#include "ISessionServicesModule.h"
|
|
#endif // SLATE_REFLECTOR_HAS_SESSION_SERVICES
|
|
|
|
#if WITH_EDITOR
|
|
#include "Framework/Docking/LayoutService.h"
|
|
#include "PropertyEditorModule.h"
|
|
#endif
|
|
|
|
#define LOCTEXT_NAMESPACE "SWidgetReflector"
|
|
|
|
/**
|
|
* Widget reflector user widget.
|
|
*/
|
|
|
|
namespace WidgetReflectorCVars
|
|
{
|
|
static bool bEnableFocusOnPick = true;
|
|
static FAutoConsoleVariableRef EnableFocusOnPick(
|
|
TEXT("Slate.EnableFocusOnPick"),
|
|
bEnableFocusOnPick,
|
|
TEXT("If true, Widget Reflector window will be automatically focused when a pick is made.")
|
|
);
|
|
}
|
|
|
|
/* Local helpers
|
|
*****************************************************************************/
|
|
|
|
namespace WidgetReflectorImpl
|
|
{
|
|
|
|
/** Command to take a snapshot. */
|
|
void TakeSnapshotCommand(const TArray<FString>&);
|
|
|
|
void DumpFontAtlasesCommand(const TArray<FString>&);
|
|
|
|
FAutoConsoleCommand CCommandWidgetReflectorTakeSnapshot(
|
|
TEXT("WidgetReflector.TakeSnapshot")
|
|
, TEXT("Take a snapshot and save the result on the local drive. ie. WidgetReflector.TakeSnapshot [Delay=1.0] [Navigation=false]")
|
|
, FConsoleCommandWithArgsDelegate::CreateStatic(&TakeSnapshotCommand));
|
|
|
|
FAutoConsoleCommand CCommandDumpFontAtlases(
|
|
TEXT("WidgetReflector.DumpFontAtlases")
|
|
, TEXT("Save all of the font atlases currently in the font cache on the local drive. A timestamp will be automatically appended to the directory where the textures will be saved. The default location is YourProject/Saved/WidgetReflector/FontAtlases_Timestamp ie. WidgetReflector.DumpFontAtlases[SaveDirectory=D:\\My\\Path] [SaveFilePrefix=ExamplePrefix]")
|
|
, FConsoleCommandWithArgsDelegate::CreateStatic(&DumpFontAtlasesCommand));
|
|
|
|
/** Information about a potential widget snapshot target */
|
|
struct FWidgetSnapshotTarget
|
|
{
|
|
/** Display name of the target (used in the UI) */
|
|
FText DisplayName;
|
|
|
|
/** Instance ID of the target */
|
|
FGuid InstanceId;
|
|
};
|
|
|
|
/** Different UI modes the widget reflector can be in */
|
|
enum class EWidgetReflectorUIMode : uint8
|
|
{
|
|
Live,
|
|
Snapshot,
|
|
};
|
|
|
|
namespace WidgetReflectorTabID
|
|
{
|
|
static const FName WidgetHierarchy = "WidgetReflector.WidgetHierarchyTab";
|
|
static const FName SnapshotWidgetPicker = "WidgetReflector.SnapshotWidgetPickerTab";
|
|
static const FName WidgetDetails = "WidgetReflector.WidgetDetailsTab";
|
|
static const FName SlateOptions = "WidgetReflector.SlateOptionsTab";
|
|
static const FName WidgetEvents = "WidgetReflector.WidgetEventsTab";
|
|
static const FName HittestGrid = "WidgetReflector.HittestGridTab";
|
|
static const FName WidgetList = "WidgetReflector.WidgetList";
|
|
}
|
|
|
|
namespace WidgetReflectorText
|
|
{
|
|
static const FText HitTestPicking = LOCTEXT("PickHitTestable", "Pick Hit-Testable Widgets");
|
|
static const FText VisualPicking = LOCTEXT("PickVisual", "Pick Painted Widgets");
|
|
static const FText Focus = LOCTEXT("ShowFocus", "Show Focus");
|
|
static const FText Focusing = LOCTEXT("ShowingFocus", "Showing Focus (Esc to Stop)");
|
|
static const FText Picking = LOCTEXT("PickingWidget", "Picking (Esc to Stop)");
|
|
}
|
|
|
|
namespace WidgetReflectorIcon
|
|
{
|
|
static const FName FocusPicking = "Icon.FocusPicking";
|
|
static const FName HitTestPicking = "Icon.HitTestPicking";
|
|
static const FName VisualPicking = "Icon.VisualPicking";
|
|
static const FName Ellipsis = "Icon.Ellipsis";
|
|
static const FName Filter = "Icon.Filter";
|
|
static const FName LoadSnapshot = "Icon.LoadSnapshot";
|
|
static const FName TakeSnapshot = "Icon.TakeSnapshot";
|
|
}
|
|
|
|
enum class EWidgetPickingMode : uint8
|
|
{
|
|
None = 0,
|
|
Focus,
|
|
HitTesting,
|
|
Drawable
|
|
};
|
|
|
|
EWidgetPickingMode ConvertToWidgetPickingMode(int32 Number)
|
|
{
|
|
if (Number < 0 || Number > static_cast<int32>(EWidgetPickingMode::Drawable))
|
|
{
|
|
return EWidgetPickingMode::None;
|
|
}
|
|
return static_cast<EWidgetPickingMode>(Number);
|
|
}
|
|
|
|
/**
|
|
* Widget reflector implementation
|
|
*/
|
|
class SWidgetReflector : public ::SWidgetReflector
|
|
{
|
|
// The reflector uses a tree that observes FWidgetReflectorNodeBase objects.
|
|
typedef STreeView<TSharedRef<FWidgetReflectorNodeBase>> SReflectorTree;
|
|
|
|
private:
|
|
|
|
//~ Begin ::SWidgetReflector implementation
|
|
virtual void Construct( const FArguments& InArgs) override;
|
|
//~ End ::SWidgetReflector implementation
|
|
|
|
void HandlePullDownAtlasesMenu(FMenuBuilder& MenuBuilder);
|
|
void HandlePullDownWindowMenu(FMenuBuilder& MenuBuilder);
|
|
|
|
TSharedRef<SDockTab> SpawnSlateOptionWidgetTab(const FSpawnTabArgs& Args);
|
|
TSharedRef<SDockTab> SpawnWidgetHierarchyTab(const FSpawnTabArgs& Args);
|
|
TSharedRef<SDockTab> SpawnSnapshotWidgetPicker(const FSpawnTabArgs& Args);
|
|
|
|
#if WITH_EDITOR
|
|
TSharedRef<SDockTab> SpawnWidgetDetails(const FSpawnTabArgs& Args);
|
|
#endif
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
TSharedRef<SDockTab> SpawnWidgetEvents(const FSpawnTabArgs& Args);
|
|
TSharedRef<SDockTab> SpawnWidgetHittestGrid(const FSpawnTabArgs& Args);
|
|
#endif
|
|
|
|
TSharedRef<SDockTab> SpawnWidgetList(const FSpawnTabArgs& Args);
|
|
|
|
public:
|
|
|
|
void HandleTabManagerPersistLayout(const TSharedRef<FTabManager::FLayout>& LayoutToSave);
|
|
void OnTabSpawned(const FName& TabIdentifier, const TSharedRef<SDockTab>& SpawnedTab);
|
|
void CloseTab(const FName& TabIdentifier);
|
|
|
|
void SaveSettings();
|
|
void LoadSettings();
|
|
|
|
void SetUIMode(const EWidgetReflectorUIMode InNewMode);
|
|
|
|
//~ Begin SCompoundWidget overrides
|
|
virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override;
|
|
//~ End SCompoundWidget overrides
|
|
|
|
//~ Begin IWidgetReflector interface
|
|
virtual bool IsInPickingMode() const override
|
|
{
|
|
return PickingMode != EWidgetPickingMode::None;
|
|
}
|
|
|
|
virtual bool IsShowingFocus() const override
|
|
{
|
|
return PickingMode == EWidgetPickingMode::Focus;
|
|
}
|
|
|
|
virtual bool IsVisualizingLayoutUnderCursor() const override
|
|
{
|
|
return PickingMode == EWidgetPickingMode::HitTesting || PickingMode == EWidgetPickingMode::Drawable;
|
|
}
|
|
|
|
virtual void OnWidgetPicked() override
|
|
{
|
|
SetPickingMode(EWidgetPickingMode::None);
|
|
}
|
|
|
|
virtual bool ReflectorNeedsToDrawIn( TSharedRef<SWindow> ThisWindow ) const override;
|
|
|
|
virtual void SetSourceAccessDelegate( FAccessSourceCode InDelegate ) override
|
|
{
|
|
SourceAccessDelegate = InDelegate;
|
|
}
|
|
|
|
virtual void SetAssetAccessDelegate(FAccessAsset InDelegate) override
|
|
{
|
|
AsseetAccessDelegate = InDelegate;
|
|
}
|
|
|
|
virtual void SetWidgetsToVisualize( const FWidgetPath& InWidgetsToVisualize ) override;
|
|
virtual int32 Visualize( const FWidgetPath& InWidgetsToVisualize, FSlateWindowElementList& OutDrawElements, int32 LayerId ) override;
|
|
//~ End IWidgetReflector interface
|
|
|
|
/**
|
|
* Generates a tool tip for the given reflector tree node.
|
|
*
|
|
* @param InReflectorNode The node to generate the tool tip for.
|
|
* @return The tool tip widget.
|
|
*/
|
|
TSharedPtr<IToolTip> GenerateToolTipForReflectorNode( TWeakPtr<FWidgetReflectorNodeBase> InReflectorNode ) const;
|
|
|
|
/**
|
|
* Mark the provided reflector nodes such that they stand out in the tree and are visible.
|
|
*
|
|
* @param WidgetPathToObserve The nodes to mark.
|
|
*/
|
|
void VisualizeAsTree( const TArray< TSharedRef<FWidgetReflectorNodeBase> >& WidgetPathToVisualize );
|
|
|
|
/**
|
|
* Draw the widget path to the picked widget as the widgets' outlines.
|
|
*
|
|
* @param InWidgetsToVisualize A widget path whose widgets' outlines to draw.
|
|
* @param OutDrawElements A list of draw elements; we will add the output outlines into it.
|
|
* @param LayerId The maximum layer achieved in OutDrawElements so far.
|
|
* @return The maximum layer ID we achieved while painting.
|
|
*/
|
|
int32 VisualizePickAsRectangles( const FWidgetPath& InWidgetsToVisualize, FSlateWindowElementList& OutDrawElements, int32 LayerId );
|
|
|
|
/**
|
|
* Draw an outline for the specified nodes.
|
|
*
|
|
* @param InNodesToDraw A widget path whose widgets' outlines to draw.
|
|
* @param WindowGeometry The geometry of the window in which to draw.
|
|
* @param OutDrawElements A list of draw elements; we will add the output outlines into it.
|
|
* @param LayerId the maximum layer achieved in OutDrawElements so far.
|
|
* @return The maximum layer ID we achieved while painting.
|
|
*/
|
|
int32 VisualizeSelectedNodesAsRectangles( const TArray<TSharedRef<FWidgetReflectorNodeBase>>& InNodesToDraw, const TSharedRef<SWindow>& VisualizeInWindow, FSlateWindowElementList& OutDrawElements, int32 LayerId );
|
|
|
|
/** Draw the actual highlight */
|
|
void DrawWidgetVisualization(const FPaintGeometry& WidgetGeometry, FLinearColor Color, FSlateWindowElementList& OutDrawElements, int32& LayerId);
|
|
|
|
/** Clear previous selection and set the selection to the live widget. */
|
|
void SelectLiveWidget( TSharedPtr<const SWidget> InWidget );
|
|
|
|
/** Set the given nodes as the root of the tree. */
|
|
void SetNodesAsReflectorTreeRoot(TArray<TSharedRef<FWidgetReflectorNodeBase>> RootNodes);
|
|
|
|
/** Filter the selected nodes before setting them as root. */
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> FilterSelectedToSetAsReflectorTreeRoot(TArray<TSharedRef<FWidgetReflectorNodeBase>> RootNodes);
|
|
|
|
/** Is there any selected node in the reflector tree. */
|
|
bool DoesReflectorTreeHasSelectedItem() const { return SelectedNodes.Num() > 0; }
|
|
|
|
/** Apply the requested filter to the reflected tree root. */
|
|
void UpdateFilteredTreeRoot();
|
|
|
|
void HandleDisplayTextureAtlases();
|
|
void HandleDisplayFontAtlases();
|
|
|
|
//~ Handle for the picking button
|
|
ECheckBoxState HandleGetPickingButtonChecked() const;
|
|
void HandlePickingModeStateChanged();
|
|
FSlateIcon HandleGetPickingModeImage() const;
|
|
FText HandleGetPickingModeText() const;
|
|
TSharedRef<SWidget> HandlePickingModeContextMenu();
|
|
void HandlePickButtonClicked(EWidgetPickingMode InPickingMode);
|
|
|
|
void SetPickingMode(EWidgetPickingMode InMode)
|
|
{
|
|
if (PickingMode != InMode)
|
|
{
|
|
// Disable visual picking, and re-enable widget caching.
|
|
VisualCapture.Disable();
|
|
|
|
// Enable the picking mode.
|
|
PickingMode = InMode;
|
|
|
|
// If we're enabling hit test, reset the visual capture entirely, we don't want to use the visual tree.
|
|
if (PickingMode == EWidgetPickingMode::HitTesting)
|
|
{
|
|
VisualCapture.Reset();
|
|
}
|
|
// If we're using the drawing picking mode enable it!
|
|
else if (PickingMode == EWidgetPickingMode::Drawable)
|
|
{
|
|
VisualCapture.Enable();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Callback to see whether the "Snapshot Target" combo should be enabled */
|
|
bool IsSnapshotTargetComboEnabled() const;
|
|
|
|
/** Callback to see whether the "Take Snapshot" button should be enabled */
|
|
bool IsTakeSnapshotButtonEnabled() const;
|
|
|
|
/** Callback for clicking the "Take Snapshot" button. */
|
|
void HandleTakeSnapshotButtonClicked();
|
|
|
|
/** Build option menu for snaphot. */
|
|
TSharedRef<SWidget> HandleSnapshotOptionsTreeContextMenu();
|
|
|
|
/** Takes a snapshot of the current state of the snapshot target. */
|
|
void TakeSnapshot();
|
|
|
|
/** Used as a callback for the "snapshot pending" notification item buttons, called when we should give up on a snapshot request */
|
|
void OnCancelPendingRemoteSnapshot();
|
|
|
|
/** Callback for when a remote widget snapshot is available */
|
|
void HandleRemoteSnapshotReceived(const TArray<uint8>& InSnapshotData);
|
|
|
|
#if SLATE_REFLECTOR_HAS_DESKTOP_PLATFORM
|
|
/** Callback for clicking the "Load Snapshot" button. */
|
|
void HandleLoadSnapshotButtonClicked();
|
|
#endif // SLATE_REFLECTOR_HAS_DESKTOP_PLATFORM
|
|
|
|
/** Called to update the list of available snapshot targets */
|
|
void UpdateAvailableSnapshotTargets();
|
|
|
|
/** Called to update the currently selected snapshot target (after the list has been refreshed) */
|
|
void UpdateSelectedSnapshotTarget();
|
|
|
|
/** Called when the list of available snapshot targets changes */
|
|
void OnAvailableSnapshotTargetsChanged();
|
|
|
|
/** Called when a node is set as root to create the breadcrum trail */
|
|
void CreateCrumbTrailForNode(TSharedRef<FWidgetReflectorNodeBase> InReflectorNode);
|
|
|
|
/** Get the display name of the currently selected snapshot target */
|
|
FText GetSelectedSnapshotTargetDisplayName() const;
|
|
|
|
/** Generate a row widget for the available targets combo box */
|
|
TSharedRef<SWidget> HandleGenerateAvailableSnapshotComboItemWidget(TSharedPtr<FWidgetSnapshotTarget> InItem) const;
|
|
|
|
/** Update the selected target when the combo box selection is changed */
|
|
void HandleAvailableSnapshotComboSelectionChanged(TSharedPtr<FWidgetSnapshotTarget> InItem, ESelectInfo::Type InSeletionInfo);
|
|
|
|
/** Callback for generating a row in the reflector tree view. */
|
|
TSharedRef<ITableRow> HandleReflectorTreeGenerateRow( TSharedRef<FWidgetReflectorNodeBase> InReflectorNode, const TSharedRef<STableViewBase>& OwnerTable );
|
|
|
|
/** Callback for getting the child items of the given reflector tree node. */
|
|
void HandleReflectorTreeGetChildren( TSharedRef<FWidgetReflectorNodeBase> InReflectorNode, TArray<TSharedRef<FWidgetReflectorNodeBase>>& OutChildren );
|
|
|
|
/** Callback for when the selection in the reflector tree has changed. */
|
|
void HandleReflectorTreeSelectionChanged( TSharedPtr<FWidgetReflectorNodeBase>, ESelectInfo::Type /*SelectInfo*/ );
|
|
|
|
/** Callback for when the context menu in the reflector tree is requested. */
|
|
TSharedRef<SWidget> HandleReflectorTreeContextMenu();
|
|
TSharedPtr<SWidget> HandleReflectorTreeContextMenuPtr();
|
|
|
|
/** Callback for when an item in the reflector tree is clicked on. */
|
|
void HandleReflectorTreeOnMouseClick(TSharedRef<FWidgetReflectorNodeBase> InReflectorNode);
|
|
|
|
/** Callback for when a crumb is clicked on. */
|
|
void HandleBreadcrumbOnClick(const TSharedRef<FWidgetReflectorNodeBase>& InReflectorNode);
|
|
|
|
/** Callback for when the menu of a crumb delimiter is requested. */
|
|
TSharedRef< SWidget > HandleBreadcrumbDelimiterMenu(const TSharedRef<FWidgetReflectorNodeBase>& InReflectorNode);
|
|
|
|
/** Callback for when the reflector tree header list changed. */
|
|
void HandleReflectorTreeHiddenColumnsListChanged();
|
|
|
|
/** Reset the filtered tree root. */
|
|
void HandleResetFilteredTreeRoot();
|
|
|
|
/** Show the start of the UMG tree. */
|
|
void HandleStartTreeWithUMG();
|
|
|
|
/** Should we display the breadcrumb trail. */
|
|
EVisibility HandleIsBreadcrumbVisible() const;
|
|
|
|
/** Should we show only the UMG tree. */
|
|
bool HandleIsStartTreeWithUMGEnabled() const { return bFilterReflectorTreeRootWithUMG; }
|
|
|
|
bool HandleHasPendingActions() const { return !bIsPendingDelayedSnapshot; }
|
|
|
|
/** Determine the text of Take Snapshot button */
|
|
FText HandleGetTakeSnapshotText() const { return bIsPendingDelayedSnapshot ? LOCTEXT("CancelSnapshotButtonText", "Cancel Snapshot") : LOCTEXT("TakeSnapshotButtonText", "Take Snapshot"); }
|
|
|
|
private:
|
|
TSharedPtr<FTabManager> TabManager;
|
|
TMap<FName, TWeakPtr<SDockTab>> SpawnedTabs;
|
|
|
|
TSharedPtr<SReflectorTree> ReflectorTree;
|
|
TArray<FString> HiddenReflectorTreeColumns;
|
|
|
|
TSharedPtr<SBreadcrumbTrail<TSharedRef<FWidgetReflectorNodeBase>> > BreadCrumb;
|
|
/** Node that are currently selected */
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> SelectedNodes;
|
|
/** The original path of the widget picked. It may include node that are now hidden by the filter */
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> PickedWidgetPath;
|
|
/** Root of the tree before filtering */
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> ReflectorTreeRoot;
|
|
/** Root of the tree after filtering */
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> FilteredTreeRoot;
|
|
|
|
/** When working with a snapshotted tree, this will contain the snapshot hierarchy and screenshot info */
|
|
FWidgetSnapshotData SnapshotData;
|
|
TSharedPtr<SWidgetSnapshotVisualizer> WidgetSnapshotVisualizer;
|
|
|
|
/** List of available snapshot targets, as well as the one we currently have selected */
|
|
TSharedPtr<SComboBox<TSharedPtr<FWidgetSnapshotTarget>>> AvailableSnapshotTargetsComboBox;
|
|
TArray<TSharedPtr<FWidgetSnapshotTarget>> AvailableSnapshotTargets;
|
|
FGuid SelectedSnapshotTargetInstanceId;
|
|
TSharedPtr<FWidgetSnapshotService> WidgetSnapshotService;
|
|
TWeakPtr<SNotificationItem> WidgetSnapshotNotificationPtr;
|
|
FGuid RemoteSnapshotRequestId;
|
|
|
|
FAccessSourceCode SourceAccessDelegate;
|
|
FAccessAsset AsseetAccessDelegate;
|
|
|
|
EWidgetReflectorUIMode CurrentUIMode;
|
|
EWidgetPickingMode PickingMode;
|
|
EWidgetPickingMode LastPickingMode;
|
|
bool bFilterReflectorTreeRootWithUMG;
|
|
|
|
#if WITH_EDITOR
|
|
TSharedPtr<IDetailsView> PropertyViewPtr;
|
|
#endif
|
|
#if WITH_SLATE_DEBUGGING
|
|
TWeakPtr<SWidgetHittestGrid> WidgetHittestGrid;
|
|
#endif
|
|
|
|
FVisualTreeCapture VisualCapture;
|
|
|
|
private:
|
|
float SnapshotDelay;
|
|
bool bIsPendingDelayedSnapshot;
|
|
bool bRequestNavigationSimulation;
|
|
double TimeOfScheduledSnapshot;
|
|
};
|
|
|
|
void SWidgetReflector::Construct( const FArguments& InArgs )
|
|
{
|
|
// If saved, LoadSettings will override these variables.
|
|
LastPickingMode = EWidgetPickingMode::HitTesting;
|
|
HiddenReflectorTreeColumns.Add(SReflectorTreeWidgetItem::NAME_Enabled.ToString());
|
|
HiddenReflectorTreeColumns.Add(SReflectorTreeWidgetItem::NAME_Volatile.ToString());
|
|
HiddenReflectorTreeColumns.Add(SReflectorTreeWidgetItem::NAME_HasActiveTimer.ToString());
|
|
HiddenReflectorTreeColumns.Add(SReflectorTreeWidgetItem::NAME_ActualSize.ToString());
|
|
HiddenReflectorTreeColumns.Add(SReflectorTreeWidgetItem::NAME_LayerId.ToString());
|
|
|
|
LoadSettings();
|
|
|
|
CurrentUIMode = EWidgetReflectorUIMode::Live;
|
|
PickingMode = EWidgetPickingMode::None;
|
|
bFilterReflectorTreeRootWithUMG = false;
|
|
|
|
SnapshotDelay = 0.0f;
|
|
bIsPendingDelayedSnapshot = false;
|
|
bRequestNavigationSimulation = false;
|
|
TimeOfScheduledSnapshot = -1.0;
|
|
|
|
WidgetSnapshotService = InArgs._WidgetSnapshotService;
|
|
|
|
#if SLATE_REFLECTOR_HAS_SESSION_SERVICES
|
|
{
|
|
TSharedPtr<ISessionManager> SessionManager = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionManager();
|
|
if (SessionManager.IsValid())
|
|
{
|
|
SessionManager->OnSessionsUpdated().AddSP(this, &SWidgetReflector::OnAvailableSnapshotTargetsChanged);
|
|
}
|
|
}
|
|
#endif // SLATE_REFLECTOR_HAS_SESSION_SERVICES
|
|
SelectedSnapshotTargetInstanceId = FApp::GetInstanceId();
|
|
UpdateAvailableSnapshotTargets();
|
|
|
|
const FName TabLayoutName = "WidgetReflector_Layout_NoStats_v2";
|
|
|
|
TSharedRef<FTabManager::FLayout> Layout = FTabManager::NewLayout(TabLayoutName)
|
|
->AddArea
|
|
(
|
|
FTabManager::NewPrimaryArea()
|
|
->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetHideTabWell(true)
|
|
->AddTab(WidgetReflectorTabID::SlateOptions, ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
// Main application area
|
|
FTabManager::NewSplitter()
|
|
->SetOrientation(Orient_Horizontal)
|
|
->Split
|
|
(
|
|
// Main application area
|
|
FTabManager::NewSplitter()
|
|
->SetOrientation(Orient_Vertical)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetHideTabWell(true)
|
|
->SetSizeCoefficient(0.7f)
|
|
->AddTab(WidgetReflectorTabID::WidgetHierarchy, ETabState::OpenedTab)
|
|
)
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetHideTabWell(true)
|
|
->SetSizeCoefficient(0.3f)
|
|
->AddTab(WidgetReflectorTabID::SnapshotWidgetPicker, ETabState::ClosedTab)
|
|
#if WITH_SLATE_DEBUGGING
|
|
->AddTab(WidgetReflectorTabID::WidgetEvents, ETabState::ClosedTab)
|
|
->AddTab(WidgetReflectorTabID::HittestGrid, ETabState::ClosedTab)
|
|
#endif
|
|
->AddTab(WidgetReflectorTabID::WidgetList, ETabState::ClosedTab)
|
|
)
|
|
)
|
|
#if WITH_EDITOR
|
|
->Split
|
|
(
|
|
FTabManager::NewStack()
|
|
->SetHideTabWell(true)
|
|
->SetSizeCoefficient(0.3f)
|
|
->AddTab(WidgetReflectorTabID::WidgetDetails, ETabState::ClosedTab)
|
|
)
|
|
#endif
|
|
)
|
|
);
|
|
|
|
auto RegisterTrackedTabSpawner = [this](const FName& TabId, const FOnSpawnTab& OnSpawnTab) -> FTabSpawnerEntry&
|
|
{
|
|
return TabManager->RegisterTabSpawner(TabId, FOnSpawnTab::CreateLambda([this, OnSpawnTab](const FSpawnTabArgs& Args) -> TSharedRef<SDockTab>
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = OnSpawnTab.Execute(Args);
|
|
OnTabSpawned(Args.GetTabId().TabType, SpawnedTab);
|
|
return SpawnedTab;
|
|
}));
|
|
};
|
|
|
|
check(InArgs._ParentTab.IsValid());
|
|
TabManager = FGlobalTabmanager::Get()->NewTabManager(InArgs._ParentTab.ToSharedRef());
|
|
TabManager->SetOnPersistLayout(FTabManager::FOnPersistLayout::CreateRaw(this, &SWidgetReflector::HandleTabManagerPersistLayout));
|
|
|
|
RegisterTrackedTabSpawner(WidgetReflectorTabID::SlateOptions, FOnSpawnTab::CreateSP(this, &SWidgetReflector::SpawnSlateOptionWidgetTab))
|
|
.SetDisplayName(LOCTEXT("OptionsTab", "Toolbar"));
|
|
|
|
RegisterTrackedTabSpawner(WidgetReflectorTabID::WidgetHierarchy, FOnSpawnTab::CreateSP(this, &SWidgetReflector::SpawnWidgetHierarchyTab))
|
|
.SetDisplayName(LOCTEXT("WidgetHierarchyTab", "Widget Hierarchy"));
|
|
|
|
RegisterTrackedTabSpawner(WidgetReflectorTabID::SnapshotWidgetPicker, FOnSpawnTab::CreateSP(this, &SWidgetReflector::SpawnSnapshotWidgetPicker))
|
|
.SetDisplayName(LOCTEXT("SnapshotWidgetPickerTab", "Snapshot Widget Picker"));
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
RegisterTrackedTabSpawner(WidgetReflectorTabID::WidgetDetails, FOnSpawnTab::CreateSP(this, &SWidgetReflector::SpawnWidgetDetails))
|
|
.SetDisplayName(LOCTEXT("WidgetDetailsTab", "Widget Details"));
|
|
}
|
|
#endif //WITH_EDITOR
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
RegisterTrackedTabSpawner(WidgetReflectorTabID::WidgetEvents, FOnSpawnTab::CreateSP(this, &SWidgetReflector::SpawnWidgetEvents))
|
|
.SetDisplayName(LOCTEXT("WidgetEventsTab", "Widget Events"));
|
|
RegisterTrackedTabSpawner(WidgetReflectorTabID::HittestGrid, FOnSpawnTab::CreateSP(this, &SWidgetReflector::SpawnWidgetHittestGrid))
|
|
.SetDisplayName(LOCTEXT("HitTestGridTab", "Hit Test Grid"));
|
|
#endif
|
|
|
|
RegisterTrackedTabSpawner(WidgetReflectorTabID::WidgetList, FOnSpawnTab::CreateSP(this, &SWidgetReflector::SpawnWidgetList))
|
|
.SetDisplayName(LOCTEXT("WidgetListTab", "Widget List"));
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
Layout = FLayoutSaveRestore::LoadFromConfig(GEditorLayoutIni, Layout);
|
|
}
|
|
#endif
|
|
|
|
FMenuBarBuilder MenuBarBuilder = FMenuBarBuilder(TSharedPtr<FUICommandList>());
|
|
#if WITH_SLATE_DEBUGGING
|
|
MenuBarBuilder.AddPullDownMenu(
|
|
LOCTEXT("DemoModeLabel", "Demo Mode"),
|
|
FText::GetEmpty(),
|
|
FNewMenuDelegate::CreateRaw(FSlateReflectorModule::GetModulePtr()->GetInputEventVisualizer(), &FInputEventVisualizer::PopulateMenu),
|
|
"DemoMode"
|
|
);
|
|
#endif
|
|
MenuBarBuilder.AddPullDownMenu(
|
|
LOCTEXT("AtlasesMenuLabel", "Atlases"),
|
|
FText::GetEmpty(),
|
|
FNewMenuDelegate::CreateSP(this, &SWidgetReflector::HandlePullDownAtlasesMenu),
|
|
"Atlases"
|
|
);
|
|
MenuBarBuilder.AddPullDownMenu(
|
|
LOCTEXT("WindowMenuLabel", "Window"),
|
|
FText::GetEmpty(),
|
|
FNewMenuDelegate::CreateSP(this, &SWidgetReflector::HandlePullDownWindowMenu),
|
|
"Window"
|
|
);
|
|
|
|
const TSharedRef<SWidget> MenuWidget = MenuBarBuilder.MakeWidget();
|
|
TabManager->SetMenuMultiBox(MenuBarBuilder.GetMultiBox(), MenuWidget);
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew(SBorder)
|
|
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
|
|
.BorderBackgroundColor(FLinearColor::Gray) // Darken the outer border
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(0.0f, 4.0f, 0.0f, 0.0f))
|
|
[
|
|
MenuWidget
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.Padding(FMargin(0.0f, 4.0f, 0.0f, 0.0f))
|
|
[
|
|
TabManager->RestoreFrom(Layout, nullptr).ToSharedRef()
|
|
]
|
|
]
|
|
];
|
|
}
|
|
|
|
void SWidgetReflector::HandlePullDownAtlasesMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("DisplayTextureAtlases", "Display Texture Atlases"),
|
|
FText::GetEmpty(),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandleDisplayTextureAtlases)
|
|
));
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("DisplayFontAtlases", "Display Font Atlases"),
|
|
FText::GetEmpty(),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandleDisplayFontAtlases)
|
|
));
|
|
}
|
|
|
|
void SWidgetReflector::HandlePullDownWindowMenu(FMenuBuilder& MenuBuilder)
|
|
{
|
|
if (!TabManager.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TabManager->PopulateLocalTabSpawnerMenu(MenuBuilder);
|
|
}
|
|
|
|
TSharedRef<SDockTab> SWidgetReflector::SpawnSlateOptionWidgetTab(const FSpawnTabArgs& Args)
|
|
{
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
.Label(LOCTEXT("ToolbarTab", "Toolbar"))
|
|
.ShouldAutosize(true)
|
|
[
|
|
SNew(SSlateOptions)
|
|
];
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> SWidgetReflector::SpawnWidgetHierarchyTab(const FSpawnTabArgs& Args)
|
|
{
|
|
FSlimHorizontalToolBarBuilder ToolbarBuilderGlobal(TSharedPtr<const FUICommandList>(), FMultiBoxCustomization::None);
|
|
|
|
TArray<FName> HiddenColumnsList;
|
|
HiddenColumnsList.Reserve(HiddenReflectorTreeColumns.Num());
|
|
for (const FString& Item : HiddenReflectorTreeColumns)
|
|
{
|
|
HiddenColumnsList.Add(*Item);
|
|
}
|
|
|
|
// Button that controls the target for the snapshot operation
|
|
AvailableSnapshotTargetsComboBox = SNew(SComboBox<TSharedPtr<FWidgetSnapshotTarget>>)
|
|
.IsEnabled(this, &SWidgetReflector::IsSnapshotTargetComboEnabled)
|
|
.ToolTipText(LOCTEXT("ChooseSnapshotTargetToolTipText", "Choose Snapshot Target"))
|
|
.OptionsSource(&AvailableSnapshotTargets)
|
|
.OnGenerateWidget(this, &SWidgetReflector::HandleGenerateAvailableSnapshotComboItemWidget)
|
|
.OnSelectionChanged(this, &SWidgetReflector::HandleAvailableSnapshotComboSelectionChanged)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(this, &SWidgetReflector::GetSelectedSnapshotTargetDisplayName)
|
|
];
|
|
|
|
FSlateIcon EmptyIcon(FWidgetReflectorStyle::GetStyleSetName(), "Icon.Empty");
|
|
ToolbarBuilderGlobal.BeginSection("Picking");
|
|
{
|
|
|
|
FTextBuilder TooltipText;
|
|
ToolbarBuilderGlobal.AddToolBarButton(
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandlePickingModeStateChanged),
|
|
FCanExecuteAction::CreateSP(this,&SWidgetReflector::HandleHasPendingActions),
|
|
FGetActionCheckState::CreateSP(this, &SWidgetReflector::HandleGetPickingButtonChecked)
|
|
),
|
|
NAME_None,
|
|
MakeAttributeSP(this,&SWidgetReflector::HandleGetPickingModeText),
|
|
TooltipText.ToText(),
|
|
MakeAttributeSP(this, &SWidgetReflector::HandleGetPickingModeImage),
|
|
EUserInterfaceActionType::ToggleButton
|
|
);
|
|
|
|
ToolbarBuilderGlobal.AddComboButton(
|
|
FUIAction(
|
|
FExecuteAction(),
|
|
FCanExecuteAction::CreateSP(this, &SWidgetReflector::HandleHasPendingActions),
|
|
FGetActionCheckState()
|
|
),
|
|
FOnGetContent::CreateSP(this, &SWidgetReflector::HandlePickingModeContextMenu),
|
|
FText::GetEmpty(),
|
|
TooltipText.ToText(),
|
|
EmptyIcon,
|
|
true
|
|
);
|
|
|
|
|
|
}
|
|
ToolbarBuilderGlobal.EndSection();
|
|
|
|
ToolbarBuilderGlobal.BeginSection("Filter");
|
|
{
|
|
|
|
FTextBuilder TooltipText;
|
|
|
|
ToolbarBuilderGlobal.AddComboButton(
|
|
FUIAction(
|
|
FExecuteAction(),
|
|
FCanExecuteAction::CreateSP(this, &SWidgetReflector::HandleHasPendingActions),
|
|
FGetActionCheckState()
|
|
),
|
|
FOnGetContent::CreateSP(this, &SWidgetReflector::HandleReflectorTreeContextMenu),
|
|
LOCTEXT("FilterLabel", "Filter"),
|
|
TooltipText.ToText(),
|
|
FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::Filter),
|
|
false
|
|
);
|
|
|
|
}
|
|
ToolbarBuilderGlobal.EndSection();
|
|
|
|
ToolbarBuilderGlobal.AddWidget(SNew(SSpacer),NAME_None,true,HAlign_Right);
|
|
ToolbarBuilderGlobal.BeginSection("Option");
|
|
{
|
|
|
|
FTextBuilder TooltipText;
|
|
|
|
ToolbarBuilderGlobal.AddToolBarButton(
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandleTakeSnapshotButtonClicked),
|
|
FCanExecuteAction::CreateSP(this, &SWidgetReflector::IsTakeSnapshotButtonEnabled),
|
|
FGetActionCheckState()
|
|
),
|
|
NAME_None,
|
|
MakeAttributeSP(this, &SWidgetReflector::HandleGetTakeSnapshotText),
|
|
TooltipText.ToText(),
|
|
FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::TakeSnapshot),
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
ToolbarBuilderGlobal.AddComboButton(
|
|
FUIAction(
|
|
FExecuteAction(),
|
|
FCanExecuteAction::CreateSP(this, &SWidgetReflector::HandleHasPendingActions),
|
|
FGetActionCheckState()
|
|
),
|
|
FOnGetContent::CreateSP(this, &SWidgetReflector::HandleSnapshotOptionsTreeContextMenu),
|
|
LOCTEXT("OptionsLabel", "Options"),
|
|
TooltipText.ToText(),
|
|
FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::Ellipsis),
|
|
false
|
|
);
|
|
|
|
#if SLATE_REFLECTOR_HAS_DESKTOP_PLATFORM
|
|
|
|
ToolbarBuilderGlobal.AddToolBarButton(
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandleLoadSnapshotButtonClicked),
|
|
FCanExecuteAction::CreateSP(this, &SWidgetReflector::HandleHasPendingActions),
|
|
FGetActionCheckState()
|
|
),
|
|
NAME_None,
|
|
LOCTEXT("LoadSnapshotButtonText", "Load Snapshot"),
|
|
TooltipText.ToText(),
|
|
FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::LoadSnapshot),
|
|
EUserInterfaceActionType::Button
|
|
);
|
|
|
|
#endif
|
|
}
|
|
ToolbarBuilderGlobal.EndSection();
|
|
ToolbarBuilderGlobal.SetStyle(&FAppStyle::Get(), "SlimToolBar");
|
|
|
|
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
|
|
|
|
.Label(LOCTEXT("WidgetHierarchyTab", "Widget Hierarchy"))
|
|
//.OnCanCloseTab_Lambda([]() { return false; }) // Can't prevent this as it stops the editor from being able to close while the widget reflector is open
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(0.0f, 2.0f))
|
|
[
|
|
SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
ToolbarBuilderGlobal.MakeWidget()
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(2.0f, 2.0f))
|
|
[
|
|
SNew(SVerticalBox)
|
|
.Visibility(this, &SWidgetReflector::HandleIsBreadcrumbVisible)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SNew(SSeparator).Thickness(5.f)
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
SAssignNew(BreadCrumb, SBreadcrumbTrail<TSharedRef<FWidgetReflectorNodeBase>>)
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
.DelimiterImage(FAppStyle::Get().GetBrush("Icons.ChevronRight"))
|
|
.TextStyle(FAppStyle::Get(), "NormalText")
|
|
.OnCrumbClicked(this, &SWidgetReflector::HandleBreadcrumbOnClick)
|
|
.GetCrumbMenuContent(this, &SWidgetReflector::HandleBreadcrumbDelimiterMenu)
|
|
]
|
|
]
|
|
|
|
+SVerticalBox::Slot()
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(0.f)
|
|
.BorderImage(FCoreStyle::Get().GetBrush("ToolPanel.GroupBorder"))
|
|
[
|
|
// The tree view that shows all the info that we capture.
|
|
SAssignNew(ReflectorTree, SReflectorTree)
|
|
.TreeItemsSource(&FilteredTreeRoot)
|
|
.OnGenerateRow(this, &SWidgetReflector::HandleReflectorTreeGenerateRow)
|
|
.OnGetChildren(this, &SWidgetReflector::HandleReflectorTreeGetChildren)
|
|
.OnSelectionChanged(this, &SWidgetReflector::HandleReflectorTreeSelectionChanged)
|
|
.OnContextMenuOpening(this, &SWidgetReflector::HandleReflectorTreeContextMenuPtr)
|
|
.OnMouseButtonClick(this, &SWidgetReflector::HandleReflectorTreeOnMouseClick)
|
|
.HighlightParentNodesForSelection(true)
|
|
.HeaderRow
|
|
(
|
|
SNew(SHeaderRow)
|
|
.CanSelectGeneratedColumn(true)
|
|
.HiddenColumnsList(HiddenColumnsList)
|
|
.OnHiddenColumnsListChanged(this, &SWidgetReflector::HandleReflectorTreeHiddenColumnsListChanged)
|
|
|
|
+SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_WidgetName)
|
|
.DefaultLabel(LOCTEXT("WidgetName", "Widget Name"))
|
|
.FillWidth(1.f)
|
|
.ShouldGenerateWidget(true)
|
|
|
|
+SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_ForegroundColor)
|
|
.DefaultLabel(LOCTEXT("ForegroundColor", "FG"))
|
|
.DefaultTooltip(LOCTEXT("ForegroundColorToolTip", "Foreground Color"))
|
|
.FixedWidth(24.0f)
|
|
|
|
+SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_Visibility)
|
|
.DefaultLabel(LOCTEXT("Visibility", "Visibility"))
|
|
.DefaultTooltip(LOCTEXT("VisibilityTooltip", "Visibility"))
|
|
.FillSized(125.0f)
|
|
|
|
+ SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_Enabled)
|
|
.DefaultLabel(LOCTEXT("Enabled", "Enabled"))
|
|
.DefaultTooltip(LOCTEXT("EnabledToolTip", "Enabled"))
|
|
.FillSized(60.0f)
|
|
|
|
+ SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_Focusable)
|
|
.DefaultLabel(LOCTEXT("Focus", "Focus"))
|
|
.DefaultTooltip(LOCTEXT("FocusableTooltip", "Focusability (Note that for hit-test directional navigation to work it must be Focusable and \"Visible\"!)"))
|
|
.FillSized(60.0f)
|
|
|
|
+ SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_HasActiveTimer)
|
|
.DefaultLabel(LOCTEXT("HasActiveTimer", "Timer"))
|
|
.DefaultTooltip(LOCTEXT("HasActiveTimerTooltip", "Has Active Timer"))
|
|
.FillSized(60.0f)
|
|
|
|
+SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_Clipping)
|
|
.DefaultLabel(LOCTEXT("Clipping", "Clipping" ))
|
|
.FillSized(100.0f)
|
|
|
|
+ SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_LayerId)
|
|
.DefaultLabel(LOCTEXT("LayerId", "LayerId"))
|
|
.FillSized(35.f)
|
|
|
|
+ SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_ActualSize)
|
|
.DefaultLabel(LOCTEXT("ActualSize", "Size"))
|
|
.FillSized(100.0f)
|
|
|
|
+SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_WidgetInfo)
|
|
.DefaultLabel(LOCTEXT("Source", "Source" ))
|
|
.FillSized(200.f)
|
|
|
|
+SHeaderRow::Column(SReflectorTreeWidgetItem::NAME_Address)
|
|
.DefaultLabel( LOCTEXT("Address", "Address") )
|
|
.FillSized(170.0f)
|
|
)
|
|
]
|
|
]
|
|
];
|
|
|
|
UpdateSelectedSnapshotTarget();
|
|
|
|
return SpawnedTab;
|
|
}
|
|
|
|
TSharedRef<SDockTab> SWidgetReflector::SpawnSnapshotWidgetPicker(const FSpawnTabArgs& Args)
|
|
{
|
|
TWeakPtr<SWidgetReflector> WeakSelf = SharedThis(this);
|
|
auto OnTabClosed = [WeakSelf](TSharedRef<SDockTab>)
|
|
{
|
|
// Tab closed - leave snapshot mode
|
|
if (TSharedPtr<SWidgetReflector> SelfPinned = WeakSelf.Pin())
|
|
{
|
|
SelfPinned->SetUIMode(EWidgetReflectorUIMode::Live);
|
|
}
|
|
};
|
|
|
|
auto OnWidgetPathPicked = [WeakSelf](const TArray<TSharedRef<FWidgetReflectorNodeBase>>& InPickedWidgetPath)
|
|
{
|
|
if (TSharedPtr<SWidgetReflector> SelfPinned = WeakSelf.Pin())
|
|
{
|
|
SelfPinned->SelectedNodes.Reset();
|
|
SelfPinned->PickedWidgetPath = InPickedWidgetPath;
|
|
SelfPinned->UpdateFilteredTreeRoot();
|
|
}
|
|
};
|
|
|
|
auto OnSnapshotWidgetPicked = [WeakSelf](FWidgetReflectorNodeBase::TPointerAsInt InSnapshotWidget)
|
|
{
|
|
if (TSharedPtr<SWidgetReflector> SelfPinned = WeakSelf.Pin())
|
|
{
|
|
SelfPinned->SelectedNodes.Reset();
|
|
FWidgetReflectorNodeUtils::FindSnaphotWidget(SelfPinned->ReflectorTreeRoot, InSnapshotWidget, SelfPinned->PickedWidgetPath);
|
|
SelfPinned->UpdateFilteredTreeRoot();
|
|
}
|
|
};
|
|
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("SnapshotWidgetPickerTab", "Snapshot Widget Picker"))
|
|
.OnTabClosed_Lambda(OnTabClosed)
|
|
[
|
|
SAssignNew(WidgetSnapshotVisualizer, SWidgetSnapshotVisualizer)
|
|
.SnapshotData(&SnapshotData)
|
|
.OnWidgetPathPicked_Lambda(OnWidgetPathPicked)
|
|
.OnSnapshotWidgetSelected_Lambda(OnSnapshotWidgetPicked)
|
|
];
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
TSharedRef<SDockTab> SWidgetReflector::SpawnWidgetDetails(const FSpawnTabArgs& Args)
|
|
{
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
|
FDetailsViewArgs DetailsViewArgs;
|
|
{
|
|
DetailsViewArgs.bAllowSearch = true;
|
|
DetailsViewArgs.bShowOptions = true;
|
|
DetailsViewArgs.bAllowMultipleTopLevelObjects = false;
|
|
DetailsViewArgs.bAllowFavoriteSystem = true;
|
|
DetailsViewArgs.bShowObjectLabel = false;
|
|
DetailsViewArgs.bHideSelectionTip = true;
|
|
}
|
|
TSharedRef<IDetailsView> PropertyView = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
|
|
PropertyViewPtr = PropertyView;
|
|
|
|
auto OnTabClosed = [this](TSharedRef<SDockTab>)
|
|
{
|
|
};
|
|
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("WidgetDetailsTab", "Widget Details"))
|
|
.OnTabClosed_Lambda(OnTabClosed)
|
|
[
|
|
PropertyView
|
|
];
|
|
}
|
|
|
|
#endif //WITH_EDITOR
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
|
|
TSharedRef<SDockTab> SWidgetReflector::SpawnWidgetEvents(const FSpawnTabArgs& Args)
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("WidgetEventsTab", "Widget Events"))
|
|
[
|
|
SNew(SWidgetEventLog, AsShared())
|
|
.OnWidgetTokenActivated(this, &SWidgetReflector::SelectLiveWidget)
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDockTab> SWidgetReflector::SpawnWidgetHittestGrid(const FSpawnTabArgs& Args)
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("HitTestGridTab", "Hit Test Grid"))
|
|
[
|
|
SAssignNew(WidgetHittestGrid, SWidgetHittestGrid, AsShared())
|
|
.OnWidgetSelected(this, &SWidgetReflector::SelectLiveWidget)
|
|
.OnVisualizeWidget(this, &SWidgetReflector::SetWidgetsToVisualize)
|
|
];
|
|
}
|
|
|
|
#endif //WITH_SLATE_DEBUGGING
|
|
|
|
TSharedRef<SDockTab> SWidgetReflector::SpawnWidgetList(const FSpawnTabArgs& Args)
|
|
{
|
|
return SNew(SDockTab)
|
|
.Label(LOCTEXT("WidgetListTab", "Widget List"))
|
|
[
|
|
SNew(SWidgetList)
|
|
.OnAccessSource(SourceAccessDelegate)
|
|
.OnAccessAsset(AsseetAccessDelegate)
|
|
];
|
|
}
|
|
|
|
void SWidgetReflector::HandleTabManagerPersistLayout(const TSharedRef<FTabManager::FLayout>& LayoutToSave)
|
|
{
|
|
#if WITH_EDITOR
|
|
FLayoutSaveRestore::SaveToConfig(GEditorLayoutIni, LayoutToSave);
|
|
#endif //WITH_EDITOR
|
|
}
|
|
|
|
void SWidgetReflector::SaveSettings()
|
|
{
|
|
GConfig->SetArray(TEXT("WidgetReflector"), TEXT("HiddenReflectorTreeColumns"), HiddenReflectorTreeColumns, *GEditorPerProjectIni);
|
|
GConfig->SetInt(TEXT("WidgetReflector"), TEXT("LastPickingMode"), static_cast<int32>(LastPickingMode), *GEditorPerProjectIni);
|
|
}
|
|
|
|
|
|
void SWidgetReflector::LoadSettings()
|
|
{
|
|
if (GConfig->DoesSectionExist(TEXT("WidgetReflector"), *GEditorPerProjectIni))
|
|
{
|
|
int32 LastPickingModeAsInt = static_cast<int32>(EWidgetPickingMode::HitTesting);
|
|
GConfig->GetInt(TEXT("WidgetReflector"), TEXT("LastPickingMode"), LastPickingModeAsInt, *GEditorPerProjectIni);
|
|
LastPickingMode = ConvertToWidgetPickingMode(LastPickingModeAsInt);
|
|
if (LastPickingMode == EWidgetPickingMode::None)
|
|
{
|
|
LastPickingMode = EWidgetPickingMode::HitTesting;
|
|
}
|
|
|
|
GConfig->GetArray(TEXT("WidgetReflector"), TEXT("HiddenReflectorTreeColumns"), HiddenReflectorTreeColumns, *GEditorPerProjectIni);
|
|
}
|
|
}
|
|
|
|
|
|
void SWidgetReflector::OnTabSpawned(const FName& TabIdentifier, const TSharedRef<SDockTab>& SpawnedTab)
|
|
{
|
|
TWeakPtr<SDockTab>* const ExistingTab = SpawnedTabs.Find(TabIdentifier);
|
|
if (!ExistingTab)
|
|
{
|
|
SpawnedTabs.Add(TabIdentifier, SpawnedTab);
|
|
}
|
|
else
|
|
{
|
|
check(!ExistingTab->IsValid());
|
|
*ExistingTab = SpawnedTab;
|
|
}
|
|
}
|
|
|
|
|
|
void SWidgetReflector::CloseTab(const FName& TabIdentifier)
|
|
{
|
|
TWeakPtr<SDockTab>* const ExistingTab = SpawnedTabs.Find(TabIdentifier);
|
|
if (ExistingTab)
|
|
{
|
|
TSharedPtr<SDockTab> ExistingTabPin = ExistingTab->Pin();
|
|
if (ExistingTabPin.IsValid())
|
|
{
|
|
ExistingTabPin->RequestCloseTab();
|
|
}
|
|
}
|
|
}
|
|
|
|
void SWidgetReflector::SetUIMode(const EWidgetReflectorUIMode InNewMode)
|
|
{
|
|
if (CurrentUIMode != InNewMode)
|
|
{
|
|
CurrentUIMode = InNewMode;
|
|
|
|
SelectedNodes.Reset();
|
|
PickedWidgetPath.Reset();
|
|
ReflectorTreeRoot.Reset();
|
|
FilteredTreeRoot.Reset();
|
|
ReflectorTree->RequestTreeRefresh();
|
|
|
|
if (CurrentUIMode == EWidgetReflectorUIMode::Snapshot)
|
|
{
|
|
TabManager->TryInvokeTab(WidgetReflectorTabID::SnapshotWidgetPicker);
|
|
}
|
|
else
|
|
{
|
|
SnapshotData.ClearSnapshot();
|
|
|
|
if (WidgetSnapshotVisualizer.IsValid())
|
|
{
|
|
WidgetSnapshotVisualizer->SnapshotDataUpdated();
|
|
}
|
|
|
|
CloseTab(WidgetReflectorTabID::SnapshotWidgetPicker);
|
|
}
|
|
}
|
|
BreadCrumb->ClearCrumbs();
|
|
}
|
|
|
|
|
|
/* SCompoundWidget overrides
|
|
*****************************************************************************/
|
|
|
|
void SWidgetReflector::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
if (bIsPendingDelayedSnapshot && FSlateApplication::Get().GetCurrentTime() > TimeOfScheduledSnapshot)
|
|
{
|
|
// TakeSnapshot leads to the widget being ticked indirectly recursively,
|
|
// so the recursion of this tick mustn't trigger a recursive snapshot.
|
|
// Immediately clear the pending snapshot flag.
|
|
bIsPendingDelayedSnapshot = false;
|
|
TimeOfScheduledSnapshot = -1.0;
|
|
|
|
TakeSnapshot();
|
|
}
|
|
}
|
|
|
|
|
|
/* IWidgetReflector overrides
|
|
*****************************************************************************/
|
|
|
|
bool SWidgetReflector::ReflectorNeedsToDrawIn( TSharedRef<SWindow> ThisWindow ) const
|
|
{
|
|
return ((SelectedNodes.Num() > 0) && (ReflectorTreeRoot.Num() > 0) && (ReflectorTreeRoot[0]->GetLiveWidget() == ThisWindow));
|
|
}
|
|
|
|
void SWidgetReflector::SetWidgetsToVisualize( const FWidgetPath& InWidgetsToVisualize )
|
|
{
|
|
ReflectorTreeRoot.Reset();
|
|
FilteredTreeRoot.Reset();
|
|
PickedWidgetPath.Reset();
|
|
SelectedNodes.Reset();
|
|
|
|
if (InWidgetsToVisualize.IsValid())
|
|
{
|
|
ReflectorTreeRoot.Add(FWidgetReflectorNodeUtils::NewLiveNodeTreeFrom(InWidgetsToVisualize.Widgets[0]));
|
|
FWidgetReflectorNodeUtils::FindLiveWidgetPath(ReflectorTreeRoot, InWidgetsToVisualize, PickedWidgetPath);
|
|
UpdateFilteredTreeRoot();
|
|
}
|
|
|
|
ReflectorTree->RequestTreeRefresh();
|
|
}
|
|
|
|
int32 SWidgetReflector::Visualize( const FWidgetPath& InWidgetsToVisualize, FSlateWindowElementList& OutDrawElements, int32 LayerId )
|
|
{
|
|
if (!InWidgetsToVisualize.IsValid() && SelectedNodes.Num() > 0 && ReflectorTreeRoot.Num() > 0)
|
|
{
|
|
TSharedPtr<SWidget> WindowWidget = ReflectorTreeRoot[0]->GetLiveWidget();
|
|
if (WindowWidget.IsValid())
|
|
{
|
|
TSharedPtr<SWindow> Window = StaticCastSharedPtr<SWindow>(WindowWidget);
|
|
return VisualizeSelectedNodesAsRectangles(SelectedNodes, Window.ToSharedRef(), OutDrawElements, LayerId);
|
|
}
|
|
}
|
|
|
|
const bool bAttemptingToVisualizeReflector = InWidgetsToVisualize.ContainsWidget(ReflectorTree.Get());
|
|
|
|
if (PickingMode == EWidgetPickingMode::Drawable)
|
|
{
|
|
TSharedPtr<FVisualTreeSnapshot> Tree = VisualCapture.GetVisualTreeForWindow(OutDrawElements.GetPaintWindow());
|
|
if (Tree.IsValid())
|
|
{
|
|
const FVector2f AbsPoint = FSlateApplication::Get().GetCursorPos();
|
|
const FVector2f WindowPoint = AbsPoint - OutDrawElements.GetPaintWindow()->GetPositionInScreen();
|
|
if (TSharedPtr<const SWidget> PickedWidget = Tree->Pick(WindowPoint))
|
|
{
|
|
FWidgetPath WidgetsToVisualize = InWidgetsToVisualize;
|
|
FSlateApplication::Get().FindPathToWidget(PickedWidget.ToSharedRef(), WidgetsToVisualize, EVisibility::All);
|
|
if (!bAttemptingToVisualizeReflector)
|
|
{
|
|
SetWidgetsToVisualize(WidgetsToVisualize);
|
|
return VisualizePickAsRectangles(WidgetsToVisualize, OutDrawElements, LayerId);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetWidgetsToVisualize(FWidgetPath{});
|
|
}
|
|
}
|
|
}
|
|
else if (!bAttemptingToVisualizeReflector)
|
|
{
|
|
SetWidgetsToVisualize(InWidgetsToVisualize);
|
|
return VisualizePickAsRectangles(InWidgetsToVisualize, OutDrawElements, LayerId);
|
|
}
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
/* SWidgetReflector implementation
|
|
*****************************************************************************/
|
|
|
|
void SWidgetReflector::SelectLiveWidget(TSharedPtr<const SWidget> InWidget)
|
|
{
|
|
bool bFound = false;
|
|
if (this->CurrentUIMode == EWidgetReflectorUIMode::Live && InWidget)
|
|
{
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> FoundList;
|
|
FWidgetReflectorNodeUtils::FindLiveWidget(ReflectorTreeRoot, InWidget, FoundList);
|
|
if (FoundList.Num() > 0)
|
|
{
|
|
for (const TSharedRef<FWidgetReflectorNodeBase>& FoundItem : FoundList)
|
|
{
|
|
ReflectorTree->SetItemExpansion(FoundItem, true);
|
|
}
|
|
ReflectorTree->RequestScrollIntoView(FoundList.Last());
|
|
ReflectorTree->SetSelection(FoundList.Last());
|
|
bFound = true;
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
ReflectorTree->ClearSelection();
|
|
}
|
|
}
|
|
|
|
namespace WidgetReflectorRecursive
|
|
{
|
|
bool FindNodeWithReflectionData(const TArray<TSharedRef<FWidgetReflectorNodeBase>>& NodeBase, TArray<TSharedRef<FWidgetReflectorNodeBase>>& Result)
|
|
{
|
|
for (const TSharedRef<FWidgetReflectorNodeBase>& Node : NodeBase)
|
|
{
|
|
if (Node->HasValidWidgetAssetData())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
}
|
|
for (const TSharedRef<FWidgetReflectorNodeBase>& Node : NodeBase)
|
|
{
|
|
if (FindNodeWithReflectionData(Node->GetChildNodes(), Result))
|
|
{
|
|
Result.Add(Node);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void SWidgetReflector::UpdateFilteredTreeRoot()
|
|
{
|
|
FilteredTreeRoot.Reset();
|
|
if (bFilterReflectorTreeRootWithUMG)
|
|
{
|
|
WidgetReflectorRecursive::FindNodeWithReflectionData(ReflectorTreeRoot, FilteredTreeRoot);
|
|
VisualizeAsTree(PickedWidgetPath);
|
|
}
|
|
else
|
|
{
|
|
FilteredTreeRoot = ReflectorTreeRoot;
|
|
VisualizeAsTree(PickedWidgetPath);
|
|
}
|
|
}
|
|
|
|
void SWidgetReflector::SetNodesAsReflectorTreeRoot(TArray<TSharedRef<FWidgetReflectorNodeBase>> RootNodes)
|
|
{
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> FilteredNodes = FilterSelectedToSetAsReflectorTreeRoot(RootNodes);
|
|
if (FilteredNodes.Num() > 0)
|
|
{
|
|
FilteredTreeRoot.Reset();
|
|
FilteredTreeRoot.Append(FilteredNodes);
|
|
ReflectorTree->RequestTreeRefresh();
|
|
TSharedPtr<FWidgetReflectorNodeBase> FirstNodeParent = FilteredNodes[0]->GetParentNode();
|
|
if (FirstNodeParent.IsValid())
|
|
{
|
|
CreateCrumbTrailForNode(FirstNodeParent.ToSharedRef());
|
|
}
|
|
else
|
|
{
|
|
BreadCrumb->ClearCrumbs();
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> SWidgetReflector::FilterSelectedToSetAsReflectorTreeRoot(TArray<TSharedRef<FWidgetReflectorNodeBase>> RootNodes)
|
|
{
|
|
if (RootNodes.Num() > 1)
|
|
{
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> ShallowestNodes;
|
|
ShallowestNodes = RootNodes;
|
|
for (int32 index = ShallowestNodes.Num() -1 ; index >= 0; index--)
|
|
{
|
|
if (ShallowestNodes.Contains(ShallowestNodes[index]->GetParentNode()))
|
|
{
|
|
ShallowestNodes.RemoveAt(index);
|
|
}
|
|
}
|
|
TSharedPtr<FWidgetReflectorNodeBase> FirstNodeParent = ShallowestNodes[0]->GetParentNode();
|
|
for (int32 index = ShallowestNodes.Num() - 1; index >= 0; index--)
|
|
{
|
|
if (ShallowestNodes[index]->GetParentNode() != FirstNodeParent)
|
|
{
|
|
ShallowestNodes.RemoveAt(index);
|
|
}
|
|
}
|
|
return ShallowestNodes;
|
|
}
|
|
return RootNodes;
|
|
}
|
|
TSharedPtr<IToolTip> SWidgetReflector::GenerateToolTipForReflectorNode( TWeakPtr<FWidgetReflectorNodeBase> InReflectorNode ) const
|
|
{
|
|
if (TSharedPtr<FWidgetReflectorNodeBase> ReflectorNode = InReflectorNode.Pin())
|
|
{
|
|
return SNew(SToolTip)
|
|
[
|
|
SNew(SReflectorToolTipWidget)
|
|
.WidgetInfoToVisualize(ReflectorNode)
|
|
];
|
|
}
|
|
return FSlateApplication::Get().MakeToolTip(LOCTEXT("MissingNode", "The node is invalid."));
|
|
}
|
|
|
|
void SWidgetReflector::VisualizeAsTree( const TArray<TSharedRef<FWidgetReflectorNodeBase>>& WidgetPathToVisualize )
|
|
{
|
|
if (WidgetPathToVisualize.Num() > 0)
|
|
{
|
|
const FLinearColor TopmostWidgetColor(1.0f, 0.0f, 0.0f);
|
|
const FLinearColor LeafmostWidgetColor(0.0f, 1.0f, 0.0f);
|
|
|
|
for (int32 WidgetIndex = 0; WidgetIndex < WidgetPathToVisualize.Num(); ++WidgetIndex)
|
|
{
|
|
const auto& CurWidget = WidgetPathToVisualize[WidgetIndex];
|
|
|
|
// Tint the item based on depth in picked path
|
|
const float ColorFactor = static_cast<float>(WidgetIndex) / static_cast<float>(WidgetPathToVisualize.Num());
|
|
CurWidget->SetTint(FMath::Lerp(TopmostWidgetColor, LeafmostWidgetColor, ColorFactor));
|
|
|
|
// Make sure the user can see the picked path in the tree.
|
|
ReflectorTree->SetItemExpansion(CurWidget, true);
|
|
}
|
|
|
|
ReflectorTree->RequestScrollIntoView(WidgetPathToVisualize.Last());
|
|
ReflectorTree->SetSelection(WidgetPathToVisualize.Last());
|
|
}
|
|
else
|
|
{
|
|
ReflectorTree->ClearSelection();
|
|
}
|
|
}
|
|
|
|
|
|
int32 SWidgetReflector::VisualizePickAsRectangles( const FWidgetPath& InWidgetsToVisualize, FSlateWindowElementList& OutDrawElements, int32 LayerId)
|
|
{
|
|
const FLinearColor TopmostWidgetColor(1.0f, 0.0f, 0.0f);
|
|
const FLinearColor LeafmostWidgetColor(0.0f, 1.0f, 0.0f);
|
|
|
|
for (int32 WidgetIndex = 0; WidgetIndex < InWidgetsToVisualize.Widgets.Num(); ++WidgetIndex)
|
|
{
|
|
const FArrangedWidget& WidgetGeometry = InWidgetsToVisualize.Widgets[WidgetIndex];
|
|
const float ColorFactor = static_cast<float>(WidgetIndex)/ static_cast<float>(InWidgetsToVisualize.Widgets.Num());
|
|
const FLinearColor Tint(1.0f - ColorFactor, ColorFactor, 0.0f, 1.0f);
|
|
|
|
// The FGeometry we get is from a WidgetPath, so it's rooted in desktop space.
|
|
// We need to APPEND a transform to the Geometry to essentially undo this root transform
|
|
// and get us back into Window Space.
|
|
// This is nonstandard so we have to go through some hoops and a specially exposed method
|
|
// in FPaintGeometry to allow appending layout transforms.
|
|
FPaintGeometry WindowSpaceGeometry = WidgetGeometry.Geometry.ToPaintGeometry();
|
|
WindowSpaceGeometry.AppendTransform(TransformCast<FSlateLayoutTransform>(Inverse(InWidgetsToVisualize.TopLevelWindow->GetPositionInScreen())));
|
|
|
|
FLinearColor Color = FMath::Lerp(TopmostWidgetColor, LeafmostWidgetColor, ColorFactor);
|
|
DrawWidgetVisualization(WindowSpaceGeometry, Color, OutDrawElements, LayerId);
|
|
}
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
int32 SWidgetReflector::VisualizeSelectedNodesAsRectangles( const TArray<TSharedRef<FWidgetReflectorNodeBase>>& InNodesToDraw, const TSharedRef<SWindow>& VisualizeInWindow, FSlateWindowElementList& OutDrawElements, int32 LayerId )
|
|
{
|
|
for (int32 NodeIndex = 0; NodeIndex < InNodesToDraw.Num(); ++NodeIndex)
|
|
{
|
|
const TSharedRef<FWidgetReflectorNodeBase>& NodeToDraw = InNodesToDraw[NodeIndex];
|
|
const FLinearColor Tint(0.0f, 1.0f, 0.0f);
|
|
|
|
// The FGeometry we get is from a WidgetPath, so it's rooted in desktop space.
|
|
// We need to APPEND a transform to the Geometry to essentially undo this root transform
|
|
// and get us back into Window Space.
|
|
// This is nonstandard so we have to go through some hoops and a specially exposed method
|
|
// in FPaintGeometry to allow appending layout transforms.
|
|
FPaintGeometry WindowSpaceGeometry(NodeToDraw->GetAccumulatedLayoutTransform(), NodeToDraw->GetAccumulatedRenderTransform(), NodeToDraw->GetLocalSize(), NodeToDraw->GetGeometry().HasRenderTransform());
|
|
WindowSpaceGeometry.AppendTransform(TransformCast<FSlateLayoutTransform>(Inverse(VisualizeInWindow->GetPositionInScreen())));
|
|
|
|
DrawWidgetVisualization(WindowSpaceGeometry, NodeToDraw->GetTint(), OutDrawElements, LayerId);
|
|
}
|
|
|
|
return LayerId;
|
|
}
|
|
|
|
void SWidgetReflector::DrawWidgetVisualization(const FPaintGeometry& WidgetGeometry, FLinearColor Color, FSlateWindowElementList& OutDrawElements, int32& LayerId)
|
|
{
|
|
WidgetGeometry.CommitTransformsIfUsingLegacyConstructor();
|
|
const FVector2D LocalSize = WidgetGeometry.GetLocalSize();
|
|
|
|
// If the size is 0 in any dimension, we're going to draw a line to represent the widget, since it's going to take up
|
|
// padding space since it's visible, even though it's zero sized.
|
|
if (FMath::IsNearlyZero(LocalSize.X) || FMath::IsNearlyZero(LocalSize.Y))
|
|
{
|
|
TArray<FVector2D> LinePoints;
|
|
LinePoints.SetNum(2);
|
|
|
|
LinePoints[0] = FVector2D::ZeroVector;
|
|
LinePoints[1] = LocalSize;
|
|
|
|
FSlateDrawElement::MakeLines(
|
|
OutDrawElements,
|
|
++LayerId,
|
|
WidgetGeometry,
|
|
LinePoints,
|
|
ESlateDrawEffect::None,
|
|
Color,
|
|
true,
|
|
2
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Draw a normal box border around the geometry
|
|
FSlateDrawElement::MakeBox(
|
|
OutDrawElements,
|
|
++LayerId,
|
|
WidgetGeometry,
|
|
FCoreStyle::Get().GetBrush(TEXT("Debug.Border")),
|
|
ESlateDrawEffect::None,
|
|
Color
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
/* SWidgetReflector callbacks
|
|
*****************************************************************************/
|
|
|
|
void SWidgetReflector::HandleDisplayTextureAtlases()
|
|
{
|
|
static const FName SlateReflectorModuleName("SlateReflector");
|
|
FModuleManager::LoadModuleChecked<ISlateReflectorModule>(SlateReflectorModuleName).DisplayTextureAtlasVisualizer();
|
|
}
|
|
|
|
void SWidgetReflector::HandleDisplayFontAtlases()
|
|
{
|
|
static const FName SlateReflectorModuleName("SlateReflector");
|
|
FModuleManager::LoadModuleChecked<ISlateReflectorModule>(SlateReflectorModuleName).DisplayFontAtlasVisualizer();
|
|
}
|
|
|
|
|
|
/* Picking button
|
|
*****************************************************************************/
|
|
|
|
ECheckBoxState SWidgetReflector::HandleGetPickingButtonChecked() const
|
|
{
|
|
return PickingMode != EWidgetPickingMode::None ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void SWidgetReflector::HandlePickingModeStateChanged()
|
|
{
|
|
if (PickingMode == EWidgetPickingMode::None)
|
|
{
|
|
SetPickingMode(LastPickingMode);
|
|
}
|
|
else
|
|
{
|
|
SetPickingMode(EWidgetPickingMode::None);
|
|
}
|
|
|
|
if (IsVisualizingLayoutUnderCursor())
|
|
{
|
|
SetUIMode(EWidgetReflectorUIMode::Live);
|
|
}
|
|
}
|
|
|
|
FSlateIcon SWidgetReflector::HandleGetPickingModeImage() const
|
|
{
|
|
|
|
|
|
switch (LastPickingMode)
|
|
{
|
|
case EWidgetPickingMode::Focus:
|
|
return FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::FocusPicking);
|
|
case EWidgetPickingMode::HitTesting:
|
|
return FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::HitTestPicking);
|
|
case EWidgetPickingMode::Drawable:
|
|
return FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::VisualPicking);
|
|
case EWidgetPickingMode::None:
|
|
default:
|
|
return FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), "Icon.Empty");
|
|
}
|
|
}
|
|
|
|
FText SWidgetReflector::HandleGetPickingModeText() const
|
|
{
|
|
if (PickingMode == EWidgetPickingMode::None)
|
|
{
|
|
switch(LastPickingMode)
|
|
{
|
|
case EWidgetPickingMode::Focus:
|
|
return WidgetReflectorText::Focus;
|
|
case EWidgetPickingMode::Drawable:
|
|
return WidgetReflectorText::VisualPicking;
|
|
case EWidgetPickingMode::HitTesting:
|
|
return WidgetReflectorText::HitTestPicking;
|
|
}
|
|
}
|
|
else if (PickingMode == EWidgetPickingMode::Focus)
|
|
{
|
|
return WidgetReflectorText::Focusing;
|
|
}
|
|
return WidgetReflectorText::Picking;
|
|
}
|
|
|
|
TSharedRef<SWidget> SWidgetReflector::HandlePickingModeContextMenu()
|
|
{
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, nullptr);
|
|
|
|
const bool bIsFocus = PickingMode == EWidgetPickingMode::Focus;
|
|
MenuBuilder.AddMenuEntry(
|
|
WidgetReflectorText::Focus,
|
|
FText::GetEmpty(),
|
|
FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::FocusPicking),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandlePickButtonClicked, EWidgetPickingMode::Focus),
|
|
FCanExecuteAction::CreateLambda([bIsFocus](){ return !bIsFocus; })
|
|
));
|
|
|
|
const bool bIsHitTestPicking = PickingMode == EWidgetPickingMode::HitTesting;
|
|
MenuBuilder.AddMenuEntry(
|
|
WidgetReflectorText::HitTestPicking,
|
|
FText::GetEmpty(),
|
|
FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::HitTestPicking),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandlePickButtonClicked, EWidgetPickingMode::HitTesting),
|
|
FCanExecuteAction::CreateLambda([bIsHitTestPicking]() { return !bIsHitTestPicking; })
|
|
));
|
|
|
|
const bool bIsDrawable = PickingMode == EWidgetPickingMode::Drawable;
|
|
MenuBuilder.AddMenuEntry(
|
|
WidgetReflectorText::VisualPicking,
|
|
FText::GetEmpty(),
|
|
FSlateIcon(FWidgetReflectorStyle::GetStyleSetName(), WidgetReflectorIcon::VisualPicking),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandlePickButtonClicked, EWidgetPickingMode::Drawable),
|
|
FCanExecuteAction::CreateLambda([bIsDrawable]() { return !bIsDrawable; })
|
|
));
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void SWidgetReflector::HandlePickButtonClicked(EWidgetPickingMode InPickingMode)
|
|
{
|
|
bool bHasChanged = LastPickingMode != InPickingMode;
|
|
LastPickingMode = InPickingMode;
|
|
SetPickingMode(PickingMode != InPickingMode ? InPickingMode : EWidgetPickingMode::None);
|
|
|
|
if (IsVisualizingLayoutUnderCursor())
|
|
{
|
|
SetUIMode(EWidgetReflectorUIMode::Live);
|
|
}
|
|
|
|
if (bHasChanged)
|
|
{
|
|
SaveSettings();
|
|
}
|
|
}
|
|
|
|
bool SWidgetReflector::IsSnapshotTargetComboEnabled() const
|
|
{
|
|
if (bIsPendingDelayedSnapshot)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
#if SLATE_REFLECTOR_HAS_SESSION_SERVICES
|
|
return !RemoteSnapshotRequestId.IsValid();
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool SWidgetReflector::IsTakeSnapshotButtonEnabled() const
|
|
{
|
|
return SelectedSnapshotTargetInstanceId.IsValid() && !RemoteSnapshotRequestId.IsValid();
|
|
}
|
|
|
|
void SWidgetReflector::HandleTakeSnapshotButtonClicked()
|
|
{
|
|
if (!bIsPendingDelayedSnapshot)
|
|
{
|
|
if (SnapshotDelay > 0.0f)
|
|
{
|
|
bIsPendingDelayedSnapshot = true;
|
|
TimeOfScheduledSnapshot = FSlateApplication::Get().GetCurrentTime() + SnapshotDelay;
|
|
}
|
|
else
|
|
{
|
|
TakeSnapshot();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bIsPendingDelayedSnapshot = false;
|
|
TimeOfScheduledSnapshot = -1.0f;
|
|
}
|
|
|
|
}
|
|
|
|
TSharedRef<SWidget> SWidgetReflector::HandleSnapshotOptionsTreeContextMenu()
|
|
{
|
|
TSharedRef<SWidget> DelayWidget = SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("DelayLabel", "Delay"))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(SSpinBox<float>)
|
|
.MinValue(0.f)
|
|
.MinDesiredWidth(40.f)
|
|
.Value_Lambda([this]() { return SnapshotDelay; })
|
|
.OnValueCommitted_Lambda([this](const float InValue, ETextCommit::Type) { SnapshotDelay = FMath::Max(0.0f, InValue); })
|
|
];
|
|
|
|
TSharedRef<SWidget> NavigationEventSimulationWidget = SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("NavigationEventSimulationLabel", "Navigation Event Simulation"))
|
|
.ToolTipText(LOCTEXT("NavigationEventSimulationTooltip", "Build a simulation of all the possible Navigation Events that can occur in the windows."))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.Padding(FMargin(4.f, 0.f))
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked_Lambda([this]() { return bRequestNavigationSimulation ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
|
|
.OnCheckStateChanged_Lambda([this](ECheckBoxState NewState) { bRequestNavigationSimulation = NewState == ECheckBoxState::Checked; })
|
|
];
|
|
|
|
return SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.Padding(2.f)
|
|
[
|
|
DelayWidget
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.Padding(2.f)
|
|
[
|
|
NavigationEventSimulationWidget
|
|
]
|
|
+ SVerticalBox::Slot()
|
|
.Padding(2.f)
|
|
[
|
|
AvailableSnapshotTargetsComboBox.ToSharedRef()
|
|
];
|
|
}
|
|
|
|
void SWidgetReflector::TakeSnapshot()
|
|
{
|
|
// Local snapshot?
|
|
if (SelectedSnapshotTargetInstanceId == FApp::GetInstanceId())
|
|
{
|
|
SetUIMode(EWidgetReflectorUIMode::Snapshot);
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
if (TSharedPtr<SWidgetHittestGrid> WidgetHittestGridPin = WidgetHittestGrid.Pin())
|
|
{
|
|
WidgetHittestGridPin->SetPause(true);
|
|
}
|
|
#endif
|
|
|
|
// Take a snapshot of any window(s) that are currently open
|
|
SnapshotData.TakeSnapshot(bRequestNavigationSimulation);
|
|
|
|
// Rebuild the reflector tree from the snapshot data
|
|
SelectedNodes.Reset();
|
|
PickedWidgetPath.Reset();
|
|
ReflectorTreeRoot = FilteredTreeRoot = SnapshotData.GetWindowsRef();
|
|
ReflectorTree->RequestTreeRefresh();
|
|
|
|
WidgetSnapshotVisualizer->SnapshotDataUpdated();
|
|
|
|
#if WITH_SLATE_DEBUGGING
|
|
if (TSharedPtr<SWidgetHittestGrid> WidgetHittestGridPin = WidgetHittestGrid.Pin())
|
|
{
|
|
WidgetHittestGridPin->SetPause(false);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Remote snapshot - these can take a while, show a progress message
|
|
FNotificationInfo Info(LOCTEXT("RemoteWidgetSnapshotPendingNotificationText", "Waiting for Remote Widget Snapshot Data"));
|
|
|
|
// Add the buttons with text, tooltip and callback
|
|
Info.ButtonDetails.Add(FNotificationButtonInfo(
|
|
LOCTEXT("CancelPendingSnapshotButtonText", "Cancel"),
|
|
LOCTEXT("CancelPendingSnapshotButtonToolTipText", "Cancel the pending widget snapshot request."),
|
|
FSimpleDelegate::CreateSP(this, &SWidgetReflector::OnCancelPendingRemoteSnapshot)
|
|
));
|
|
|
|
// We will be keeping track of this ourselves
|
|
Info.bFireAndForget = false;
|
|
|
|
// Launch notification
|
|
WidgetSnapshotNotificationPtr = FSlateNotificationManager::Get().AddNotification(Info);
|
|
|
|
if (WidgetSnapshotNotificationPtr.IsValid())
|
|
{
|
|
WidgetSnapshotNotificationPtr.Pin()->SetCompletionState(SNotificationItem::CS_Pending);
|
|
}
|
|
|
|
RemoteSnapshotRequestId = WidgetSnapshotService->RequestSnapshot(SelectedSnapshotTargetInstanceId, FWidgetSnapshotService::FOnWidgetSnapshotResponse::CreateSP(this, &SWidgetReflector::HandleRemoteSnapshotReceived));
|
|
|
|
if (!RemoteSnapshotRequestId.IsValid())
|
|
{
|
|
TSharedPtr<SNotificationItem> WidgetSnapshotNotificationPin = WidgetSnapshotNotificationPtr.Pin();
|
|
|
|
if (WidgetSnapshotNotificationPin.IsValid())
|
|
{
|
|
WidgetSnapshotNotificationPin->SetText(LOCTEXT("RemoteWidgetSnapshotFailedNotificationText", "Remote Widget Snapshot Failed"));
|
|
WidgetSnapshotNotificationPin->SetCompletionState(SNotificationItem::CS_Fail);
|
|
WidgetSnapshotNotificationPin->ExpireAndFadeout();
|
|
|
|
WidgetSnapshotNotificationPtr.Reset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SWidgetReflector::OnCancelPendingRemoteSnapshot()
|
|
{
|
|
TSharedPtr<SNotificationItem> WidgetSnapshotNotificationPin = WidgetSnapshotNotificationPtr.Pin();
|
|
|
|
if (WidgetSnapshotNotificationPin.IsValid())
|
|
{
|
|
WidgetSnapshotNotificationPin->SetText(LOCTEXT("RemoteWidgetSnapshotAbortedNotificationText", "Aborted Remote Widget Snapshot"));
|
|
WidgetSnapshotNotificationPin->SetCompletionState(SNotificationItem::CS_Fail);
|
|
WidgetSnapshotNotificationPin->ExpireAndFadeout();
|
|
|
|
WidgetSnapshotNotificationPtr.Reset();
|
|
}
|
|
|
|
WidgetSnapshotService->AbortSnapshotRequest(RemoteSnapshotRequestId);
|
|
RemoteSnapshotRequestId = FGuid();
|
|
}
|
|
|
|
void SWidgetReflector::HandleRemoteSnapshotReceived(const TArray<uint8>& InSnapshotData)
|
|
{
|
|
{
|
|
TSharedPtr<SNotificationItem> WidgetSnapshotNotificationPin = WidgetSnapshotNotificationPtr.Pin();
|
|
|
|
if (WidgetSnapshotNotificationPin.IsValid())
|
|
{
|
|
WidgetSnapshotNotificationPin->SetText(LOCTEXT("RemoteWidgetSnapshotReceivedNotificationText", "Remote Widget Snapshot Data Received"));
|
|
WidgetSnapshotNotificationPin->SetCompletionState(SNotificationItem::CS_Success);
|
|
WidgetSnapshotNotificationPin->ExpireAndFadeout();
|
|
|
|
WidgetSnapshotNotificationPtr.Reset();
|
|
}
|
|
}
|
|
|
|
RemoteSnapshotRequestId = FGuid();
|
|
|
|
SetUIMode(EWidgetReflectorUIMode::Snapshot);
|
|
|
|
// Load up the remote data
|
|
SnapshotData.LoadSnapshotFromBuffer(InSnapshotData);
|
|
|
|
// Rebuild the reflector tree from the snapshot data
|
|
SelectedNodes.Reset();
|
|
PickedWidgetPath.Reset();
|
|
ReflectorTreeRoot = FilteredTreeRoot = SnapshotData.GetWindowsRef();
|
|
ReflectorTree->RequestTreeRefresh();
|
|
|
|
WidgetSnapshotVisualizer->SnapshotDataUpdated();
|
|
}
|
|
|
|
#if SLATE_REFLECTOR_HAS_DESKTOP_PLATFORM
|
|
|
|
void SWidgetReflector::HandleLoadSnapshotButtonClicked()
|
|
{
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
|
|
if (DesktopPlatform)
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow(SharedThis(this));
|
|
|
|
TArray<FString> OpenFilenames;
|
|
const bool bOpened = DesktopPlatform->OpenFileDialog(
|
|
(ParentWindow.IsValid()) ? ParentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr,
|
|
LOCTEXT("LoadSnapshotDialogTitle", "Load Widget Snapshot").ToString(),
|
|
FPaths::GameAgnosticSavedDir(),
|
|
TEXT(""),
|
|
TEXT("Slate Widget Snapshot (*.widgetsnapshot)|*.widgetsnapshot"),
|
|
EFileDialogFlags::None,
|
|
OpenFilenames
|
|
);
|
|
|
|
if (bOpened && SnapshotData.LoadSnapshotFromFile(OpenFilenames[0]))
|
|
{
|
|
SetUIMode(EWidgetReflectorUIMode::Snapshot);
|
|
|
|
// Rebuild the reflector tree from the snapshot data
|
|
ReflectorTreeRoot = SnapshotData.GetWindowsRef();
|
|
ReflectorTree->RequestTreeRefresh();
|
|
|
|
WidgetSnapshotVisualizer->SnapshotDataUpdated();
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // SLATE_REFLECTOR_HAS_DESKTOP_PLATFORM
|
|
|
|
void SWidgetReflector::UpdateAvailableSnapshotTargets()
|
|
{
|
|
AvailableSnapshotTargets.Reset();
|
|
|
|
#if SLATE_REFLECTOR_HAS_SESSION_SERVICES
|
|
{
|
|
TSharedPtr<ISessionManager> SessionManager = FModuleManager::LoadModuleChecked<ISessionServicesModule>("SessionServices").GetSessionManager();
|
|
if (SessionManager.IsValid())
|
|
{
|
|
TArray<TSharedPtr<ISessionInfo>> AvailableSessions;
|
|
SessionManager->GetSessions(AvailableSessions);
|
|
|
|
for (const auto& AvailableSession : AvailableSessions)
|
|
{
|
|
// Only allow sessions belonging to the current user
|
|
if (AvailableSession->GetSessionOwner() != FApp::GetSessionOwner())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<TSharedPtr<ISessionInstanceInfo>> AvailableInstances;
|
|
AvailableSession->GetInstances(AvailableInstances);
|
|
|
|
for (const auto& AvailableInstance : AvailableInstances)
|
|
{
|
|
FWidgetSnapshotTarget SnapshotTarget;
|
|
SnapshotTarget.DisplayName = FText::Format(LOCTEXT("SnapshotTargetDisplayNameFmt", "{0} ({1})"), FText::FromString(AvailableInstance->GetInstanceName()), FText::FromString(AvailableInstance->GetPlatformName()));
|
|
SnapshotTarget.InstanceId = AvailableInstance->GetInstanceId();
|
|
|
|
AvailableSnapshotTargets.Add(MakeShareable(new FWidgetSnapshotTarget(SnapshotTarget)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
// No session services, just add an entry that lets us snapshot ourself
|
|
FWidgetSnapshotTarget SnapshotTarget;
|
|
SnapshotTarget.DisplayName = FText::FromString(FApp::GetInstanceName());
|
|
SnapshotTarget.InstanceId = FApp::GetInstanceId();
|
|
|
|
AvailableSnapshotTargets.Add(MakeShareable(new FWidgetSnapshotTarget(SnapshotTarget)));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SWidgetReflector::UpdateSelectedSnapshotTarget()
|
|
{
|
|
if (AvailableSnapshotTargetsComboBox.IsValid())
|
|
{
|
|
const TSharedPtr<FWidgetSnapshotTarget>* FoundSnapshotTarget = AvailableSnapshotTargets.FindByPredicate([this](const TSharedPtr<FWidgetSnapshotTarget>& InAvailableSnapshotTarget) -> bool
|
|
{
|
|
return InAvailableSnapshotTarget->InstanceId == SelectedSnapshotTargetInstanceId;
|
|
});
|
|
|
|
if (FoundSnapshotTarget)
|
|
{
|
|
AvailableSnapshotTargetsComboBox->SetSelectedItem(*FoundSnapshotTarget);
|
|
}
|
|
else if (AvailableSnapshotTargets.Num() > 0)
|
|
{
|
|
SelectedSnapshotTargetInstanceId = AvailableSnapshotTargets[0]->InstanceId;
|
|
AvailableSnapshotTargetsComboBox->SetSelectedItem(AvailableSnapshotTargets[0]);
|
|
}
|
|
else
|
|
{
|
|
SelectedSnapshotTargetInstanceId = FGuid();
|
|
AvailableSnapshotTargetsComboBox->SetSelectedItem(nullptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SWidgetReflector::OnAvailableSnapshotTargetsChanged()
|
|
{
|
|
UpdateAvailableSnapshotTargets();
|
|
UpdateSelectedSnapshotTarget();
|
|
}
|
|
|
|
FText SWidgetReflector::GetSelectedSnapshotTargetDisplayName() const
|
|
{
|
|
if (AvailableSnapshotTargetsComboBox.IsValid())
|
|
{
|
|
TSharedPtr<FWidgetSnapshotTarget> SelectedSnapshotTarget = AvailableSnapshotTargetsComboBox->GetSelectedItem();
|
|
if (SelectedSnapshotTarget.IsValid())
|
|
{
|
|
return SelectedSnapshotTarget->DisplayName;
|
|
}
|
|
}
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
TSharedRef<SWidget> SWidgetReflector::HandleGenerateAvailableSnapshotComboItemWidget(TSharedPtr<FWidgetSnapshotTarget> InItem) const
|
|
{
|
|
return SNew(STextBlock)
|
|
.Text(InItem->DisplayName);
|
|
}
|
|
|
|
void SWidgetReflector::HandleAvailableSnapshotComboSelectionChanged(TSharedPtr<FWidgetSnapshotTarget> InItem, ESelectInfo::Type InSeletionInfo)
|
|
{
|
|
if (InItem.IsValid())
|
|
{
|
|
SelectedSnapshotTargetInstanceId = InItem->InstanceId;
|
|
}
|
|
else
|
|
{
|
|
SelectedSnapshotTargetInstanceId = FGuid();
|
|
}
|
|
}
|
|
|
|
TSharedRef<ITableRow> SWidgetReflector::HandleReflectorTreeGenerateRow( TSharedRef<FWidgetReflectorNodeBase> InReflectorNode, const TSharedRef<STableViewBase>& OwnerTable )
|
|
{
|
|
return SNew(SReflectorTreeWidgetItem, OwnerTable)
|
|
.WidgetInfoToVisualize(InReflectorNode)
|
|
.ToolTip(GenerateToolTipForReflectorNode(InReflectorNode))
|
|
.SourceCodeAccessor(SourceAccessDelegate)
|
|
.AssetAccessor(AsseetAccessDelegate);
|
|
}
|
|
|
|
void SWidgetReflector::HandleReflectorTreeGetChildren(TSharedRef<FWidgetReflectorNodeBase> InReflectorNode, TArray<TSharedRef<FWidgetReflectorNodeBase>>& OutChildren)
|
|
{
|
|
OutChildren = InReflectorNode->GetChildNodes();
|
|
}
|
|
|
|
void SWidgetReflector::HandleReflectorTreeSelectionChanged( TSharedPtr<FWidgetReflectorNodeBase>, ESelectInfo::Type /*SelectInfo*/ )
|
|
{
|
|
SelectedNodes = ReflectorTree->GetSelectedItems();
|
|
|
|
if (CurrentUIMode == EWidgetReflectorUIMode::Snapshot)
|
|
{
|
|
WidgetSnapshotVisualizer->SetSelectedWidgets(SelectedNodes);
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
TArray<UObject*> SelectedWidgetObjects;
|
|
for (TSharedRef<FWidgetReflectorNodeBase>& Node : SelectedNodes)
|
|
{
|
|
TSharedPtr<SWidget> Widget = Node->GetLiveWidget();
|
|
if (Widget.IsValid())
|
|
{
|
|
TSharedPtr<FReflectionMetaData> ReflectinMetaData = Widget->GetMetaData<FReflectionMetaData>();
|
|
if (ReflectinMetaData.IsValid())
|
|
{
|
|
if (UObject* SourceObject = ReflectinMetaData->SourceObject.Get())
|
|
{
|
|
SelectedWidgetObjects.Add(SourceObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GIsEditor && SelectedWidgetObjects.Num() > 0)
|
|
{
|
|
if (WidgetReflectorCVars::bEnableFocusOnPick)
|
|
{
|
|
TabManager->TryInvokeTab(WidgetReflectorTabID::WidgetDetails);
|
|
}
|
|
|
|
if (PropertyViewPtr.IsValid())
|
|
{
|
|
PropertyViewPtr->SetObjects(SelectedWidgetObjects);
|
|
}
|
|
}
|
|
//else
|
|
//{
|
|
// CloseTab(WidgetReflectorTabID::WidgetDetails);
|
|
//}
|
|
#endif
|
|
}
|
|
|
|
TSharedRef<SWidget> SWidgetReflector::HandleReflectorTreeContextMenu()
|
|
{
|
|
// We spawn a large tooltip, close it immediately to prevent context menu from hiding.
|
|
FSlateApplication::Get().CloseToolTip();
|
|
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, nullptr);
|
|
|
|
bool bHasFilteredTreeRoot = ReflectorTreeRoot != FilteredTreeRoot;
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("SetAsRootLabel", "Selected node as root"),
|
|
LOCTEXT("SetAsRootTooltip", "Set selected node as the root of the graph"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::SetNodesAsReflectorTreeRoot, SelectedNodes),
|
|
FCanExecuteAction::CreateSP(this, &SWidgetReflector::DoesReflectorTreeHasSelectedItem)
|
|
));
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ShowOnlyUMGLabel", "UMG as root"),
|
|
LOCTEXT("ShowOnlyUMGTooltip", "Set UMG as the root of the graph"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandleStartTreeWithUMG),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &SWidgetReflector::HandleIsStartTreeWithUMGEnabled)
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::ToggleButton);
|
|
|
|
MenuBuilder.AddMenuSeparator();
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
LOCTEXT("ResetRoot", "Reset filter"),
|
|
FText::GetEmpty(),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::HandleResetFilteredTreeRoot),
|
|
FCanExecuteAction::CreateLambda([bHasFilteredTreeRoot](){ return bHasFilteredTreeRoot; })
|
|
));
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
TSharedPtr<SWidget> SWidgetReflector::HandleReflectorTreeContextMenuPtr()
|
|
{
|
|
return HandleReflectorTreeContextMenu();
|
|
}
|
|
|
|
void SWidgetReflector::HandleReflectorTreeHiddenColumnsListChanged()
|
|
{
|
|
#if WITH_EDITOR
|
|
if (ReflectorTree && ReflectorTree->GetHeaderRow())
|
|
{
|
|
const TArray<FName> HiddenColumnIds = ReflectorTree->GetHeaderRow()->GetHiddenColumnIds();
|
|
HiddenReflectorTreeColumns.Reset(HiddenColumnIds.Num());
|
|
for (const FName Id : HiddenColumnIds)
|
|
{
|
|
HiddenReflectorTreeColumns.Add(Id.ToString());
|
|
}
|
|
SaveSettings();
|
|
}
|
|
#endif
|
|
}
|
|
void SWidgetReflector::CreateCrumbTrailForNode(TSharedRef<FWidgetReflectorNodeBase> InReflectorNode)
|
|
{
|
|
FWidgetPath PathToWidget;
|
|
TSharedRef<SWidget> SelectedNodeAsWidget = InReflectorNode.Get().GetLiveWidget().ToSharedRef();
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> PickedNodePath;
|
|
FWidgetReflectorNodeUtils::FindLiveWidget(ReflectorTreeRoot, SelectedNodeAsWidget, PickedNodePath);
|
|
BreadCrumb->ClearCrumbs();
|
|
if (PickedNodePath.Num() > 1)
|
|
{
|
|
for (int32 i = 0; i < PickedNodePath.Num(); i++)
|
|
{
|
|
TSharedPtr<SWidget> Parent = PickedNodePath[i]->GetLiveWidget();
|
|
FText WidgetType = FWidgetReflectorNodeUtils::GetWidgetType(Parent);
|
|
BreadCrumb->PushCrumb(WidgetType, PickedNodePath[i]);
|
|
}
|
|
}
|
|
}
|
|
void SWidgetReflector::HandleReflectorTreeOnMouseClick(TSharedRef<FWidgetReflectorNodeBase> InReflectorNode)
|
|
{
|
|
const FModifierKeysState ModKeyState = FSlateApplication::Get().GetModifierKeys();
|
|
|
|
if (ModKeyState.IsLeftAltDown())
|
|
{
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> RootNodes;
|
|
RootNodes.Add(InReflectorNode);
|
|
SetNodesAsReflectorTreeRoot(RootNodes);
|
|
}
|
|
}
|
|
}
|
|
|
|
void SWidgetReflector::HandleBreadcrumbOnClick(const TSharedRef<FWidgetReflectorNodeBase>& InReflectorNode)
|
|
{
|
|
FilteredTreeRoot.Reset();
|
|
FilteredTreeRoot.Add(InReflectorNode);
|
|
BreadCrumb->PopCrumb();
|
|
ReflectorTree->RequestTreeRefresh();
|
|
}
|
|
|
|
TSharedRef< SWidget > SWidgetReflector::HandleBreadcrumbDelimiterMenu(const TSharedRef<FWidgetReflectorNodeBase>& InReflectorNode)
|
|
{
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, nullptr);
|
|
|
|
bool bHasFilteredTreeRoot = ReflectorTreeRoot != FilteredTreeRoot;
|
|
TArray<TSharedRef< FWidgetReflectorNodeBase>> Children = InReflectorNode->GetChildNodes();
|
|
for (int32 ChildIndex = 0; ChildIndex < Children.Num(); ++ChildIndex)
|
|
{
|
|
TSharedPtr<SWidget> ChildWidget = Children[ChildIndex]->GetLiveWidget();
|
|
FText WidgetType = FWidgetReflectorNodeUtils::GetWidgetType(ChildWidget);
|
|
TArray<TSharedRef<FWidgetReflectorNodeBase>> RootNodes;
|
|
RootNodes.Add(Children[ChildIndex]);
|
|
MenuBuilder.AddMenuEntry(
|
|
WidgetType,
|
|
FText::GetEmpty(),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP(this, &SWidgetReflector::SetNodesAsReflectorTreeRoot, RootNodes)
|
|
|
|
));
|
|
}
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
EVisibility SWidgetReflector::HandleIsBreadcrumbVisible() const
|
|
{
|
|
return BreadCrumb->HasCrumbs() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
void SWidgetReflector::HandleResetFilteredTreeRoot()
|
|
{
|
|
BreadCrumb->ClearCrumbs();
|
|
bFilterReflectorTreeRootWithUMG = false;
|
|
UpdateFilteredTreeRoot();
|
|
ReflectorTree->RequestTreeRefresh();
|
|
}
|
|
|
|
void SWidgetReflector::HandleStartTreeWithUMG()
|
|
{
|
|
bFilterReflectorTreeRootWithUMG = !bFilterReflectorTreeRootWithUMG;
|
|
UpdateFilteredTreeRoot();
|
|
ReflectorTree->RequestTreeRefresh();
|
|
}
|
|
|
|
// console command
|
|
|
|
static FDelegateHandle TakeSnapshotEndFrameHandle;
|
|
|
|
void TakeSnapshotCommand_EndFrame(double RequestedTime, bool bRequestNavigation)
|
|
{
|
|
if (RequestedTime <= FApp::GetCurrentTime())
|
|
{
|
|
FWidgetSnapshotData SnapshotData;
|
|
SnapshotData.TakeSnapshot(bRequestNavigation);
|
|
FString Filename = FPaths::CreateTempFilename(*FPaths::GameAgnosticSavedDir(), TEXT(""), TEXT(".widgetsnapshot"));
|
|
SnapshotData.SaveSnapshotToFile(Filename);
|
|
|
|
FCoreDelegates::OnEndFrame.Remove(TakeSnapshotEndFrameHandle);
|
|
TakeSnapshotEndFrameHandle.Reset();
|
|
}
|
|
}
|
|
|
|
void TakeSnapshotCommand(const TArray<FString>& Args)
|
|
{
|
|
FCoreDelegates::OnEndFrame.Remove(TakeSnapshotEndFrameHandle);
|
|
|
|
float RequestedDelay = 0.001f;
|
|
bool bRequestNavigation = false;
|
|
for (const FString& Arg : Args)
|
|
{
|
|
if (FParse::Value(*Arg, TEXT("Delay="), RequestedDelay))
|
|
{
|
|
continue;
|
|
}
|
|
if (FParse::Bool(*Arg, TEXT("Navigation="), bRequestNavigation))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
const double CurrentTime = FApp::GetCurrentTime();
|
|
const double RequestedTimeDelay = CurrentTime + (double)RequestedDelay;
|
|
TakeSnapshotEndFrameHandle = FCoreDelegates::OnEndFrame.AddStatic(&TakeSnapshotCommand_EndFrame, RequestedTimeDelay, bRequestNavigation);
|
|
}
|
|
|
|
void DumpFontAtlasesCommand(const TArray<FString>& Args)
|
|
{
|
|
FString SaveDirectoryName = FPaths::ProjectSavedDir() / TEXT("WidgetReflector") / TEXT("FontAtlases");
|
|
FString SaveFilePrefix;
|
|
for (const FString& Arg : Args)
|
|
{
|
|
if (FParse::Value(*Arg, TEXT("SaveDirectory"), SaveDirectoryName))
|
|
{
|
|
continue;
|
|
}
|
|
if (FParse::Value(*Arg, TEXT("SaveFilePrefix"), SaveFilePrefix))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// We append the timestamp on to the directory names to differentiate between various runs of the command.
|
|
SaveDirectoryName = FString::Format(TEXT("{0}_{1}"), {SaveDirectoryName, FDateTime::Now().ToString()});
|
|
SaveDirectoryName = FPaths::ConvertRelativePathToFull(SaveDirectoryName);
|
|
UE_LOG(LogSlate, Display, TEXT("Dumping all current font atlases to directory '%s'. All textures will be saved with a user provided file prefix of '%s'"), *SaveDirectoryName, *SaveFilePrefix);
|
|
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
|
|
TSharedRef<FSlateFontCache> FontCache = FSlateApplication::Get().GetRenderer()->GetFontCache();
|
|
int32 NumFontAtlases = FontCache->GetNumAtlasPages();
|
|
for (int32 Index = 0; Index < NumFontAtlases; ++Index)
|
|
{
|
|
// @TODOFonts: Refactor this out into its own function so the widget reflector could have a button to save a single texture page or save all texture pages
|
|
ISlateFontTexture* FontTexture = FontCache->GetFontTexture(Index);
|
|
if (!FontTexture)
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("Font texture page %d is not a valid font texture. This page will not be saved."), Index);
|
|
continue;
|
|
}
|
|
|
|
TArray<uint8> FontTextureData;
|
|
FontTexture->GetAtlasDataCopy(FontTextureData);
|
|
if (FontTextureData.IsEmpty())
|
|
{
|
|
UE_LOG(LogSlate, Log, TEXT("Texture page %d has no texture data. The texture will not be saved."), Index);
|
|
continue;
|
|
}
|
|
// We need this to get access to the height and width of the texture
|
|
FSlateShaderResource* ShaderResource = FontTexture->GetSlateTexture();
|
|
if (!ShaderResource)
|
|
{
|
|
UE_LOG(LogSlate, Log, TEXT("Unable to retrieve the shader resource for texture page %d. Skipping saving this texture."), Index);
|
|
continue;
|
|
}
|
|
ESlateFontAtlasContentType ContentType = FontTexture->GetContentType();
|
|
ERawImageFormat::Type ImageFormat = ERawImageFormat::Invalid;
|
|
EGammaSpace GammaSpace = EGammaSpace::Invalid;
|
|
switch (ContentType)
|
|
{
|
|
case ESlateFontAtlasContentType::Alpha:
|
|
ImageFormat = ERawImageFormat::G8;
|
|
GammaSpace = EGammaSpace::Linear;
|
|
break;
|
|
case ESlateFontAtlasContentType::Color:
|
|
ImageFormat = ERawImageFormat::BGRA8;
|
|
GammaSpace = EGammaSpace::sRGB;
|
|
break;
|
|
case ESlateFontAtlasContentType::Msdf:
|
|
ImageFormat = ERawImageFormat::BGRA8;
|
|
GammaSpace = EGammaSpace::Linear;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
FImageView ImageView((void*) FontTextureData.GetData(), ShaderResource->GetWidth(), ShaderResource->GetHeight(), 1 /* InNumSlices*/, ImageFormat, GammaSpace);
|
|
TArray64<uint8> SaveImageBuffer;
|
|
bool bSavedImage = ImageWrapperModule.CompressImage(SaveImageBuffer, EImageFormat::PNG, ImageView);
|
|
if (bSavedImage)
|
|
{
|
|
FString SaveFileName = FString::Format(TEXT("{0}FontAtlasPage_{1}.png"), { SaveFilePrefix, FString::FromInt(Index)});
|
|
FString AbsoluteSaveFileName = SaveDirectoryName / SaveFileName;
|
|
UE_LOG(LogSlate, Display, TEXT("Saving font texture '%s'"), *AbsoluteSaveFileName);
|
|
if (!FFileHelper::SaveArrayToFile(SaveImageBuffer, *AbsoluteSaveFileName))
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("Failed to save font texture '%s'"), *AbsoluteSaveFileName);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogSlate, Warning, TEXT("Failed to convert texture page %d to a buffer to be saved."), Index);
|
|
}
|
|
}
|
|
}
|
|
} // namespace WidgetReflectorImpl
|
|
|
|
TSharedRef<SWidgetReflector> SWidgetReflector::New()
|
|
{
|
|
return MakeShareable( new WidgetReflectorImpl::SWidgetReflector() );
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|