Editor/KelvinFloatControlWidget.cs
using System;
using Sandbox;
using RotSoft.Lighting;
using Editor;

namespace RotSoft.Lighting;

/// <summary>
/// A <see cref="FloatControlWidget"/> for float properties marked with <see cref="KelvinSliderAttribute"/>:
/// paints the kelvin (blackbody) gradient as the slider track, with a thermostat icon. The property's
/// <see cref="RangeAttribute"/> supplies the track bounds.
/// </summary>
[CustomEditor( typeof( float ), WithAllAttributes = new[] { typeof( KelvinSliderAttribute ) } )]
public class KelvinFloatControlWidget : FloatControlWidget
{
	/// <summary>Cached kelvin gradient strip, rebuilt only when the bar's width (or DPI) changes.</summary>
	Pixmap _gradient;

	/// <summary>Pixel size the gradient was baked at; rebuilt when width or DPI changes.</summary>
	Vector2 _gradientSize;

	public KelvinFloatControlWidget( SerializedProperty property ) : base( property )
	{
		Icon = "thermostat";
		RangeStep = 1f; // snap to whole kelvin so values stay integers, without the [Step] spin arrows

		SliderPaint = ( rect, _ ) =>
		{
			// A thin, full-width track strip (like the regular float slider's track), centered vertically.
			const float TrackHeight = 6f;
			const float ThumbWidth = 6f;
			const float ThumbHeight = 12f;

			var centerY = rect.Top + rect.Height / 2f;
			var track = new Rect( rect.Left, centerY - TrackHeight / 2f, rect.Width, TrackHeight );

			EnsureGradient( track.Width );
			if ( _gradient is not null )
				Paint.Draw( track, _gradient, 1f, TrackHeight / 2f );

			// White thumb at the value position, like the regular float slider.
			var current = SerializedProperty.GetValue<float>();
			var t = MathX.LerpInverse( current, Range.x, Range.y, true ).Clamp( 0f, 1f );
			var thumbX = rect.Left + t * (rect.Width - ThumbWidth);
			Paint.Antialiasing = true;

			// Even soft shadow ring around the thumb, so no bare thumb edge meets the bright gradient.
			Paint.ClearPen();
			Paint.Antialiasing = true;
			Paint.SetBrush( Color.Black.WithAlpha( 0.35f ) );
			Paint.DrawRect( new Rect( thumbX - 1f, centerY - ThumbHeight / 2f - 1f, ThumbWidth + 2f, ThumbHeight + 2f ), 2f );

			// Thumb itself.
			Paint.ClearPen();
			Paint.Antialiasing = true;
			Paint.SetBrush( Color.White.Darken( 0.1f ) );
			Paint.DrawRect( new Rect( thumbX, centerY - ThumbHeight / 2f, ThumbWidth, ThumbHeight ), 2f );
		};
	}

	/// <summary>Bakes the kelvin gradient into a cached pixmap, rebuilt only when the bar's width (or DPI) changes.</summary>
	void EnsureGradient( float width )
	{
		var dpi = DpiScale;
		var size = new Vector2( Math.Max( (int)(width * dpi), 1 ), Math.Max( (int)(10f * dpi), 1 ) );

		if ( _gradient is not null && _gradientSize == size ) return;

		_gradient = new Pixmap( size );
		_gradientSize = size;

		using ( Paint.ToPixmap( _gradient ) )
		{
			Paint.Antialiasing = true;
			Paint.ClearPen();

			int steps = (int)size.x;
			float denom = Math.Max( steps - 1, 1 );
			for ( int i = 0; i < steps; i++ )
			{
				float frac = i / denom;
				Paint.SetBrush( new Temperature( MathX.LerpTo( Range.x, Range.y, frac, true ) ).ToColor() );
				Paint.DrawRect( new Rect( i, 0, 1f, size.y ) );
			}
		}
	}
}