Editor/SubTerrainBlendWidget.cs
// Copyright (c) 2026 SubZero Studios LLC. All rights reserved.

using System;
using Sandbox;

namespace Editor.SubTerrain;

/// <summary>
/// Paint Texture sidebar: tmat list, Ground / Wear / Path, Steep / All, live mix.
/// </summary>
internal class SubTerrainBlendWidget : Widget
{
	readonly SubTerrainTool _tool;
	readonly SubTerrainPaintModeStrip _modes;
	readonly SubTerrainPairPreview _pairPreview;
	readonly Widget _listHost;
	readonly Widget _optionsHost;
	readonly Label _live;
	Widget _limitRow;
	Checkbox _keepPairBox;
	Checkbox _sampledBox;
	Checkbox _packCenterBox;
	int _listedCount = -1;
	SubTerrainPaintLayer _sheetLayer = (SubTerrainPaintLayer)(-1);
	bool _sheetSteep;

	const string IntentSuffix = " Alt sample | Ctrl fade | Shift smooth | ring yellow/green = Alt/Shift.";

	public SubTerrainBlendWidget( SubTerrainTool tool, Widget parent ) : base( parent )
	{
		_tool = tool;
		Layout = Layout.Column();
		Layout.Spacing = 4;

		_modes = Layout.Add( new SubTerrainPaintModeStrip( tool, this ) );
		_pairPreview = Layout.Add( new SubTerrainPairPreview( tool, this ) );
		SyncPairPreview();
		_listHost = Layout.Add( new Widget( this ) );
		_listHost.Layout = Layout.Column();
		_listHost.Layout.Spacing = 0;
		_optionsHost = Layout.Add( new Widget( this ) );
		_optionsHost.Layout = Layout.Column();
		_optionsHost.Layout.Spacing = 4;
		_live = Layout.Add( new Label( "" ) );

		RebuildList();
		RebuildOptions();
		RefreshCopy();
	}

	void OnGroundClicked() => SetLayer( SubTerrainPaintLayer.Base );

	void OnWearClicked() => SetLayer( SubTerrainPaintLayer.Overlay );

	void OnPathClicked()
	{
		var terrain = _tool.PeekTerrain();
		int ground = SubTerrainSplatUtil.FindPathGround( terrain, _tool.PaintSettings );
		int wear = _tool.PaintSettings.MaterialIndex;
		if ( wear == ground )
		{
			wear = SubTerrainSplatUtil.FindPathWear( terrain, ground );
			_tool.PaintSettings.MaterialIndex = wear;
			SubTerrainPaintTool.TrySetSplatChannel( wear );
		}

		SetLayer( SubTerrainPaintLayer.Path );
	}

	void SetLayer( SubTerrainPaintLayer layer )
	{
		SubTerrainPaintStore.Capture( _tool.PaintSettings );
		SubTerrainPaintStore.Apply( _tool.PaintSettings, layer );
		SubTerrainPaintTool.ActiveLayer = layer;
		RebuildOptions();
		_tool.RefreshPaintChrome();
		RefreshCopy();
		_modes.Update();
		SyncPairPreview();
	}

	void SyncPairPreview()
	{
		if ( _pairPreview is null )
			return;

		// Ground writes one tmat - a pair thumb is dead chrome.
		bool show = _tool.PaintSettings.Layer != SubTerrainPaintLayer.Base;
		_pairPreview.Visible = show;
		_pairPreview.FixedHeight = show ? 36 : 0;
		_pairPreview.MinimumHeight = show ? 36 : 0;
		_pairPreview.MaximumHeight = show ? 36 : 0;
		if ( show )
			_pairPreview.Update();
	}

	internal void SelectGround() => OnGroundClicked();
	internal void SelectWear() => OnWearClicked();
	internal void SelectPath() => OnPathClicked();

	internal void OnMaterialSelectionChanged()
	{
		RefreshCopy();
		SyncPairPreview();
		_listHost?.Update();
		if ( _listHost?.Layout is null )
			return;

		foreach ( var child in _listHost.Children )
			child?.Update();
	}

	internal void SelectSteep()
	{
		_tool.PaintSettings.OnlyOnSlope = true;
		RebuildOptions();
		RefreshCopy();
	}

	internal void SelectAll()
	{
		_tool.PaintSettings.OnlyOnSlope = false;
		RebuildOptions();
		RefreshCopy();
	}

	void OnKeepPairToggled()
	{
		if ( _keepPairBox is null )
			return;

		_tool.PaintSettings.MixOnly = _keepPairBox.Value;
		RefreshCopy();
	}

	void OnSampledToggled()
	{
		if ( _sampledBox is null )
			return;

		_tool.PaintSettings.LimitToSampledGround = _sampledBox.Value;
		RefreshCopy();
	}

	void OnPackCenterToggled()
	{
		if ( _packCenterBox is null )
			return;

		_tool.PaintSettings.PackCenter = _packCenterBox.Value;
		RebuildOptions();
		RefreshCopy();
		SyncPairPreview();
	}

	void RebuildList()
	{
		if ( _listHost is null )
			return;

		_listHost.Layout ??= Layout.Column();
		_listHost.Layout.Clear( true );

		var terrain = _tool.PeekTerrain();
		var materials = terrain?.Storage?.Materials;
		int count = materials?.Count ?? 0;
		_listedCount = count;

		if ( count == 0 )
		{
			_listHost.Layout.Add( new Label( "This terrain has no materials to paint." ) );
			return;
		}

		for ( int i = 0; i < count; i++ )
		{
			var name = SubTerrainSplatUtil.MaterialName( terrain, i );
			_listHost.Layout.Add( new SubTerrainMaterialRow( _tool, i, name, _listHost ) );
		}
	}

	void RebuildOptions()
	{
		if ( _optionsHost is null )
			return;

		_optionsHost.Layout ??= Layout.Column();
		_optionsHost.Layout.Clear( true );
		_limitRow = null;
		_keepPairBox = null;
		_sampledBox = null;
		_packCenterBox = null;
		_sheetLayer = _tool.PaintSettings.Layer;
		_sheetSteep = _tool.PaintSettings.OnlyOnSlope;

		var so = _tool.PaintSettings.GetSerialized();
		if ( so is null )
			return;

		if ( _sheetLayer == SubTerrainPaintLayer.Overlay )
			_optionsHost.Layout.Add( new SubTerrainWhereStrip( _tool, this ) );

		var sheet = new ControlSheet { IncludePropertyNames = false };
		if ( _sheetLayer == SubTerrainPaintLayer.Overlay )
			AddSettingRow( sheet, so, nameof( SubTerrainPaintSettings.HowWorn ) );
		else if ( _sheetLayer == SubTerrainPaintLayer.Path )
			AddSettingRow( sheet, so, nameof( SubTerrainPaintSettings.CenterMix ) );

		if ( _sheetLayer == SubTerrainPaintLayer.Overlay && _sheetSteep )
			AddSettingRow( sheet, so, nameof( SubTerrainPaintSettings.SlopeDegrees ) );

		_optionsHost.Layout.Add( sheet );

		if ( _sheetLayer == SubTerrainPaintLayer.Overlay )
		{
			_keepPairBox = new Checkbox( "Keep pair" );
			_keepPairBox.Value = _tool.PaintSettings.MixOnly;
			_keepPairBox.ToolTip = "Only move mix. Do not change the base/overlay pair (avoids texel stairs next to a different pair).";
			_keepPairBox.Toggled += OnKeepPairToggled;
			_optionsHost.Layout.Add( _keepPairBox );

			_sampledBox = new Checkbox( "Stay on sampled" );
			_sampledBox.Value = _tool.PaintSettings.LimitToSampledGround;
			_sampledBox.ToolTip = "Alt-click locks to that ground base. Wear will not paint other bases.";
			_sampledBox.Toggled += OnSampledToggled;
			_optionsHost.Layout.Add( _sampledBox );
			_limitRow = _sampledBox;
		}
		else if ( _sheetLayer == SubTerrainPaintLayer.Path )
		{
			_packCenterBox = new Checkbox( "Pack center" );
			_packCenterBox.Value = _tool.PaintSettings.PackCenter;
			_packCenterBox.ToolTip = "On: solid path core along the stroke, soft apron outside. Off: soft ground + path apron only.";
			_packCenterBox.Toggled += OnPackCenterToggled;
			_optionsHost.Layout.Add( _packCenterBox );

			if ( _tool.PaintSettings.PackCenter )
			{
				var coreSheet = new ControlSheet { IncludePropertyNames = false };
				AddSettingRow( coreSheet, so, nameof( SubTerrainPaintSettings.PackCore ) );
				_optionsHost.Layout.Add( coreSheet );
			}
		}
	}

	static void AddSettingRow( ControlSheet sheet, SerializedObject so, string name )
	{
		if ( so.TryGetProperty( name, out var prop ) && prop is not null )
			sheet.AddRow( prop );
	}

	void RefreshCopy()
	{
		if ( _live is null )
			return;

		var line = WriteLine();
		if ( _tool.PaintSettings.Layer == SubTerrainPaintLayer.Overlay && _tool.PaintSettings.Dithers )
			line += " | dither";

		_live.Text = line;
		_live.ToolTip = IntentTip();
		_live.Color = Theme.TextControl;
	}

	string WriteLine()
	{
		var settings = _tool.PaintSettings;
		var layer = settings.Layer;
		var terrain = _tool.PeekTerrain();
		string mix = settings.MixCap.ToString( "0.00" );

		if ( layer == SubTerrainPaintLayer.Path )
		{
			int ground = SubTerrainSplatUtil.FindPathGround( terrain, settings );
			int wear = settings.MaterialIndex;
			if ( ground == wear )
				return "Click the packed tmat. Alt-click the ground.";

			var gName = SubTerrainSplatUtil.MaterialName( terrain, ground );
			var wName = SubTerrainSplatUtil.MaterialName( terrain, wear );
			return settings.PackCenter
				? $"{wName} center | {gName} | {wName} {mix} | core {settings.PackCore:0.00}"
				: $"{gName} | {wName} {mix} | soft";
		}

		if ( layer == SubTerrainPaintLayer.Base )
			return SubTerrainSplatUtil.MaterialName( terrain, settings.MaterialIndex );

		var name = SubTerrainSplatUtil.MaterialName( terrain, settings.MaterialIndex );
		string lockOn = "";
		if ( settings.LimitToSampledGround && settings.HasSampledGround )
			lockOn = $" on {SubTerrainSplatUtil.MaterialName( terrain, settings.SampledGroundIndex )}";

		if ( settings.MixOnly )
			return $"mix {name} {mix}{lockOn}";

		return settings.OnlyOnSlope
			? $"{name} {mix} steep{lockOn}"
			: $"{name} {mix}{lockOn}";
	}

	string IntentTip()
	{
		var settings = _tool.PaintSettings;
		string body = settings.Layer switch
		{
			SubTerrainPaintLayer.Path => settings.PackCenter
				? "Pack center on. Raise Core for a wider solid middle. Center mix is the soft apron. Brush Size widens the whole blend."
				: "Pack center off. Soft apron only: ground + path. Center mix is the apron mix.",
			SubTerrainPaintLayer.Base => "Ground writes the selected tmat as base and overlay, mix 0.",
			_ => "Wear writes overlay mix. Steep skips flats. Ctrl reduces. Alt-click locks Stay on sampled."
		};

		return body + IntentSuffix;
	}

	[EditorEvent.Frame]
	public void Refresh()
	{
		if ( _live is null || _modes is null || _listHost is null || _optionsHost is null )
			return;

		_tool.RefreshPaintChrome();

		if ( !_tool.PaintArmed )
			return;

		var terrain = _tool.PeekTerrain();
		int count = terrain?.Storage?.Materials?.Count ?? 0;
		if ( count != _listedCount )
			RebuildList();

		var settings = _tool.PaintSettings;
		if ( _sheetLayer != settings.Layer || _sheetSteep != settings.OnlyOnSlope )
			RebuildOptions();

		if ( _keepPairBox is not null )
			_keepPairBox.Value = settings.MixOnly;

		if ( _sampledBox is not null )
		{
			_sampledBox.Value = settings.LimitToSampledGround;
			_sampledBox.Enabled = settings.HasSampledGround;
			if ( !settings.HasSampledGround )
				_sampledBox.ToolTip = "Alt-click the ground to lock Stay on sampled.";
			else
				_sampledBox.ToolTip = "Locked to the Alt-clicked ground base. Uncheck to paint any base.";
		}

		if ( _packCenterBox is not null )
			_packCenterBox.Value = settings.PackCenter;

		if ( _limitRow is not null )
			_limitRow.Enabled = settings.HasSampledGround;

		RefreshCopy();
		_modes.Update();
		SyncPairPreview();

		if ( !_tool.StatusValid || terrain is null || !terrain.IsValid() )
			return;

		if ( !SubTerrainSplatUtil.TryTexel( terrain, _tool.StatusHitLocal, out var x, out var y ) )
			return;

		var mat = SubTerrainSplatUtil.Read( terrain, x, y );
		if ( mat.IsHole )
		{
			_live.Text = WriteLine() + " | hole";
			return;
		}

		var baseName = SubTerrainSplatUtil.MaterialName( terrain, mat.BaseTextureId );
		var overlayName = SubTerrainSplatUtil.MaterialName( terrain, mat.OverlayTextureId );
		float mix = mat.BlendFactor / 255f;
		string there = baseName == overlayName || mix < 0.02f
			? baseName
			: $"{baseName} + {overlayName} {mix:0.00}";

		string extra = "";
		if ( SubTerrainSplatUtil.TrySlopeDegrees( terrain, x, y, out var degrees ) )
		{
			int deg = (int)MathF.Round( degrees );
			bool steepGate = settings.Layer == SubTerrainPaintLayer.Overlay && settings.OnlyOnSlope;
			if ( steepGate )
				extra = degrees >= settings.SlopeDegrees ? $" | steep {deg}" : $" | skip {deg}";
		}

		if ( SubTerrainSplatUtil.HasSeam( terrain, x, y ) )
			extra += " | seam";

		_live.Text = $"{WriteLine()} | {there}{extra}";
	}
}

internal class SubTerrainPairPreview : Widget
{
	readonly SubTerrainTool _tool;
	const float ThumbSize = 24f;

	public SubTerrainPairPreview( SubTerrainTool tool, Widget parent ) : base( parent )
	{
		_tool = tool;
		FixedHeight = 36;
		MinimumHeight = 36;
		MaximumHeight = 36;
		ToolTip = "Pair this stroke will write.";
	}

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

		if ( !Visible || _tool.PaintSettings.Layer == SubTerrainPaintLayer.Base )
			return;

		Paint.Antialiasing = true;
		Paint.ClearPen();
		Paint.SetBrush( Color.White.WithAlpha( 0.04f ) );
		Paint.DrawRect( LocalRect );

		ResolvePair( out int left, out int right );
		float y = (Height - ThumbSize) * 0.5f;
		float gap = 8f;
		float total = ThumbSize * 2f + gap;
		float x0 = (Width - total) * 0.5f;

		DrawThumb( new Rect( x0, y, ThumbSize, ThumbSize ), left );
		DrawThumb( new Rect( x0 + ThumbSize + gap, y, ThumbSize, ThumbSize ), right );
	}

	void ResolvePair( out int left, out int right )
	{
		var settings = _tool.PaintSettings;
		var terrain = _tool.PeekTerrain();
		int selected = settings.MaterialIndex;

		if ( settings.Layer == SubTerrainPaintLayer.Path )
		{
			left = SubTerrainSplatUtil.FindPathGround( terrain, settings );
			right = selected;
			return;
		}

		if ( settings.Layer == SubTerrainPaintLayer.Overlay )
		{
			left = settings.HasSampledGround
				? settings.SampledGroundIndex
				: HoverBaseOr( terrain, selected );
			right = selected;
			return;
		}

		left = selected;
		right = selected;
	}

	int HoverBaseOr( Terrain terrain, int fallback )
	{
		if ( terrain is null || !_tool.StatusValid )
			return fallback;

		if ( !SubTerrainSplatUtil.TryTexel( terrain, _tool.StatusHitLocal, out var x, out var y ) )
			return fallback;

		var mat = SubTerrainSplatUtil.Read( terrain, x, y );
		if ( mat.IsHole )
			return fallback;

		return mat.BaseTextureId;
	}

	void DrawThumb( Rect rect, int index )
	{
		Paint.ClearPen();
		Paint.SetBrush( Theme.SurfaceBackground.WithAlpha( 0.35f ) );
		Paint.DrawRect( rect, 3 );

		var path = MaterialPath( index );
		var asset = string.IsNullOrWhiteSpace( path ) ? null : AssetSystem.FindByPath( path );
		if ( asset is null || asset.IsDeleted )
			return;

		var thumb = asset.GetAssetThumb( true );
		if ( thumb is not null )
			Paint.Draw( rect, thumb );
	}

	string MaterialPath( int index )
	{
		var mats = _tool.PeekTerrain()?.Storage?.Materials;
		if ( mats is null || index < 0 || index >= mats.Count )
			return null;

		return mats[index]?.ResourcePath;
	}

	[EditorEvent.Frame]
	public void TickPreview()
	{
		if ( !Visible || _tool.PaintSettings.Layer == SubTerrainPaintLayer.Base )
			return;

		Update();
	}
}

internal class SubTerrainPaintModeStrip : Widget
{
	readonly SubTerrainTool _tool;
	readonly SubTerrainBlendWidget _host;
	const int SlotCount = 3;
	int _hover = -1;

	public SubTerrainPaintModeStrip( SubTerrainTool tool, SubTerrainBlendWidget host ) : base( host )
	{
		_tool = tool;
		_host = host;
		FixedHeight = 28;
		MinimumHeight = 28;
		MaximumHeight = 28;
		Cursor = CursorShape.Finger;
		MouseTracking = true;
		ToolTip = "Ground is a solid tmat. Wear is overlay mix. Path packs a path tmat (optional Pack center) with a ground+path apron.";
	}

	protected override void OnMouseClick( MouseEvent e )
	{
		int slot = SlotAt( e.LocalPosition.x );
		if ( slot == 0 )
			_host.SelectGround();
		else if ( slot == 1 )
			_host.SelectWear();
		else
			_host.SelectPath();

		Update();
	}

	protected override void OnMouseMove( MouseEvent e )
	{
		base.OnMouseMove( e );
		int next = SlotAt( e.LocalPosition.x );
		if ( next == _hover )
			return;

		_hover = next;
		Update();
	}

	protected override void OnMouseLeave()
	{
		_hover = -1;
		Update();
		base.OnMouseLeave();
	}

	int SlotAt( float x )
	{
		float w = Math.Max( Width, 1f );
		int slot = (int)(x / w * SlotCount);
		return Math.Clamp( slot, 0, SlotCount - 1 );
	}

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

		Paint.Antialiasing = true;
		Paint.ClearPen();
		Paint.SetBrush( Color.White.WithAlpha( 0.04f ) );
		Paint.DrawRect( LocalRect );

		var layer = _tool.PaintSettings.Layer;
		float slotW = Width / SlotCount;
		for ( int i = 0; i < SlotCount; i++ )
		{
			var rect = new Rect( i * slotW, 0, slotW, Height );
			bool selected = i switch
			{
				0 => layer == SubTerrainPaintLayer.Base,
				1 => layer == SubTerrainPaintLayer.Overlay,
				_ => layer == SubTerrainPaintLayer.Path
			};

			Paint.ClearPen();
			if ( selected )
				Paint.SetBrush( Theme.Primary.WithAlpha( 0.20f ) );
			else if ( _hover == i )
				Paint.SetBrush( Color.White.WithAlpha( 0.06f ) );
			else
				Paint.SetBrush( Color.Transparent );

			Paint.DrawRect( rect );

			if ( i > 0 )
			{
				Paint.ClearPen();
				Paint.SetBrush( Color.White.WithAlpha( 0.10f ) );
				Paint.DrawRect( new Rect( rect.Left, rect.Top + 5, 1, rect.Height - 10 ) );
			}

			string label = i switch
			{
				0 => "Ground",
				1 => "Wear",
				_ => "Path"
			};

			Paint.SetPen( selected ? Theme.Primary : Theme.TextControl );
			Paint.DrawText( rect, label, TextFlag.Center );
		}
	}
}

internal class SubTerrainWhereStrip : Widget
{
	readonly SubTerrainTool _tool;
	readonly SubTerrainBlendWidget _host;
	const int SlotCount = 2;
	int _hover = -1;

	public SubTerrainWhereStrip( SubTerrainTool tool, SubTerrainBlendWidget host ) : base( host )
	{
		_tool = tool;
		_host = host;
		FixedHeight = 28;
		MinimumHeight = 28;
		MaximumHeight = 28;
		Cursor = CursorShape.Finger;
		MouseTracking = true;
		ToolTip = "Steep skips ground that is too flat. All paints the whole brush, including shelves.";
	}

	protected override void OnMouseClick( MouseEvent e )
	{
		if ( SlotAt( e.LocalPosition.x ) == 0 )
			_host.SelectSteep();
		else
			_host.SelectAll();

		Update();
	}

	protected override void OnMouseMove( MouseEvent e )
	{
		base.OnMouseMove( e );
		int next = SlotAt( e.LocalPosition.x );
		if ( next == _hover )
			return;

		_hover = next;
		ToolTip = next == 0
			? "Skip ground that is too flat. Hover a hill; the footer says steep or skip."
			: "Paint the whole brush, including shelves.";
		Update();
	}

	protected override void OnMouseLeave()
	{
		_hover = -1;
		ToolTip = "Steep skips ground that is too flat. All paints the whole brush, including shelves.";
		Update();
		base.OnMouseLeave();
	}

	int SlotAt( float x )
	{
		float w = Math.Max( Width, 1f );
		int slot = (int)(x / w * SlotCount);
		return Math.Clamp( slot, 0, SlotCount - 1 );
	}

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

		Paint.Antialiasing = true;
		Paint.ClearPen();
		Paint.SetBrush( Color.White.WithAlpha( 0.04f ) );
		Paint.DrawRect( LocalRect );

		bool steep = _tool.PaintSettings.OnlyOnSlope;
		float slotW = Width / SlotCount;
		for ( int i = 0; i < SlotCount; i++ )
		{
			var rect = new Rect( i * slotW, 0, slotW, Height );
			bool selected = i == 0 ? steep : !steep;

			Paint.ClearPen();
			if ( selected )
				Paint.SetBrush( Theme.Primary.WithAlpha( 0.20f ) );
			else if ( _hover == i )
				Paint.SetBrush( Color.White.WithAlpha( 0.06f ) );
			else
				Paint.SetBrush( Color.Transparent );

			Paint.DrawRect( rect );

			if ( i > 0 )
			{
				Paint.ClearPen();
				Paint.SetBrush( Color.White.WithAlpha( 0.10f ) );
				Paint.DrawRect( new Rect( rect.Left, rect.Top + 5, 1, rect.Height - 10 ) );
			}

			Paint.SetPen( selected ? Theme.Primary : Theme.TextControl );
			Paint.DrawText( rect, i == 0 ? "Steep" : "All", TextFlag.Center );
		}
	}

	[EditorEvent.Frame]
	public void TickSelect()
	{
		Update();
	}
}

internal class SubTerrainMaterialRow : Widget
{
	readonly SubTerrainTool _tool;
	readonly int _index;
	readonly string _name;
	readonly string _resourcePath;

	const float RowHeight = 42f;
	const float ThumbSize = 28f;

	public SubTerrainMaterialRow( SubTerrainTool tool, int index, string name, Widget parent ) : base( parent )
	{
		_tool = tool;
		_index = index;
		_name = name;
		_resourcePath = tool.PeekTerrain()?.Storage?.Materials is { Count: > 0 } mats && index >= 0 && index < mats.Count
			? mats[index]?.ResourcePath
			: null;
		FixedHeight = RowHeight;
		MinimumHeight = RowHeight;
		MaximumHeight = RowHeight;
		Cursor = CursorShape.Finger;
		ToolTip = name;
		MouseTracking = true;
	}

	protected override void OnMouseClick( MouseEvent e )
	{
		var settings = _tool.PaintSettings;
		bool path = settings.Layer == SubTerrainPaintLayer.Path;
		bool alt = Application.KeyboardModifiers.HasFlag( KeyboardModifiers.Alt );

		if ( path && alt )
		{
			if ( _index == settings.MaterialIndex )
				return;

			settings.PathGroundIndex = _index;
			Update();
			return;
		}

		if ( path && _index == SubTerrainSplatUtil.FindPathGround( _tool.PeekTerrain(), settings ) )
		{
			settings.PathGroundIndex = _index;
			Update();
			return;
		}

		settings.MaterialIndex = _index;
		SubTerrainPaintTool.TrySetSplatChannel( _index );
		Update();
	}

	protected override void OnDoubleClick( MouseEvent e )
	{
		if ( string.IsNullOrWhiteSpace( _resourcePath ) )
			return;

		var asset = AssetSystem.FindByPath( _resourcePath );
		if ( asset is null || asset.IsDeleted )
			return;

		asset.OpenInEditor();
	}

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

		var settings = _tool.PaintSettings;
		bool selected = settings.MaterialIndex == _index;
		bool path = settings.Layer == SubTerrainPaintLayer.Path;
		bool pathGround = path && SubTerrainSplatUtil.FindPathGround( _tool.PeekTerrain(), settings ) == _index;

		Paint.Antialiasing = true;
		Paint.ClearPen();
		if ( selected )
			Paint.SetBrush( Theme.Primary.WithAlpha( 0.18f ) );
		else if ( IsUnderMouse )
			Paint.SetBrush( Color.White.WithAlpha( 0.06f ) );
		else
			Paint.SetBrush( Color.Transparent );

		Paint.DrawRect( LocalRect );

		var thumbRect = new Rect( 8, (RowHeight - ThumbSize) * 0.5f, ThumbSize, ThumbSize );
		Paint.ClearPen();
		Paint.SetBrush( Theme.SurfaceBackground.WithAlpha( 0.35f ) );
		Paint.DrawRect( thumbRect, 3 );

		var asset = string.IsNullOrWhiteSpace( _resourcePath ) ? null : AssetSystem.FindByPath( _resourcePath );
		if ( asset is not null && !asset.IsDeleted )
		{
			var thumb = asset.GetAssetThumb( true );
			if ( thumb is not null )
				Paint.Draw( thumbRect, thumb );
		}

		float textLeft = thumbRect.Right + 8f;
		Paint.SetPen( selected ? Color.White : Theme.TextControl );
		Paint.DrawText( new Rect( textLeft, 0, Math.Max( 40f, Width - textLeft - 56f ), Height ), _name, TextFlag.LeftCenter );

		string role = null;
		if ( pathGround )
			role = "ground";
		else if ( path && selected )
			role = "path";
		else if ( !path && selected && settings.Layer == SubTerrainPaintLayer.Overlay )
			role = settings.MixCap.ToString( "0.00" );

		if ( role is not null )
		{
			Paint.SetPen( Color.White.WithAlpha( selected ? 0.70f : 0.45f ) );
			Paint.DrawText( new Rect( Width - 56, 0, 44, Height ), role, TextFlag.RightCenter );
		}
	}

	[EditorEvent.Frame]
	public void TickSelect()
	{
		bool path = _tool.PaintSettings.Layer == SubTerrainPaintLayer.Path;
		ToolTip = path
			? "Click the packed tmat. Alt-click the ground under it. Double-click opens the asset."
			: _name + " | Double-click opens the asset.";
		Update();
	}
}