Files
UnrealEngine/Engine/Source/Runtime/Slate/Public/Widgets/Input/SSpinBox.h
2025-05-18 13:04:45 +08:00

533 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Fonts/SlateFontInfo.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Input/CursorReply.h"
#include "Input/Events.h"
#include "Input/Reply.h"
#include "InputCoreTypes.h"
#include "Layout/Margin.h"
#include "Layout/Visibility.h"
#include "Misc/Attribute.h"
#include "Rendering/DrawElements.h"
#include "Styling/CoreStyle.h"
#include "Styling/SlateColor.h"
#include "Styling/SlateTypes.h"
#include "Templates/IsIntegral.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/NumericTypeInterface.h"
#include "Widgets/Input/SEditableText.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SCompoundWidget.h"
#include "Widgets/Text/STextBlock.h"
#include <limits>
namespace SpinBoxPrivate
{
SLATE_API extern bool bUseSpinBoxMouseMoveOptimization;
}
/*
* This function compute a slider position by simulating two log on both side of the neutral value
* Example a slider going from 0.0 to 2.0 with a neutral value of 1.0, the user will have a lot of precision around the neutral value
* on both side.
|
|| |
| -_ _-
| --__ __--
| ----__________----
----------------------------------
0 1 2
The function return a float representing the slider fraction used to position the slider handle
FractionFilled: this is the value slider position with no exponent
StartFractionFilled: this is the neutral value slider position with no exponent
SliderExponent: this is the slider exponent
*/
SLATE_API float SpinBoxComputeExponentSliderFraction(float FractionFilled, float StartFractionFilled, float SliderExponent);
/**
* A Slate SpinBox resembles traditional spin boxes in that it is a widget that provides
* keyboard-based and mouse-based manipulation of a numeric value.
* Mouse-based manipulation: drag anywhere on the spinbox to change the value.
* Keyboard-based manipulation: click on the spinbox to enter text mode.
*/
template<typename NumericType>
class SSpinBox
: public SCompoundWidget
{
public:
/** Notification for numeric value change */
DECLARE_DELEGATE_OneParam(FOnValueChanged, NumericType);
/** Notification for numeric value committed */
DECLARE_DELEGATE_TwoParams(FOnValueCommitted, NumericType, ETextCommit::Type);
/** Notification when the max/min spinner values are changed (only apply if SupportDynamicSliderMaxValue or SupportDynamicSliderMinValue are true) */
DECLARE_DELEGATE_FourParams(FOnDynamicSliderMinMaxValueChanged, NumericType, TWeakPtr<SWidget>, bool, bool);
/** Optional customization of the display value based on the current value. */
DECLARE_DELEGATE_RetVal_OneParam(TOptional<FText>, FOnGetDisplayValue, NumericType);
SLATE_BEGIN_ARGS(SSpinBox<NumericType>)
: _Style(&FCoreStyle::Get().GetWidgetStyle<FSpinBoxStyle>("SpinBox"))
, _Value(0)
, _MinFractionalDigits(DefaultMinFractionalDigits)
, _MaxFractionalDigits(DefaultMaxFractionalDigits)
, _AlwaysUsesDeltaSnap(false)
, _EnableSlider(true)
, _Delta(0)
, _ShiftMultiplier(10.f)
, _CtrlMultiplier(0.1f)
, _SupportDynamicSliderMaxValue(false)
, _SupportDynamicSliderMinValue(false)
, _SliderExponent(1.f)
, _EnableWheel(true)
, _BroadcastValueChangesPerKey(false)
, _Font(FCoreStyle::Get().GetFontStyle(TEXT("NormalFont")))
, _ContentPadding(FMargin(2.0f, 1.0f))
, _OnValueChanged()
, _OnValueCommitted()
, _ClearKeyboardFocusOnCommit(false)
, _SelectAllTextOnCommit(true)
, _MinDesiredWidth(0.0f)
, _Justification(ETextJustify::Left)
, _KeyboardType(Keyboard_Default)
, _PreventThrottling(true)
, _RevertTextOnEscape(true)
{}
/** The style used to draw this spinbox */
SLATE_STYLE_ARGUMENT(FSpinBoxStyle, Style)
/** The value to display */
SLATE_ATTRIBUTE(NumericType, Value)
/** The minimum value that can be entered into the text edit box */
SLATE_ATTRIBUTE(TOptional< NumericType >, MinValue)
/** The maximum value that can be entered into the text edit box */
SLATE_ATTRIBUTE(TOptional< NumericType >, MaxValue)
/** The minimum value that can be specified by using the slider, defaults to MinValue */
SLATE_ATTRIBUTE(TOptional< NumericType >, MinSliderValue)
/** The maximum value that can be specified by using the slider, defaults to MaxValue */
SLATE_ATTRIBUTE(TOptional< NumericType >, MaxSliderValue)
/** The minimum fractional digits the spin box displays, defaults to 1 */
SLATE_ATTRIBUTE(TOptional< int32 >, MinFractionalDigits)
/** The maximum fractional digits the spin box displays, defaults to 6 */
SLATE_ATTRIBUTE(TOptional< int32 >, MaxFractionalDigits)
/** Whether typed values should use delta snapping, defaults to false */
SLATE_ATTRIBUTE(bool, AlwaysUsesDeltaSnap)
/** Whether this spin box should have slider feature enabled, defaults to true */
SLATE_ATTRIBUTE(bool, EnableSlider)
/** Delta to increment the value as the slider moves. If not specified will determine automatically */
SLATE_ATTRIBUTE(NumericType, Delta)
/** How many pixel the mouse must move to change the value of the delta step */
SLATE_ATTRIBUTE_DEPRECATED(int32, ShiftMouseMovePixelPerDelta, 5.4, "Shift Mouse Move Pixel Per Delta is deprecated and incrementing by a fixed delta per pixel is no longer supported. Please use ShiftMultiplier and CtrlMultiplier which will multiply the step per mouse move")
/** Multiplier to use when shift is held down */
SLATE_ATTRIBUTE(float, ShiftMultiplier)
/** Multiplier to use when ctrl is held down */
SLATE_ATTRIBUTE(float, CtrlMultiplier)
/** If we're an unbounded spinbox, what value do we divide mouse movement by before multiplying by Delta. Requires Delta to be set. */
SLATE_ATTRIBUTE(int32, LinearDeltaSensitivity)
/** Tell us if we want to support dynamically changing of the max value using alt */
SLATE_ATTRIBUTE(bool, SupportDynamicSliderMaxValue)
/** Tell us if we want to support dynamically changing of the min value using alt */
SLATE_ATTRIBUTE(bool, SupportDynamicSliderMinValue)
/** Called right after the max slider value is changed (only relevant if SupportDynamicSliderMaxValue is true) */
SLATE_EVENT(FOnDynamicSliderMinMaxValueChanged, OnDynamicSliderMaxValueChanged)
/** Called right after the min slider value is changed (only relevant if SupportDynamicSliderMinValue is true) */
SLATE_EVENT(FOnDynamicSliderMinMaxValueChanged, OnDynamicSliderMinValueChanged)
/** Use exponential scale for the slider */
SLATE_ATTRIBUTE(float, SliderExponent)
/** When use exponential scale for the slider which is the neutral value */
SLATE_ATTRIBUTE(NumericType, SliderExponentNeutralValue)
/** Whether this spin box should have mouse wheel feature enabled, defaults to true */
SLATE_ARGUMENT(bool, EnableWheel)
/** True to broadcast every time we type */
SLATE_ARGUMENT(bool, BroadcastValueChangesPerKey)
/** Step to increment or decrement the value by when scrolling the mouse wheel. If not specified will determine automatically */
SLATE_ATTRIBUTE(TOptional< NumericType >, WheelStep)
/** Font used to display text in the slider */
SLATE_ATTRIBUTE(FSlateFontInfo, Font)
/** Padding to add around this widget and its internal widgets */
SLATE_ATTRIBUTE(FMargin, ContentPadding)
/** Called when the value is changed by slider or typing */
SLATE_EVENT(FOnValueChanged, OnValueChanged)
/** Called when the value is committed (by pressing enter) */
SLATE_EVENT(FOnValueCommitted, OnValueCommitted)
/** Called right before the slider begins to move */
SLATE_EVENT(FSimpleDelegate, OnBeginSliderMovement)
/** Called right after the slider handle is released by the user */
SLATE_EVENT(FOnValueChanged, OnEndSliderMovement)
/** Called to allow customization of what text is displayed when not typing. An empty value falls back to the default behavior. */
SLATE_EVENT(FOnGetDisplayValue, OnGetDisplayValue)
/** Whether to clear keyboard focus when pressing enter to commit changes */
SLATE_ATTRIBUTE(bool, ClearKeyboardFocusOnCommit)
/** Whether to select all text when pressing enter to commit changes */
SLATE_ATTRIBUTE(bool, SelectAllTextOnCommit)
/** Minimum width that a spin box should be */
SLATE_ATTRIBUTE(float, MinDesiredWidth)
/** How should the value be justified in the spinbox. */
SLATE_ATTRIBUTE(ETextJustify::Type, Justification)
/** What keyboard to display. */
SLATE_ATTRIBUTE(EKeyboardType, KeyboardType)
/** Provide custom type conversion functionality to this spin box */
SLATE_ATTRIBUTE(TSharedPtr< INumericTypeInterface<NumericType> >, TypeInterface)
/** If refresh requests for the viewport should happen for all value changes **/
SLATE_ARGUMENT(bool, PreventThrottling)
/** If the text should be reverted when pressing the escape key **/
SLATE_ARGUMENT(bool, RevertTextOnEscape)
/** Menu extender for the right-click context menu */
SLATE_EVENT(FMenuExtensionDelegate, ContextMenuExtender)
SLATE_END_ARGS()
SLATE_API SSpinBox();
SLATE_API virtual ~SSpinBox();
/**
* Construct the widget
*
* @param InArgs A declaration from which to construct the widget
*/
SLATE_API void Construct(const FArguments& InArgs);
SLATE_API virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override;
SLATE_API void Tick(const FGeometry& AlottedGeometry, const double InCurrentTime, const float InDeltaTime);
SLATE_API const bool CommitWithMultiplier(const FPointerEvent& MouseEvent);
/**
* The system calls this method to notify the widget that a mouse button was pressed within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
* @return Whether the event was handled along with possible requests for the system to take action.
*/
SLATE_API virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
/**
* The system calls this method to notify the widget that a mouse button was release within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
* @return Whether the event was handled along with possible requests for the system to take action.
*/
SLATE_API virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
SLATE_API void ApplySliderMaxValueChanged(float SliderDeltaToAdd, bool UpdateOnlyIfHigher);
SLATE_API void ApplySliderMinValueChanged(float SliderDeltaToAdd, bool UpdateOnlyIfLower);
/**
* The system calls this method to notify the widget that a mouse moved within it. This event is bubbled.
*
* @param MyGeometry The Geometry of the widget receiving the event
* @param MouseEvent Information about the input event
* @return Whether the event was handled along with possible requests for the system to take action.
*/
SLATE_API virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
/**
* Called when the mouse wheel is spun. This event is bubbled.
*
* @param MouseEvent Mouse event
* @return Returns whether the event was handled, along with other possible actions
*/
SLATE_API virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override;
SLATE_API virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) const override;
SLATE_API virtual bool SupportsKeyboardFocus() const override;
SLATE_API virtual FReply OnFocusReceived(const FGeometry& MyGeometry, const FFocusEvent& InFocusEvent) override;
SLATE_API virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
SLATE_API virtual bool HasKeyboardFocus() const override;
/** Return the Value attribute */
SLATE_API TAttribute<NumericType> GetValueAttribute() const;
/** See the Value attribute */
SLATE_API NumericType GetValue() const;
SLATE_API void SetValue(const TAttribute<NumericType>& InValueAttribute);
/** See the MinValue attribute */
SLATE_API NumericType GetMinValue() const;
SLATE_API void SetMinValue(const TAttribute<TOptional<NumericType>>& InMinValue);
/** See the MaxValue attribute */
SLATE_API NumericType GetMaxValue() const;
SLATE_API void SetMaxValue(const TAttribute<TOptional<NumericType>>& InMaxValue);
/** See the MinSliderValue attribute */
SLATE_API bool IsMinSliderValueBound() const;
SLATE_API NumericType GetMinSliderValue() const;
SLATE_API void SetMinSliderValue(const TAttribute<TOptional<NumericType>>& InMinSliderValue);
/** See the MaxSliderValue attribute */
SLATE_API bool IsMaxSliderValueBound() const;
SLATE_API NumericType GetMaxSliderValue() const;
SLATE_API void SetMaxSliderValue(const TAttribute<TOptional<NumericType>>& InMaxSliderValue);
/** See the MinFractionalDigits attribute */
SLATE_API int32 GetMinFractionalDigits() const;
SLATE_API void SetMinFractionalDigits(const TAttribute<TOptional<int32>>& InMinFractionalDigits);
/** See the MaxFractionalDigits attribute */
SLATE_API int32 GetMaxFractionalDigits() const;
SLATE_API void SetMaxFractionalDigits(const TAttribute<TOptional<int32>>& InMaxFractionalDigits);
/** See the AlwaysUsesDeltaSnap attribute */
SLATE_API bool GetAlwaysUsesDeltaSnap() const;
SLATE_API void SetAlwaysUsesDeltaSnap(bool bNewValue);
/** See the EnableSlider attribute */
SLATE_API bool GetEnableSlider() const;
SLATE_API void SetEnableSlider(bool bNewValue);
/** See the Delta attribute */
SLATE_API NumericType GetDelta() const;
SLATE_API void SetDelta(NumericType InDelta);
/** See the SliderExponent attribute */
SLATE_API float GetSliderExponent() const;
SLATE_API void SetSliderExponent(const TAttribute<float>& InSliderExponent);
/** See the MinDesiredWidth attribute */
SLATE_API float GetMinDesiredWidth() const;
SLATE_API void SetMinDesiredWidth(const TAttribute<float>& InMinDesiredWidth);
SLATE_API const FSpinBoxStyle* GetWidgetStyle() const;
SLATE_API void SetWidgetStyle(const FSpinBoxStyle* InStyle);
SLATE_API void InvalidateStyle();
SLATE_API void SetTextBlockFont(FSlateFontInfo InFont);
SLATE_API void SetTextJustification(ETextJustify::Type InJustification);
SLATE_API void SetTextClearKeyboardFocusOnCommit(bool bNewValue);
SLATE_API void SetTextRevertTextOnEscape(bool bNewValue);
SLATE_API void SetTextSelectAllTextOnCommit(bool bNewValue);
/** Reset the cached string. Typically used when the value is the same but the display format changed (through the callback). */
SLATE_API void ResetCachedValueString();
protected:
/** Make the spinbox switch to keyboard-based input mode. */
SLATE_API void EnterTextMode();
/** Make the spinbox switch to mouse-based input mode. */
SLATE_API void ExitTextMode();
/** @return the value being observed by the spinbox as a string */
SLATE_API FString GetValueAsString() const;
/** @return the value being observed by the spinbox as FText - todo: spinbox FText support (reimplement me) */
SLATE_API FText GetValueAsText() const;
/** @return the value to be displayed when not manually editing text */
SLATE_API FText GetDisplayValue() const;
/**
* Invoked when the text in the text field changes
*
* @param NewText The value of the text in the text field
*/
SLATE_API void TextField_OnTextChanged(const FText& NewText);
/**
* Invoked when the text field commits its text.
*
* @param NewText The value of text coming from the editable text field.
* @param CommitInfo Information about the source of the commit
*/
SLATE_API void TextField_OnTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo);
/** How user changed the value in the spinbox */
enum ECommitMethod
{
CommittedViaSpin,
CommittedViaTypeIn,
CommittedViaArrowKey,
CommittedViaCode,
CommittedViaSpinMultiplier
};
/**
* Call this method when the user's interaction has changed the value
*
* @param NewValue Value resulting from the user's interaction
* @param CommitMethod Did the user type in the value or spin to it.
* @param OriginalCommitInfo If the user typed in the value, information about the source of the commit
*/
SLATE_API void CommitValue(NumericType NewValue, double NewSpinValue, ECommitMethod CommitMethod, ETextCommit::Type OriginalCommitInfo);
SLATE_API void NotifyValueCommitted(NumericType CurrentValue) const;
/** @return true when we are in keyboard-based input mode; false otherwise */
SLATE_API bool IsInTextMode() const;
/** Calculates range fraction. Possible to use on full numeric range */
SLATE_API static float Fraction(double InValue, double InMinValue, double InMaxValue);
private:
// New value to be Committed on Tick for MouseMove events
// This exists to insulate high-frequency mouse move events which can fire many times during input processing
// from the side-effects of committing the spinbox value
struct FPendingCommitValue
{
double NewValue;
ECommitMethod CommitMethod;
};
/** The default minimum fractional digits */
static constexpr int32 DefaultMinFractionalDigits = 1;
/** The default maximum fractional digits */
static constexpr int32 DefaultMaxFractionalDigits = 6;
TAttribute<NumericType> ValueAttribute;
FOnValueChanged OnValueChanged;
FOnValueCommitted OnValueCommitted;
FSimpleDelegate OnBeginSliderMovement;
FOnValueChanged OnEndSliderMovement;
TSharedPtr<STextBlock> TextBlock;
TSharedPtr<SEditableText> EditableText;
/** Interface that defines conversion functionality for the templated type */
TAttribute<TSharedPtr< INumericTypeInterface<NumericType> >> InterfaceAttr;
/** True when no range is specified, spinner can be spun indefinitely */
bool bUnlimitedSpinRange;
void UpdateIsSpinRangeUnlimited();
const FSpinBoxStyle* Style;
const FSlateBrush* BackgroundHoveredBrush;
const FSlateBrush* BackgroundActiveBrush;
const FSlateBrush* BackgroundBrush;
const FSlateBrush* ActiveFillBrush;
const FSlateBrush* HoveredFillBrush;
const FSlateBrush* InactiveFillBrush;
float DistanceDragged;
TAttribute<NumericType> Delta;
TAttribute<float> ShiftMultiplier;
TAttribute<float> CtrlMultiplier;
TAttribute<int32> LinearDeltaSensitivity;
TAttribute<float> SliderExponent;
TAttribute<NumericType> SliderExponentNeutralValue;
TAttribute< TOptional<NumericType> > MinValue;
TAttribute< TOptional<NumericType> > MaxValue;
TAttribute< TOptional<NumericType> > MinSliderValue;
TAttribute< TOptional<NumericType> > MaxSliderValue;
TAttribute< TOptional<int32> > MinFractionalDigits;
TAttribute< TOptional<int32> > MaxFractionalDigits;
TAttribute<bool> AlwaysUsesDeltaSnap;
TAttribute<bool> EnableSlider;
TAttribute<bool> SupportDynamicSliderMaxValue;
TAttribute<bool> SupportDynamicSliderMinValue;
TAttribute< TOptional<NumericType> > WheelStep;
FOnDynamicSliderMinMaxValueChanged OnDynamicSliderMaxValueChanged;
FOnDynamicSliderMinMaxValueChanged OnDynamicSliderMinValueChanged;
FOnGetDisplayValue OnGetDisplayValue;
/** Prevents the spinbox from being smaller than desired in certain cases (e.g. when it is empty) */
TAttribute<float> MinDesiredWidth;
float GetTextMinDesiredWidth() const;
/** Check whether a typed character is valid */
bool IsCharacterValid(TCHAR InChar) const;
/**
* Rounds the submitted value to the correct value if it's an integer.
* For int64, not all values can be represented by a double. We can only round until we reach that limit.
* This function should only be used when we drag the value. We accept that we can't drag huge numbers.
*/
NumericType RoundIfIntegerValue(double ValueToRound) const;
void CancelMouseCapture();
/** Tracks which cursor is currently dragging the slider (e.g., the mouse cursor or a specific finger) */
int32 PointerDraggingSliderIndex;
/** Cached mouse position to restore after scrolling. */
FIntPoint CachedMousePosition;
/**
* This value represents what the spinbox believes the value to be, regardless of delta and the user binding to an int.
* The spinbox will always count using floats between values, this is important to keep it flowing smoothly and feeling right,
* and most importantly not conflicting with the user truncating the value to an int.
*/
double InternalValue;
/** The state of InternalValue before a drag operation was started */
NumericType PreDragValue;
/**
* This is the cached value the user believes it to be (usually different due to truncation to an int). Used for identifying
* external forces on the spinbox and syncing the internal value to them. Synced when a value is committed to the spinbox.
*/
NumericType CachedExternalValue;
/** Used to prevent per-frame re-conversion of the cached numeric value to a string. */
FString CachedValueString;
/** Whetever the interfaced setting changed and the CachedValueString needs to be recomputed. */
mutable bool bCachedValueStringDirty;
/** Whether the user is dragging the slider */
bool bDragging;
/** Re-entrant guard for the text changed handler */
bool bIsTextChanging;
/*
* Holds whether or not to prevent throttling during mouse capture
* When true, the viewport will be updated with every single change to the value during dragging
*/
bool bPreventThrottling;
/** Does this spin box have the mouse wheel feature enabled? */
bool bEnableWheel = true;
/** True to broadcast every time we type. */
bool bBroadcastValueChangesPerKey = false;
TOptional<FPendingCommitValue> PendingCommitValue;
/*
* Gets the default amount to change the slider when delta is not applicable.
* Control takes priority over shift
*/
double GetDefaultStepSize(const FInputEvent& InputEvent);
/** Gets the default amount to change the slider when delta is not applicable. **/
const double StepSize = 1.f;
/** Step size to use when range is below SmallStepSizeMax. **/
const double SmallStepSize = .1f;
/** Largest numerical value to use the SmallStepSize instead of StepSize. **/
const double SmallStepSizeMax = 10.f;
};