// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/Map.h" #include "Containers/UnrealString.h" #include "CoreMinimal.h" #include "Delegates/Delegate.h" #include "HAL/Platform.h" #include "InputBehaviorSet.h" #include "InteractiveToolActionSet.h" #include "Internationalization/Text.h" #include "Misc/AssertionMacros.h" #include "Misc/Optional.h" #include "Shader.h" #include "Templates/EnableIf.h" #include "Templates/Function.h" #include "Templates/Models.h" #include "Templates/UniquePtr.h" #include "Templates/UnrealTemplate.h" #include "ToolContextInterfaces.h" #include "UObject/Class.h" #include "UObject/Object.h" #include "UObject/ObjectMacros.h" #include "UObject/ObjectPtr.h" #include "UObject/Package.h" #include "UObject/UObjectGlobals.h" #include "InteractiveTool.generated.h" class FCanvas; class FInteractiveToolActionSet; class FProperty; class IToolsContextRenderAPI; class UInputBehavior; class UInteractiveTool; class UInteractiveToolManager; struct FPropertyChangedEvent; /** Passed to UInteractiveTool::Shutdown to indicate how Tool should shut itself down*/ UENUM(BlueprintType) enum class EToolShutdownType : uint8 { /** Tool cleans up and exits. Pass this to tools that do not have Accept/Cancel options. */ Completed = 0, /** Tool commits current preview to scene */ Accept = 1, /** Tool discards current preview without modifying scene */ Cancel = 2 }; /** * FInteractiveToolInfo provides information about a tool (name, tooltip, etc) */ struct FInteractiveToolInfo { /** Name of Tool. May be FText::Empty(), but will default to Tool->GetClass()->GetDisplayNameText() in InteractiveTool constructor */ FText ToolDisplayName = FText::GetEmpty(); }; class FWatchablePropertySet { public: // // Property watching infrastructure // class FPropertyWatcher { public: virtual ~FPropertyWatcher() = default; virtual void CheckAndUpdate() = 0; virtual void SilentUpdate() = 0; }; template class TPropertyWatcher : public FPropertyWatcher { public: using FValueGetter = TFunction; using FChangedCallback = TFunction; using FNotEqualTestFunction = TFunction; // Define "!=" for PropType /** * Describes a type having a "!=" comparasion operator. (Note: it would probably be cleaner to require operator==, * however some types have operator!= but no operator==, and we do only use != in the code.) */ struct CInequalityComparable { template auto Requires(bool& Result, const T& A, const T& B) -> decltype( Result = A != B ); }; // If PropType is CInequalityComparable, allow the two-argument constructor and default to using the type's // existing != operator. // If PropType is not CInequalityComparable, the caller must use the three-argument constructor to provide a // "not equal" function. // Two-argument constructor, enabled only if PropType is CInequalityComparable template>::Type> TPropertyWatcher(const PropType& Property, FChangedCallback OnChangedIn) : Cached(), GetValue([&Property]() {return Property; }), OnChanged(MoveTemp(OnChangedIn)), NotEqual([](const PropType& A, const PropType& B) { return A != B; }) {} // Two-argument constructor, enabled only if PropType is CInequalityComparable template>::Type> TPropertyWatcher(FValueGetter GetValueIn, FChangedCallback OnChangedIn) : Cached(), GetValue(MoveTemp(GetValueIn)), OnChanged(MoveTemp(OnChangedIn)), NotEqual([](const PropType& A, const PropType& B) { return A != B; }) {} TPropertyWatcher(const PropType& Property, FChangedCallback OnChangedIn, FNotEqualTestFunction NotEqualIn) : Cached(), GetValue([&Property]() {return Property; }), OnChanged(MoveTemp(OnChangedIn)), NotEqual(MoveTemp(NotEqualIn)) {} TPropertyWatcher(FValueGetter GetValueIn, FChangedCallback OnChangedIn, FNotEqualTestFunction NotEqualIn) : Cached(), GetValue(MoveTemp(GetValueIn)), OnChanged(MoveTemp(OnChangedIn)), NotEqual(MoveTemp(NotEqualIn)) {} void CheckAndUpdate() final { PropType Value = GetValue(); if ((!Cached.IsSet()) || NotEqual(Cached.GetValue(), Value)) { Cached = Value; OnChanged(Cached.GetValue()); } } void SilentUpdate() final { Cached = GetValue(); } private: TOptional Cached; FValueGetter GetValue; FChangedCallback OnChanged; FNotEqualTestFunction NotEqual; }; FWatchablePropertySet() = default; FWatchablePropertySet(const FWatchablePropertySet&) = delete; FWatchablePropertySet& operator=(const FWatchablePropertySet&) = delete; void CheckAndUpdateWatched() { for ( auto& PropWatcher : PropertyWatchers ) { PropWatcher->CheckAndUpdate(); } } void SilentUpdateWatched() { for ( auto& PropWatcher : PropertyWatchers ) { PropWatcher->SilentUpdate(); } } /** * Silently updates just a single watcher, using an index gotten from WatchProperty. * Useful when you want watching to still work for other properties changed in the * same tick. */ void SilentUpdateWatcherAtIndex(int32 i) { check(i >= 0 && i < PropertyWatchers.Num()); PropertyWatchers[i]->SilentUpdate(); } /** @return Index of the watcher, which can be used in SilentUpdateWatcherAtIndex */ template int32 WatchProperty(const PropType& ValueIn, typename TPropertyWatcher::FChangedCallback OnChangedIn) { return PropertyWatchers.Emplace(MakeUnique>(ValueIn, OnChangedIn)); } /** @return Index of the watcher, which can be used in SilentUpdateWatcherAtIndex */ template int32 WatchProperty(typename TPropertyWatcher::FValueGetter GetValueIn, typename TPropertyWatcher::FChangedCallback OnChangedIn) { return PropertyWatchers.Emplace(MakeUnique>(GetValueIn, OnChangedIn)); } /** @return Index of the watcher, which can be used in SilentUpdateWatcherAtIndex */ template int32 WatchProperty(const PropType& ValueIn, typename TPropertyWatcher::FChangedCallback OnChangedIn, typename TPropertyWatcher::FNotEqualTestFunction NotEqualsIn) { return PropertyWatchers.Emplace(MakeUnique>(ValueIn, OnChangedIn, NotEqualsIn)); } /** @return Index of the watcher, which can be used in SilentUpdateWatcherAtIndex */ template int32 WatchProperty(typename TPropertyWatcher::FValueGetter GetValueIn, typename TPropertyWatcher::FChangedCallback OnChangedIn, typename TPropertyWatcher::FNotEqualTestFunction NotEqualsIn) { return PropertyWatchers.Emplace(MakeUnique>(GetValueIn, OnChangedIn, NotEqualsIn)); } private: TArray> PropertyWatchers; }; /** This delegate is used by UInteractiveToolPropertySet */ DECLARE_MULTICAST_DELEGATE_TwoParams(FInteractiveToolPropertySetModifiedSignature, UObject*, FProperty*); /** * A UInteractiveTool contains a set of UObjects that contain "properties" of the Tool, ie * the configuration flags, parameters, etc that control the Tool. Currently any UObject * can be added as a property set, however there is no automatic mechanism for those child * UObjects to notify the Tool when a property changes. * * If you make your property set UObjects subclasses of UInteractiveToolPropertySet, then * when the Tool Properties are changed *in the Editor*, the parent Tool will be automatically notified. * You can override UInteractiveTool::OnPropertyModified() to act on these notifications */ UCLASS(Transient, MinimalAPI) class UInteractiveToolPropertySet : public UObject, public FWatchablePropertySet { GENERATED_BODY() public: /** @return the multicast delegate that is called when properties are modified */ FInteractiveToolPropertySetModifiedSignature& GetOnModified() { return OnModified; } /** Return true if this property set is enabled. Enabled/Disable state is intended to be used to control things like visibility in UI/etc. */ bool IsPropertySetEnabled() const { return bIsPropertySetEnabled; } // // Setting saving/serialization // /** * Save and restore values of current Tool Properties between tool invocations. The standard usage of * this setup is to call PropertySet->RestoreProperties() in the UInteractiveTool::Setup() implementation, * and PropertySet->SaveProperties() in the UInteractiveTool::Shutdown() implementation. * * The default behaviour of these functions is to Save or Restore every property in the property set. It is not * necessary to save/restore all possible Properties (in many cases this would not make sense), so individual * properties may be skipped by adding the "TransientToolProperty" tag to their metadata on a property by property basis. * * Property sets which need more exotic behaviour upon Save and Restore may override these routines * * GetDynamicPropertyCache() can be used to return an instance of the specified property set subclass which * may be used as a place to save/restore these properties by customized Save/Restore functions * * Note: the current design of this system assumes that the CDO will keep the referenced objects alive. * This assumption is incorrect in Runtime builds, and some external mechanism must be used to keep * the elements in the CachedPropertiesMap alive. */ /** * Save the values of this PropertySet with the given CacheIdentifier. * The Tool parameter is currently ignored. */ INTERACTIVETOOLSFRAMEWORK_API virtual void SaveProperties(UInteractiveTool* SaveFromTool, const FString& CacheIdentifier = TEXT("")); /** * Restore the values of the Property Set with the given CacheIdentifier. * The Tool parameter is currently ignored. */ INTERACTIVETOOLSFRAMEWORK_API virtual void RestoreProperties(UInteractiveTool* RestoreToTool, const FString& CacheIdentifier = TEXT("")); protected: // Utility func used to implement the default Save/RestoreProperties funcs INTERACTIVETOOLSFRAMEWORK_API virtual void SaveRestoreProperties(UInteractiveTool* RestoreToTool, const FString& CacheIdentifier, bool bSaving); /** * GetDynamicPropertyCache return class-internal objects that subclasses can use to save/restore properties. * @param CacheIdentifier multiple versions of the UInteractiveToolPropertySet can be stored, CacheIdentifier indicates which one to use * @param bWasCreatedOut true is returned here if this is the first time the object was seen * @return instance of the current subclass that can be used to save/restore values */ INTERACTIVETOOLSFRAMEWORK_API TObjectPtr GetDynamicPropertyCache(const FString& CacheIdentifier, bool& bWasCreatedOut); public: #if WITH_EDITOR /** * Posts a message to the OnModified delegate with the modified FProperty * @warning Please consider listening to OnModified instead of overriding this function * @warning this function is currently only called in Editor (not at runtime) */ INTERACTIVETOOLSFRAMEWORK_API virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent); #endif protected: UPROPERTY(Transient, DuplicateTransient, NonTransactional, SkipSerialization) TMap> CachedPropertiesMap; // Controls whether a property set is shown in the UI. Transient so that disabling a PropertySet in one tool doesn't disable it in others. UPROPERTY(Transient, DuplicateTransient, SkipSerialization, meta=(TransientToolProperty)) bool bIsPropertySetEnabled = true; friend class UInteractiveTool; // so that tool can enable/disable FInteractiveToolPropertySetModifiedSignature OnModified; }; /** * UInteractiveTool is the base class for all Tools in the InteractiveToolsFramework. * A Tool is is a "lightweight mode" that may "own" one or more Actors/Components/etc in * the current scene, may capture certain input devices or event streams, and so on. * The base implementation essentially does nothing but provide sane default behaviors. * * The BaseTools/ subfolder contains implementations of various kinds of standard * "tool behavior", like a tool that responds to a mouse click, etc, that can be * extended to implement custom behaviors. * * In the framework, you do not create instances of UInteractiveTool yourself. * You provide a UInteractiveToolBuilder implementation that can properly construct * an instance of your Tool, this is where for example default parameters would be set. * The ToolBuilder is registered with the ToolManager, and then UInteractiveToolManager::ActivateTool() * is used to kick things off. * * @todo callback/delegate for if/when .InputBehaviors changes * @todo callback/delegate for when tool properties change */ UCLASS(Transient, MinimalAPI) class UInteractiveTool : public UObject, public IInputBehaviorSource { GENERATED_BODY() public: INTERACTIVETOOLSFRAMEWORK_API UInteractiveTool(); /** * Called by ToolManager to initialize the Tool *after* ToolBuilder::BuildTool() has been called */ INTERACTIVETOOLSFRAMEWORK_API virtual void Setup(); /** * Called by ToolManager to shut down the Tool * @param ShutdownType indicates how the tool should shutdown (ie Accept or Cancel current preview, etc) */ INTERACTIVETOOLSFRAMEWORK_API virtual void Shutdown(EToolShutdownType ShutdownType); /** * Allow the Tool to do any custom drawing (ie via PDI/RHI) * @param RenderAPI Abstraction that provides access to Rendering in the current ToolsContext */ INTERACTIVETOOLSFRAMEWORK_API virtual void Render(IToolsContextRenderAPI* RenderAPI); /** * Allow the Tool to do any custom screen space drawing * @param Canvas the FCanvas to use to do the drawing * @param RenderAPI Abstraction that provides access to Rendering in the current ToolsContext */ INTERACTIVETOOLSFRAMEWORK_API virtual void DrawHUD( FCanvas* Canvas, IToolsContextRenderAPI* RenderAPI ); /** * Non overrideable func which does processing and calls the tool's OnTick * @param DeltaTime the time delta since last tick */ INTERACTIVETOOLSFRAMEWORK_API virtual void Tick(float DeltaTime) final; /** * @return ToolManager that owns this Tool */ INTERACTIVETOOLSFRAMEWORK_API virtual UInteractiveToolManager* GetToolManager() const; /** * @return true if this Tool support being Cancelled, ie calling Shutdown(EToolShutdownType::Cancel) */ INTERACTIVETOOLSFRAMEWORK_API virtual bool HasCancel() const; /** * @return true if this Tool support being Accepted, ie calling Shutdown(EToolShutdownType::Accept) */ INTERACTIVETOOLSFRAMEWORK_API virtual bool HasAccept() const; /** * @return true if this Tool is currently in a state where it can be Accepted. This may be false if for example there was an error in the Tool. */ INTERACTIVETOOLSFRAMEWORK_API virtual bool CanAccept() const; // // Input Behaviors support // /** * Add an input behavior for this Tool. Typically only effective when called during tool Setup(), as * the behavior set gets submitted to the input router after that call. * @param Behavior behavior to add * @param Source Optional pointer that could be used to identify the behavior for removal later. */ INTERACTIVETOOLSFRAMEWORK_API virtual void AddInputBehavior(UInputBehavior* Behavior, void* Source = nullptr); /** * Remove all input behaviors that had the given source pointer set during their addition. * Typically only effective when called during tool Setup(), as the behavior set gets submitted * to the input router after that call. * @param Source Identifying pointer */ INTERACTIVETOOLSFRAMEWORK_API virtual void RemoveInputBehaviorsBySource(void* Source); /** * @return Current input behavior set. */ INTERACTIVETOOLSFRAMEWORK_API virtual const UInputBehaviorSet* GetInputBehaviors() const; // // Property support // /** * @return list of property UObjects for this tool (ie to add to a DetailsViewPanel, for example) */ INTERACTIVETOOLSFRAMEWORK_API virtual TArray GetToolProperties(bool bEnabledOnly = true) const; /** * OnPropertySetsModified is broadcast whenever the contents of the ToolPropertyObjects array is modified */ DECLARE_MULTICAST_DELEGATE(OnInteractiveToolPropertySetsModified); OnInteractiveToolPropertySetsModified OnPropertySetsModified; /** * OnPropertyModifiedDirectlyByTool is broadcast whenever the ToolPropertyObjects array stays the same, but a property * inside of one of the objects is changed internally by the tool. This allows any external display of such properties * to properly update. In a DetailsViewPanel, for instance, it refreshes certain cached states such as edit condition * states for other properties. * * This should only broadcast when the tool itself is responsible for the change, so it typically isn't broadcast * from the tool's OnPropertyModified function. */ DECLARE_MULTICAST_DELEGATE_OneParam(OnInteractiveToolPropertyInternallyModified, UObject*); OnInteractiveToolPropertyInternallyModified OnPropertyModifiedDirectlyByTool; /** * Automatically called by UInteractiveToolPropertySet.OnModified delegate to notify Tool of child property set changes * @param PropertySet which UInteractiveToolPropertySet was modified * @param Property which FProperty in the set was modified */ virtual void OnPropertyModified(UObject* PropertySet, FProperty* Property) { } protected: /** The current set of InputBehaviors provided by this Tool */ UPROPERTY(Transient, DuplicateTransient, NonTransactional, SkipSerialization) TObjectPtr InputBehaviors; /** The current set of Property UObjects provided by this Tool. May contain pointer to itself. */ UPROPERTY(Transient, DuplicateTransient, NonTransactional, SkipSerialization) TArray> ToolPropertyObjects; /** * Add a Property object for this Tool * @param Property object to add */ INTERACTIVETOOLSFRAMEWORK_API virtual void AddToolPropertySource(UObject* PropertyObject); /** * Add a PropertySet object for this Tool * @param PropertySet Property Set object to add */ INTERACTIVETOOLSFRAMEWORK_API virtual void AddToolPropertySource(UInteractiveToolPropertySet* PropertySet); /** * Remove a PropertySet object from this Tool. If found, will broadcast OnPropertySetsModified * @param PropertySet property set to remove. * @return true if PropertySet is found and removed */ INTERACTIVETOOLSFRAMEWORK_API virtual bool RemoveToolPropertySource(UInteractiveToolPropertySet* PropertySet); /** * Replace a PropertySet object on this Tool with another property set. If replaced, will broadcast OnPropertySetsModified * @param CurPropertySet property set to remove * @param ReplaceWith property set to add * @param bSetToEnabled if true, ReplaceWith property set is explicitly enabled (otherwise enable/disable state is unmodified) * @return true if CurPropertySet is found and replaced */ INTERACTIVETOOLSFRAMEWORK_API virtual bool ReplaceToolPropertySource(UInteractiveToolPropertySet* CurPropertySet, UInteractiveToolPropertySet* ReplaceWith, bool bSetToEnabled = true); /** * Enable/Disable a PropertySet object for this Tool. If found and state was modified, will broadcast OnPropertySetsModified * @param PropertySet Property Set object to modify * @param bEnabled whether to enable or disable * @return true if PropertySet was found */ INTERACTIVETOOLSFRAMEWORK_API virtual bool SetToolPropertySourceEnabled(UInteractiveToolPropertySet* PropertySet, bool bEnabled); /** * Call after changing a propertyset internally in the tool to allow external views of the property * set to update properly. This is meant as an outward notification mechanism, not a way to to * pass along notifications, so don't call this if the property is changed externally (i.e., this * should not usually be called from OnPropertyModified unless the tool adds changes of its own). */ INTERACTIVETOOLSFRAMEWORK_API virtual void NotifyOfPropertyChangeByTool(UInteractiveToolPropertySet* PropertySet) const; enum EAcceptWarning { NoWarning, EmptyForbidden }; /** * Helper function to update a standard warning when we need to explain why "Accept" is disabled * Note that calling this base implementation will clear any unrelated warnings. * Note that this function is not automatically called by the base class. * * @param Warning Reason that tool cannot be accepted (or EAcceptWarning::NoWarning if no warning need be displayed) */ INTERACTIVETOOLSFRAMEWORK_API virtual void UpdateAcceptWarnings(EAcceptWarning Warning); private: // Tracks whether the UpdateAcceptWarnings function showed a warning the last time it was called. // Used to avoid clearing the tool display message in cases where it was not set by this function. bool bLastShowedAcceptWarning = false; protected: /** * Allow the Tool to do any necessary processing on Tick * @param DeltaTime the time delta since last tick */ virtual void OnTick(float DeltaTime){}; // // Action support/system // // Your Tool subclass can register a set of "Actions" it can execute // by overloading RegisterActions(). Then external systems can use GetActionSet() to // find out what Actions your Tool supports, and ExecuteAction() to run those actions. // public: /** * Get the internal Action Set for this Tool. The action set is created and registered on-demand. * @return pointer to initialized Action set */ INTERACTIVETOOLSFRAMEWORK_API virtual FInteractiveToolActionSet* GetActionSet(); /** * Request that the Action identified by ActionID be executed. * Default implementation forwards these requests to internal ToolActionSet. */ INTERACTIVETOOLSFRAMEWORK_API virtual void ExecuteAction(int32 ActionID); protected: /** * Override this function to register the set of Actions this Tool supports, using FInteractiveToolActionSet::RegisterAction. * Note that for the actions to be triggered, you will also need to add corresponding registration per tool * -- see Engine\Plugins\Editor\ModelingToolsEditorMode\Source\ModelingToolsEditorMode\Public\ModelingToolsActions.h for examples */ INTERACTIVETOOLSFRAMEWORK_API virtual void RegisterActions(FInteractiveToolActionSet& ActionSet); private: /** * Set of actions this Tool can execute. This variable is allocated on-demand. * Use GetActionSet() instead of accessing this pointer directly! */ FInteractiveToolActionSet* ToolActionSet = nullptr; // // Tool Information (name, icon, help text, etc) // public: /** * @return ToolInfo structure for this Tool */ virtual FInteractiveToolInfo GetToolInfo() const { return DefaultToolInfo; } /** * Replace existing ToolInfo with new data */ virtual void SetToolInfo(const FInteractiveToolInfo& NewInfo) { DefaultToolInfo = NewInfo; } /** * Set Tool name */ virtual void SetToolDisplayName(const FText& NewName) { DefaultToolInfo.ToolDisplayName = NewName; } private: /** * ToolInfo for this Tool */ FInteractiveToolInfo DefaultToolInfo; private: // InteractionMechanic needs to be able to talk to Tool internals, eg property sets, behaviors, etc friend class UInteractionMechanic; };