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();
}
}