An editor UI control that extends LineEdit to allow numeric text input with drag-to-adjust behavior snapping to a grid. It tracks mouse press/move/release, computes a value from horizontal drag distance using per-pixel scaling and snap size, updates the displayed text with a format string, and calls a callback with the new value.
using System;
using Editor;
using Sandbox;
namespace Sunless.Architecture;
// Values step on the field's SNAP, not freely; typing still works.
public sealed class ArchNumberEdit : LineEdit
{
const float Threshold = 3f;
readonly float snap;
readonly float perPixel;
readonly string format;
readonly Action<float> pulled;
Vector2 pressed;
float anchor;
bool dragging;
public ArchNumberEdit( string text, float snap, float perPixel, string format, Action<float> pulled ) : base( text )
{
this.snap = MathF.Max( 0.001f, snap );
this.perPixel = MathF.Max( this.snap, perPixel );
this.format = format;
this.pulled = pulled;
ToolTip = "Drag sideways to pull the value along the grid.";
}
protected override void OnMousePress( MouseEvent e )
{
base.OnMousePress( e );
pressed = e.LocalPosition;
anchor = float.TryParse( Text, out var parsed ) ? parsed : 0f;
dragging = false;
}
protected override void OnMouseMove( MouseEvent e )
{
if ( (e.ButtonState & MouseButtons.Left) == 0 )
{
base.OnMouseMove( e );
return;
}
var travelled = e.LocalPosition.x - pressed.x;
// Below the threshold it's still a click, so caret and selection stay with the base.
if ( !dragging && MathF.Abs( travelled ) < Threshold )
{
base.OnMouseMove( e );
return;
}
dragging = true;
Cursor = CursorShape.SizeAll;
var value = ArchGridService.Snap( anchor + travelled * perPixel, snap );
Text = value.ToString( format );
pulled?.Invoke( value );
}
protected override void OnMouseReleased( MouseEvent e )
{
base.OnMouseReleased( e );
dragging = false;
Cursor = CursorShape.Arrow;
}
}