// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Containers/Array.h" #include "Containers/ContainerAllocationPolicies.h" #include "Containers/SortedMap.h" #include "CurveEditorScreenSpace.h" #include "CurveEditorTypes.h" #include "HAL/Platform.h" #include "Math/NumericLimits.h" #include "Math/TransformCalculus2D.h" #include "Math/Vector2D.h" #include "Misc/Attribute.h" #include "Misc/Optional.h" #include "Templates/Less.h" #include "Templates/SharedPointer.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SCompoundWidget.h" #include "CurveEditorSettings.h" #include "CurveDrawInfo.h" class FCurveEditor; class FSlateRect; class FText; class SCurveEditorPanel; class FCurveModel; class FCurveEditorAxis; class SRetainerWidget; class SCurveEditorView; enum class ECurveEditorAxisOrientation : uint8; namespace UE::CurveEditor { class FCurveDrawParamsCache; } /** * Identifier for a specific axis on a view. These identifiers are transient and should not be stored externally. */ struct FCurveEditorViewAxisID { FCurveEditorViewAxisID() : Index(std::numeric_limits::max()) {} explicit FCurveEditorViewAxisID(uint8 InIndex) : Index(InIndex) { check(InIndex < std::numeric_limits::max()); } explicit operator bool() const { return Index != std::numeric_limits::max(); } friend uint32 GetTypeHash(FCurveEditorViewAxisID In) { return GetTypeHash(In.Index); } friend bool operator==(FCurveEditorViewAxisID A, FCurveEditorViewAxisID B) { return A.Index == B.Index; } friend bool operator!=(FCurveEditorViewAxisID A, FCurveEditorViewAxisID B) { return A.Index != B.Index; } friend bool operator<(FCurveEditorViewAxisID A, FCurveEditorViewAxisID B) { return A.Index < B.Index; } private: operator int32() const { check(Index != std::numeric_limits::max()); return Index; } FCurveEditorViewAxisID& operator=(int32 InIndex) { check(InIndex >= 0 && InIndex < 255); Index = static_cast(InIndex); return *this; } friend SCurveEditorView; uint8 Index; }; /** * This is the base widget type for all views that exist on a curve editor panel. A view may contain 0 or more curves (stored in CurveInfoByID). * SCurveEditorViews are directly housed within a single SCurveEditorViewContainer which arranges each view vertically in order. * * View types: * A view may have a centrally registered ID (See FCurveEditorViewRegistry and FCurveModel::GetSupportedViews) which allows any external curve model type to express support for any other view type. * 3 built-in views are provided: * Absolute: Shows 1 or more curves on a single 2D-scrollable grid using a single zoomable view-space. * Normalized: Shows 1 or more curves on a single grid scrollable horizontally with all curves normalized to the extents of the view * Stacked: Shows 1 or more curves on separate fixed-height grids scrollable horizontally each curve normalized to the extents of its own grid * Unregistered curve views may still be added to the view by calling SCurveEditorPanel::AddView * * Space Transformations: * Views must define a valid 2-dimensional space (ViewSpace) that is used to convert from a pixel position to the virtual view space. * Views may additionally implement per-curve transformations from view space to curve space, to allow for specific layouts/scales of curves within them. * Per-curve transforms are stored inside FCurveInfo::ViewToCurveTransform * * Sizing: * Views may be size in one of 2 ways: * * Auto sized (bAutoSize=true): views derive their height from the widget's desired size; * * Stretched (bAutoSize=false): views will be stretched to the size of the parent panel with a sensible minimum height. */ class CURVEEDITOR_API SCurveEditorView : public SCompoundWidget { public: /** * Default constructor */ SCurveEditorView(); ~SCurveEditorView(); /** * Get the default screen space ultility that defines this view's input, output and pixel metrics */ FCurveEditorScreenSpace GetViewSpace() const; /** * Get the screen space ultility that defines this view's input, output and pixel metrics for the specified axis combination */ FCurveEditorScreenSpace GetViewSpace(const FName& InHorizontalAxis, const FName& InVerticalAxis) const; /** * Get the screen space ultility that defines the specified curves's input, output and pixel metrics. * The resulting struct defines the transformation from this view's widget pixel space to the curve input/output space */ FCurveEditorScreenSpace GetCurveSpace(FCurveModelID CurveID) const; /** @return The transform used to translate view space (absolute key coordinates) to the curve space (e.g. 0-1 range in normalized view). */ FTransform2d GetViewToCurveTransform(FCurveModelID CurveID) const; /** * Retrieve the horizontal screen-space information for the specified axis */ FCurveEditorScreenSpaceH GetHorizontalAxisSpace(FCurveEditorViewAxisID ID) const; /** * Retrieve the vertical screen-space information for the specified axis */ FCurveEditorScreenSpaceV GetVerticalAxisSpace(FCurveEditorViewAxisID ID) const; /** * Retrueve the axis ID assigned to the specified curve and orientation */ FCurveEditorViewAxisID GetAxisForCurve(FCurveModelID CurveID, ECurveEditorAxisOrientation AxisOrientation) const; /** * Retrueve the axis associated with the specified ID and orientation */ TSharedPtr GetAxis(FCurveEditorViewAxisID ID, ECurveEditorAxisOrientation AxisOrientation) const; /** * Check whether this view should auto size to fit its FixedHeight or child slot content */ bool ShouldAutoSize() const { return bAutoSize; } /** * Check whether this view can be interacted with. This should be checked by any tool or edit mode attempting to manipulated curves on this view. */ bool IsInteractive() const { return bInteractive; } /** * Check whether this view has space for any more curves */ bool HasCapacity() const { return MaximumCapacity == 0 || CurveInfoByID.Num() < MaximumCapacity; } /** * Retrieve the number of curves that exist on this view */ int32 NumCurves() const { return CurveInfoByID.Num(); } /** * Retrieve this view's input bounds, accounting for any offsets or padding from the outer container */ void GetInputBounds(double& OutInputMin, double& OutInputMax) const; /** * Retrieve this view's minimum output bound in view space */ double GetOutputMin() const { return OutputMin; } /** * Retrieve this view's maximum output bound in view space */ double GetOutputMax() const { return OutputMax; } /** * Set this view's output bounds */ void SetOutputBounds(double InOutputMin, double InOutputMax, FCurveEditorViewAxisID AxisID = FCurveEditorViewAxisID()); /** * Set this view's input bounds */ void SetInputBounds(double InInputMin, double InInputMax, FCurveEditorViewAxisID AxisID = FCurveEditorViewAxisID()); /** * Zoom this view in or out around its center point * * @param Amount The amount to zoom by horizontally and vertically as a factor of the current size */ void Zoom(const FVector2D& Amount); /** * Zoom this view in or out around the specified point * * @param Amount The amount to zoom by horizontally and vertically as a factor of the current size * @param TimeOrigin The time origin to zoom around * @param ValueOrigin The value origin to zoom around */ void ZoomAround(const FVector2D& Amount, double InputOrigin, double OutputOrigin); /** This should be called every tick by an owning widget, to see if the cache is valid, which will then recreate it and invalidate widget*/ virtual void CheckCacheAndInvalidateIfNeeded(); UE_DEPRECATED(5.3, "Use UpdateViewToTransformCurves(double InputMin, double InputMax) instead.") virtual void UpdateViewToTransformCurves() {} /** Function to make sure to update the view to the transform curves, we need to do this before we cache*/ virtual void UpdateViewToTransformCurves(double InputMin, double InputMax) {}; /** Frame the view vertially by the input and output bounds, peformaing any custom clipping as needed */ virtual void FrameVertical(double InOutputMin, double InOutputMax, FCurveEditorViewAxisID AxisID = FCurveEditorViewAxisID()); /** Frame the view horizontall by the input and output bounds, peformaing any custom clipping as needed */ virtual void FrameHorizontal(double InInputMin, double InInputMax, FCurveEditorViewAxisID AxisID = FCurveEditorViewAxisID()); /** Returns the cure editor associated with this view, or nullptr if the view is not specific to a curve editor */ TSharedPtr GetCurveEditor() const; public: /** * Retrieve all the curve points that overlap the specified rectangle in widget space * * @param WidgetRectangle The rectangle to hit test against. May not hit points that would exist outside of the view's visible bounds. * @param OutPoints (required) pointer to an array to populate with overlapping points * @return Whether any points were within the widget range */ virtual bool GetPointsWithinWidgetRange(const FSlateRect& WidgetRectangle, TArray* OutPoints) const { return false; } /** * Retrieve all the curve points, if any of the interpolating points overlap the specified rectangle in widget space * * @param WidgetRectangle The rectangle to hit test against. May not hit points that would exist outside of the view's visible bounds. * @param OutPoints (required) pointer to an array to populate with overlapping points * @return Whether any points were within the widget range */ virtual bool GetCurveWithinWidgetRange(const FSlateRect& WidgetRectangle, TArray* OutPoints) const { return false; } /** * Tries to retrieve all curve points that overlap the rectangle. * If no points are found, selects the overlapping curves instead. * * @param WidgetRectangle The rectangle to hit test against. May not hit points that would exist outside of the view's visible bounds. * @param OutPoints (required) pointer to an array to populate with overlapping points * * @return Whether anything was selected. */ bool GetPointsThenCurveWithinWidgetRange(const FSlateRect& WidgetRectangle, TArray* OutPoints) const { return GetPointsWithinWidgetRange(WidgetRectangle, OutPoints) || GetCurveWithinWidgetRange(WidgetRectangle, OutPoints); } /** * Retrieve the id of the hovered curve */ virtual TOptional GetHoveredCurve() const { return TOptional(); } /** * Bind UI commands for this view */ virtual void BindCommands() {} /** * Invoked when a curve has been added or removed from this list. * @note: Care should be taken here to not impede performance as this will get called each time a curve is added/removed */ virtual void OnCurveListChanged() {} /** * Should the tools respect the global snapping rules for Time (X axis) input when manipulating a curve in this view. Can be set to false to ignore the snap setting. */ virtual bool IsTimeSnapEnabled() const { return true; } /** * Should the tools respect the global snapping rules for Value (Y axis) input when manipulating a curve in this view. Can be set to false to ignore the snap setting. */ virtual bool IsValueSnapEnabled() const { return true; } virtual void GetGridLinesX(TSharedRef CurveEditor, TArray& MajorGridLines, TArray& MinorGridLines, TArray* MajorGridLabels = nullptr) const {} virtual void GetGridLinesY(TSharedRef CurveEditor, TArray& MajorGridLines, TArray& MinorGridLines, TArray* MajorGridLabels = nullptr) const {} /** Request a new render from the retainer widget */ void RefreshRetainer(); /** Update the custom axes if necessary */ void UpdateCustomAxes(); protected: /** Gets info about the curves being drawn. Converts actual curves into an abstract series of lines/points/handles/etc. */ UE_DEPRECATED(5.6, "Instead use the CurveDrawParamsCache, use FCurveDrawParamsCache::GetCurveDrawParamsSynchronous to get Curve Draw Parms as with this function.") void GetCurveDrawParams(TArray& OutDrawParams); /** Gets curve draw params get the cache by calling a single curve */ UE_DEPRECATED(5.6, "Instead use GetCurveDrawParamsCache, use FCurveDrawParamsCache::GetCurveDrawParamSynchronous to get Curve Draw Parm as with this function.") void GetCurveDrawParam(TSharedPtr& CurveEditor, const FCurveModelID& ModelID, FCurveModel* CurveModel, FCurveDrawParams& OutDrawParam) const; /** Update all the curve view transforms from curve models */ void UpdateCurveViewTransformsFromModels(); // ~SWidget interface virtual FVector2D ComputeDesiredSize(float LayoutScaleMultiplier) const override; virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; private: /** Only the curve editor panel can add curves to views to ensure internal consistency between the views and SCurveEditorPanel::CurveViews */ friend struct FCurveEditorPanelViewTracker; /** Add a curve to this view */ void AddCurve(FCurveModelID CurveID); /** Add a curve from this view */ void RemoveCurve(FCurveModelID CurveID); public: /** (Default: false) When true, this view has been pinned to the top of the view container. Only takes affect from the next call to SCurveEditorPanel::RebuildCurveViews. */ uint8 bPinned : 1; /** (Default: true) When true, this view should accept interactive operations such as drags and tool interaction */ uint8 bInteractive : 1; /** (Default: false) When true, this view has fixed vertical bounds that should never be changed by zooming or panning */ uint8 bFixedOutputBounds : 1; /** (Default: true) See class notes - defines whether this view should size to its desired size (true) or stretch to the height of the panel (false). Only takes affect from the next call to SCurveEditorPanel::RebuildCurveViews. */ uint8 bAutoSize : 1; /** (Default: false) Defines whether this view should remain on the panel UI even if it does not represent any curves. */ uint8 bAllowEmpty : 1; /** (Default: false) When true, custom axes need to be rebuilt before use. */ uint8 bAllowModelViewTransforms : 1; /** (Default: false) When true, custom axes need to be rebuilt before use. */ uint8 bUpdateModelViewTransforms : 1; /** (Default: true) When true, this view has models that need the default grid lines drawing. */ uint8 bNeedsDefaultGridLinesH : 1; /** (Default: true) When true, this view has models that need the default grid lines drawing. */ uint8 bNeedsDefaultGridLinesV : 1; /** (Default: 0) Should be assigned on Construction of derived types. Defines a custom sort bias to use when sorting the stack of views (before sorting by pinned/unpinned state) */ int8 SortBias = 0; /** This view's type identifier. Assigned by the curve editor panel after construction. */ ECurveEditorViewID ViewTypeID = ECurveEditorViewID::Invalid; /** Transient integer that is assigned each time the view container order is changed, to guarantee relative ordering of views when the list changes */ int32 RelativeOrder = TNumericLimits::Max(); /** The maximum number of curves allowed on this view, or 0 for no limit */ int32 MaximumCapacity = 0; /** (Optional) Attribute that defines a fixed height for this view. Overrides ChildSlot's desired size when set */ TAttribute FixedHeight; protected: /** The curve editor associated with this view. */ TWeakPtr WeakCurveEditor; /** This view's minimum visible output value */ double OutputMin = 0.0; /** This view's maximum visible output value */ double OutputMax = 1.0; struct FCurveInfo { /** The linear index of the curve within this view determined by the order curves were added. */ int32 CurveIndex; FTransform2d ViewToCurveTransform; FCurveEditorViewAxisID HorizontalAxis; FCurveEditorViewAxisID VerticalAxis; }; /** Map from curve identifier to specific info pertaining to that curve for this view. * @note: Should only be added to or removed from in AddCurve/RemoveCurve. Derived types must only change the FCurveInfo contained within this map. */ TSortedMap> CurveInfoByID; struct FAxisInfo { TSharedPtr Axis; double Min = 0.0; double Max = 1.0; int32 UseCount = 0; }; TArray CustomHorizontalAxes; TArray CustomVerticalAxes; FAxisInfo& GetHorizontalAxisInfo(FCurveEditorViewAxisID ID) { check(ID); return CustomHorizontalAxes[ID]; } FAxisInfo& GetVerticalAxisInfo(FCurveEditorViewAxisID ID) { check(ID); return CustomVerticalAxes[ID]; } /** Flag enum signifying how the curve cache has changed since it was last generated Note for a data change it may only effect certain data(curves) not every drawn curve*/ enum class UE_DEPRECATED(5.6, "Instead use the CurveDrawParamsCache, call FCurveDrawParamsCache::ECurveCacheFlags to get a corresponding type.") ECurveCacheFlags : uint8 { CheckCurves = 0, // The cache may be valid need to check each curve to see if they are still valid All = 1 << 0, // Get all }; /** Curve cache flags that change based upon data or view getting modified*/ UE_DEPRECATED(5.6, "Instead use the CurveDrawParamsCache, call FCurveDrawParamsCache::GetCurveCacheFlags to get current flags. Use FCurveDrawParamsCache::Update to update them.") PRAGMA_DISABLE_DEPRECATION_WARNINGS ECurveCacheFlags CurveCacheFlags; PRAGMA_ENABLE_DEPRECATION_WARNINGS /** Curve draw parameters that are re-generated on tick if the cache has changed. We generate them once and then they're used in multiple places per frame. */ UE_DEPRECATED(5.5, "Instead use the CurveDrawParamsCache, call GetCurveDrawParamsSynchronous.") TArray CachedDrawParams; /** Cache for curve draw params */ TSharedPtr CurveDrawParamsCache; /** Set of Cached values we need to check each tick to see if we need to redo cache*/ struct UE_DEPRECATED(5.6, "Instead use the CurveDrawParamsCache, call FCurveDrawParamsCache::FCachedCurveEditorData to get a corresponding type.") FCachedValuesToCheck { /** Serial number cached from FCurveEditor::GetActiveCurvesSerialNumber() on tick */ uint32 CachedActiveCurvesSerialNumber; /** Serial number bached from CurveEditorSelecstion::GetSerialNumber */ uint32 CachedSelectionSerialNumber; /** Cached Tangent Visibility*/ ECurveEditorTangentVisibility CachedTangentVisibility; /** Cached input and output min max values to see if we need to recalc curves, though we need to poll it's safer*/ double CachedInputMin, CachedInputMax, CachedOutputMin, CachedOutputMax; /** Cached Geometry Size*/ FVector2D CachedGeometrySize; }; UE_DEPRECATED(5.6, "Instead use the CurveDrawParamsCache, use FCurveDrawParamsCache::GetCurveEditorData to get cached values. Use FCurveDrawParamsCache::Update to update them.") PRAGMA_DISABLE_DEPRECATION_WARNINGS FCachedValuesToCheck CachedValues; PRAGMA_ENABLE_DEPRECATION_WARNINGS /** Possible pointer to a retainer widget that we may need to force update*/ TSharedPtr RetainerWidget; public: void SetRetainerWidget(TSharedPtr& InWidget) { RetainerWidget = InWidget; } };