Editor/RectEditorExport/RectView.cs

namespace Editor.rectedittemplateexporter;

public enum DragState
{
	None,
	WaitingForMovement,
	Dragging,
}

enum GridSnapMode
{
	Nearest,
	RoundDown,
	RoundUp,
}

public class RectView : Widget
{
	private readonly Window Session;
	private Document Document => Session.Document;

	private Rect DrawRect;
	private Pixmap SourceImage;
	private Pixmap ScaledImage;
	private DragState DragState;
	private Vector2 DragStartPos;
	private Rect NewRect;
	private List<Document.Rectangle> RectanglesUnderCursor;

	public RectView( Window session ) : base( session )
	{
		Session = session;

		Name = "Rect View";
		WindowTitle = "Rect View";
		SetWindowIcon( "space_dashboard" );

		MouseTracking = true;
		FocusMode = FocusMode.Click;

		DrawRect = GetDrawRect();
	}

	private Vector2 SnapUVToGrid( Vector2 uv )
	{
		var gridCountX = GetGridCountX();
		var gridCountY = GetGridCountY();

		var x = (int)(gridCountX * uv.x + 0.5f);
		var y = (int)(gridCountY * uv.y + 0.5f);

		return new Vector2( x / (float)gridCountX, y / (float)gridCountY );
	}

	private Vector2 PixelToUV_OnGrid( Vector2 vPixel )
	{
		return SnapUVToGrid( PixelToUV( vPixel ) );
	}

	private void DragUpdate( Vector2 mousePos )
	{
		var minStart = PixelToUV_OnGrid( DragStartPos );
		var maxStart = PixelToUV_OnGrid( DragStartPos );
		var current = PixelToUV_OnGrid( mousePos );
		var min = Vector2.Min( current, minStart );
		var max = Vector2.Max( current, maxStart );

		NewRect = new Rect( min, max - min );

		Update();
	}

	[EditorEvent.Frame]
	protected void OnFrame()
	{
		FindRectanglesUnderCursor( FromScreen( Application.CursorPosition ) );
	}

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

		if ( DragState == DragState.WaitingForMovement && (NewRect.Width > 0.0f || NewRect.Height > 0.0f) )
		{
			DragState = DragState.Dragging;
		}

		if ( DragState != DragState.None )
		{
			DragUpdate( e.LocalPosition );
		}
	}

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

		if ( e.Button == MouseButtons.Left )
		{
			DragState = DragState.WaitingForMovement;
			DragStartPos = e.LocalPosition;
		}
	}

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

		if ( e.Button == MouseButtons.Left )
		{
			if ( DragState == DragState.Dragging )
			{
				if ( Document is not null && NewRect.Width > 0.0f && NewRect.Height > 0.0f )
				{
					Document.SelectRectangle( Document.AddRectangle( NewRect ), SelectionOperation.Set );
					Session.Snapshot( "Create Rectangle" );
				}
			}
			else
			{
				Document.SelectRectangle( GetFirstRectangleUnderCursor(), SelectionOperation.Set );
				Session.Snapshot( "Select Rectangle" );
			}

			DragState = DragState.None;
			NewRect = default;
		}
	}

	public void SetMaterial( Material material )
	{
		SourceImage = null;
		ScaledImage = null;

		if ( material is null )
			return;

		var texture = material.FirstTexture;
		if ( texture is null )
			return;

		SourceImage = Pixmap.FromTexture( texture, false );
		if ( SourceImage is null )
			return;

		UpdateScaledBackgroundImage();
	}

	private void UpdateScaledBackgroundImage()
	{
		ScaledImage = SourceImage?.Resize( DrawRect.Size );
	}

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

		DrawRect = GetDrawRect();

		UpdateScaledBackgroundImage();
	}

	protected override void OnPaint()
	{
		Paint.ClearPen();

		Paint.SetBrush( Theme.ControlBackground );
		Paint.DrawRect( LocalRect );

		Paint.SetBrush( Color.Gray );
		Paint.DrawRect( DrawRect );

		if ( ScaledImage is not null )
		{
			Paint.Draw( DrawRect, ScaledImage );
		}

		if ( Session.GridEnabled )
		{
			DrawGrid();
		}

		DrawRectangleSet( Document?.Rectangles );

		if ( DragState == DragState.Dragging )
		{
			var topLeft = UVToPixel( NewRect.TopLeft );
			var bottomRight = UVToPixel( NewRect.BottomRight );
			var newRect = new Rect( topLeft, bottomRight - topLeft );
			Paint.ClearBrush();
			Paint.SetPen( Color.Yellow, 3 );
			Paint.DrawRect( newRect );
		}
	}

	private Vector2 UVToPixel( Vector2 uv )
	{
		return new Vector2( (int)((uv.x * DrawRect.Width) + DrawRect.Left), (int)((uv.y * DrawRect.Height) + DrawRect.Top) );
	}

	private Vector2 PixelToUV( Vector2 pixel )
	{
		return new Vector2( (pixel.x - DrawRect.Left) / DrawRect.Width, (pixel.y - DrawRect.Top) / DrawRect.Height );
	}

	private int GetGridPower()
	{
		return Session.GridEnabled ? Session.GridPower : 10;
	}

	private int GetGridCountX()
	{
		var width = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Width, 1 );
		var height = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Height, 1 );
		var gridPower = GetGridPower();

		if ( width >= height )
		{
			return 1 << gridPower;
		}
		else
		{
			return (1 << gridPower) * width / height;
		}
	}

	private int GetGridCountY()
	{
		var width = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Width, 1 );
		var height = SourceImage is null ? 512 : System.Math.Max( (int)SourceImage.Height, 1 );
		var gridPower = GetGridPower();

		if ( height >= width )
		{
			return 1 << gridPower;
		}
		else
		{
			return (1 << gridPower) * height / width;
		}
	}

	public Document.Rectangle GetFirstRectangleUnderCursor()
	{
		return RectanglesUnderCursor?.FirstOrDefault();
	}

	public void FindRectanglesUnderCursor( Vector2 mousePos )
	{
		RectanglesUnderCursor = FindRectanglesContainingPoint( PixelToUV( mousePos ) );

		Update();
	}

	public List<Document.Rectangle> FindRectanglesContainingPoint( Vector2 vPoint )
	{
		return Document.Rectangles
			 .Where( rectangle => rectangle.IsPointInRectangle( vPoint ) )
			 .Select( rectangle => new { Rectangle = rectangle, Distance = rectangle.DistanceFromPointToCenter( vPoint ) } )
			 .OrderBy( item => item.Distance )
			 .Select( item => item.Rectangle )
			 .ToList();
	}

	private void DrawRectangleSet( IEnumerable<Document.Rectangle> rectangles )
	{
		if ( rectangles is null )
			return;

		foreach ( var rectangle in rectangles.Where( x => !Document.IsRectangleSelected( x ) ) )
		{
			Paint.SetBrush( rectangle.Color.WithAlpha( 0.5f ) );
			Paint.SetPen( Color.Black.WithAlpha( 192 / 255.0f ), 1 );
			DrawRectangle( rectangle );
		}

		foreach ( var rectangle in Document.SelectedRectangles )
		{
			Paint.SetBrush( new Color32( 255, 255, 0, 64 ) );
			Paint.SetPen( new Color32( 255, 255, 0 ), 1 );
			DrawRectangle( rectangle );
		}

		var rectangleUnderCursor = GetFirstRectangleUnderCursor();
		if ( rectangleUnderCursor is not null && !Document.IsRectangleSelected( rectangleUnderCursor ) )
		{
			Paint.SetBrush( new Color32( 0, 255, 0, 64 ) );
			Paint.SetPen( Color.Green );
			DrawRectangle( rectangleUnderCursor );
		}
	}

	private void DrawRectangle( Document.Rectangle rectangle, int nMinInset = 0, int nMaxInset = 0 )
	{
		if ( rectangle is null )
			return;

		var minPoint = UVToPixel( rectangle.Min );
		var maxPoint = UVToPixel( rectangle.Max );
		minPoint += new Vector2( nMinInset, nMinInset );
		maxPoint -= new Vector2( nMaxInset + 1, nMaxInset + 1 );

		Paint.DrawRect( new Rect( minPoint, maxPoint - minPoint ) );
	}

	private void DrawGrid()
	{
		const float gridOpacity = 64 / 255.0f;

		var gridCountX = GetGridCountX();
		var gridCountY = GetGridCountY();

		var stepX = 1.0f / gridCountX;
		var stepY = 1.0f / gridCountY;

		var rect = DrawRect;

		Paint.ClearBrush();

		for ( int ix = 0; ix <= gridCountX; ++ix )
		{
			var u = ix * stepX;
			var gx = UVToPixel( new Vector2( u, 0 ) ).x;

			if ( gx > rect.Left )
			{
				Paint.SetPen( new Color( 1, 1, 1, gridOpacity ) );
				Paint.DrawLine( new Vector2( gx - 1, rect.Top + 1 ), new Vector2( gx - 1, rect.Height + rect.Top - 2 ) );
			}

			if ( gx < (rect.Left + rect.Width) )
			{
				Paint.SetPen( new Color( 0, 0, 0, gridOpacity ) );
				Paint.DrawLine( new Vector2( gx, rect.Top + 1 ), new Vector2( gx, rect.Height + rect.Top - 2 ) );
			}
		}

		for ( int iy = 0; iy <= gridCountY; ++iy )
		{
			var v = iy * stepY;
			var gy = UVToPixel( new Vector2( 0, v ) ).y;

			if ( gy > rect.Top )
			{
				Paint.SetPen( new Color( 1, 1, 1, gridOpacity ) );

				if ( gy == (rect.Top + rect.Height) )
				{
					Paint.DrawLine( new Vector2( rect.Left, gy - 1 ), new Vector2( rect.Width + rect.Left - 1, gy - 1 ) );
				}
				else
				{
					Paint.DrawLine( new Vector2( rect.Left + 1, gy - 1 ), new Vector2( rect.Width + rect.Left - 2, gy - 1 ) );
				}

			}

			if ( gy < (rect.Top + rect.Height) )
			{
				Paint.SetPen( new Color( 0, 0, 0, gridOpacity ) );

				if ( gy == rect.Top )
				{
					Paint.DrawLine( new Vector2( rect.Left, gy ), new Vector2( rect.Width + rect.Left - 1, gy ) );
				}
				else
				{
					Paint.DrawLine( new Vector2( rect.Left + 1, gy ), new Vector2( rect.Width + rect.Left - 2, gy ) );
				}
			}
		}
	}

	private Rect GetDrawRect()
	{
		const int marigin = 16;
		const int drawSnapSize = 4;

		var imageSize = SourceImage is null ? 0 : SourceImage.Size;
		var widgetWidth = System.Math.Max( (int)Width - (marigin * 2), 128 );
		var widgetHeight = System.Math.Max( (int)Height - (marigin * 2), 128 );
		var imageWidth = System.Math.Max( (int)imageSize.x, 1 );
		var imageHeight = System.Math.Max( (int)imageSize.y, 1 );

		int drawWidth;
		int drawHeight;

		if ( (imageWidth > 0) && (imageHeight > 0) )
		{
			var aspect = imageWidth / (float)imageHeight;
			var relativeWidth = (int)(widgetWidth / System.MathF.Max( aspect, 1.0f ));
			var relativeHeight = (int)(widgetHeight * System.MathF.Min( aspect, 1.0f ));

			if ( relativeWidth <= relativeHeight )
			{
				drawWidth = widgetWidth;
				drawHeight = widgetWidth * imageHeight / imageWidth;
			}
			else
			{
				drawHeight = widgetHeight;
				drawWidth = widgetHeight * imageWidth / imageHeight;
			}
		}
		else
		{
			var drawSize = System.Math.Min( widgetWidth, widgetHeight );
			drawHeight = drawSize;
			drawWidth = drawSize;
		}

		drawWidth = drawWidth / drawSnapSize * drawSnapSize;
		drawHeight = drawHeight / drawSnapSize * drawSnapSize;

		return new Rect( marigin, marigin, drawWidth, drawHeight );
	}
}