Editor/MessageDialog.cs
using System;
using Sandbox;
using Editor;

/// <summary>
/// Simple modal dialog for surfacing tool errors to the user.
/// </summary>
public class MessageDialog : DockWindow
{
	private readonly string _title;
	private readonly string _message;
	private readonly string _detail;
	private readonly string _buttonLabel;
	private Vector2 _mousePos;
	private Rect _closeRect;

	private MessageDialog( string title, string message, string detail, string buttonLabel )
	{
		_title = title;
		_message = message;
		_detail = detail;
		_buttonLabel = string.IsNullOrWhiteSpace( buttonLabel ) ? "Close" : buttonLabel;
		Title = title;
		Size = new Vector2( 560, string.IsNullOrWhiteSpace( detail ) ? 200 : 340 );
	}

	public static void Show( string title, string message, string detail = null, string buttonLabel = "Close" )
	{
		var dialog = new MessageDialog( title, message, detail, buttonLabel );
		dialog.Show();
	}

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

		var pad = 20f;
		var w = Width - pad * 2;
		var y = 20f;

		Paint.SetDefaultFont( size: 13, weight: 700 );
		Paint.SetPen( Color.White );
		Paint.DrawText( new Rect( pad, y, w, 22 ), _title, TextFlag.LeftCenter );
		y += 34;

		y = DrawWrappedText( _message, pad, y, w, 10, Color.White.WithAlpha( 0.9f ), 17f );

		if ( !string.IsNullOrWhiteSpace( _detail ) )
		{
			y += 8;
			Paint.SetBrush( Color.Black.WithAlpha( 0.18f ) );
			Paint.SetPen( Color.Red.WithAlpha( 0.2f ) );
			var detailRect = new Rect( pad, y, w, Height - y - 72 );
			Paint.DrawRect( detailRect, 4 );
			DrawWrappedText( _detail, pad + 10, y + 10, w - 20, 9, Color.Orange.WithAlpha( 0.85f ), 15f, detailRect.Bottom - 12 );
		}

		var btnW = 120f;
		var btnH = 32f;
		var btnRect = new Rect( Width - pad - btnW, Height - pad - btnH, btnW, btnH );
		var hovered = btnRect.IsInside( _mousePos );

		Paint.SetBrush( Color.White.WithAlpha( hovered ? 0.12f : 0.06f ) );
		Paint.SetPen( Color.White.WithAlpha( hovered ? 0.35f : 0.2f ) );
		Paint.DrawRect( btnRect, 4 );
		Paint.SetDefaultFont( size: 11, weight: 600 );
		Paint.SetPen( Color.White.WithAlpha( hovered ? 0.95f : 0.75f ) );
		Paint.DrawText( btnRect, _buttonLabel, TextFlag.Center );

		_closeRect = btnRect;
	}

	private float DrawWrappedText( string text, float x, float y, float width, int fontSize, Color color, float lineHeight, float maxY = float.MaxValue )
	{
		Paint.SetDefaultFont( size: fontSize );
		Paint.SetPen( color );

		foreach ( var paragraph in text.Replace( "\r", "" ).Split( '\n' ) )
		{
			if ( y > maxY )
				return y;

			if ( string.IsNullOrWhiteSpace( paragraph ) )
			{
				y += lineHeight;
				continue;
			}

			var words = paragraph.Split( ' ', StringSplitOptions.RemoveEmptyEntries );
			var line = "";
			foreach ( var word in words )
			{
				var candidate = string.IsNullOrEmpty( line ) ? word : $"{line} {word}";
				if ( candidate.Length * 6.2f > width && !string.IsNullOrEmpty( line ) )
				{
					Paint.DrawText( new Rect( x, y, width, lineHeight ), line, TextFlag.LeftTop );
					y += lineHeight;
					if ( y > maxY )
						return y;
					line = word;
				}
				else
				{
					line = candidate;
				}
			}

			if ( !string.IsNullOrEmpty( line ) )
			{
				Paint.DrawText( new Rect( x, y, width, lineHeight ), line, TextFlag.LeftTop );
				y += lineHeight;
			}
		}

		return y;
	}

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

		if ( _closeRect.IsInside( e.LocalPosition ) )
			Close();
	}

	protected override void OnMouseMove( MouseEvent e )
	{
		base.OnMouseMove( e );
		_mousePos = e.LocalPosition;
		Update();
	}
}