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

namespace SboxUiDesigner.EditorUi.Widgets;

/// <summary>
/// Color row replacement for the Details panel — instead of a small swatch
/// next to a hex LineEdit, this widget IS the color, painted full-width with
/// the hex value overlaid in a contrasting color.
///
/// Click anywhere → opens <see cref="SuiColorPickerPopup"/> (live commits
/// during drag, final commit on close). Right-click → context menu (copy
/// hex / paste hex / clear).
/// </summary>
public sealed class SuiColorSwatchField : Widget
{
	private string _hex = "";
	private Action<string> _onCommit;
	private Rect _clearButtonRect;

	public SuiColorSwatchField( Widget parent ) : base( parent )
	{
		// Match the height of the other field types (text / dropdown / etc)
		// so rows line up. No max width — fill the row.
		FixedHeight = 26;
		Cursor = CursorShape.Finger;
		ToolTip = "Click to pick a color · right-click for clear / copy / paste";
		MouseTracking = true;
		SetStyles( "background-color: transparent; border: none;" );
	}

	public void SetValue( string hex )
	{
		_hex = hex ?? "";
		Update();
	}

	public void SetCommitHandler( Action<string> onCommit )
	{
		_onCommit = onCommit;
	}

	protected override void OnPaint()
	{
		var rect = LocalRect;
		var hasColor = !string.IsNullOrEmpty( _hex ) && Color.TryParse( _hex, out _ );

		// Hit area for clear button — unused now (right-click menu replaces it).
		_clearButtonRect = default;

		// 1. OUTER FRAME — same dark bg + 1px border as other input fields.
		Paint.SetBrush( new Color( 20 / 255f, 20 / 255f, 19 / 255f ) );
		Paint.SetPen( Color.White.WithAlpha( 0.10f ) );
		Paint.DrawRect( rect, 3 );

		// 2. INNER COLOR PATCH — sits inside the frame with 3px padding so
		// the dark border reads as a visible "box".
		var inner = new Rect(
			rect.Left + 3,
			rect.Top + 3,
			rect.Width - 6,
			rect.Height - 6 );

		if ( hasColor )
		{
			PaintCheckerboard( inner );
			Color.TryParse( _hex, out var color );
			Paint.SetBrush( color );
			Paint.ClearPen();
			Paint.DrawRect( inner, 2 );
		}
		else
		{
			// Empty: leave the inner area showing a neutral grey.
			Paint.SetBrush( new Color( 70 / 255f, 72 / 255f, 78 / 255f ) );
			Paint.ClearPen();
			Paint.DrawRect( inner, 2 );
		}
	}

	private static void PaintCheckerboard( Rect rect )
	{
		Paint.ClearPen();
		Paint.SetBrush( new Color( 0.6f, 0.6f, 0.6f ) );
		Paint.DrawRect( rect, 3 );
		Paint.SetBrush( new Color( 0.4f, 0.4f, 0.4f ) );
		const float cell = 6;
		var cols = (int)MathF.Ceiling( rect.Width / cell );
		var rows = (int)MathF.Ceiling( rect.Height / cell );
		for ( int y = 0; y < rows; y++ )
			for ( int x = 0; x < cols; x++ )
				if ( ((x + y) & 1) != 0 )
					Paint.DrawRect( new Rect( rect.Left + x * cell, rect.Top + y * cell, cell, cell ) );
	}

	protected override void OnMousePress( MouseEvent e )
	{
		if ( !e.LeftMouseButton ) return;
		// Click on the × clear button → wipe the color.
		if ( _clearButtonRect.Width > 0 && PointInside( _clearButtonRect, e.LocalPosition ) )
		{
			_hex = "";
			Update();
			_onCommit?.Invoke( "" );
			return;
		}
		OpenPicker();
	}

	protected override void OnContextMenu( ContextMenuEvent e )
	{
		OpenContextMenuAt();
	}

	private static bool PointInside( Rect r, Vector2 p )
		=> p.x >= r.Left && p.x <= r.Right && p.y >= r.Top && p.y <= r.Bottom;

	private void OpenPicker()
	{
		Color.TryParse( _hex, out var start );
		if ( start.a == 0 && start.r == 0 && start.g == 0 && start.b == 0 ) start = Color.White;

		var picker = SuiColorPickerPopup.OpenColorPopup( start, c =>
		{
			_hex = ColorToHex( c );
			Update();
			_onCommit?.Invoke( _hex );
		} );
		picker.EditingFinished += () =>
		{
			// Final safety commit so the document state is in sync with whatever
			// the popup last reported, even if the live callback was missed.
			_onCommit?.Invoke( _hex );
		};
		picker.Cleared += () =>
		{
			_hex = "";
			Update();
			_onCommit?.Invoke( "" );
		};
	}

	private void OpenContextMenuAt()
	{
		var m = new Menu( this );
		m.AddOption( "Copy hex", "content_copy", () =>
		{
			if ( !string.IsNullOrEmpty( _hex ) )
				EditorUtility.Clipboard.Copy( _hex );
		} );
		m.AddOption( "Paste hex", "content_paste", () =>
		{
			var pasted = EditorUtility.Clipboard.Paste();
			if ( !string.IsNullOrEmpty( pasted ) && Color.TryParse( pasted, out _ ) )
			{
				_hex = pasted;
				Update();
				_onCommit?.Invoke( _hex );
			}
		} );
		m.AddSeparator();
		m.AddOption( "Clear", "delete", () =>
		{
			_hex = "";
			Update();
			_onCommit?.Invoke( _hex );
		} );
		m.OpenAtCursor( true );
	}

	private static string ColorToHex( Color c )
	{
		var r = (int)Math.Clamp( c.r * 255, 0, 255 );
		var g = (int)Math.Clamp( c.g * 255, 0, 255 );
		var b = (int)Math.Clamp( c.b * 255, 0, 255 );
		var a = (int)Math.Clamp( c.a * 255, 0, 255 );
		return a < 255 ? $"#{r:x2}{g:x2}{b:x2}{a:x2}" : $"#{r:x2}{g:x2}{b:x2}";
	}
}