using Sandbox.Internal; namespace Editor; /// /// The tab widget that sits at the top of the ControlSheet if you add [Features] /// public class FeatureTabWidget : Widget { List Tabs = new List(); Layout TopRowLayout; Layout TabLayout; Layout ContentLayout; string _cookie; public string StateCookie { get => _cookie; set { if ( _cookie == value ) return; _cookie = value; Restore(); } } public FeatureTabWidget( Widget parent ) : base( parent ) { VerticalSizeMode = SizeMode.CanGrow; HorizontalSizeMode = SizeMode.Flexible; Layout = Layout.Column(); Layout.Margin = new Sandbox.UI.Margin( 0, 4, 0, 0 ); TopRowLayout = Layout.AddRow(); TopRowLayout.AddSpacingCell( 8 ); TabLayout = TopRowLayout.AddRow(); { IconButton add = new IconButton( "add", OpenAddFeaturesMenu ); add.Background = Color.Transparent; add.Foreground = Color.White.WithAlpha( 0.6f ); add.ForegroundActive = Theme.Primary; add.IconSize = 15; TopRowLayout.Add( add ); } TopRowLayout.AddStretchCell(); ContentLayout = Layout.AddColumn(); ContentLayout.Margin = new Sandbox.UI.Margin( 0 ); } public void OpenAddFeaturesMenu() { var menu = new ContextMenu( this ); int iAdded = 0; foreach ( var tab in Tabs ) { if ( tab.FeatureEnabled is null ) continue; var state = tab.FeatureEnabled.As.Bool; if ( state ) continue; string icon = null; string title = tab.FeatureEnabled.DisplayName; if ( tab.FeatureEnabled.TryGetAttribute( out var attribute ) ) { icon = attribute.Icon; title = attribute.Title; } menu.AddOption( new Option( title, icon ) { Triggered = tab.SetFeatureEnabled } ); iAdded++; } if ( iAdded == 0 ) { menu.AddOption( new Option( "No features available" ) { Enabled = false } ); } menu.OpenAtCursor(); } void AddTab( FeatureTabOption tab ) { Tabs.Add( tab ); TabLayout.Add( tab ); if ( Tabs.Count == 1 ) { Select( tab ); } } public void Select( FeatureTabOption option ) { foreach ( var tab in Tabs.Where( x => x.IsSelected ) ) { tab.IsSelected = false; tab.HidePage(); } option.IsSelected = true; option.ShowPage( ContentLayout ); Save(); } bool TrySelectIndex( int index ) { if ( index < 0 ) return false; if ( index >= Tabs.Count() ) return false; var tab = Tabs[index]; if ( !tab.Visible ) return false; Select( tab ); return true; } public void OnVisibilityChanged( FeatureTabOption option ) { if ( !option.IsSelected ) return; var idx = Tabs.IndexOf( option ); if ( idx < 0 ) idx = 0; for ( int i = 0; i < Tabs.Count; i++ ) { if ( TrySelectIndex( idx + i ) ) return; if ( TrySelectIndex( idx - i ) ) return; } } private void Save() { if ( string.IsNullOrEmpty( StateCookie ) ) return; var selected = Tabs.FirstOrDefault( x => x.IsSelected ); if ( selected is null ) { EditorCookie.Remove( $"featuretab.{StateCookie}" ); return; } EditorCookie.Set( $"featuretab.{StateCookie}", selected.UniqueId ); } private void Restore() { if ( string.IsNullOrEmpty( StateCookie ) ) return; var pageName = EditorCookie.Get( $"featuretab.{StateCookie}", null ); if ( string.IsNullOrWhiteSpace( pageName ) ) return; var target = Tabs.Where( x => x.UniqueId == pageName ).FirstOrDefault(); if ( target is null ) return; Select( target ); } protected override void OnPaint() { Paint.ClearPen(); Paint.SetBrush( InspectorHeader.BackgroundColor ); Paint.DrawRect( TopRowLayout.OuterRect.Grow( 100, 50, 100, 0 ) ); } internal void AddFeature( IControlSheet.Feature feature ) { string title = feature.Name; string icon = feature.Icon; string desc = feature.Description; Color tint = tint = Theme.GetTint( feature.Tint ); if ( string.IsNullOrEmpty( title ) ) { title = "General"; } var tab = new FeatureTabOption( this, title, icon, desc, tint, this ); tab.Feature = feature; if ( tab.FeatureEnabled is not null && !tab.FeatureEnabled.As.Bool ) { tab.Visible = false; } AddTab( tab ); } } public class FeatureTabOption : Widget { public string Icon { get; set; } public string Description { get; set; } public bool ShowText { get; set; } = true; public int Index { get; set; } public bool IsSelected { get; set; } public Color Tint { get; set; } public IControlSheet.Feature Feature { get; set; } public string UniqueId => Name; /// /// If this feature has an option to enable and disable it, this is it. /// public SerializedProperty FeatureEnabled => Feature.EnabledProperty; Widget Page; FeatureTabWidget Owner; public FeatureTabOption( FeatureTabWidget owner, string name, string icon, string desc, Color tint, Widget parent ) : base( parent ) { Owner = owner; Icon = icon; Name = name; Tint = tint; Description = desc; Cursor = CursorShape.Finger; ToolTip = GetTooltip(); VerticalSizeMode = SizeMode.CanGrow; } protected override Vector2 SizeHint() { var height = 24; if ( !ShowText && !IsSelected ) { return height; } Paint.SetDefaultFont( 8 ); var textSize = Paint.MeasureText( Name ); textSize.x += 16; textSize.y += 8; if ( !string.IsNullOrWhiteSpace( Icon ) ) { textSize.x += 22; } textSize.y = height; return textSize; } protected override void OnMouseClick( MouseEvent e ) { base.OnMouseClick( e ); Owner?.Select( this ); } protected override void OnMousePress( MouseEvent e ) { base.OnMousePress( e ); if ( e.LeftMouseButton ) { Owner?.Select( this ); } } public bool CanFeatureBeRemoved => FeatureEnabled is not null && FeatureEnabled.As.Bool; public void RemoveFeature() { if ( FeatureEnabled is not null ) { FeatureEnabled.As.Bool = false; } UpdateVisibility(); } protected override void OnContextMenu( ContextMenuEvent e ) { Owner?.Select( this ); e.Accepted = true; var menu = new ContextMenu( this ); { var o = new Option( $"Remove {Name} Feature", "close" ); o.Enabled = CanFeatureBeRemoved; o.Triggered = RemoveFeature; menu.AddOption( o ); } { menu.AddSeparator(); menu.AddOption( $"Copy {Name} Properties", "content_copy", () => { ClipboardTools.CopyProperties( Name, Feature.Properties ); } ); var pasteOption = menu.AddOption( $"Paste {Name} Properties", "content_paste", () => { ClipboardTools.PasteProperties( Name, Feature.Properties ); } ); pasteOption.Enabled = ClipboardTools.CanPasteProperties( Name, Feature.Properties ); menu.AddOption( "Reset all to Default", "restart_alt", () => { foreach ( var prop in Feature.Properties ) { prop.SetValue( prop.GetDefault() ); } } ); } if ( FeatureEnabled is not null && CodeEditor.CanOpenFile( FeatureEnabled.SourceFile ) ) { menu.AddSeparator(); var filename = System.IO.Path.GetFileName( FeatureEnabled.SourceFile ); menu.AddOption( $"Jump to code", "code", action: () => CodeEditor.OpenFile( FeatureEnabled.SourceFile, FeatureEnabled.SourceLine ) ); } menu.OpenAtCursor(); } public void UpdateVisibility() { if ( FeatureEnabled is null ) return; bool enabled = FeatureEnabled.As.Bool; if ( Visible == enabled ) return; Visible = enabled; Owner.OnVisibilityChanged( this ); } public void SetFeatureEnabled() { if ( FeatureEnabled is null ) return; if ( FeatureEnabled.As.Bool ) return; FeatureEnabled.As.Bool = true; UpdateVisibility(); Owner.Select( this ); } protected override void OnPaint() { Paint.ClearPen(); Paint.ClearBrush(); Paint.Antialiasing = true; Paint.TextAntialiasing = true; var r = LocalRect; Paint.ClearPen(); float alpha = 1; if ( IsSelected ) { var rr = LocalRect.Shrink( 0, 0, 0, 0 ); var underline = rr; underline.Bottom = underline.Top + 2; Paint.SetBrush( Theme.WidgetBackground.Lighten( 0.2f ) ); Paint.DrawRect( rr.Grow( 0, 0, 0, 0 ), 4 ); Paint.SetBrush( Theme.WidgetBackground ); Paint.DrawRect( rr.Grow( 0, -1, 0, 8 ), 3 ); } else { alpha = 0.3f; if ( Paint.HasMouseOver ) alpha += 0.4f; } if ( !ShowText && !IsSelected ) { Paint.ClearBrush(); Paint.SetPen( Tint.WithAlpha( alpha ) ); Paint.DrawIcon( r, Icon, 12.0f ); return; } Paint.SetDefaultFont( 8 ); Paint.ClearBrush(); Paint.SetPen( Tint.WithAlpha( alpha ) ); if ( string.IsNullOrEmpty( Icon ) ) { Paint.DrawText( r, Name, TextFlag.Center ); } else { r.Left += 12.0f; var nameRect = Paint.DrawText( r.Shrink( 0, 2, 0, 2 ), Name, TextFlag.Center ); r.Left -= nameRect.Width + 24.0f; //r.Bottom += 6; alpha = alpha + 0.2f; alpha = alpha.Clamp( 0, 1 ); Paint.ClearBrush(); Paint.SetPen( Tint.WithAlpha( alpha ) ); Paint.DrawIcon( r, Icon, 13.0f, TextFlag.Center | TextFlag.DontClip ); } } /// /// Hide the page - we've been tabbed away /// internal void HidePage() { if ( Page is null ) return; Page.Hidden = true; } /// /// Convert the Properties into a ControlSheet /// void CreatePage() { var page = new Widget( this ); page.Hidden = true; page.VerticalSizeMode = SizeMode.CanGrow; page.HorizontalSizeMode = SizeMode.Flexible; var sheet = new ControlSheet(); sheet.Margin = 0; IControlSheet.AddProperties( sheet, Feature.Properties.Where( x => x != FeatureEnabled ).ToList(), false ); page.Layout = sheet; Page = page; } /// /// Show the page, create it and add it to the layout if it doesn't already exist /// internal void ShowPage( Layout contentLayout ) { if ( !Page.IsValid() ) { CreatePage(); } contentLayout.Add( Page ); Page.Hidden = false; } string GetTooltip() { var str = ""; str += $"{Name}"; str += ""; if ( !string.IsNullOrWhiteSpace( Description ) ) { str += $"

{Description}"; } return str; } }