Editor/Widgets/CurveCoordinateProperty.cs
using Editor;
using Sandbox;
using System;

namespace AltCurves.Widgets
{
	/// <summary>
	/// Basically a FloatProperty, but modified for the specific use case
	/// In particular instead of a single value we're incrementing/decrementing, 
	/// we pipe it back to the AltCurveEditor in the form of X/Y translations
	/// for multiple selected keyframes. 
	/// Mainly because it's not viable to have it rebind to each appropriate key on selections.
	/// </summary>
	internal class CurveCoordinateProperty : Widget
	{
		/// <summary>
		/// Number of selected keyframes, used to determine if we should display the value
		/// </summary>
		public int SelectedKeyframes
		{
			get => _selectedKeyframes;
			set { _selectedKeyframes = value; UpdateText(); }
		}
		private int _selectedKeyframes = 0;

		/// <summary>
		/// The value of the selected keyframe, if we have 1 selected.
		/// </summary>
		public float SelectedValue
		{
			get => _selectedValue;
			set { _selectedValue = value; UpdateText(); }
		}
		private float _selectedValue = 0.0f;

		public string Label { get; set; }
		public string Icon { get; set; } = "multiple_stop";

		public Color HighlightColor = Theme.Green;

		public LineEdit LineEdit { get; init; }

		Vector2 _lastDragPos;
		float _yDragAccum = 0.0f;

		public Action OnDraggingStart { get; set; }
		public Action<float> OnDragging { get; set; }
		public Action OnDraggingEnd { get; set; }

		/// <summary>
		/// Called when we've had a manual input requesting a specific float value
		/// </summary>
		public Action<float> OnManualInput { set; get; }

		public CurveCoordinateProperty( Widget parent ) : base( parent )
		{
			LineEdit = new LineEdit( this );
			LineEdit.TextEdited += LineEdit_TextEdited;
			LineEdit.MinimumSize = Theme.RowHeight;
			LineEdit.NoSystemBackground = true;
			LineEdit.TranslucentBackground = true;
			LineEdit.Alignment = TextFlag.LeftCenter;
			LineEdit.SetStyles( "background-color: transparent;" );
			LineEdit.RegexValidator = "^[-+]?[0-9]*(?:\\.[0-9]*)?$";
			Cursor = CursorShape.SizeH;

			MinimumSize = Theme.RowHeight;
			MaximumSize = new Vector2( 4096, Theme.RowHeight );
		}

		public CurveCoordinateProperty( string label, Widget parent ) : this( parent )
		{
			Label = label;
		}

		private void LineEdit_TextEdited( string obj )
		{
			if ( float.TryParse( obj, out float value ) )
			{
				OnManualInput?.Invoke( value );
			}
		}

		private void UpdateText()
		{
			if ( SelectedKeyframes == 0 )
				LineEdit.Text = "";
			else if ( SelectedKeyframes == 1 )
				LineEdit.Text = SelectedValue.ToString();
			else
				LineEdit.Text = $"{SelectedKeyframes} selected";
		}

		protected override void DoLayout()
		{
			base.DoLayout();
			var h = Size.y;
			LineEdit.Position = new Vector2( h, 0 );
			LineEdit.Size = Size - new Vector2( h, 0 );
		}

		protected override void OnPaint()
		{
			base.OnPaint();

			var h = Size.y;
			bool hovered = IsUnderMouse;
			if ( !Enabled ) hovered = false;

			Paint.Antialiasing = true;
			Paint.TextAntialiasing = true;

			Paint.ClearPen();
			Paint.SetBrush( Theme.ControlBackground );
			Paint.DrawRect( LocalRect, Theme.ControlRadius );

			// icon box
			Paint.ClearPen();
			Paint.SetBrush( HighlightColor.Darken( hovered ? 0.7f : 0.8f ).Desaturate( 0.8f ) );
			Paint.DrawRect( new Rect( 0, 0, h, h ).Grow( -1 ), Theme.ControlRadius - 1.0f );

			// flatten right (we need a DrawRect with uneven corners)
			Paint.DrawRect( new Rect( h - Theme.ControlRadius, 0, Theme.ControlRadius, h ).Grow( -1 ) );

			Paint.SetPen( HighlightColor.Darken( hovered ? 0.0f : 0.1f ).Desaturate( hovered ? 0.0f : 0.2f ) );

			if ( string.IsNullOrEmpty( Label ) )
			{
				Paint.DrawIcon( new Rect( 0, h ), Icon, h - 6, TextFlag.Center );
			}
			else
			{
				Paint.SetFont( "Poppins", 9, 450 );
				Paint.DrawText( new Rect( 1, h - 1 ), Label, TextFlag.Center );
			}
		}

		protected override void OnMousePress( MouseEvent e )
		{
			base.OnMousePress( e );

			if ( e.RightMouseButton )
			{
				LineEdit.Focus();
				LineEdit.SelectAll();
			}

			if ( e.LeftMouseButton && !ReadOnly && e.LocalPosition.x < Height )
			{
				LineEdit.Blur();
				_lastDragPos = e.LocalPosition;
				e.Accepted = true;
				OnDraggingStart?.Invoke();
			}
		}

		protected override void OnMouseReleased( MouseEvent e )
		{
			base.OnMouseReleased( e );

			if ( e.LeftMouseButton && !ReadOnly )
			{
				OnDraggingEnd?.Invoke();
				LineEdit.Focus();
				e.Accepted = true;
			}
		}

		protected override void OnMouseMove( MouseEvent e )
		{
			base.OnMouseMove( e );

			if ( e.ButtonState.HasFlag( MouseButtons.Left ) )
			{
				var delta = e.LocalPosition - _lastDragPos;
				_lastDragPos = e.LocalPosition;
				_yDragAccum += delta.y;

				int bracket = (Math.Abs( _yDragAccum ) / 50).FloorToInt() + 1;
				var incr = (float)(delta.x / 20 * bracket * 3);

				SelectedValue += incr;
				OnDragging?.Invoke( incr );

				SignalValuesChanged();

				e.Accepted = true;
			}
		}
	}
}