Editor/Widgets/PropertySheet/PropertySheetGroup.cs
using Sandbox.UI;
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using static Facepunch.ActionGraphs.Node;
namespace Panelize;
public class PropertySheetGroup : Widget
{
public abstract class Entry
{
public abstract Widget Build();
}
public class PropertyEntry : Entry
{
SerializedProperty property;
public PropertyEntry(SerializedProperty prop)
{
property = prop;
}
public override Widget Build( )
{
var row = PropertySheetRow.Create( property );
return row;
}
}
public class WidgetBuilderEntry : Entry
{
Func<Widget> builder;
string text;
bool textColumn;
public WidgetBuilderEntry(Func<Widget> builder, string label = "", bool labelSeperate = false)
{
ArgumentNullException.ThrowIfNull( builder );
this.builder = builder;
text = label;
textColumn = labelSeperate;
}
public override Widget Build()
{
Widget widget = builder();
if(!string.IsNullOrEmpty(text))
{
Widget body = new();
body.Layout = textColumn ? Layout.Column() : Layout.Row();
PropertySheetLabel label = new()
{
Text = text
};
body.Layout.Add( label );
body.Layout.Add( widget );
return body;
}
return widget;
}
}
public string Id { get; set; }
string groupCookie;
List<Entry> entries = new();
Widget Body;
bool hasHeader;
public PropertySheetGroup( string name )
{
if ( !string.IsNullOrWhiteSpace( name ) )
{
groupCookie = $"propertysheetgroup.{name}";
}
Id = name;
VerticalSizeMode = SizeMode.Flexible;
HorizontalSizeMode = SizeMode.Flexible;
Layout = Layout.Column();
Layout.Margin = new Margin( 0, 0, 12, 0 );
bool open = false;
GroupHeader headerWidget = null;
if ( groupCookie is not null )
{
open = EditorCookie.Get( groupCookie, open );
}
visibilityDebounce = 10;
if ( !string.IsNullOrWhiteSpace( name ) )
{
headerWidget = new GroupHeader( this );
Layout.Add( headerWidget );
headerWidget.Title = name;
hasHeader = true;
}
Body = new Widget();
Body.Hidden = true;
Body.VerticalSizeMode = SizeMode.Flexible;
Body.HorizontalSizeMode = SizeMode.Flexible;
Body.Layout = Layout.Column();
Body.Layout.Margin = new Margin( hasHeader ? 16 : 4, 4, 0, 4 );
Body.Layout.Spacing = 2;
Layout.Add( Body );
if ( hasHeader )
{
firstOpen = BuildContents;
headerWidget.OnToggled += SetVisible;
if ( open )
headerWidget.Toggle();
}
else
{
BuildContents();
Body.Visible = true;
}
}
Action firstOpen;
public void AddProperty(SerializedProperty prop, bool createWidget = true)
{
var row = PropertySheetRow.Create( prop );
entries.Add( new PropertyEntry(prop) );
}
public void AddProperty(PropertySheetRow row)
{
entries.Add( new PropertyEntry(row.Property) );
Body.Layout.Add( row );
}
public Widget AddBuilder(Func<Widget> builder, string label = "")
{
ArgumentNullException.ThrowIfNull( builder );
entries.Add( new WidgetBuilderEntry( builder, label ) );
Widget widget = builder();
Body.Layout.Add( widget );
return widget;
}
public void ClearProperties()
{
entries.Clear();
}
void SetVisible( bool visible )
{
using var x = SuspendUpdates.For( Parent );
firstOpen?.Invoke();
firstOpen = null;
Body.Hidden = !visible;
UpdateGeometry();
Parent?.UpdateGeometry();
if ( groupCookie is not null )
{
EditorCookie.Set( groupCookie, !visible );
}
}
public void BuildContents()
{
Body.Layout.Clear( true );
foreach ( var entry in entries )
{
Widget row = entry.Build();
if ( row.IsValid() )
{
Body.Layout.Add( row );
}
}
}
RealTimeSince visibilityDebounce = 0;
[EditorEvent.Frame]
public void UpdateVisibility()
{
if ( Parent is null )
return;
if ( visibilityDebounce < 0.2f )
return;
visibilityDebounce = Random.Shared.Float( 0, 0.1f );
Visible = entries.Count > 0;
}
protected override void OnPaint()
{
base.OnPaint();
Color outline = Theme.WindowBackground.Lighten( 0.3f );
if ( hasHeader && !Body.Hidden )
{
Paint.SetBrushAndPen( outline );
{
var r = LocalRect.Shrink( 8, 4, 0, 0 );
r.Width = 3;
Paint.DrawRect( r, 5 );
}
{
var r = LocalRect.Shrink( 10, 0, 0, 0 );
r.Top = r.Bottom - 3;
r.Width = 8;
Paint.DrawRect( r, 5 );
}
}
const float UNDERLINE_HEIGHT = 2f;
var underline = LocalRect;
underline.Top += underline.Height - UNDERLINE_HEIGHT;
underline.Height = UNDERLINE_HEIGHT;
Paint.SetBrushAndPen( outline );
Paint.DrawRect( underline );
}
}
file class GroupHeader : Widget
{
public GroupHeader( Widget parent ) : base( parent )
{
FixedHeight = ControlWidget.ControlRowHeight;
MinimumWidth = FixedHeight;
HorizontalSizeMode = SizeMode.Flexible;
VerticalSizeMode = SizeMode.CanGrow;
Layout = Layout.Row();
Layout.Spacing = 5;
Layout.Margin = new Margin( 16, 0, 0, 0 );
Layout.AddSpacingCell( 10 );
Layout.AddStretchCell();
}
public string Title { get; set; }
protected override void OnMousePress( MouseEvent e )
{
base.OnMousePress( e );
if ( e.Button == MouseButtons.Left )
{
Toggle();
}
}
protected override void OnDoubleClick( MouseEvent e )
{
e.Accepted = false;
}
protected override void OnPaint()
{
var backgroundRect = LocalRect.Shrink( 3, 4, 4, 4 );
backgroundRect.Height = 22 - 8;
//backgroundRect.Right = textRect.Right + 8;
backgroundRect.Width = backgroundRect.Height;
//Log.Info( $"{Title} : {LocalRect}" );
// Background
{
Paint.SetBrushAndPen( Theme.WindowBackground.Lighten( 0.2f ).WithAlphaMultiplied( state ? 1 : 0.5f ) );
Paint.DrawRect( backgroundRect, 6 );
}
Paint.ClearBrush();
Rect iconRect = default;
if ( state )
{
Paint.Pen = Color.White.WithAlpha( 0.2f );
iconRect = Paint.DrawIcon( backgroundRect, "remove", 12, TextFlag.Center );
}
else
{
Paint.Pen = Color.White.WithAlpha( 0.4f );
iconRect = Paint.DrawIcon( backgroundRect, "add", 12, TextFlag.Center );
}
Paint.Pen = Theme.ControlText.WithAlpha( state ? 1 : 0.8f );
Paint.SetFont( "Roboto", 18, 600, sizeInPixels: true);
var textRect = Paint.MeasureText( LocalRect, Title, TextFlag.LeftCenter );
float offset = iconRect.Width + 8f;
textRect.Left += offset;
textRect.Right += offset;
Paint.DrawText( textRect, Title, TextFlag.LeftCenter );
}
bool state = false;
public void Toggle()
{
state = !state;
OnToggled?.Invoke( state );
if ( CookieName is not null )
{
ProjectCookie.Set( CookieName, state );
}
}
public Action<bool> OnToggled;
string _cookieName;
public string CookieName
{
get
{
return _cookieName;
}
set
{
_cookieName = value;
var newState = ProjectCookie.Get<bool>( _cookieName, state );
if ( newState == state ) return;
Toggle();
}
}
}