Editor/EffigyEditor/EffigyMaterialBrushBar.cs
using Editor;
using Effigy;
using Sandbox;
using System;
namespace Marionette.EditorTools;
/// <summary>
/// The material brush's floating bar: which material is loaded, and how big the ring is.
///
/// THE MATERIAL IS SHOWN, NOT CHOSEN. It comes from the Materials browser, which the Paint
/// workspace already opens — putting a second picker here would be two controls answering one
/// question, and the browser is the one with thumbnails. So this reads it back instead, because a
/// brush that does not say what it is loaded with is a brush you have to test on the model to
/// identify.
///
/// Same shape and height as EffigyPaintBar and EffigySculptBar, so the three brushes put their
/// controls in the same place on screen.
/// </summary>
internal sealed class EffigyMaterialBrushBar : Widget
{
public const float BarHeight = 28f;
private readonly Editor.Label _material;
private readonly EffigyNumericField _radius;
private MaterialBrushSession _session;
/// <summary>Raised when the radius moved, so the viewport redraws its ring at the new size.</summary>
public Action Changed { get; set; }
public EffigyMaterialBrushBar( Widget parent ) : base( parent )
{
TranslucentBackground = true;
NoSystemBackground = true;
Visible = false;
FixedHeight = BarHeight;
FixedWidth = 460f;
Layout = Layout.Row();
Layout.Spacing = 8;
Layout.Margin = new Sandbox.UI.Margin( 0 );
Layout.Add( new Editor.Label( "Material" ) { Color = Theme.TextControl.WithAlpha( 0.6f ) } );
_material = new Editor.Label( "none" ) { Color = Theme.TextControl };
Layout.Add( _material, 1 );
Layout.Add( new Editor.Label( "Radius" ) { Color = Theme.TextControl.WithAlpha( 0.6f ) } );
_radius = new EffigyNumericField( this, 0.25f, "u" )
{
Min = 1e-4f,
ValueEdited = OnRadiusEdited,
FixedWidth = 90f,
};
Layout.Add( _radius );
}
public Color GapColor { get; set; } = Theme.ControlBackground;
protected override void OnPaint()
{
Paint.ClearPen();
Paint.SetBrush( GapColor );
Paint.DrawRect( LocalRect );
}
public void Bind( MaterialBrushSession session )
{
_session = session;
Visible = session is not null;
if ( session is not null )
Refresh();
}
/// <summary>The loaded material, named the way the prompt names it — the filename, because the
/// full asset path is mostly folders and the bar has one line.</summary>
public void SetMaterial( string material )
{
if ( !_material.IsValid() )
return;
_material.Text = string.IsNullOrWhiteSpace( material )
? "none — pick one in the Materials browser"
: System.IO.Path.GetFileNameWithoutExtension( material );
_material.Color = string.IsNullOrWhiteSpace( material )
? Theme.Yellow.WithAlpha( 0.9f )
: Theme.TextControl;
}
public void Refresh()
{
if ( _session is null )
return;
_radius.SetValue( _session.Radius );
}
private void OnRadiusEdited( float value )
{
if ( _session is null )
return;
// Clamped rather than refused, the same call the paint bar makes: a zero radius is a brush
// that covers nothing, and a tool that stops working because a box was cleared is not one.
_session.Radius = MathF.Max( value, 1e-4f );
Changed?.Invoke();
}
}