Editor/SubTerrainBrushSizeWidget.cs
// Copyright (c) 2026 SubZero Studios LLC. All rights reserved.
using System;
using Sandbox;
namespace Editor.SubTerrain;
/// <summary>
/// Brush Size row: piecewise track (fine under 3500, mid to 18000, coarse to 35000) plus type-in.
/// </summary>
internal sealed class SubTerrainBrushSizeWidget : Widget
{
readonly SubTerrainTool _tool;
readonly SubTerrainBrushSizeTrack _track;
readonly LineEdit _field;
bool _syncing;
public SubTerrainBrushSizeWidget( SubTerrainTool tool, Widget parent ) : base( parent )
{
_tool = tool;
Layout = Layout.Row();
Layout.Spacing = 6;
Layout.Margin = new Sandbox.UI.Margin( 0, 2, 10, 2 );
var label = Layout.Add( new Label( "Size" ) );
label.MinimumWidth = 72;
label.Color = Theme.TextControl;
label.ToolTip = "World units. Fine under 3500 (step 50), mid to 18000 (step 500), then to 35000 (step 1000). Left / Right or Ctrl+[ / ] steps.";
_track = new SubTerrainBrushSizeTrack( this );
Layout.Add( _track, 1 );
_field = new LineEdit();
_field.MinimumWidth = 52;
_field.MaximumWidth = 56;
_field.ToolTip = "Type an exact size (8-35000).";
_field.EditingFinished += OnFieldFinished;
Layout.Add( _field );
FixedHeight = 28;
MinimumHeight = 28;
MaximumHeight = 28;
SyncFromSettings();
}
public void SyncFromSettings()
{
if ( _tool?.BrushSettings is null )
return;
_syncing = true;
int size = SubTerrainBrushSize.Clamp( _tool.BrushSettings.Size );
_tool.BrushSettings.Size = size;
_field.Text = size.ToString();
_track.Update();
_syncing = false;
}
public void SetSizeFromTrack( float t01 )
{
if ( _syncing || _tool?.BrushSettings is null )
return;
int size = SubTerrainBrushSize.SizeFromTrack( t01 );
if ( size == _tool.BrushSettings.Size )
{
_track.Update();
return;
}
_tool.BrushSettings.Size = size;
_syncing = true;
_field.Text = size.ToString();
_syncing = false;
_track.Update();
}
void OnFieldFinished()
{
if ( _syncing || _tool?.BrushSettings is null )
return;
if ( !int.TryParse( _field.Text.Trim(), out int parsed ) )
{
SyncFromSettings();
return;
}
_tool.BrushSettings.Size = SubTerrainBrushSize.Clamp( parsed );
SyncFromSettings();
}
}
internal sealed class SubTerrainBrushSizeTrack : Widget
{
readonly SubTerrainBrushSizeWidget _host;
bool _dragging;
public SubTerrainBrushSizeTrack( SubTerrainBrushSizeWidget host ) : base( host )
{
_host = host;
MouseTracking = true;
Cursor = CursorShape.Finger;
MinimumHeight = 22;
MaximumHeight = 22;
ToolTip = "Drag: fine under 3500, mid to 18000, coarse to 35000. Left / Right or Ctrl+[ / ] use the same steps.";
}
protected override void OnMousePress( MouseEvent e )
{
if ( e.Button == MouseButtons.Left )
{
_dragging = true;
ApplyAt( e.LocalPosition.x );
e.Accepted = true;
return;
}
base.OnMousePress( e );
}
protected override void OnMouseMove( MouseEvent e )
{
base.OnMouseMove( e );
if ( _dragging )
ApplyAt( e.LocalPosition.x );
}
protected override void OnMouseReleased( MouseEvent e )
{
if ( e.Button == MouseButtons.Left )
_dragging = false;
base.OnMouseReleased( e );
}
void ApplyAt( float x )
{
float w = Math.Max( Width - 8f, 1f );
float t = Math.Clamp( (x - 4f) / w, 0f, 1f );
_host.SetSizeFromTrack( t );
}
protected override void OnPaint()
{
base.OnPaint();
Paint.Antialiasing = true;
Paint.ClearPen();
var track = new Rect( 4, Height * 0.5f - 2f, Math.Max( Width - 8f, 1f ), 4f );
Paint.SetBrush( Color.White.WithAlpha( 0.08f ) );
Paint.DrawRect( track, 2 );
// Piecewise band ticks (fine / mid).
float fineX = track.Left + track.Width * SubTerrainBrushSize.FineTrack;
float midX = track.Left + track.Width * SubTerrainBrushSize.MidTrack;
Paint.SetBrush( Color.White.WithAlpha( 0.14f ) );
Paint.DrawRect( new Rect( fineX, track.Top - 2f, 1f, track.Height + 4f ) );
Paint.DrawRect( new Rect( midX, track.Top - 2f, 1f, track.Height + 4f ) );
var tool = SubTerrainTool.ActiveInstance;
int size = tool?.BrushSettings is not null
? SubTerrainBrushSize.Clamp( tool.BrushSettings.Size )
: SubTerrainBrushSize.Min;
float t = SubTerrainBrushSize.TrackFromSize( size );
float thumbX = track.Left + track.Width * t;
float thumbR = 6f;
Paint.SetBrush( Theme.Primary );
Paint.DrawCircle( new Vector2( thumbX, Height * 0.5f ), thumbR );
}
}