Editor UI helper for modal numeric input. NumericInputSession stores buffered numeric characters, parses them to a float using invariant culture, exposes activation lifecycle and editing operations (append digit, decimal, toggle sign, backspace, confirm). NumericInputShortcuts wires keyboard shortcuts to the active NumericInputSession via ModalOperationArbiter.
#nullable enable
using Editor;
using System.Globalization;
namespace BlenderActions;
/// <summary>Stores and parses numeric input for one modal transform operation.</summary>
public sealed class NumericInputSession
{
/// <summary>Stores buffered numeric input characters.</summary>
private string _text = string.Empty;
/// <summary>Tracks whether numeric or modal confirmation has been requested.</summary>
private bool _confirmRequested;
/// <summary>Gets whether this numeric input session is accepting input.</summary>
public bool IsActive { get; private set; }
/// <summary>Attempts to parse the buffered text as an invariant floating-point value.</summary>
public bool HasValue => TryGetValue(out _);
/// <summary>Handles is null or empty.</summary>
public string DisplayText => string.IsNullOrEmpty(_text) ? "0" : _text;
/// <summary>Binds the operation to the active scene context and starts its modal lifecycle.</summary>
public void Begin()
{
_text = string.Empty;
_confirmRequested = false;
IsActive = true;
}
/// <summary>Ends numeric input and clears its buffered state.</summary>
public void End()
{
_text = string.Empty;
_confirmRequested = false;
IsActive = false;
}
/// <summary>Attempts to parse the buffered text as an invariant floating-point value.</summary>
public bool TryGetValue(out float value)
{
if(!IsActive)
{
value = 0f;
return false;
}
return float.TryParse(
_text,
NumberStyles.Float,
CultureInfo.InvariantCulture,
out value);
}
/// <summary>Consumes and clears a pending numeric confirmation request.</summary>
public bool ConsumeConfirmRequest()
{
if(!_confirmRequested)
return false;
_confirmRequested = false;
return true;
}
/// <summary>Appends one digit while numeric input is active.</summary>
public void AppendDigit(char digit)
{
if(IsActive)
_text += digit;
}
/// <summary>Appends a decimal separator when one is not already present.</summary>
public void EnterDecimal()
{
if(!IsActive || _text.Contains('.'))
return;
_text = string.IsNullOrEmpty(_text)
? "0."
: _text == "-"
? "-0."
: _text + '.';
}
/// <summary>Toggles the sign of the buffered numeric value.</summary>
public void ToggleNegative()
{
if(!IsActive)
return;
_text = _text.StartsWith("-")
? _text[1..]
: "-" + _text;
}
/// <summary>Removes the last buffered numeric character.</summary>
public void Backspace()
{
if(IsActive && _text.Length > 0)
_text = _text[..^1];
}
/// <summary>Requests confirmation when the buffered numeric value is valid.</summary>
public void Confirm()
{
if(HasValue)
_confirmRequested = true;
}
}
/// <summary>Routes numeric keyboard shortcuts to the active modal operation.</summary>
public static class NumericInputShortcuts
{
/// <summary>Appends the digit zero to active numeric input.</summary>
[Shortcut("blender_actions.numeric_0", "0", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Zero() => Append('0');
/// <summary>Appends the digit one to active numeric input.</summary>
[Shortcut("blender_actions.numeric_1", "1", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void One() => Append('1');
/// <summary>Appends the digit two to active numeric input.</summary>
[Shortcut("blender_actions.numeric_2", "2", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Two() => Append('2');
/// <summary>Appends the digit three to active numeric input.</summary>
[Shortcut("blender_actions.numeric_3", "3", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Three() => Append('3');
/// <summary>Appends the digit four to active numeric input.</summary>
[Shortcut("blender_actions.numeric_4", "4", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Four() => Append('4');
/// <summary>Appends the digit five to active numeric input.</summary>
[Shortcut("blender_actions.numeric_5", "5", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Five() => Append('5');
/// <summary>Appends the digit six to active numeric input.</summary>
[Shortcut("blender_actions.numeric_6", "6", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Six() => Append('6');
/// <summary>Appends the digit seven to active numeric input.</summary>
[Shortcut("blender_actions.numeric_7", "7", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Seven() => Append('7');
/// <summary>Appends the digit eight to active numeric input.</summary>
[Shortcut("blender_actions.numeric_8", "8", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Eight() => Append('8');
/// <summary>Appends the digit nine to active numeric input.</summary>
[Shortcut("blender_actions.numeric_9", "9", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Nine() => Append('9');
/// <summary>Routes decimal input to the active numeric session.</summary>
[Shortcut("blender_actions.numeric_decimal", ".", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Decimal() => ModalOperationArbiter.NumericInput?.EnterDecimal();
/// <summary>Routes sign toggling to the active numeric session.</summary>
[Shortcut("blender_actions.numeric_negative", "-", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Negative() => ModalOperationArbiter.NumericInput?.ToggleNegative();
/// <summary>Removes the last buffered numeric character.</summary>
[Shortcut("blender_actions.numeric_backspace", "BACKSPACE", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Backspace() => ModalOperationArbiter.NumericInput?.Backspace();
/// <summary>Requests confirmation when the buffered numeric value is valid.</summary>
[Shortcut("blender_actions.numeric_confirm", "ENTER", typeof(SceneViewportWidget), ShortcutType.Widget)]
private static void Confirm() => ModalOperationArbiter.NumericInput?.Confirm();
/// <summary>Routes a digit to the active numeric session.</summary>
private static void Append(char digit)
{
ModalOperationArbiter.NumericInput?.AppendDigit(digit);
}
}