Search the source of every open source package.
51 results
using Editor;
using Sandbox;
using Sandbox.Helpers;
using System.Collections.Generic;
using System.Linq;
namespace SFXR.Editor;
[CustomEditor( typeof( List<SFXRSequencer.Note> ) )]
public class SFXRNotesListControlWidget : ControlWidget
{
private SerializedCollection Collection;
private Layout Content;
private Button addButton;
public override bool SupportsMultiEdit => false;
SFXRSequencer Sequencer;
public SFXRNotesListControlWidget( SerializedProperty property )
: base( property )
{
SetSizeMode( SizeMode.Ignore, SizeMode.Ignore );
base.Layout = Layout.Column();
base.Layout.Spacing = 2f;
if ( property.TryGetAsObject( out var obj ) && obj is SerializedCollection collection )
{
if ( property.Parent.Targets.First() is SFXRSequencer sequencer )
{
Sequencer = sequencer;
}
Collection = collection;
Collection.OnEntryAdded = Rebuild;
Collection.OnEntryRemoved = Rebuild;
Content = Layout.Column();
base.Layout.Add( Content );
Layout layout = base.Layout.AddRow();
layout.Margin = 8;
layout.AddStretchCell();
addButton = layout.Add( new Button( "Add Note" )
{
ToolTip = "Add new note",
} );
addButton.MinimumWidth = 200;
addButton.Clicked = () => AddEntry();
layout.AddStretchCell();
Rebuild();
}
}
public void Rebuild()
{
Content.Clear( deleteWidgets: true );
Content.Margin = 0f;
Layout layout = Layout.Column();
layout.Spacing = 2f;
int num = 0;
int count = Collection.Count();
for ( int i = 0; i < count; i++ )
{
var item = Collection.ElementAt( i );
int index = num;
var itemLayout = Layout.Row();
itemLayout.Spacing = 4f;
// try to get object
if ( item.TryGetAsObject( out var obj ) )
{
var thing = new SFXRNoteSheet( obj );
itemLayout.Add( thing );
}
else
{
var thing = ControlWidget.Create( item );
thing.MinimumHeight = 100;
itemLayout.Add( thing );
}
var buttonLayout = Layout.Column();
if ( i > 0 )
{
buttonLayout.Add( new IconButton( "arrow_upward", delegate
{
MoveUp( index );
} )
{
Background = Color.Transparent,
FixedWidth = ControlWidget.ControlRowHeight,
FixedHeight = ControlWidget.ControlRowHeight,
ToolTip = "Move note up"
} );
}
else
{
buttonLayout.AddSpacingCell( 25 );
}
buttonLayout.Add( new IconButton( "delete", delegate
{
RemoveEntry( index );
} )
{
Background = Color.Red,
FixedWidth = ControlWidget.ControlRowHeight,
FixedHeight = ControlWidget.ControlRowHeight,
ToolTip = "Delete note"
} );
if ( i < count - 1 )
{
buttonLayout.Add( new IconButton( "arrow_downward", delegate
{
MoveDown( index );
} )
{
Background = Color.Transparent,
FixedWidth = ControlWidget.ControlRowHeight,
FixedHeight = ControlWidget.ControlRowHeight,
ToolTip = "Move note down"
} );
}
else
{
buttonLayout.AddSpacingCell( 25 );
}
itemLayout.Add( buttonLayout );
layout.Add( itemLayout );
num++;
}
MinimumHeight = 50 + (num * 105);
Content.Add( layout );
Content.Margin = ((num > 0) ? 3 : 0);
}
private void AddEntry()
{
Collection.Add( new SFXRSequencer.Note() );
}
private void RemoveEntry( int index )
{
Collection.RemoveAt( index );
}
private void MoveUp( int index )
{
// Move the index up in Sequencer.Notes list
if ( index > 0 )
{
var note = Sequencer.Notes[index];
Sequencer.Notes.RemoveAt( index );
Sequencer.Notes.Insert( index - 1, note );
}
Rebuild();
}
private void MoveDown( int index )
{
// Move the index down in Sequencer.Notes list
if ( index < Sequencer.Notes.Count - 1 )
{
var note = Sequencer.Notes[index];
Sequencer.Notes.RemoveAt( index );
Sequencer.Notes.Insert( index + 1, note );
}
Rebuild();
}
protected override void OnPaint()
{
}
public void AddEffectDialog( Button source )
{
var s = new SFXREffectTypeSelector( this );
s.OnSelect += ( t ) => AddEffect( t );
s.OpenAt( source.ScreenRect.BottomLeft, animateOffset: new Vector2( 0, -4 ) );
s.FixedWidth = source.Width;
}
void AddEffect( TypeDescription type )
{
if ( !type.TargetType.IsAssignableTo( typeof( SFXREffect ) ) )
{
Log.Error( $"Type {type.TargetType} is not assignable to {typeof( SFXREffect )}" );
return;
}
SFXREffect effect = type.Create<SFXREffect>();
Collection.Add( effect );
Log.Info( effect );
}
}
using Editor;
public static class MyEditorMenu
{
[Menu( "Editor", "CrosshairBuilder/My Menu Option" )]
public static void OpenMyMenu()
{
EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
}
}
global using System;
global using System.Linq;
global using System.Collections.Generic;
global using Editor;
global using Sandbox;
global using PathTool;
global using Application = Editor.Application;
using Editor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.IO;
using Editor;
using Sandbox;
using System.Text;
class SpectogramWidget : Widget
{
private short[] samples;
private int sampleRate;
private List<int> splitPoints = new List<int>();
private int? dragPoint = null;
private Label loadingLabel;
private Label dropLabel;
private bool isLoading = true;
public SoundFile CurrentSound { get; private set; }
public SpectogramWidget(SoundFile soundFile) : base(null)
{
MinimumSize = 100;
MouseTracking = true;
AcceptDrops = true;
loadingLabel = new Label(this);
loadingLabel.Text = "Loading audio data...";
loadingLabel.Visible = false;
dropLabel = new Label(this);
dropLabel.Text = "Drop a sound file here";
dropLabel.SetStyles("font-size: 18px; color: #aaa; text-align: center;");
if (soundFile != null)
{
LoadSound(soundFile);
}
}
public async void LoadSound(SoundFile soundFile)
{
CurrentSound = soundFile;
isLoading = true;
samples = null;
splitPoints.Clear();
loadingLabel.Visible = true;
dropLabel.Visible = false;
await LoadAudioDataAsync(soundFile);
}
private async Task LoadAudioDataAsync(SoundFile soundFile)
{
try
{
await soundFile.LoadAsync();
samples = await soundFile.GetSamplesAsync();
if (samples == null)
{
loadingLabel.Text = "Failed to load audio data";
return;
}
sampleRate = soundFile.Rate;
splitPoints.Add(0);
splitPoints.Add(samples.Length - 1);
loadingLabel.Visible = false;
isLoading = false;
Update();
}
catch (Exception ex)
{
loadingLabel.Text = $"Error loading audio: {ex.Message}";
}
}
protected override void DoLayout()
{
base.DoLayout();
if (loadingLabel != null)
{
loadingLabel.Position = new Vector2(10, Height / 2 - 10);
loadingLabel.Size = new Vector2(Width - 20, 20);
}
if (dropLabel != null)
{
dropLabel.Position = new Vector2(10, Height / 2 - 10);
dropLabel.Size = new Vector2(Width - 20, 20);
}
}
public override void OnDragDrop(DragEvent e)
{
base.OnDragDrop(e);
if (!e.Data.HasFileOrFolder) return;
var asset = AssetSystem.FindByPath(e.Data.FileOrFolder);
if (asset?.AssetType != AssetType.SoundFile) return;
var soundFile = SoundFile.Load(asset.Path);
if (soundFile != null)
{
LoadSound(soundFile);
}
}
public override void OnDragHover(DragEvent e)
{
base.OnDragHover(e);
if (!e.Data.HasFileOrFolder) return;
var asset = AssetSystem.FindByPath(e.Data.FileOrFolder);
if (asset?.AssetType != AssetType.SoundFile) return;
e.Action = DropAction.Link;
}
protected override void OnMouseClick(MouseEvent e)
{
if (isLoading) return;
base.OnMouseClick(e);
if (e.Button == MouseButtons.Left)
{
var samplePos = (int)(e.LocalPosition.x / Width * samples.Length);
var nearPoint = splitPoints.FirstOrDefault(p => Math.Abs(p - samplePos) < (samples.Length / Width * 5));
if (nearPoint != default)
{
dragPoint = splitPoints.IndexOf(nearPoint);
}
else
{
splitPoints.Add(samplePos);
splitPoints.Sort();
Update();
}
}
}
protected override void OnMouseMove(MouseEvent e)
{
if (isLoading) return;
base.OnMouseMove(e);
if (dragPoint.HasValue)
{
var samplePos = (int)(e.LocalPosition.x / Width * samples.Length);
splitPoints[dragPoint.Value] = samplePos;
splitPoints.Sort();
Update();
}
}
protected override void OnMouseReleased(MouseEvent e)
{
if (isLoading) return;
base.OnMouseReleased(e);
dragPoint = null;
}
protected override void OnPaint()
{
base.OnPaint();
if (isLoading || samples == null)
{
return;
}
Paint.ClearPen();
Paint.SetBrush(Theme.Grey.WithAlpha(0.1f));
Paint.DrawRect(LocalRect);
Paint.SetPen(Theme.Blue);
var samplesPerPixel = samples.Length / Width;
for (int x = 0; x < Width; x++)
{
var startSample = (int)(x * samplesPerPixel);
var endSample = Math.Min(startSample + samplesPerPixel, samples.Length);
var max = short.MinValue;
var min = short.MaxValue;
for (int i = startSample; i < endSample; i++)
{
max = Math.Max(max, samples[i]);
min = Math.Min(min, samples[i]);
}
var y1 = Height / 2 + (min / (float)short.MaxValue * Height / 2);
var y2 = Height / 2 + (max / (float)short.MaxValue * Height / 2);
Paint.DrawLine(new Vector2(x, y1), new Vector2(x, y2));
}
Paint.SetPen(Theme.Red);
foreach (var point in splitPoints)
{
var x = point / (float)samples.Length * Width;
Paint.DrawLine(new Vector2(x, 0), new Vector2(x, Height));
}
}
public List<int> GetSplitPoints()
{
return new List<int>(splitPoints);
}
public void SplitCurrentSound(Action<SoundFile> onSoundCreated)
{
if (CurrentSound == null || samples == null) return;
var splitPoints = GetSplitPoints();
if (splitPoints.Count < 2) return;
try
{
var baseFileName = Path.GetFileNameWithoutExtension(CurrentSound.ResourcePath);
var outputDir = Path.Combine(
Project.Current.GetAssetsPath(),
"generated",
$"{baseFileName}_splits"
);
Directory.CreateDirectory(outputDir);
for (int i = 0; i < splitPoints.Count - 1; i++)
{
var start = splitPoints[i];
var end = splitPoints[i + 1];
var length = end - start;
var segmentSamples = new short[length];
Array.Copy(samples, start, segmentSamples, 0, length);
var wavPath = Path.Combine(outputDir, $"{baseFileName}_part_{i + 1}.wav");
using (var writer = new BinaryWriter(File.Create(wavPath)))
{
writer.Write(Encoding.ASCII.GetBytes("RIFF"));
writer.Write(36 + (segmentSamples.Length * 2));
writer.Write(Encoding.ASCII.GetBytes("WAVE"));
writer.Write(Encoding.ASCII.GetBytes("fmt "));
writer.Write(16);
writer.Write((short)1);
writer.Write((short)CurrentSound.Channels);
writer.Write(CurrentSound.Rate);
writer.Write(CurrentSound.Rate * CurrentSound.Channels * 2);
writer.Write((short)(CurrentSound.Channels * 2));
writer.Write((short)16);
writer.Write(Encoding.ASCII.GetBytes("data"));
writer.Write(segmentSamples.Length * 2);
foreach (var sample in segmentSamples)
{
writer.Write(sample);
}
}
var asset = AssetSystem.RegisterFile(wavPath);
if (asset != null)
{
var soundFile = SoundFile.Load(asset.RelativePath);
if (soundFile != null)
{
onSoundCreated?.Invoke(soundFile);
}
}
}
}
catch (Exception ex)
{
Log.Error($"Error splitting sound: {ex.Message}");
}
}
}using Editor;
using Sandbox;
using System.Linq;
namespace SFXR.Editor;
//[CustomEditor( typeof( SFXRComponent ) )]
public class SFXRComponentEditor : ComponentEditorWidget
{
//ParticleFloatControlWidget
public SFXRComponentEditor( SerializedObject obj ) : base( obj )
{
var defaultInspector = new PropertyControlSheet();
defaultInspector.AddObject( obj );
Layout = Layout.Column();
Layout.Add( defaultInspector );
Layout.AddSpacingCell( 5 );
}
}using Sandbox;
using Editor;
using System;
using System.Collections.Generic;
using System.Linq;
namespace SFXR.Editor;
//[CustomEditor( typeof( SFXRFloat ) )]
// public class SFXRFloatControlWidget : ControlWidget
// {
// SerializedObject Target;
// public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
// {
// SetSizeMode( SizeMode.Ignore, SizeMode.Default );
// if ( !property.TryGetAsObject( out Target ) )
// return;
// Layout = Layout.Row();
// Layout.Spacing = 2;
// var value = Target.GetProperty( "Value" );
// FloatControlWidget valueWidget = new FloatControlWidget( value );
// if ( property.TryGetAttribute<RangeAttribute>( out var attribute ) )
// {
// valueWidget.Range = new Vector2( attribute.Min, attribute.Max );
// valueWidget.RangeStep = attribute.Step;
// valueWidget.HasRange = attribute.Slider;
// // if ( attribute.Slider )
// // {
// // sliderWidget = new FloatSlider( this );
// // sliderWidget.HighlightColor = Theme.Grey;
// // sliderWidget.Minimum = attribute.Min;
// // sliderWidget.Maximum = attribute.Max;
// // sliderWidget.Step = attribute.S;
// // sliderWidget.OnValueEdited = delegate
// // {
// // base.SerializedProperty.As.Float = sliderWidget.Value;
// // };
// // }
// }
// Layout.Add( valueWidget );
// var locked = Target.GetProperty( "Locked" );
// IconButton lockedButton = new IconButton( "lock_open" );
// lockedButton.OnClick = () =>
// {
// if ( Target.Targets.First() is SFXRFloat sfxrFloat )
// {
// sfxrFloat.Locked = !sfxrFloat.Locked;
// Log.Info( sfxrFloat.Locked );
// lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
// lockedButton.Update();
// }
// };
// lockedButton.ToolTip = "Lock/Unlock this value";
// Layout.Add( lockedButton );
// }
// protected override void OnPaint()
// {
// }
// }
// public class SFXRFloatControlWidget : FloatControlWidget
// {
// SerializedObject Target;
// public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
// {
// if ( !property.TryGetAsObject( out Target ) )
// return;
// var locked = Target.GetProperty( "Locked" );
// IconButton lockedButton = new IconButton( "lock_open" );
// lockedButton.OnClick = () =>
// {
// // if ( Target.Targets.First() is SFXRFloat sfxrFloat )
// // {
// // sfxrFloat.Locked = !sfxrFloat.Locked;
// // Log.Info( sfxrFloat.Locked );
// // lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
// // lockedButton.Update();
// // }
// };
// lockedButton.ToolTip = "Lock/Unlock this value";
// //Layout.Add( lockedButton );
// }
// protected override void PaintControl()
// {
// if ( Target is null ) return;
// base.PaintControl();
// }
// protected override void OnPaint()
// {
// if ( Target is null ) return;
// base.OnPaint();
// }
// }using Editor;
using Sandbox;
using System.Linq;
namespace SFXR.Editor;
[CustomEditor( typeof( SFXRControls ) )]
public class SFXRSoundControlWidget : ControlWidget
{
SerializedObject Target;
public SFXRSoundControlWidget( SerializedProperty property ) : base( property )
{
if ( !property.TryGetAsObject( out Target ) )
return;
var component = property.Parent.Targets.First() as SFXRComponent;
// Randomize Button
var btnRandomize = new Button( "Randomize All", "casino" );
btnRandomize.Clicked = () =>
{
component.Randomize();
component.PlaySound();
};
btnRandomize.ToolTip = "Randomize all sound properties";
// Play Sound Button
var btnPlaySound = new Button( "Play Sound", "play_arrow" );
btnPlaySound.Clicked = () =>
{
component.PlaySound();
};
btnPlaySound.MinimumWidth = 200;
btnPlaySound.ToolTip = "Play the current sound";
// Mutate Button
var btnMutate = new Button( "Mutate", "shuffle" );
btnMutate.Clicked = () =>
{
component.Mutate();
component.PlaySound();
};
btnMutate.ToolTip = "Mutate all sound properties";
// Randomize Pickup Button
var btnRandomizePickup = new Button( "", "monetization_on" );
btnRandomizePickup.Clicked = () =>
{
component.RandomizePickup();
component.PlaySound();
};
btnRandomizePickup.ToolTip = "Generate random pickup sound";
// Randomize Laser Button
var btnRandomizeLaser = new Button( "", "bolt" );
btnRandomizeLaser.Clicked = () =>
{
component.RandomizeLaser();
component.PlaySound();
};
btnRandomizeLaser.ToolTip = "Generate random laser sound";
// Randomize Explosion Button
var btnRandomizeExplosion = new Button( "", "flare" );
btnRandomizeExplosion.Clicked = () =>
{
component.RandomizeExplosion();
component.PlaySound();
};
btnRandomizeExplosion.ToolTip = "Generate random explosion sound";
// Randomize Powerup Button
var btnRandomizePowerup = new Button( "", "star" );
btnRandomizePowerup.Clicked = () =>
{
component.RandomizePowerup();
component.PlaySound();
};
btnRandomizePowerup.ToolTip = "Generate random powerup sound";
// Randomize Hit Hurt Button
var btnRandomizeHit = new Button( "", "sentiment_very_dissatisfied" );
btnRandomizeHit.Clicked = () =>
{
component.RandomizeHit();
component.PlaySound();
};
btnRandomizeHit.ToolTip = "Generate random hit/hurt sound";
// Randomize Jump Button
var btnRandomizeJump = new Button( "", "settings_accessibility" );
btnRandomizeJump.Clicked = () =>
{
component.RandomizeJump();
component.PlaySound();
};
btnRandomizeJump.ToolTip = "Generate random jump sound";
// Randomize Blip Select Button
var btnRandomizeBlipSelect = new Button( "", "menu" );
btnRandomizeBlipSelect.Clicked = () =>
{
component.RandomizeBlip();
component.PlaySound();
};
btnRandomizeBlipSelect.ToolTip = "Generate random blip/select sound";
Layout = Layout.Column();
Layout.Spacing = 2;
Layout.Margin = new Sandbox.UI.Margin( 0, 4 );
MinimumHeight = 90;
var grid = Layout.Grid();
grid.Spacing = 2;
grid.AddCell( 0, 0, btnRandomizePickup );
grid.AddCell( 1, 0, btnRandomizeLaser );
grid.AddCell( 2, 0, btnRandomizeExplosion );
grid.AddCell( 3, 0, btnRandomizePowerup );
grid.AddCell( 4, 0, btnRandomizeHit );
grid.AddCell( 5, 0, btnRandomizeJump );
grid.AddCell( 6, 0, btnRandomizeBlipSelect );
var randomRow = Layout.Row();
randomRow.Spacing = 2;
randomRow.Add( btnRandomize );
randomRow.Add( btnMutate );
Layout.Add( grid );
Layout.Add( randomRow );
Layout.Add( btnPlaySound );
}
protected override void OnPaint()
{
}
}using Sandbox;
namespace Editor;
[GameResource( "Motivation", "motivate", "Citizens motivate you every 15-30 minutes.", Icon = "support_agent", Category = "Editor", IconBgColor = "#E4E2E4", IconFgColor = "#93BDDD" )]
public class MotivationResource : GameResource
{
[ImageAssetPath]
public string[] Portraits { get; set; }
public string[] Messages { get; set; }
/// <summary>
/// Selects a random citizen portrait from this type.
/// </summary>
/// <returns>Portrait file path</returns>
public string GetPortrait()
{
var portraitPath = Game.Random.FromArray( Portraits );
return FileSystem.Mounted.GetFullPath( portraitPath );
}
/// <summary>
/// Selects a random motivational response from this type.
/// </summary>
/// <returns>Response with linebreaks</returns>
public string GetMessage()
{
return Game.Random.FromArray( Messages );
}
}
using Sandbox;
using Editor;
public class ReconnecterBar : ToolbarGroup
{
[Event("tools.headerbar.build", Priority = 150)]
public static void OnBuildHeaderToolbar(HeadBarEvent e)
{
e.RightCenter.Add(new ReconnecterBar(null));
e.RightCenter.AddSpacingCell(8);
}
public ReconnecterBar(Widget parent) : base(parent, "Reconnecter", null)
{
ToolTip = "Auto Reconnect Clients";
}
public override void Build()
{
AddToggleButton("Auto Reconnect Clients", "autorenew", () => ReconnecterEditor.autoReconnectEnabled, SetAutoReconnect);
AddToggleButton("Allow Instance Launching", "person_add", () => ReconnecterEditor.allowLaunchInstance, SetAllowLaunchInstance);
AddButton("Force Reconnect Clients", "group", ForceAutoReconnect);
}
public void SetAutoReconnect(bool enabled)
{
ReconnecterEditor.autoReconnectEnabled = enabled;
}
public void SetAllowLaunchInstance(bool enabled)
{
ReconnecterEditor.allowLaunchInstance = enabled;
}
public void ForceAutoReconnect()
{
ReconnecterEditor.CreateSessionText(true);
}
}using Sandbox;
using Sandbox.Internal;
using System.Diagnostics;
using System.IO;
public static class ReconnecterEditor
{
public static string packageFolders => $"{Project.Current.Package.Org.Ident}/{Project.Current.Package.Ident}";
public static string dataFolder => Editor.FileSystem.Root.GetFullPath($"/data/{packageFolders}");
public static bool autoReconnectEnabled
{
get
{
return GlobalToolsNamespace.EditorCookie.Get<bool>("reconnecter_enabled", true);
}
set
{
GlobalToolsNamespace.EditorCookie.Set<bool>("reconnecter_enabled", value);
}
}
public static bool allowLaunchInstance
{
get
{
return GlobalToolsNamespace.EditorCookie.Get<bool>("reconnecter_allowLaunchInstance", true);
}
set
{
GlobalToolsNamespace.EditorCookie.Set<bool>("reconnecter_allowLaunchInstance", value);
}
}
static ReconnecterEditor()
{
ReconnecterSystem.RegisterOnRequestWriteSession(CreateSessionText);
}
public static void CreateSessionText(bool force = false)
{
if (!autoReconnectEnabled && !force)
{
return;
}
string filePath = $"{dataFolder}/{ReconnecterSystem.SESSION_FILE_PATH}";
string destinationDirectory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
File.WriteAllText(filePath, System.DateTime.UtcNow.ToString());
//Log.Info($"CreateSessionText() filePath: {filePath}");
}
[Event("scene.play", Priority = int.MinValue)]
public static void ScenePlay()
{
ReconnecterSystem.OnPlayInEditor();
if (!autoReconnectEnabled || !allowLaunchInstance)
{
return;
}
Process[] processes = Process.GetProcessesByName("sbox");
bool isProcessRunning = processes.Length > 0;
if (isProcessRunning)
{
return;
}
SpawnProcess();
}
public static void SpawnProcess()
{
var p = new Process();
p.StartInfo.FileName = "sbox.exe";
p.StartInfo.WorkingDirectory = System.Environment.CurrentDirectory;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.UseShellExecute = false;
p.StartInfo.ArgumentList.Add("-joinlocal");
// This doesn't seem to work because it doesn't use Steam's Launch Options?
p.StartInfo.ArgumentList.Add("-sw");
p.Start();
}
}using System;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Reflection;
using Sandbox;
using System.Collections.Generic;
using System.Linq;
namespace Editor;
public static class SoundAddons
{
private static string previousSoundGenerationPromp;
private static string previousSoundGenerationDuration;
// [ContextMenuFor( typeof( SoundFile ), "Generate sound", "create" )]
public static void Generate( SoundEvent soundEvent, Action<SoundFile> finished )
{
string defaultInput = !string.IsNullOrEmpty( previousSoundGenerationPromp )
? previousSoundGenerationPromp
: "A dog barking";
Dialog.AskString( ( description ) =>
{
previousSoundGenerationPromp = description;
Dialog.AskString( async ( durationStr ) =>
{
previousSoundGenerationDuration = durationStr;
if ( !float.TryParse( durationStr, out float duration ) || duration < 0.5f || duration > 22f )
{
Log.Error( "Duration must be between 0.5 and 22 seconds" );
return;
}
try
{
string apiKey = SoundSettings.Settings.ElevenLabsApiKey;
var client = new HttpClient();
client.DefaultRequestHeaders.Add( "xi-api-key", apiKey );
var content = new StringContent( JsonSerializer.Serialize( new
{
text = description,
duration_seconds = duration,
prompt_influence = 0.3
} ), Encoding.UTF8, "application/json" );
var response = await client.PostAsync(
"https://api.elevenlabs.io/v1/sound-generation",
content
);
if ( response.IsSuccessStatusCode )
{
var bytes = await response.Content.ReadAsByteArrayAsync();
string safeFileName = description.Replace( " ", "_" ).Replace( "/", "_" ).Replace( "\\", "_" );
var mp3Path = Path.Combine(
SoundSettings.Settings.GenerationPath,
$"{safeFileName}_{DateTime.Now:yyyyMMddHHmmss}.mp3"
);
var fullMp3Path = Path.Combine( Project.Current.GetAssetsPath(), mp3Path );
Directory.CreateDirectory( Path.GetDirectoryName( fullMp3Path ) );
await File.WriteAllBytesAsync( fullMp3Path, bytes );
Log.Info( "File saved to: " + fullMp3Path );
var mp3Asset = AssetSystem.RegisterFile( fullMp3Path );
if ( mp3Asset != null )
{
var soundFile = SoundFile.Load( mp3Asset.RelativePath );
if ( soundFile != null )
{
finished(soundFile);
Log.Info( $"Successfully generated and linked sound: {mp3Asset.RelativePath}" );
}
else
{
Log.Error( "Failed to load generated sound file" );
}
}
else
{
Log.Error( "Failed to register generated mp3 file as asset" );
}
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
Log.Error( $"Failed to generate sound: {response.StatusCode}. Error: {errorContent}" );
}
}
catch ( Exception ex )
{
Log.Error( $"Error generating sound: {ex.Message}" );
}
},
"Enter duration in seconds (0.5 to 22):",
"Generate",
"Cancel",
previousSoundGenerationDuration,
"Set Sound Duration" );
},
"Describe the sound effect to generate:",
"Next",
"Cancel",
defaultInput,
"Generate Sound Effect" );
}
// [ContextMenuFor( typeof( SoundFile ), "Split sound", "content_cut" )]
public static async void SplitSound( Widget parent, SerializedProperty property )
{
var soundResource = property.GetValue<SoundFile>();
if ( soundResource == null || !soundResource.IsValid )
{
Log.Error( "No valid sound file selected" );
return;
}
var dialog = new Dialog();
dialog.Window.Title = "Split Sound";
dialog.Window.Size = new Vector2( 800, 400 );
dialog.Layout = Layout.Column();
var mainLayout = dialog.Layout.AddColumn();
mainLayout.Margin = 16f;
var loadingLabel = new Label( "Loading audio data..." );
mainLayout.Add( loadingLabel );
dialog.Show();
var samples = await soundResource.GetSamplesAsync();
if ( samples == null )
{
loadingLabel.Text = "Failed to load audio samples";
return;
}
loadingLabel.Destroy();
var spectogramWidget = new SpectogramWidget( soundResource );
mainLayout.Add( spectogramWidget, 1 );
var controlsLayout = mainLayout.AddRow();
var splitButton = new Button.Primary( "Split", "content_cut" );
controlsLayout.Add( splitButton );
splitButton.Clicked = () =>
{
var splitPoints = spectogramWidget.GetSplitPoints();
if ( splitPoints.Count < 1 ) return;
var listControlWidget = parent.GetAncestor<ListControlWidget>();
if ( listControlWidget == null )
{
Log.Error( "Could not find ListControlWidget parent" );
return;
}
try
{
var baseFileName = Path.GetFileNameWithoutExtension( soundResource.ResourcePath );
var outputDir = Path.Combine(
Project.Current.GetAssetsPath(),
"generated",
$"{baseFileName}_splits"
);
Directory.CreateDirectory( outputDir );
var newSoundFiles = new List<SoundFile>();
for ( int i = 0; i < splitPoints.Count - 1; i++ )
{
var start = splitPoints[i];
var end = splitPoints[i + 1];
var length = end - start;
var segmentSamples = new short[length];
Array.Copy( samples, start, segmentSamples, 0, length );
var wavPath = Path.Combine( outputDir, $"{baseFileName}_part_{i + 1}.wav" );
using ( var writer = new BinaryWriter( File.Create( wavPath ) ) )
{
writer.Write( Encoding.ASCII.GetBytes( "RIFF" ) );
writer.Write( 36 + (segmentSamples.Length * 2) );
writer.Write( Encoding.ASCII.GetBytes( "WAVE" ) );
writer.Write( Encoding.ASCII.GetBytes( "fmt " ) );
writer.Write( 16 );
writer.Write( (short)1 );
writer.Write( (short)soundResource.Channels );
writer.Write( soundResource.Rate );
writer.Write( soundResource.Rate * soundResource.Channels * 2 );
writer.Write( (short)(soundResource.Channels * 2) );
writer.Write( (short)16 );
writer.Write( Encoding.ASCII.GetBytes( "data" ) );
writer.Write( segmentSamples.Length * 2 );
foreach ( var sample in segmentSamples )
{
writer.Write( sample );
}
}
var asset = AssetSystem.RegisterFile( wavPath );
if ( asset != null )
{
var soundFile = SoundFile.Load( asset.RelativePath );
if ( soundFile != null )
{
newSoundFiles.Add( soundFile );
}
}
}
if ( newSoundFiles.Count > 0 )
{
try
{
var collectionField = listControlWidget.GetType()
.GetFields( BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public )
.FirstOrDefault( f => typeof( SerializedCollection ).IsAssignableFrom( f.FieldType ) );
if ( collectionField == null )
{
return;
}
var collection = collectionField.GetValue( listControlWidget ) as SerializedCollection;
if ( collection == null )
{
return;
}
if ( property.TryGetAsObject( out var original ) )
{
foreach ( var soundFile in newSoundFiles )
{
collection.Add( soundFile );
}
collection.Remove( property );
}
}
catch ( Exception ex )
{
Log.Error( $"Error manipulating collection: {ex.Message}" );
Log.Error( $"Stack trace: {ex.StackTrace}" );
}
}
dialog.Close();
}
catch ( Exception ex )
{
Log.Error( $"Error splitting sound: {ex.Message}" );
}
};
}
}using Sandbox;
using Editor;
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox.UI;
namespace SFXR.Editor;
public class SFXRNoteSheet : Widget
{
public SFXRNoteSheet( SerializedObject obj ) : base( null )
{
var propertySheet = new PropertyControlSheet();
propertySheet.AddObject( obj );
Layout = Layout.Column();
Layout.Add( propertySheet );
}
protected override void OnPaint()
{
base.OnPaint();
Color color = Color.FromRgb( 0x111111 );
Paint.SetBrush( color );
Paint.SetPen( color );
Paint.DrawRect( new Rect( 0, 0, Width, Height ), 2 );
}
}
//[CustomEditor( typeof( SFXRFloat ) )]
// public class SFXRFloatControlWidget : ControlWidget
// {
// SerializedObject Target;
// public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
// {
// SetSizeMode( SizeMode.Ignore, SizeMode.Default );
// if ( !property.TryGetAsObject( out Target ) )
// return;
// Layout = Layout.Row();
// Layout.Spacing = 2;
// var value = Target.GetProperty( "Value" );
// FloatControlWidget valueWidget = new FloatControlWidget( value );
// if ( property.TryGetAttribute<RangeAttribute>( out var attribute ) )
// {
// valueWidget.Range = new Vector2( attribute.Min, attribute.Max );
// valueWidget.RangeStep = attribute.Step;
// valueWidget.HasRange = attribute.Slider;
// // if ( attribute.Slider )
// // {
// // sliderWidget = new FloatSlider( this );
// // sliderWidget.HighlightColor = Theme.Grey;
// // sliderWidget.Minimum = attribute.Min;
// // sliderWidget.Maximum = attribute.Max;
// // sliderWidget.Step = attribute.S;
// // sliderWidget.OnValueEdited = delegate
// // {
// // base.SerializedProperty.As.Float = sliderWidget.Value;
// // };
// // }
// }
// Layout.Add( valueWidget );
// var locked = Target.GetProperty( "Locked" );
// IconButton lockedButton = new IconButton( "lock_open" );
// lockedButton.OnClick = () =>
// {
// if ( Target.Targets.First() is SFXRFloat sfxrFloat )
// {
// sfxrFloat.Locked = !sfxrFloat.Locked;
// Log.Info( sfxrFloat.Locked );
// lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
// lockedButton.Update();
// }
// };
// lockedButton.ToolTip = "Lock/Unlock this value";
// Layout.Add( lockedButton );
// }
// protected override void OnPaint()
// {
// }
// }
// public class SFXRFloatControlWidget : FloatControlWidget
// {
// SerializedObject Target;
// public SFXRFloatControlWidget( SerializedProperty property ) : base( property )
// {
// if ( !property.TryGetAsObject( out Target ) )
// return;
// var locked = Target.GetProperty( "Locked" );
// IconButton lockedButton = new IconButton( "lock_open" );
// lockedButton.OnClick = () =>
// {
// // if ( Target.Targets.First() is SFXRFloat sfxrFloat )
// // {
// // sfxrFloat.Locked = !sfxrFloat.Locked;
// // Log.Info( sfxrFloat.Locked );
// // lockedButton.Icon = sfxrFloat.Locked ? "lock" : "lock_open";
// // lockedButton.Update();
// // }
// };
// lockedButton.ToolTip = "Lock/Unlock this value";
// //Layout.Add( lockedButton );
// }
// protected override void PaintControl()
// {
// if ( Target is null ) return;
// base.PaintControl();
// }
// protected override void OnPaint()
// {
// if ( Target is null ) return;
// base.OnPaint();
// }
// }using Editor;
public static class MyEditorMenu
{
[Menu( "Editor", "fuib/My Menu Option" )]
public static void OpenMyMenu()
{
EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
}
}
using System.Collections.Generic;
using System.Linq;
using Editor;
using Editor.ActionGraphs;
using Editor.Widgets;
using Microsoft.VisualBasic;
using Sandbox;
[CustomEditor( typeof( WebsocketTools ) )]
public class WebsocketToolsControlWidget : ControlWidget
{
public WebsocketToolsControlWidget( SerializedProperty property ) : base( property )
{
Layout = Layout.Column();
if ( property.IsNull )
{
property.SetValue( new WebsocketTools() );
}
var serializedObject = property.GetValue<WebsocketTools>()?.GetSerialized();
if ( serializedObject is null ) return;
var controlSheet = new ControlSheet();
controlSheet.AddObject( serializedObject );
Layout.Add( controlSheet );
}
}
/*
[CustomEditor( typeof( JsonTags ) )]
public class JsonTagsControlWidget : ControlWidget
{
public JsonTagsControlWidget( SerializedProperty property ) : base( property )
{
Layout = Layout.Column();
PaintBackground = false;
if ( property.IsNull )
{
property.SetValue( new JsonTags() );
}
var serializedObject = property.GetValue<JsonTags>()?.GetSerialized();
if ( serializedObject is null ) return;
serializedObject.TryGetProperty( nameof( JsonTags.value ), out var value );
serializedObject.TryGetProperty( nameof( JsonTags.tag ), out var tag );
var controlSheet = new ControlSheet();
controlSheet.AddRow( tag );
controlSheet.AddRow( value );
Layout.Add( controlSheet );
}
}
*/
/*
[EditorForAssetType( "message" )]
public class WebsocketMessageEditor : DockWindow, IAssetEditor
{
public WebsocketMessage Message;
public Asset _asset;
public WebsocketMessageEditor()
{
WindowTitle = "Websocket Message Editor";
MinimumSize = new Vector2( 100, 100 );
Size = new Vector2( 500, 500 );
Log.Info( All );
}
public void AssetOpen( Asset asset )
{
Show();
Log.Info( asset.AssetType );
Open( asset.AbsolutePath, asset );
}
public void SelectMember( string memberName )
{
// Implement the logic to select a member here
}
public bool CanOpenMultipleAssets => true; // Or false, depending on your requirements
protected override void RestoreDefaultDockLayout()
{
}
public void Open( string path, Asset asset = null )
{
if ( !string.IsNullOrEmpty( path ) )
{
asset ??= AssetSystem.FindByPath( path );
}
if ( asset is null ) return;
if ( asset == _asset )
{
Focus();
return;
}
var message = asset.LoadResource<WebsocketMessage>();
if ( message is null ) return;
_asset = asset;
Message = message;
var mainWidget = new MainWidget( this );
DockManager.AddDock( null, mainWidget, DockArea.Left, DockManager.DockProperty.HideOnClose );
}
public void Save()
{
if ( _asset is null ) return;
Log.Info( "Saving" );
_asset ??= AssetSystem.RegisterFile( _asset.AbsolutePath );
_asset.SaveToDisk( Message );
}
}
public class MainWidget : Widget
{
public WebsocketMessageEditor Editor { get; set; }
public List<Dictionary<string, string>> dictionaries = new();
public List<DictionaryProperty<string, string>> dictionaryProperties = new();
public int Tags { get; set; } = 0;
public MainWidget( WebsocketMessageEditor editor ) : base( null )
{
Editor = editor;
Name = "Message Editor";
WindowTitle = "Message Editor";
Layout = Layout.Column();
MinimumWidth = 450f;
var serializedObject = Editor.Message?.GetSerialized();
if ( serializedObject is null ) return;
serializedObject.TryGetProperty( nameof( WebsocketMessage.message ), out var message );
var controlSheet = new ControlSheet();
controlSheet.AddRow( message );
Layout.Add( controlSheet );
Layout.Add( new Label( "JSON Tags" ) );
var jsonTagButton = new Button( "Add new JSON Tag" );
jsonTagButton.Clicked += () =>
{
Tags++;
var dictionary = new Dictionary<string, string>();
var property = new DictionaryProperty<string, string>( this );
property.Value = dictionary;
property.Height = 30;
property.Width = 300;
property.SetProperty( "Key", "Tag" + Tags );
dictionaryProperties.Add( property );
dictionaries.Add( dictionary );
Layout.Add( property );
};
Layout.Add( jsonTagButton );
var saveButton = new Button( "Save" );
saveButton.Clicked += () => Save();
Layout.Add( saveButton );
}
private void Save()
{
Editor.Message.jsonTags = dictionaries;
Editor.Save();
}
}*/
using System;
using System.Linq;
using System.Text;
using Editor;
using NPBehave;
using Sandbox.Utils;
namespace Sandbox.BehaviorTreeVisualizer;
public class BehaviorTreeNode : TreeNode<Node>
{
private Color _nodeColor;
Color NodeColor
{
get => _nodeColor;
set
{
if( _nodeColor == value )
return;
_nodeColor = value;
Dirty();
}
}
public BehaviorTreeNode( Node dir ) : base( dir )
{
Height = 40;
}
protected override void BuildChildren()
{
Clear();
if ( Value is Container container )
{
foreach ( var child in container.DebugChildren )
{
AddItem( CreateChildFor( child ) );
}
}
}
protected virtual TreeNode CreateChildFor( Node child ) => new BehaviorTreeNode( child );
public override void OnSelectionChanged( bool state )
{
if ( state )
{
TreeView?.Toggle( this );
}
base.OnSelectionChanged( state );
}
public override bool OnContextMenu()
{
var m = new Editor.Menu( TreeView );
if ( Value.CurrentState == Node.State.Active )
{
m.AddOption( "Stop", action: () =>
{
Value.Stop();
} );
}
else if(Value is Root root && Value.CurrentState == Node.State.Inactive )
{
m.AddOption( "Start", action: () => root.Start() );
}
m.OpenAtCursor( false );
return true;
}
public override int GetHashCode()
{
return HashCode.Combine(Value.GetHashCode(), Value.CurrentState, Value.IsActive, Value.Name, Value.ComputedLabel) ;
}
public override int ValueHash => GetHashCode();
protected override void Think()
{
Color newColor = Theme.Black;
if ( Value.IsActive )
{
newColor = Theme.Green;
}
else if ( Value.DebugLastSuccessAt < 0.5f )
{
newColor = Theme.Green;
}
else if ( Value.DebugLastFailureAt < 0.5f )
{
newColor = Theme.Red;
}
NodeColor = newColor;
base.Think();
}
protected override void RebuildOnDirty()
{
TreeView?.Update();
if(Value is Container container && container.DebugChildren.Length != Children.Count() )
{
BuildChildren();
}
//base.RebuildOnDirty();
}
protected override void OnHashChanged()
{
Dirty();
if(Value is Container container && container.DebugChildren.Length != Children.Count() )
{
BuildChildren();
}
}
public override void OnPaint( VirtualWidget item )
{
var open = item.IsOpen;
var backgroundRect = item.Rect;
backgroundRect.Bottom -= 1;
if ( item.Selected )
{
Paint.SetPen( Theme.Primary.WithAlpha( 0.9f ) );
Paint.SetBrush( NodeColor.WithAlpha( 0.6f ) );
Paint.DrawRect( backgroundRect.Shrink( 2 ) );
}
else if ( item.Hovered )
{
Paint.SetPen( Theme.Primary.WithAlpha( 0.9f ) );
Paint.SetBrush( NodeColor.WithAlpha( 0.7f ) );
Paint.DrawRect( backgroundRect.Shrink( 1 ) );
}
else
{
Paint.ClearPen();
Paint.SetBrush( NodeColor.WithAlpha( 0.7f ) );
Paint.DrawRect( backgroundRect );
}
var rect = backgroundRect.Shrink( 8 );
if ( !string.IsNullOrWhiteSpace( Value.DebugIcon ) )
{
Paint.SetPen( Theme.Yellow.WithAlphaMultiplied( 1.0f) );
var i = Paint.DrawIcon( rect, Value.DebugIcon, 22, TextFlag.LeftCenter );
rect.Left = i.Right + 8;
}
Paint.SetPen( Theme.White.WithAlpha(1.0f));
Paint.SetFont( "Poppins", 12, 450 );
StringBuilder text = new StringBuilder();
if ( !string.IsNullOrWhiteSpace( Value.Label ) )
{
text.Append( $" {Value.Label}" );
}
if( !string.IsNullOrWhiteSpace( Value.ComputedLabel ) )
{
text.Append( $" {Value.ComputedLabel}" );
}
if ( text.Length == 0 )
{
text.Append( Value.Name.AddSpacesToSentence( ) );
}
else
{
text.Append( $" ({Value.Name.AddSpacesToSentence( )})" );
}
var textRect = Paint.DrawText( rect, text.ToString(), TextFlag.LeftCenter );
}
}
using Sandbox;
using System;
using System.Linq;
namespace Editor;
public static class MotivationManager
{
static RealTimeUntil Cooldown;
static MotivationManager()
{
Game.SetRandomSeed( DateTime.Now.Second );
Cooldown = 5;
}
private static bool _hasMotivation => NoticeManager.All.FirstOrDefault( x => x is MotivationNotice ) != null;
private static SoundFile sound = SoundFile.Load("sounds/baka.wav");
[EditorEvent.FrameAttribute]
public static void Frame()
{
if ( NoticeManager.All.Any( x => x.GetType().ToString() == "Editor.CodeCompileNotice" && x is NoticeWidget widget && widget.BorderColor == Theme.Red ) )
{
ShowBaka();
}
else
{
HideBaka();
}
}
private static void ShowBaka()
{
if (_hasMotivation) return;
EditorUtility.PlayRawSound( "sounds/baka.wav" );
var s = new MotivationNotice();
NoticeManager.Remove( s, 30 );
}
private static void HideBaka()
{
if ( _hasMotivation )
{
NoticeManager.Remove( NoticeManager.All.FirstOrDefault( x => x is MotivationNotice ) );
}
}
}
using System;
using System.IO;
using System.Text.Json;
using Editor;
using Sandbox;
public class SoundSettings : Widget
{
public class Config : ConfigData
{
public string ElevenLabsApiKey { get; set; } = "";
public string GenerationPath { get; set; } = "generated";
}
private LineEdit apiKeyInput;
private LineEdit pathInput;
private static Config _settings;
private Button saveButton;
public static Config Settings
{
get
{
if (_settings == null)
_settings = EditorUtility.LoadProjectSettings<Config>("settings.json");
return _settings;
}
}
public SoundSettings() : base(null)
{
Layout = Layout.Column();
var mainLayout = Layout.Add(Layout.Column());
mainLayout.Margin = 16;
var apiKeyGroup = mainLayout.AddRow();
apiKeyGroup.Add(new Label("ElevenLabs API Key:"));
apiKeyInput = new LineEdit(this);
apiKeyInput.MinimumWidth = 300;
apiKeyInput.PlaceholderText = "Enter your ElevenLabs API key";
apiKeyInput.Text = Settings.ElevenLabsApiKey;
apiKeyGroup.Add(apiKeyInput);
mainLayout.AddSpacingCell(8);
var pathGroup = mainLayout.AddRow();
pathGroup.Add(new Label("Generation Path:"));
pathInput = new LineEdit(this);
pathInput.MinimumWidth = 300;
pathInput.PlaceholderText = "Enter generation path (relative to assets)";
pathInput.Text = Settings.GenerationPath;
pathGroup.Add(pathInput);
mainLayout.AddStretchCell();
var buttonLayout = mainLayout.AddRow();
buttonLayout.AddStretchCell();
saveButton = new Button.Primary("Save", "save");
saveButton.Clicked = SaveSettings;
buttonLayout.Add(saveButton);
}
private void SaveSettings()
{
Settings.ElevenLabsApiKey = apiKeyInput.Text;
Settings.GenerationPath = pathInput.Text;
EditorUtility.SaveProjectSettings(Settings, "settings.json");
Dialog.AskConfirm(() => {}, "Settings saved successfully.", "Success", "Okay");
}
[Menu("Editor", "AI Tools/Settings")]
public static void OpenSettings()
{
var dialog = new Dialog();
dialog.Window.Title = "Sound Generation Settings";
dialog.Window.Size = new Vector2(500, 200);
dialog.Layout = Layout.Column();
dialog.Layout.Add(new SoundSettings());
dialog.Show();
}
public static string GetGenerationPath()
{
return Path.Combine(Project.Current.GetAssetsPath(), Settings.GenerationPath);
}
}using Editor;
using Sandbox;
using System.Linq;
namespace TikTokTTS.Editor;
[CustomEditor( typeof( TikTokSpeech ) )]
public class TikTokSpeechComponentEditor : ComponentEditorWidget
{
SerializedObject Target;
public TikTokSpeechComponentEditor( SerializedObject obj ) : base( obj )
{
Target = obj;
Layout = Layout.Column();
var sheet = new PropertyControlSheet();
sheet.AddObject( obj );
Layout.Add( sheet );
Layout.Spacing = 2;
MinimumHeight = 70;
var buttonPanel = Layout.Column();
buttonPanel.Margin = new Sandbox.UI.Margin( 12, 0 );
var button = new Button( "Speak", "play_arrow" )
{
Width = 200,
Clicked = () =>
{
var component = Target.Targets.First() as TikTokSpeech;
component?.Speak();
}
};
buttonPanel.Add( button );
Layout.Add( buttonPanel );
}
protected override void OnPaint()
{
}
}using Sandbox;
using Editor;
namespace SFXR.Editor;
public class PropertyControlSheet : GridLayout
{
public SerializedObject TargetObject { get; set; }
int rows = 0;
public PropertyControlSheet() : base()
{
Margin = new Sandbox.UI.Margin( 16, 8, 16, 8 );
HorizontalSpacing = 10;
VerticalSpacing = 2;
SetColumnStretch( 1, 2 );
SetMinimumColumnWidth( 0, 120 );
}
public void AddObject( SerializedObject obj )
{
foreach ( var entry in obj )
{
if ( !entry.HasAttribute<PropertyAttribute>() )
{
continue;
}
if ( entry.PropertyType.Name.StartsWith( "Action" ) )
{
continue;
}
AddRow( entry );
}
}
/// <summary>
/// Add a serialized property row. This will create an editor for the row and a label.
/// </summary>
public void AddRow( SerializedProperty property, float labelIndent = 0.0f )
{
var editor = ControlWidget.Create( property );
if ( editor is null )
return;
if ( editor.IsWideMode )
{
AddCell( 0, rows++, new Label( property.DisplayName ) { MinimumHeight = Theme.RowHeight, Alignment = TextFlag.LeftCenter }, 2, 1, TextFlag.LeftTop );
var lo = AddCell( 0, rows, Layout.Column(), 2, 1, TextFlag.LeftTop );
lo.Margin = new Sandbox.UI.Margin( 16, 0, 0, 0 );
lo.Add( editor );
}
else
{
int cell = 0;
var label = AddCell( cell++, rows, new Label( property.DisplayName ), 1, 1, TextFlag.LeftTop );
label.MinimumHeight = Theme.RowHeight;
label.Alignment = TextFlag.LeftCenter;
label.SetStyles( "color: #aaa;" );
label.ToolTip = property.Description ?? property.DisplayName;
if ( labelIndent > 0 )
{
label.ContentMargins = new Sandbox.UI.Margin( labelIndent, 0, 0, 0 );
}
AddCell( cell++, rows, editor, 1, 1, TextFlag.LeftTop );
}
rows++;
}
/// <summary>
/// Add a layout to a double wide cell
/// </summary>
public void AddLayout( Layout layout )
{
AddCell( 0, rows++, layout, 2, 1, TextFlag.LeftTop );
}
}using System;
using System.Threading.Tasks;
class LibraryListProxy : ListView
{
public Action<Package> OnLibrarySelected { get; set; }
public bool ShowInstalled { get; set; }
public bool ShowAvailable { get; set; }
public string Filter { get; set; } = "";
public LibraryListProxy( Widget parent ) : base( parent )
{
ItemContextMenu = ShowItemContext;
ItemSelected = OnItemClicked;
ItemSize = new Vector2( -1, 64 );
}
public void OnItemClicked( object value )
{
if ( value is LibraryProject lib )
{
OnLibrarySelected?.Invoke( lib.Project.Package );
}
if ( value is Package p )
{
OnLibrarySelected?.Invoke( p );
}
}
private void ShowItemContext( object obj )
{
LibraryProject project = obj as LibraryProject;
Package package = obj as Package ?? project?.Project.Package;
var m = new Menu();
if ( package is not null && package.Org.Ident != "local" )
{
m.AddOption( "View in Browser", "public", () => EditorUtility.OpenFolder( package.Url ) );
m.AddSeparator();
}
if ( project is not null )
{
m.AddOption( "Project Properties", "tune", () => _ = ProjectSettingsWindow.OpenForProject( project.Project ) );
m.AddSeparator();
m.AddOption( "Publish Project", "upload_file", () => _ = ProjectSettingsWindow.OpenForProject( project.Project, "upload" ) );
m.AddSeparator();
m.AddOption( "Show in Explorer", "folder", () => EditorUtility.OpenFolder( project.Project.RootDirectory.FullName ) );
}
m.OpenAt( Editor.Application.CursorPosition );
}
protected override void PaintItem( VirtualWidget item )
{
if ( item.Object is LibraryProject c )
PaintItem( item, c.Project.Package );
if ( item.Object is Package p )
PaintItem( item, p );
}
private void PaintItem( VirtualWidget item, Package c )
{
var rect = item.Rect;
if ( Paint.HasPressed )
rect = rect.Shrink( 2, 2, 0, 0 );
var library = LibrarySystem.All.Where( x => x.Project.Package.Ident == c.Ident && x.Project.Package.Org.Ident == c.Org.Ident && x.Project.Package.Ident != "libraryimporter").FirstOrDefault();
var pen = Color.White.WithAlpha( 0.6f );
if ( Paint.HasMouseOver || Paint.HasSelected )
{
Paint.SetBrush( Theme.Primary.WithAlpha( Paint.HasMouseOver ? 0.8f : 0.5f ) );
Paint.ClearPen();
Paint.DrawRect( rect, 4 );
pen = Color.White.WithAlpha( Paint.HasMouseOver ? 1 : 0.9f );
Paint.ClearBrush();
}
rect = rect.Shrink( 4 );
// Icon
{
var iconRect = rect;
iconRect.Width = iconRect.Height;
iconRect = iconRect.Shrink( 8 );
Paint.SetBrushAndPen( Theme.Grey.WithAlpha( 0.4f ) );
if ( !string.IsNullOrWhiteSpace( c.Thumb ) && !c.Thumb.StartsWith( "/" ) )
{
Paint.Draw( iconRect, c.Thumb );
}
else
{
Paint.DrawRect( iconRect, 4 );
}
rect.Left = iconRect.Right + 8;
}
//header
{
rect.Top += 8;
Paint.SetFont( "Poppins", 11, 450 );
Paint.Pen = pen;
var r = Paint.DrawText( rect, c.Title, TextFlag.LeftTop );
r.Left = r.Right + 8;
r.Right = rect.Right;
r.Bottom -= 3;
rect.Top = r.Bottom + 4;
var installedVersion = library is not null ? $"v{library?.Version}" : "";
Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
Paint.SetDefaultFont();
Paint.DrawText( r, $"{c.Org.Title} {installedVersion}", TextFlag.LeftBottom );
}
// body text
{
Paint.Pen = pen.WithAlphaMultiplied( 0.6f );
Paint.SetDefaultFont();
Paint.DrawText( rect, c.Summary, TextFlag.LeftTop );
}
}
protected override void OnPaint()
{
Paint.ClearPen();
Paint.SetBrush( Theme.ControlBackground );
Paint.DrawRect( LocalRect );
Paint.Antialiasing = true;
Paint.TextAntialiasing = true;
base.OnPaint();
}
[EditorEvent.Frame]
public void Frame()
{
if ( !Visible ) return;
if ( ShowInstalled )
{
if ( SetContentHash( GetContentHash(), 0.5f ) )
{
SetItems(LibrarySystem.All.Where(x => x.Project.Package.Ident != "libraryimporter"));
SelectItem(LibrarySystem.All.FirstOrDefault(x => x.Project.Package.Ident != "libraryimporter") );
}
}
if ( ShowAvailable )
{
if ( SetContentHash( GetContentHash(), 0.5f ) )
{
_ = UpdateAsync();
}
}
}
async Task UpdateAsync()
{
var search = "";
if ( !string.IsNullOrEmpty( Filter ) ) search = Filter + " ";
var found = await Package.FindAsync( $"{search}type:library sort:updated", 200, 0 );
SetItems( found.Packages );
SelectItem( found.Packages.FirstOrDefault() );
}
int GetContentHash()
{
if ( ShowInstalled )
return LibrarySystem.All.GetHashCode();
return HashCode.Combine( Filter );
}
}
using Editor;
using Sandbox;
using Sandbox.Internal;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using static DressinTerry;
using static Editor.EditorEvent;
using static Sandbox.ClothingContainer;
public static class ImporterEditorMenu
{
public static string dressingTerryDataFolder => $"{Editor.FileSystem.Root.GetFullPath("/data/coomzy/dressin_terry")}";
public static string dressingTerryProjectRootFolder = $"{Project.Current.RootDirectory.FullName}/Assets/dressin_terry";
public static string dressingTerryProjectCharactersFolder = $"{dressingTerryProjectRootFolder}/characters";
[Menu("Editor", "Library/Dressin Terry/Import All Characters to Project")]
public static void OpenMyMenu()
{
var dressingTerryCharactersFolder = $"{dressingTerryDataFolder}/characters";
var filePaths = Directory.EnumerateFiles(dressingTerryCharactersFolder, $"*.{extension}", SearchOption.AllDirectories);
bool hasFailed = false;
foreach (var filePath in filePaths)
{
string relativePath = Path.GetRelativePath(dressingTerryCharactersFolder, filePath);
string destinationPath = Path.Combine(dressingTerryProjectCharactersFolder, relativePath);
string destinationDirectory = Path.GetDirectoryName(destinationPath);
if (!Directory.Exists(destinationDirectory))
{
Directory.CreateDirectory(destinationDirectory);
}
var fileContents = File.ReadAllText(filePath);
var characterContainer = ClothingContainer.CreateFromJson(fileContents);
if (!CreateCharacterFromContainer(characterContainer, destinationPath))
{
hasFailed = true;
Log.Info($"Copied FAILED {filePath} to {destinationPath}");
continue;
}
Log.Info($"Copied {filePath} to {destinationPath}");
}
if (EditorPreferences.NotificationSounds)
{
if (hasFailed)
{
EditorUtility.PlayRawSound("sounds/editor/fail.wav");
}
else
{
EditorUtility.PlayRawSound("sounds/editor/success.wav");
}
}
}
public static bool CreateCharacterFromContainer(ClothingContainer clothingContainer, string destinationPath)
{
Log.Info($"CreateCharacterFromContainer destinationPath: {destinationPath}");
string destinationPathWithoutExtension = destinationPath.Replace(extension, "");
var characterAsset = AssetSystem.CreateResource(extension, destinationPathWithoutExtension);
if (!characterAsset.TryLoadResource<DressinTerryCharacter>(out var character))
{
Log.Error($"characterAsset: {characterAsset} failed to TryLoadResource");
return false;
}
character.clothingEntries = clothingContainer.Clothing.Select(inst => ClothingInst.Convert(inst)).ToList();
character.characterHeight = clothingContainer.Height;
if (!characterAsset.SaveToDisk(character))
{
Log.Error($"characterAsset: {characterAsset} failed to save to disk");
return false;
}
AssetSystem.RegisterFile(destinationPath);
return true;
}
[Event("reload"), Hotload]
static void Register_Event()
{
RegisterOnRequestCreateCharacter(DressinTerry_OnRequestCreateCharacter);
}
static void DressinTerry_OnRequestCreateCharacter(ClothingContainer clothingContainer, string relativeFilePath)
{
string rootFilePath = dressingTerryProjectCharactersFolder;
string desiredFileName = relativeFilePath ?? "Generated";
string fileName = desiredFileName;
string filePath = $"{rootFilePath}/{fileName}.{extension}";
if (File.Exists(filePath))
{
int generatedCount = 0;
do
{
generatedCount++;
fileName = $"{desiredFileName}_{generatedCount}";
filePath = $"{rootFilePath}/{fileName}.{extension}";
}
while (File.Exists(filePath));
}
CreateCharacterFromContainer(clothingContainer, filePath);
}
const string DOCS_LINK = "https://sbox.game/coomzy/dressin_terry/news/how-to-use-6358c4a9";
[Menu("Editor", "Library/Dressin Terry/Documentation")]
public static void OpenDocs()
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(DOCS_LINK) { UseShellExecute = true });
}
catch (System.ComponentModel.Win32Exception)
{
Log.Error($"Could not open the URL: {DOCS_LINK}");
}
}
}using Editor;
using Sandbox;
using System.Linq;
namespace SFXR.Editor;
[CustomEditor( typeof( SFXREnvelope ) )]
public class SFXREnvelopeEditor : ComponentEditorWidget
{
SFXREnvelope Envelope;
public SFXREnvelopeEditor( SerializedObject obj ) : base( obj )
{
var defaultInspector = new PropertyControlSheet();
defaultInspector.AddObject( obj );
Envelope = obj.Targets.First() as SFXREnvelope;
Layout = Layout.Column();
Layout.Add( defaultInspector );
Layout.AddSpacingCell( 5 );
Layout.AddSpacingCell( 90 );
}
protected override void OnPaint()
{
base.OnPaint();
Rect adsrRect = new Rect( 12, 138, Width - 30, 80 );
Paint.SetBrush( Theme.ControlBackground );
Paint.SetPen( Theme.ControlBackground );
Paint.DrawRect( adsrRect, 2 );
Paint.SetPen( Theme.Green, 2 );
Envelope.GetCurve().DrawLine( adsrRect, 3, 0, 1 );
}
}using Editor.MeshEditor;
using Editor.ShaderGraph;
namespace PathTool.Editor;
/// Create a new path
/// Left Click = Add Node
/// Escape = Delete Node
/// Enter = Create Path
[EditorTool]
[Title( "Path" )]
[Icon( "route" )]
[Group( "9" )]
[Order(10000)]
public partial class PathTool : EditorTool
{
public bool InProgress => nodes == null || nodes.Count <= 1;
Vector3 origin;
Type pathType;
Type nodeType;
Path path;
List<PathNode> nodes => path?.Nodes;
PathNode selectedNode;
bool hasDeleted = false;
public PathTool()
{
AllowGameObjectSelection = false;
}
public override void OnEnabled()
{
base.OnEnabled();
if(pathType == null || !pathType.IsAssignableTo( typeof( Path ) ) )
{
SelectPathType( EditorTypeLibrary.GetType<Path>() );
}
AllowGameObjectSelection = false;
Selection.Clear();
Selection.Set( this );
}
public override void OnUpdate()
{
const float NODE_RADIUS = 2.5f;
const float ARROW_WIDTH = 1.5f;
const float ARROW_LENGTH = 4f;
float textSize = 22 * Gizmo.Settings.GizmoScale * Application.DpiScale;
bool nodeSelected = false;
if ( nodes.Count > 0 )
{
using(Gizmo.Scope("Path", origin))
{
for ( int i = 0; i < nodes.Count; i++ )
{
var node = nodes[i];
Vector3 pos = node.Position;
using(Gizmo.Scope($"Node{i}", pos ) )
{
Gizmo.Draw.Color = Color.White;
if(i < nodes.Count - 1)
{
Gizmo.Draw.Arrow( Vector3.Zero, nodes[i + 1].Position - pos, ARROW_LENGTH, ARROW_WIDTH );
}
if(!nodeSelected && Gizmo.Pressed.This)
{
SelectNode( node );
}
if (node == selectedNode)
{
Gizmo.Draw.Color = Color.Green;
if(Gizmo.Control.Position( "NodePosition", Vector3.Zero, out Vector3 delta ))
{
node.Position += Gizmo.Snap(delta, delta);
}
}
Gizmo.Draw.ScreenText( $"{i + 1}", Gizmo.Camera.ToScreen( pos + origin ) + Vector2.Up * NODE_RADIUS * 3, size: textSize );
Gizmo.Draw.SolidSphere( Vector3.Zero, NODE_RADIUS );
Gizmo.Hitbox.Sphere( new Sphere( Vector3.Zero, NODE_RADIUS ) );
}
}
Gizmo.Draw.Color = Color.Yellow;
Gizmo.Draw.Arrow( nodes[nodes.Count - 1].Position, nodes[0].Position, ARROW_LENGTH, ARROW_WIDTH );
}
}
if ( !Gizmo.Pressed.Any && Gizmo.WasLeftMousePressed )
{
CreateNode();
}
if ( Application.FocusWidget is not null )
{
if ( Application.IsKeyDown( KeyCode.Enter ) )
{
var obj = CreatePath();
Selection.Clear();
Selection.Add( obj );
EditorToolManager.CurrentModeName = null;
}
else if(!hasDeleted)
{
if ( Application.IsKeyDown( KeyCode.Escape ) )
{
hasDeleted = true;
RemoveLastNode();
}
else if(selectedNode != null && Application.IsKeyDown(KeyCode.Backspace))
{
hasDeleted = true;
RemoveNode( selectedNode );
}
}
if ( !Application.IsKeyDown( KeyCode.Escape ) )
{
hasDeleted = false;
}
}
}
public override void OnSelectionChanged()
{
base.OnSelectionChanged();
if ( !Selection.OfType<GameObject>().Any() )
{
Selection.Set( this );
return;
}
EditorToolManager.CurrentModeName = "object";
//_finished = true;
}
private void CreateNode()
{
var tr = Trace.UseRenderMeshes( true )
.UsePhysicsWorld( true )
.Run();
if ( !tr.Hit ) return;
Vector3 position = tr.EndPosition;
if ( Gizmo.Settings.SnapToGrid != Gizmo.IsCtrlPressed )
{
position = position.SnapToGrid( Gizmo.Settings.GridSpacing );
}
if(nodes.Count == 0)
{
origin = position;
position = Vector3.Zero;
}
else
{
position = position - origin;
}
PathNode node = EditorTypeLibrary.Create<PathNode>( nodeType );
node.Position = position;
nodes.Add( node );
SelectNode( node );
UpdateStatus();
}
private void SelectNode(PathNode node)
{
//Log.Info( node );
selectedNode = node;
BuildControlSheet();
}
private void RemoveLastNode()
{
if ( nodes.Count > 0 )
{
RemoveNode( nodes[nodes.Count - 1] );
}
}
public void RemoveNode(PathNode node)
{
nodes.Remove( node );
Selection.Clear();
Selection.Set( this );
UpdateStatus();
}
private GameObject CreatePath()
{
GameObject obj = Scene.CreateObject();
obj.Name = "Path";
obj.MakeNameUnique();
obj.Transform.Position = origin;
var comp = obj.Components.Create(EditorTypeLibrary.GetType(pathType));
try
{
comp.GetSerialized().GetProperty( "Nodes" ).SetValue( nodes );
}
catch(Exception e)
{
Log.Info($"Error while serializing: {e.Message}");
}
return obj;
}
private IEnumerable<TypeDescription> GetPathTypes()
{
return EditorTypeLibrary.GetTypes<Path>();
}
}
using System;
using Editor;
using Sandbox;
namespace TikTokTTS.Editor;
public class PropertyControlSheet : GridLayout
{
public SerializedObject TargetObject { get; set; }
int rows = 0;
public PropertyControlSheet() : base()
{
Margin = new Sandbox.UI.Margin( 16, 8, 16, 8 );
HorizontalSpacing = 10;
VerticalSpacing = 2;
SetColumnStretch( 1, 2 );
SetMinimumColumnWidth( 0, 120 );
}
public void AddObject( SerializedObject obj )
{
foreach ( var entry in obj )
{
if ( !entry.HasAttribute<PropertyAttribute>() )
{
continue;
}
if ( entry.PropertyType.Name.StartsWith( "Action" ) )
{
continue;
}
AddRow( entry );
}
}
/// <summary>
/// Add a serialized property row. This will create an editor for the row and a label.
/// </summary>
public void AddRow( SerializedProperty property, float labelIndent = 0.0f )
{
var editor = ControlWidget.Create( property );
if ( editor is null )
return;
if ( editor.IsWideMode )
{
AddCell( 0, rows++, new Label( property.DisplayName ) { MinimumHeight = Theme.RowHeight, Alignment = TextFlag.LeftCenter }, 2, 1, TextFlag.LeftTop );
var lo = AddCell( 0, rows, Layout.Column(), 2, 1, TextFlag.LeftTop );
lo.Margin = new Sandbox.UI.Margin( 16, 0, 0, 0 );
lo.Add( editor );
}
else
{
int cell = 0;
var label = AddCell( cell++, rows, new Label( property.DisplayName ), 1, 1, TextFlag.LeftTop );
label.MinimumHeight = Theme.RowHeight;
label.Alignment = TextFlag.LeftCenter;
label.SetStyles( "color: #aaa;" );
label.ToolTip = property.Description ?? property.DisplayName;
if ( labelIndent > 0 )
{
label.ContentMargins = new Sandbox.UI.Margin( labelIndent, 0, 0, 0 );
}
AddCell( cell++, rows, editor, 1, 1, TextFlag.LeftTop );
}
rows++;
}
/// <summary>
/// Add a layout to a double wide cell
/// </summary>
public void AddLayout( Layout layout )
{
AddCell( 0, rows++, layout, 2, 1, TextFlag.LeftTop );
}
}// using Editor;
// using Sandbox;
// using System.Linq;
// namespace SFXR.Editor;
// [CustomEditor( typeof( SFXREffect ) )]
// public class SFXREffectControlWidget : ControlWidget
// {
// public SFXREffectControlWidget( SerializedProperty property ) : base( property )
// {
// SetSizeMode( SizeMode.Ignore, SizeMode.Ignore );
// if ( !property.TryGetAsObject( out var target ) )
// return;
// // var component = property.Parent.Targets.First() as SFXRComponent;
// // Get the SFXRSound
// //var sound = property.Parent.Targets.First() as SFXRSound;
// Layout = Layout.Column();
// Layout.Spacing = 2;
// Layout.Margin = new Sandbox.UI.Margin( 0, 0 );
// SFXREffect effect = target.Targets.First() as SFXREffect;
// var properties = TypeLibrary.GetPropertyDescriptions( effect.GetType() );
// foreach ( var prop in properties )
// {
// Log.Info( prop );
// }
// }
// public void AddRow( SerializedProperty property, float labelIndent = 0.0f )
// {
// var editor = ControlWidget.Create( property );
// if ( editor is null )
// return;
// Layout.Add( editor );
// }
// }
using Editor;
using Sandbox;
using SceneLoading;
//[CustomEditor(typeof(SceneLoadingClass))]
public sealed class SceneLoadingResourceCustomEditor : ControlWidget
{
public SceneLoadingResourceCustomEditor( SerializedProperty property ) : base( property )
{
Layout = Layout.Column();
if ( property.IsNull )
{
property.SetValue( new SceneLoadingClass() );
}
var so = property.GetValue<SceneLoadingClass>()?.GetSerialized();
if ( so is null ) return;
var controlSheet = new ControlSheet();
controlSheet.AddObject( so );
Layout.Add( controlSheet );
}
}
using Editor;
public static class MyEditorMenu
{
[Menu( "Editor", "Top Down Player Controller/My Menu Option" )]
public static void OpenMyMenu()
{
EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
}
}
namespace PathTool.Editor;
[CustomEditor( typeof( PathNode ) )]
public class PathNodeEditor : ControlWidget
{
SerializedObject obj;
public PathNodeEditor( SerializedProperty property ) : base( property )
{
if ( !property.TryGetAsObject( out obj ) ) return;
TranslucentBackground = true;
ControlSheet properties = new ControlSheet();
properties.AddObject( obj );
Layout = properties;
}
}
using System;
using Editor;
using Sandbox;
using System.Reflection;
using Sandbox.Internal;
using static Editor.EditorEvent;
using System.Collections.Generic;
using System.Linq;
using static Editor.Label;
[Dock("Editor", "Project Settings", "manage_search")]
public class ProjectSettingsDock : Widget
{
NavigationView view;
public ProjectSettingsDock(Widget parent) : base(parent)
{
Layout = Layout.Column();
Layout.Margin = 4;
Layout.Spacing = 4;
view = new NavigationView(this);
view.MinimumSize = 0;
//Layout.Add(View);
view.SetSizeMode(SizeMode.Default, SizeMode.Flexible);
var scroller = new ScrollArea(this);
scroller.Canvas = view;
scroller.Canvas.SetSizeMode(SizeMode.Default, SizeMode.Flexible);
scroller.Canvas.Layout = Layout.Column();
Layout.Add(scroller);
scroller.Canvas.Layout.AddStretchCell();
view.MenuTop.Add(new Label("Project Settings"));
view.MenuTop.AddSpacingCell(16);
Rebuild();
}
[Hotload(Priority = 10)]
public void Rebuild()
{
var lastOptionTitle = view?.CurrentOption?.Title;
view.ClearPages();
var types = GlobalGameNamespace.TypeLibrary.GetTypes<ProjectSettingNonGenericBase>();
var optionTitleToOption = new Dictionary<string, NavigationView.Option>();
types = types.OrderBy(t => t.TargetType.Name);
foreach (var type in types)
{
if (type.IsAbstract)
continue;
var targetType = type.TargetType;
Type constructedType = typeof(ProjectSetting<>).MakeGenericType(targetType);
var instanceProperty = constructedType.GetProperty("instance", BindingFlags.Static | BindingFlags.Public);
var inst = instanceProperty.GetValue(null);
var gameResourceInst = inst as GameResource;
string title = FormatToReadable(targetType.Name);
var option = view.AddPage(title, null, new ProjectSettingsInspector(this, gameResourceInst));
optionTitleToOption[title] = option;
}
// Custom Widget Support
var attributesTypes = GlobalToolsNamespace.EditorTypeLibrary.GetTypesWithAttribute<ProjectSettingsWidgetAttribute>();
foreach (var attributeInst in attributesTypes)
{
if (attributeInst.Type?.TargetType == null)
continue;
bool isWidgetType = attributeInst.Type.TargetType.IsSubclassOf(typeof(Widget));
if (!isWidgetType)
{
Log.Warning($"ProjectSettingsDock failed to create widget instance for '{attributeInst.Type.TargetType}' are you use this inherits from Widget?");
continue;
}
var widgetInst = Activator.CreateInstance(attributeInst.Type.TargetType, this) as Widget;
if (widgetInst == null)
{
Log.Error($"ProjectSettingsDock failed to create instance for type '{attributeInst.Type.TargetType}'");
continue;
}
string title = attributeInst.Attribute.title;
if (string.IsNullOrEmpty(title))
{
title = FormatToReadable(attributeInst.Type.Name);
}
var option = view.AddPage(title, null, widgetInst);
optionTitleToOption[title] = option;
}
// Keeps the select option after a rebuild, this was really annoying me
if (!string.IsNullOrEmpty(lastOptionTitle))
{
if (optionTitleToOption.TryGetValue(lastOptionTitle, out var option))
{
if (option != null)
{
view.CurrentOption = option;
}
}
}
}
public static string FormatToReadable(string input)
{
// Add space before each uppercase letter that is followed by a lowercase letter or another uppercase letter group.
string formattedInput = System.Text.RegularExpressions.Regex.Replace(input, "(?<=.)([A-Z][a-z])", " $1");
// Add space between an acronym and the next capitalized word (e.g., "XMLParser" becomes "XML Parser")
formattedInput = System.Text.RegularExpressions.Regex.Replace(formattedInput, "(?<=[a-z])([A-Z])", " $1");
// Capitalize the first letter of the resulting string
return Char.ToUpper(formattedInput[0]) + formattedInput.Substring(1);
}
}
public class ProjectSettingsInspector : Widget
{
public ProjectSettingsInspector(Widget parent, GameResource gameResource) : base(parent)
{
Layout = Layout.Column();
SetSizeMode(SizeMode.Default, SizeMode.Flexible);
var sheet = new ControlSheet();
var so = gameResource.GetSerialized();
var scroller = new ScrollArea(this);
scroller.Canvas = new Widget(this);
scroller.Canvas.SetSizeMode(SizeMode.Default, SizeMode.Flexible);
scroller.Canvas.Layout = Layout.Column();
Layout.Add(scroller);
so.OnPropertyChanged += x =>
{
var asset = AssetSystem.FindByPath(gameResource.ResourcePath);
if (asset != null)
{
bool saved = asset.SaveToDisk(gameResource);
if (!saved)
{
Log.Error($"ProjectSettingsInspector '{gameResource?.ResourceName}' failed to save, is the file read only? Please report it");
}
}
else
{
Log.Error($"ProjectSettingsInspector '{gameResource?.ResourceName}' failed to get asset, this should not happen! Please report it");
}
};
sheet.AddObject(so);
scroller.Canvas.Layout.Add(sheet);
scroller.Canvas.Layout.AddStretchCell();
}
}
// Can't do because you can't reference library editor projects
/*[AttributeUsage(AttributeTargets.Class)]
public class ProjectSettingsWidgetAttribute : Attribute
{
public string group { get; }
public string title { get; }
public ProjectSettingsWidgetAttribute(string group = null, string title = null)
{
this.group = group;
this.title = title;
}
}*/using System;
using NPBehave;
using Exception = System.Exception;
namespace Sandbox.BehaviorTreeVisualizer;
public class BlackboardProperty : SerializedProperty
{
private string Key { get; set; }
private Blackboard Data { get; set; }
public override string Name => Key;
public override string DisplayName => Key;
public bool HasValue => Data.IsSet( Key );
public object Value => Data.Get( Key );
public override Type PropertyType => Data.Get( Key ).GetType();
public override SerializedObject Parent => Data.GetSerialized();
public BlackboardProperty( string key, Blackboard data )
{
Key = key;
Data = data;
}
public override void SetValue<T>( T value )
{
Data[Key] = value;
}
public override T GetValue<T>( T defaultValue = default(T) )
{
return HasValue ? Data.Get<T>( Key ) : defaultValue;
}
// Not sure if this is correct, just stole it from action graph code
public override bool TryGetAsObject( out SerializedObject obj )
{
obj = null;
var description = EditorTypeLibrary.GetType( PropertyType );
if ( description == null )
{
return false;
}
try
{
if ( !PropertyType.IsValueType )
{
if ( !HasValue )
return false;
obj = EditorTypeLibrary.GetSerializedObject( Value );
return true;
}
obj = EditorTypeLibrary.GetSerializedObject( () => HasValue && Value is not null ? Value : Activator.CreateInstance( PropertyType ),
description, this );
return true;
}
catch ( Exception e )
{
Log.Warning( e );
obj = null;
return false;
}
}
}
using System;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
using static Editor.EditorEvent;
using Sandbox.Internal;
using System.Text.Json.Serialization;
public static class EasySaveChecker
{
[Hotload]
public static void CheckEasySaves()
{
var types = GlobalGameNamespace.TypeLibrary.GetTypes<EasySaveNonGenericBase>();
List<Type> easySaveSubclasses = new List<Type>();
foreach (var type in types)
{
if (type.IsAbstract)
continue;
easySaveSubclasses.Add(type.TargetType);
}
foreach(var easySaveSubclass in easySaveSubclasses)
{
//Log.Info($"easySaveSubclass = {easySaveSubclass.Name}");
var fields = easySaveSubclass.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
foreach (var field in fields)
{
if (field.Name.Contains("k__BackingField"))
{
var propertyName = field.Name.Replace("k__BackingField", string.Empty).Trim('<', '>');
var property = easySaveSubclass.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
if (property != null)
{
var setMethod = property.GetSetMethod(nonPublic: true);
if (setMethod != null && setMethod.IsPrivate)
{
Log.Error($"{easySaveSubclass.Name} contains a property '{field.Name}' with a private setter which will not be serialized by JSON. If this is intentional add the JsonIgnore attribute");
}
}
continue;
}
if (field.IsDefined(typeof(JsonIgnoreAttribute), false))
{
continue;
}
Log.Error($"{easySaveSubclass.Name} contains a field '{field.Name}' which will not be serialized by JSON. If this is intentional add the JsonIgnore attribute");
}
}
}
}
using Sandbox;
using System.Linq;
using static Sandbox.ResourceLibrary;
namespace Editor;
public class MotivationNotice : NoticeWidget
{
public string Portrait { get; init; }
public string Message { get; init; }
public string Bubble => FileSystem.Mounted.GetFullPath( "portraits/bubble.png" );
protected override Vector2 SizeHint() => new( 512, 384 );
public MotivationNotice()
{
var personality = Game.Random.FromArray( GetAll<MotivationResource>().ToArray() );
Portrait = personality.GetPortrait();
Message = personality.GetMessage();
}
protected override void OnPaint()
{
Paint.SetPen( Theme.Black );
Paint.SetDefaultFont( 16 );
var rect = LocalRect.Align( 350, TextFlag.LeftBottom );
Paint.Draw( rect, Portrait );
rect = LocalRect.Align( 250, TextFlag.RightTop );
Paint.Draw( rect, Bubble );
rect = rect.Shrink( 0, 0, 0, 55 );
Paint.DrawText( rect, Message );
}
}
using Editor;
using Editor.Assets;
using Editor.Audio;
using Editor.Inspectors;
using Editor.MapEditor;
using Editor.MeshEditor;
using Editor.ShaderGraph.Nodes;
using Sandbox;
using Sandbox.Audio;
using Sandbox.UI;
using Sandbox.Utility;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static Editor.Inspectors.AssetInspector;
using static Editor.TreeNode;
//TODO: Ignore 1 grid axis when normal surface is offgrid;
//Prefab selection
namespace Editor.PrefabPlacer;
[CanEdit(typeof(PlaceObj))] // Asset file extension, can do typeof(Class) for non-assets
public class PlacerInspector : InspectorWidget
{
PlaceObj placer;
public static Label header;
// If this isn't an Asset Inspector, use CharacterInspector(SerializedObject so) : base(so)
public PlacerInspector( SerializedObject so ) : base( so )
{
if ( so.Targets.FirstOrDefault() is not PlaceObj tool )
return;
placer = tool;
Layout = Layout.Column();
Layout.Margin = 4;
Layout.Spacing = 4;
Rebuild();
}
// Rebuild the UI every hotload, so we catch changes to the Asset
[EditorEvent.Hotload]
void Rebuild()
{
Layout?.Clear( true );
// Create a header
header = Layout.Add( new Label( "Select an object", this ) );
header.SetStyles( "font-size: 38px; font-weight: 500; font-family: Poppins" );
var button = Layout.Add( new Button.Primary( "Open all", "list_alt", this )
{
Clicked = () => { placer.OpenAllFunctions(); }
} );
//button.Pressed = () => PlaceObj.OpenAllFunctions(button.ScreenPosition);
//currentPrefabText = new Label() { Text = "none" };
//window.Layout.Add( currentPrefabText );
Layout.AddSpacingCell( 8 );
var surfaceCheckbox = Layout.Add( new Checkbox( "Align to surface", this ) );
surfaceCheckbox.StateChanged += ( CheckState state ) =>
{
PlaceObj.useSurfaceNormal = state == CheckState.On;
};
var selectCheckbox = Layout.Add( new Checkbox( "Select when placed", this ) );
selectCheckbox.StateChanged += ( CheckState state ) =>
{
PlaceObj.selectOnPlace = state == CheckState.On;
};
var customSwitch = Layout.Add( new Checkbox( "Show only custom prefabs (BROKEN)", this ));
Layout.AddSpacingCell( 8 );
var deltaFloat = Layout.Add( new IntProperty( this ) { HighlightColor = Theme.Yellow, Icon = "height", ToolTip = "Distance from surface", Value = 8 } );
deltaFloat.OnChildValuesChanged += ( Widget w ) => PlaceObj.placeDeltaDistance = deltaFloat.Value;
Layout.AddStretchCell();
}
}
[EditorTool("tools.ketal.prefab-placer")] // this class is an editor tool
[Title( "Object placement tool" )] // title of your tool
[Icon( "dashboard_customize" )]
[Group( "-1" )]
public partial class PlaceObj : EditorTool
{
Vector3 placeOffsetPos;
Rotation normalRot;
public GameObject prefabTemplate;
public static float placeDeltaDistance;
public static bool useSurfaceNormal = false;
public static bool selectOnPlace = false;
public bool showOnlyCustom;
PrefabFile prefabItem;
Widget window;
private Layout ControlLayout { get; set; }
[Shortcut( "tools.ketal.prefab-placer", "Shift+E", typeof( SceneViewportWidget ) )]
public static void ActivateTool()
{
EditorToolManager.SetTool( nameof( PlaceObj ) );
}
public override void OnDisabled()
{
Selection.Clear();
}
public override void OnEnabled()
{
useSurfaceNormal = false;
placeDeltaDistance = 8f;
selectOnPlace = false;
AllowGameObjectSelection = false;
Selection.Clear();
Selection.Set( this );
}
public void OpenAllFunctions()
{
var menu = new Menu();
var prefabs = AssetSystem.All.Where( x => x.AssetType.FileExtension == "prefab" )
.Where( x => x.RelativePath.StartsWith( "templates/gameobject/" ) )
.Select( x => x.LoadResource<PrefabFile>() )
.Where( x => x.ShowInMenu )
.OrderByDescending( x => x.MenuPath.Count( x => x == '/' ) )
.ThenBy( x => x.MenuPath )
.ToArray();
foreach ( var entry in prefabs )
{
menu.AddOption( entry.MenuPath.Split( '/' ), entry.MenuIcon, () =>
{
using var scope = SceneEditorSession.Scope();
prefabItem = entry;
PlacerInspector.header.Text = entry.ResourceName;
PlacerInspector.header.SetStyles( "font-size: 38px; font-weight: 500; font-family: Poppins; color:#B0E24D" );
} );
}
menu.OpenAtCursor();
}
public override void OnUpdate()
{
var tr = Trace
.UseRenderMeshes( true )
.UsePhysicsWorld( true )
.Run();
if ( !tr.Hit )
{
var plane = new Plane( Vector3.Up, 0f );
if ( plane.TryTrace( new Ray( tr.StartPosition, tr.Direction ), out tr.EndPosition, true ) )
{
tr.Hit = true;
tr.Normal = plane.Normal;
}
}
if ( tr.Hit )
{
if ( EditorScene.GizmoSettings.SnapToGrid )
{
tr.EndPosition = tr.EndPosition.SnapToGrid( EditorScene.GizmoSettings.GridSpacing, true, true, true );
}
using ( Gizmo.Scope( "tool", new Transform( tr.EndPosition, Rotation.LookAt( tr.Normal ) ) ) )
{
Gizmo.Draw.Color = Color.Red;
Gizmo.Draw.LineCircle( 0, 0.5f );
Gizmo.Draw.Color = Color.White.WithAlpha( 0.5f );
Gizmo.Draw.LineCircle( 0, 3 );
Gizmo.Draw.Color = Color.White.WithAlpha( 0.3f );
Gizmo.Draw.LineCircle( 0, 6 );
Gizmo.Draw.Color = Color.White.WithAlpha( 0.2f );
Gizmo.Draw.LineCircle( 0, 12 );
Gizmo.Draw.Color = Gizmo.Colors.Blue;
Gizmo.Transform = new Transform( tr.EndPosition + tr.Normal * placeDeltaDistance, Rotation.LookAt( tr.Normal ) );
Gizmo.Draw.LineBBox( BBox.FromPositionAndSize( 0, 4f ) );
if ( Gizmo.HasClicked ) AddObject(prefabItem, tr.EndPosition + tr.Normal * placeDeltaDistance, tr);
}
}
}
//adds an empty gameobject at location, should replace it with library later
void AddObject(PrefabFile entry, Vector3 pos, SceneTraceResult tr)
{
using ( Gizmo.Scope( "tool" ) )
{
if ( entry != null )
{
using var scope = SceneEditorSession.Scope();
var go = SceneUtility.GetPrefabScene( entry )?.Clone();
go.BreakFromPrefab();
go.Name = entry.MenuPath.Split( '/' ).Last();
if ( useSurfaceNormal )
{
go.Transform.Local = new Transform( pos, Rotation.LookAt( tr.Normal ));
Log.Info( tr.Normal );
}
else
{
go.Transform.Local = new Transform( pos, normalRot );
}
if ( selectOnPlace )
{
//EditorToolManager.SetTool( nameof( ObjectEditorTool ) );
Selection.Set( go );
}
}
}
}
}
global using Sandbox;
global using Editor;
global using System.Collections.Generic;
global using System.Linq;
global using Editor;
global using System.Collections.Generic;
global using System.Linq;
global using Sandbox;
using System;
using System.IO;
using Sandbox.Internal;
public static class LibraryImporter
{
//[Event("localaddons.changed")]
public static void CheckCookiePrompt()
{
if (ProjectCookie == null)
{
return;
}
var hasOfferedLibraryImport = ProjectCookie.Get("easysaving.hasOfferedLibraryImport", false);
if (!hasOfferedLibraryImport)
{
ProjectCookie.Set("easysaving.hasOfferedLibraryImport", true);
System.Reflection.MethodInfo saveMethod = ProjectCookie.GetType().GetMethod("Save", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (saveMethod != null)
{
saveMethod.Invoke(ProjectCookie, null);
}
else
{
Log.Error("CookieContainer Method 'Save' not found");
}
OpenMyMenu();
}
}
public static void OpenMyMenu()
{
var confirm = new PopupWindow(
"Import UI from Library? (Easy Saving)",
"UI doesn't work in libraries, so if you want the options screens that comes with this library you need to import it into your project",
"No",
new Dictionary<string, Action>()
{
{ "Yes", () => ImportDisabledLibraryFiles() }
}
);
confirm.Show();
}
[Menu("Editor", "Library/Easy Saving/Import Disabled Library Files")]
public static void ImportDisabledLibraryFiles()
{
string libraryFolder = new System.IO.DirectoryInfo(Editor.FileSystem.Content.GetFullPath("/")).FullName.Replace("Assets", "");
string projectFolder = new System.IO.DirectoryInfo(Editor.FileSystem.ProjectSettings.GetFullPath("/")).FullName.Replace("ProjectSettings", "");
string source = $"{libraryFolder}code";
string target = @$"{projectFolder}code\libraries";
string[] files = Directory.GetFiles(source, "*.DISABLED", SearchOption.AllDirectories);
Log.Info($"Moving .DISABLED files from source: {source}");
foreach (string file in files)
{
string relativePath = Path.GetRelativePath(source, file);
string targetPath = Path.Combine(target, relativePath);
targetPath = RemoveDisabledExtension(targetPath);
Directory.CreateDirectory(Path.GetDirectoryName(targetPath));
if (File.Exists(targetPath))
{
if ((File.GetAttributes(targetPath) & FileAttributes.ReadOnly) != 0)
{
File.SetAttributes(targetPath, FileAttributes.Normal);
}
File.Delete(targetPath);
}
File.Copy(file, targetPath);
Log.Info($"Moved: {file} -> {targetPath}");
}
Log.Info("All files have been moved successfully.");
}
static string RemoveDisabledExtension(string filePath)
{
if (filePath.EndsWith(".DISABLED", StringComparison.OrdinalIgnoreCase))
{
return filePath.Substring(0, filePath.Length - ".DISABLED".Length);
}
return filePath;
}
}
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Editor;
using NPBehave;
using Sandbox.UI;
using Checkbox = Editor.Checkbox;
using Label = Editor.Label;
using Option = Sandbox.UI.Option;
namespace Sandbox.BehaviorTreeVisualizer;
[Dock( "Editor", "Behavior Tree", "list" )]
public class BehaviorTreeWidget : Widget
{
public DropDown? SelectBehaviorTree { get; set; }
public ScrollArea? scroller { get; set; }
private Checkbox ShowBlackboard { get; set; }
public Widget? blackboardWidget { get; set; }
public Widget? treeWidget { get; set; }
protected TreeView? TreeView { get; set; }
GameObject? _lastSelected = null;
Dictionary<PropertyInfo, Root> _behaviorTrees = new();
Root? _selected = null;
public BehaviorTreeWidget(Widget parent) : base( parent )
{
Layout = Layout.Column();
BuildUI();
}
[EditorEvent.Hotload]
private void BuildUI()
{
Layout.Clear( true) ;
if ( _behaviorTrees.Count > 1 )
{
var selection = new ComboBox( this );
foreach (var behaviorTree in _behaviorTrees)
{
selection.AddItem( $"{behaviorTree.Key.DeclaringType} ({behaviorTree.Key.Name}) - {behaviorTree.Value.Name} ({behaviorTree.Value.Label})", onSelected: () =>
{
if ( _selected != behaviorTree.Value )
{
_selected = behaviorTree.Value;
BuildUI();
}
} );
}
if ( _selected != null )
{
var pair = _behaviorTrees.FirstOrDefault( e => e.Value == _selected );
if ( pair.Key != null && pair.Value != null )
selection.TrySelectNamed(
$"{pair.Key.DeclaringType} ({pair.Key.Name}) - {pair.Value.Name} ({pair.Value.Label})" );
}
else
{
_selected = _behaviorTrees.First(e=>e.Value.IsActive).Value;
}
Layout.Add( selection );
}
else
{
if ( _lastSelected.IsValid() && !TryGetBehaviorTree( _lastSelected, out _selected ) )
{
_selected = null;
}
}
if ( _lastSelected.IsValid() && _selected != null)
{
if(_selected != null)
{
scroller = new ScrollArea( this ) {
Canvas = new Widget
{
Layout = Layout.Column(),
VerticalSizeMode = SizeMode.CanGrow,
HorizontalSizeMode = SizeMode.Flexible
}
};
ShowBlackboard = new Checkbox( "Show Blackboard", this ) {
StateChanged = (state =>
{
if ( state == CheckState.On )
{
blackboardWidget = BuildBlackboardUI( _selected.Blackboard );
scroller.Canvas.Layout.Add( blackboardWidget );
}
else if ( state == CheckState.Off )
{
blackboardWidget?.Destroy();
}
} )
};
scroller.Canvas.Layout.Add( ShowBlackboard );
treeWidget = BuildBehaviorTree( _selected );
scroller.Canvas.Layout.Add( treeWidget );
Layout.Add( scroller );
}
else
{
Layout.Add(new Label( "Behavior tree found but null - is the game started?", this ));
}
}
else
{
Layout.Add(new Label( "No behavior tree on selected gameobject", this ));
}
}
private Widget BuildBlackboardUI( Blackboard behaviorBlackboard )
{
Widget widget = new Widget( );
widget.Layout = Layout.Column();
Label blackboardLabel = new Label.Subtitle( "Blackboard values:" );
var ps = new ControlSheet();
behaviorBlackboard.Keys.ForEach( key =>
{
var property = new BlackboardProperty( key, behaviorBlackboard );
ps.AddRow( property );
});
//ps.AddRow( behaviorBlackboard.GetSerialized().GetProperty( "_data" ));
widget.Layout.Add( blackboardLabel );
widget.Layout.Add( ps );
widget.Layout.AddSeparator( true );
return widget;
}
private Widget BuildBehaviorTree( Root behavior )
{
Widget widget = new Widget( );
widget.Layout = Layout.Column();
Label subtitle = new Label.Subtitle( "Tree:" );
widget.Layout.Add( subtitle );
TreeView treeView = new TreeView
{
ExpandForSelection = true
};
treeView.AddItem( new BehaviorTreeNode( behavior ) );
widget.Layout.Add( treeView );
TreeView = treeView;
return widget;
}
bool TryGetBehaviorTree( GameObject go, out Root? behavior)
{
behavior = null;
bool propertyFound = false;
foreach (Component component in go.Components.GetAll())
{
// Using reflection to check if the component have a public property of type Root
var type = component.GetType();
var property = type.GetProperties().FirstOrDefault( e => e.PropertyType == typeof(Root) );
if ( property != null )
{
propertyFound = true;
behavior = property.GetValue( component ) as Root;
if( behavior != null )
return true;
}
}
return propertyFound;
}
Dictionary<PropertyInfo, Root> GetBehaviorTrees( GameObject? go )
{
Dictionary<PropertyInfo, Root> behaviors = new();
if(go == null)
return behaviors;
foreach (Component component in go.Components.GetAll())
{
// Using reflection to check if the component have a public property of type Root
var type = component.GetType();
var property = type.GetProperties().FirstOrDefault( e => e.PropertyType == typeof(Root) );
if ( property != null )
{
if( property.GetValue( component ) is Root behavior )
behaviors.Add( property, behavior );
}
}
return behaviors;
}
[EditorEvent.Frame]
public void CheckForChanges()
{
var activeScene = SceneEditorSession.Active;
if ( activeScene != null )
{
var selected = activeScene.Selection.FirstOrDefault() as GameObject;
if ( selected != _lastSelected)
{
_lastSelected = selected;
_behaviorTrees = GetBehaviorTrees( _lastSelected );
BuildUI();
}
}
}
}
using Foliage;
namespace Editor.FoliagePainter;
/// <summary>
/// Brushes you can use
/// </summary>
public class FoliageList
{
public FoliageResource Selected { get; set; }
public List<FoliageResource> AllFoliage = new();
public FoliageList()
{
LoadAll();
}
public void LoadAll()
{
AllFoliage = FoliageResource.All.ToList();
Selected = AllFoliage.FirstOrDefault();
}
}
public class FoliageType
{
public string Name { get; private set; }
public Texture Texture { get; private set; }
public Pixmap Pixmap { get; private set; }
public void Set( string name )
{
Texture = Texture.Load( FileSystem.Mounted, $"materials/tools/terrain/brushes/{name}.png" );
}
internal static FoliageType LoadFromFile( string filename )
{
var foliageType = new FoliageType();
foliageType.Name = System.IO.Path.GetFileNameWithoutExtension( filename );
foliageType.Texture = Texture.Load( FileSystem.Content, filename );
foliageType.Pixmap = Pixmap.FromFile( FileSystem.Content.GetFullPath( filename ) );
return foliageType;
}
}
namespace PathTool.Editor;
[CanEdit( typeof( PathTool ) )]
public partial class PathToolInspector : InspectorWidget
{
public PathToolInspector( SerializedObject so ) : base( so )
{
if ( so.Targets.FirstOrDefault() is not PathTool tool )
return;
Layout = Layout.Column();
Layout.Add( tool.BuildUI() );
}
}
public partial class PathTool
{
private StatusWidget Header { get; set; }
private Layout ControlLayout { get; set; }
public Widget BuildUI()
{
var widget = new Widget( null );
widget.Layout = Layout.Column();
widget.Layout.Margin = 4;
Header = new StatusWidget( widget );
UpdateStatus();
widget.Layout.Add( Header );
widget.Layout.AddSpacingCell( 8 );
{
var layout = widget.Layout.AddRow();
layout.Spacing = 4;
var pathTypeComboBox = new ComboBox();
pathTypeComboBox.MinimumHeight = 40f;
foreach ( var type in GetPathTypes() )
{
var displayInfo = DisplayInfo.ForType( type.TargetType );
if ( !displayInfo.Browsable ) continue;
pathTypeComboBox.AddItem( displayInfo.Name, displayInfo.Icon ?? "square", () => SelectPathType(type) );
}
layout.Add( pathTypeComboBox, 1 );
}
widget.Layout.AddSpacingCell( 8 );
ControlLayout = widget.Layout.AddColumn();
BuildControlSheet();
widget.Layout.AddSpacingCell( 8 );
widget.Layout.AddStretchCell();
return widget;
}
public void OnEdited( SerializedProperty property )
{
}
private void SelectPathType(TypeDescription t)
{
var target = t.TargetType;
if ( !target.IsAssignableTo( typeof( Path ) ) )
{
Log.Warning( $"Type {t} isnt a path!" );
return;
}
pathType = target;
if(t.IsGenericType)
{
nodeType = target.GetGenericArguments()[0];
}
else
{
nodeType = typeof( PathNode );
}
var nodes = path?.Nodes ?? new List<PathNode>();
path = EditorTypeLibrary.Create<Path>( pathType );
path.Nodes = nodes;
}
private void BuildControlSheet()
{
if ( !ControlLayout.IsValid() )
return;
ControlLayout.Clear( true );
var props = path.GetSerialized().Where( ShowPathProperty );
if(props.Any())
{
var pathHeader = new Label( "Properties" )
{
Margin = 2f
};
var pathSheet = new ControlSheet();
foreach(var prop in props)
{
pathSheet.AddRow( prop );
}
ControlLayout.Add( pathHeader );
ControlLayout.Add( pathSheet );
}
if ( selectedNode != null )
{
var selectedHeader = new Label( "Selected Node" )
{
Margin = 2f
};
var so = selectedNode.GetSerialized();
so.OnPropertyChanged += OnEdited;
var selectedSheet = new ControlSheet();
selectedSheet.AddObject( so );
ControlLayout.Add( selectedHeader );
ControlLayout.Add( selectedSheet );
}
}
private static readonly string[] ignoredProperties = new string[] { "Enabled", "Nodes" };
private bool ShowPathProperty(SerializedProperty prop)
{
if ( !EditorTypeLibrary.TryGetType( prop.PropertyType, out var type ) )
return false;
var description = EditorTypeLibrary.GetType( prop.Parent.TypeName ).GetProperty( prop.Name );
if ( description == null ) return false;
bool show = prop.IsEditable && description.IsGetMethodPublic && description.IsSetMethodPublic &&
!ignoredProperties.Contains(prop.Name);
if(show)
{
Log.Info($"Prop: {prop.Name} ({type})");
}
return show;
}
private void UpdateStatus()
{
Header.Text = $"{(InProgress ? "Placing" : "Create")} Path";
Header.LeadText = InProgress ? "Place nodes to create a path." : "Press Enter to complete the path.";
Header.Color = InProgress ? Theme.Blue : Theme.Green;
Header.Icon = InProgress ? "check_circle_outline" : "view_in_ar";
Header.Update();
}
private class StatusWidget : Widget
{
public string Icon { get; set; }
public string Text { get; set; }
public string LeadText { get; set; }
public Color Color { get; set; }
public StatusWidget( Widget parent ) : base( parent )
{
MinimumSize = 48;
SetSizeMode( SizeMode.Default, SizeMode.CanShrink );
}
protected override void OnPaint()
{
var rect = new Rect( 0, Size );
Paint.ClearPen();
Paint.SetBrush( Theme.Black.Lighten( 0.9f ) );
Paint.DrawRect( rect );
rect.Left += 8;
Paint.SetPen( Color );
var iconRect = Paint.DrawIcon( rect, Icon, 24, TextFlag.LeftCenter );
rect.Top += 8;
rect.Left = iconRect.Right + 8;
Paint.SetPen( Color );
Paint.SetDefaultFont( 10, 500 );
var titleRect = Paint.DrawText( rect, Text, TextFlag.LeftTop );
rect.Top = titleRect.Bottom + 2;
Paint.SetPen( Color.WithAlpha( 0.6f ) );
Paint.SetDefaultFont( 8, 400 );
Paint.DrawText( rect, LeadText, TextFlag.LeftTop );
}
}
}
using Editor;
public static class MyEditorMenu
{
[Menu( "Editor", "Viper's Citzen Controller/My Menu Option" )]
public static void OpenMyMenu()
{
EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
}
}
using Foliage;
namespace Editor.FoliagePainter;
using Editor;
using Sandbox;
using System;
using System.IO;
using System.Linq;
[Sandbox.CustomEditor( typeof( FoliageRenderer.OptionsWidget ) )]
public class FoliageRendererButtons : ControlWidget
{
FoliageRenderer FoliageManager { get; set; }
public FoliageRendererButtons( Sandbox.SerializedProperty property ) : base( property )
{
FoliageManager = property.Parent.Targets.First() as FoliageRenderer;
Layout = Layout.Column();
Layout.Spacing = 2;
Rebuild();
}
[EditorEvent.Hotload]
void Rebuild()
{
Layout.Clear( true );
{
var button = new Button( this );
button.Text = "Delete All Foliage";
button.ToolTip = "Removes all foliage from this renderer";
button.Clicked += ResetResource;
Layout.Add( button );
}
{
var button = new Button( this );
button.Text = "Update Foliage";
button.ToolTip = "Update all foliage renderers if a model was changed in a .fol asset";
button.Clicked += UpdateResource;
Layout.Add( button );
}
}
void ResetResource()
{
FoliageManager.ClearAll();
Rebuild();
}
void UpdateResource()
{
FoliageManager.UpdateRenderers();
}
}
using System;
using Foliage;
namespace Editor.FoliagePainter;
public class FoliageSettings
{
[Property, Range( 8, 512 )] public int Size { get; set; } = 50;
[Property, Range( 0, 100 )] public float PaintSpeed { get; set; } = 5;
[Property, ResourceType(".fol")] public FoliageResource Foliage { get; set; }
[Property] public bool EraseOnlySelectedFoliage { get; set; } = true;
}
public class FoliageSettingsWidgetWindow : WidgetWindow
{
class FoliageSelectedWidget : Widget
{
public FoliageSelectedWidget( Widget parent ) : base( parent )
{
MinimumSize = new( 48, 48 );
Cursor = CursorShape.Finger;
}
protected override void OnMouseClick( MouseEvent e )
{
var popup = new PopupWidget( null );
popup.Position = Application.CursorPosition;
popup.Visible = true;
popup.Layout = Layout.Column();
popup.Layout.Margin = 10;
popup.MaximumSize = new Vector2( 300, 150 );
}
}
public FoliageSettingsWidgetWindow( Widget parent, SerializedObject so ) : base( parent, "Foliage Settings" )
{
Layout = Layout.Row();
Layout.Margin = 4;
MaximumWidth = 300.0f;
var cs = new ControlSheet();
cs.AddRow( so.GetProperty( nameof( FoliageSettings.Size ) ) );
cs.AddRow( so.GetProperty( nameof( FoliageSettings.PaintSpeed) ) );
cs.AddRow( so.GetProperty( nameof( FoliageSettings.Foliage ) ) );
cs.AddRow( so.GetProperty( nameof( FoliageSettings.EraseOnlySelectedFoliage ) ) );
cs.SetMinimumColumnWidth( 0, 50 );
cs.Margin = new Sandbox.UI.Margin( 8, 0, 4, 0 );
var text = Layout.Column( );
text.Add( new Label.Body( "LMB = paint" ) );
text.Add( new Label.Body( "shift+LMB = erase" ));
text.Alignment = TextFlag.LeftBottom;
text.Margin = new Sandbox.UI.Margin( 16, 6, 4, 0 );
var l = Layout.Column();
l.Add( cs );
l.Add( text );
Layout.Add( l );
}
}
using Editor;
using Sandbox;
using System.Linq;
namespace SFXR.Editor;
[CustomEditor( typeof( SFXRSequencerControls ) )]
public class SFXRSequencerControlWidget : ControlWidget
{
SerializedObject Target;
public SFXRSequencerControlWidget( SerializedProperty property ) : base( property )
{
if ( !property.TryGetAsObject( out Target ) )
return;
var component = property.Parent.Targets.First() as SFXRSequencer;
Layout = Layout.Column();
Layout.Spacing = 2;
Layout.Margin = new Sandbox.UI.Margin( 0, 4 );
MinimumHeight = 70;
Layout.Add( new Button( "Play Sequence", "play_arrow" )
{
Width = 200,
Clicked = () =>
{
component.PlaySequence();
}
} );
Layout.Add( new Button( "Stop Sequence", "stop" )
{
Width = 200,
Clicked = () =>
{
component.StopSequence();
}
} );
}
protected override void OnPaint()
{
}
}using System;
using System.Reflection;
using Editor;
using Sandbox;
using System.Linq;
using System.Collections.Generic;
using static Editor.EditorEvent;
using Sandbox.Internal;
using System.IO;
public static class ProjectSettingCreator
{
[Hotload(Priority = -10)]
public static void CreateProjectSettingFiles()
{
var types = GlobalGameNamespace.TypeLibrary.GetTypes<ProjectSettingNonGenericBase>();
foreach(var type in types)
{
if (type.IsAbstract)
continue;
var targetType = type.TargetType;
var instance = Activator.CreateInstance(targetType) as GameResource;
Type constructedType = typeof(ProjectSetting<>).MakeGenericType(targetType);
var filePathProperty = constructedType.GetProperty("fullFilePathWithoutExtension", BindingFlags.Static | BindingFlags.Public);
var fileExtensionProperty = constructedType.GetProperty("fileExtension", BindingFlags.Static | BindingFlags.Public);
string filePath = filePathProperty != null ? filePathProperty.GetValue(null) as string : null;
string fileExtension = fileExtensionProperty != null ? fileExtensionProperty.GetValue(null) as string : null;
if (string.IsNullOrEmpty(filePath) || string.IsNullOrEmpty(fileExtension))
{
Log.Error($"Could not retrieve filePath or fileExtension for '{targetType}'");
continue;
}
var targetDirectory = Path.GetDirectoryName(filePath);
if (!Directory.Exists(targetDirectory))
{
Directory.CreateDirectory(targetDirectory);
}
AssetSystem.CreateResource(fileExtension, filePath);
AssetSystem.RegisterFile($"{filePath}.{fileExtension}");
}
}
}
using Editor;
public static class MyEditorMenu
{
[Menu( "Editor", "playercontroller/My Menu Option" )]
public static void OpenMyMenu()
{
EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
}
}
using Editor;
using Sandbox;
using Sandbox.Helpers;
using System.Collections.Generic;
using System.Linq;
namespace SFXR.Editor;
[CustomEditor( typeof( List<SFXREffect> ) )]
public class SFXREffectsListControlWidget : ControlWidget
{
private SerializedCollection Collection;
private Layout Content;
private Button addButton;
public override bool SupportsMultiEdit => false;
public SFXREffectsListControlWidget( SerializedProperty property )
: base( property )
{
SetSizeMode( SizeMode.Ignore, SizeMode.Ignore );
base.Layout = Layout.Column();
base.Layout.Spacing = 2f;
if ( property.TryGetAsObject( out var obj ) && obj is SerializedCollection collection )
{
Collection = collection;
Collection.OnEntryAdded = Rebuild;
Collection.OnEntryRemoved = Rebuild;
Content = Layout.Column();
base.Layout.Add( Content );
Layout layout = base.Layout.AddRow();
layout.Margin = 8;
layout.AddStretchCell();
addButton = layout.Add( new Button( "Add Effect" )
{
ToolTip = "Add new effect",
} );
addButton.MinimumWidth = 200;
addButton.Clicked = () => AddEffectDialog( addButton );
layout.AddStretchCell();
Rebuild();
}
}
public void Rebuild()
{
Content.Clear( deleteWidgets: true );
Content.Margin = 0f;
Layout layout = Layout.Column();
layout.Spacing = 2f;
int num = 0;
foreach ( SerializedProperty item in Collection )
{
int index = num;
var itemLayout = Layout.Row();
var thing = ControlWidget.Create( item );
thing.MinimumHeight = 120;
itemLayout.Add( thing );
itemLayout.Add( new IconButton( "remove", delegate
{
RemoveEntry( index );
} )
{
Background = Color.Transparent,
FixedWidth = ControlWidget.ControlRowHeight,
FixedHeight = ControlWidget.ControlRowHeight
} );
layout.Add( itemLayout );
num++;
}
Content.Add( layout );
Content.Margin = ((num > 0) ? 3 : 0);
}
private void AddEntry()
{
Collection.Add( null );
}
private void RemoveEntry( int index )
{
Collection.RemoveAt( index );
}
protected override void OnPaint()
{
// Paint.Antialiasing = true;
// Paint.ClearPen();
// Paint.SetBrush( Theme.ControlText.Darken( 0.6f ) );
// if ( Collection.Count() > 0 )
// {
// Rect rect = Content.OuterRect;
// Paint.DrawRect( in rect, 2f );
// Vector2 point = addButton.Position;
// Vector2 size = addButton.Size;
// rect = new Rect( in point, in size );
// float left = 0f;
// float top = 8f;
// float right = 0f;
// float bottom = 0f;
// rect = rect.Grow( in left, in top, in right, in bottom );
// Paint.DrawRect( in rect, 2f );
// }
// else
// {
// Vector2 point = addButton.Position;
// Vector2 size = addButton.Size;
// Rect rect = new Rect( in point, in size );
// float left = 0f;
// float top = 0f;
// float right = 0f;
// float bottom = 0f;
// rect = rect.Grow( in left, in top, in right, in bottom );
// Paint.DrawRect( in rect, 2f );
// }
}
public void AddEffectDialog( Button source )
{
var s = new SFXREffectTypeSelector( this );
s.OnSelect += ( t ) => AddEffect( t );
s.OpenAt( source.ScreenRect.BottomLeft, animateOffset: new Vector2( 0, -4 ) );
s.FixedWidth = source.Width;
}
void AddEffect( TypeDescription type )
{
if ( !type.TargetType.IsAssignableTo( typeof( SFXREffect ) ) )
{
Log.Error( $"Type {type.TargetType} is not assignable to {typeof( SFXREffect )}" );
return;
}
SFXREffect effect = type.Create<SFXREffect>();
Collection.Add( effect );
}
}
using System;
namespace Editor.FoliagePainter;
using Foliage;
[EditorTool] // this class is an editor tool
[Title( "Foliage" )] // title of your tool
[Icon( "grass" )] // icon name from https://fonts.google.com/icons?selected=Material+Icons
public class FoliagePainter : EditorTool
{
private float PaintProgress { get; set; } = 0f;
public FoliageSettings FoliageSettings { get; private set; } = new();
public override void OnEnabled()
{
AllowGameObjectSelection = false;
if ( GetSelectedComponent<FoliageRenderer>() == null )
{
Selection.Clear();
var first = Scene.GetAllComponents<FoliageRenderer>().FirstOrDefault();
if ( first is not null ) Selection.Add( first.GameObject );
}
var foliageSettings = new FoliageSettingsWidgetWindow( SceneOverlay, EditorUtility.GetSerializedObject( FoliageSettings ) );
AddOverlay( foliageSettings, TextFlag.RightBottom, 10 );
}
private bool Painted { get; set; } = false;
private void Paint(SceneTraceResult tr)
{
var paintTarget = GetSelectedComponent<FoliageRenderer>();
if ( paintTarget == null ) { return; }
var randomPos =
new Vector3( Random.Shared.Float( FoliageSettings.Size * -1, FoliageSettings.Size ),
Random.Shared.Float( FoliageSettings.Size * -1, FoliageSettings.Size ), FoliageSettings.Size ).RotateAround( Vector3.Zero,
Rotation.LookAt( tr.Normal ) * Rotation.FromPitch( 90f ) );
randomPos = randomPos.ClampLength( FoliageSettings.Size );
var paintTr = Scene.Trace.Ray( new Ray( tr.HitPosition + randomPos,Rotation.LookAt( tr.Normal ) * Rotation.FromPitch( 90f ).Down ), FoliageSettings.Size+2 )
.UseRenderMeshes( true )
.UsePhysicsWorld( false )
.WithoutTags( "invisible" )
.Run();
if ( paintTr.Hit )
{
if ( 1-((paintTr.Normal.Dot( new Vector3( 0, 0, 1 ) ) + 1f) / 2f) > FoliageSettings.Foliage.MaxNormal / 180 )
{
return;
}
var randomRot =
Rotation.FromYaw( FoliageSettings.Foliage.RandomRoll ? Random.Shared.Float( 0, 360 ) : 0 );
if ( FoliageSettings.Foliage.RandomRotation )
{
randomRot = Rotation.Random;
}
var transform = new Transform( paintTr.HitPosition+(tr.Normal* (FoliageSettings.Foliage.ZOffset.GetValue())) );
if ( FoliageSettings.Foliage.AlignToNormal )
{
transform = transform.WithRotation( Rotation.LookAt( paintTr.Normal )*Rotation.FromPitch( 90f )*randomRot );
}
else
{
transform = transform.WithRotation( randomRot );
}
transform = transform.WithScale(
FoliageSettings.Foliage.Scale.GetValue());
paintTarget.PaintFoliage( FoliageSettings.Foliage ,transform );
Painted = true;
}
}
public override void OnUpdate()
{
var tr = Scene.Trace.Ray( Gizmo.CurrentRay, 5000 )
.UseRenderMeshes( true )
.UsePhysicsWorld( false )
.WithoutTags( "invisible" )
.Run();
if ( tr.Hit )
{
using ( Gizmo.Scope( "cursor" ) )
{
Gizmo.Transform = new Transform( tr.HitPosition, Rotation.LookAt( tr.Normal ) );
Gizmo.Draw.LineCircle( 0, FoliageSettings.Size );
}
if ( Gizmo.IsLeftMouseDown && Application.KeyboardModifiers.HasFlag( Sandbox.KeyboardModifiers.Shift ) )
{
var paintTarget = GetSelectedComponent<FoliageRenderer>();
if ( paintTarget == null ) { return; }
if ( FoliageSettings.EraseOnlySelectedFoliage )
{
paintTarget.EraseFoliage( tr.HitPosition, FoliageSettings.Size, FoliageSettings.Foliage.ResourceId );
}
else
{
paintTarget.EraseFoliage( tr.HitPosition, FoliageSettings.Size, 0 );
}
Painted = true;
}
if ( !Gizmo.IsLeftMouseDown && Painted )
{
Painted = false;
SceneEditorSession.Active.FullUndoSnapshot("Paint Grass");
var paintTarget = GetSelectedComponent<FoliageRenderer>();
if ( paintTarget == null ) { return; }
paintTarget.UpdateRenderers();
}
if ( Gizmo.IsLeftMouseDown && !Application.KeyboardModifiers.HasFlag( Sandbox.KeyboardModifiers.Shift ))
{
if ( GetSelectedComponent<FoliageRenderer>() == null )
{
Selection.Clear();
var first = Scene.GetAllComponents<FoliageRenderer>().FirstOrDefault();
if ( first is not null ) Selection.Add( first.GameObject );
return;
}
if ( FoliageSettings.Foliage == null ) { return;}
PaintProgress += Time.Delta * FoliageSettings.PaintSpeed * 3f;
if ( PaintProgress >= 1 )
{
for ( int i = 0; i < (int)PaintProgress.Floor(); i++ )
{
Paint( tr );
}
PaintProgress = 0;
}
}
}
}
}
using System.Linq;
using Editor;
using Sandbox;
public static class AIToolsMenu
{
[Menu( "Editor", "AI Tools/Generate sound" )]
public static void Generate()
{
if (SoundSettings.Settings.ElevenLabsApiKey.Length < 1)
{
Dialog.AskConfirm(() => {}, "Set your ElevenLabs API key first in settings.", "Error", "Okay");
return;
}
var inspectorObject = EditorUtility.InspectorObject as Asset;
if (inspectorObject == null)
{
Dialog.AskConfirm(() => {}, "Open SoundEvent in Inspector first.", "Error", "Okay");
return;
}
var resource = inspectorObject.LoadResource();
if (resource == null)
{
Dialog.AskConfirm(() => {}, "Failed to load resource.", "Error", "Okay");
return;
}
if (!(resource is SoundEvent))
{
Dialog.AskConfirm(() => {}, "Selected asset must be a SoundEvent.", "Error", "Okay");
return;
}
var soundEvent = resource as SoundEvent;
SoundAddons.Generate(soundEvent, (soundFile) => {
soundEvent.Sounds.Add(soundFile);
EditorUtility.InspectorObject = null;
EditorUtility.InspectorObject = inspectorObject;
});
}
[Menu("Editor", "AI Tools/Split sound")]
public static void Split()
{
var asset = EditorUtility.InspectorObject as Asset;
var soundEvent = asset.LoadResource() as SoundEvent;
var dialog = new Dialog();
dialog.Window.Title = "Split Sound";
dialog.Window.Size = new Vector2(800, 400);
dialog.Layout = Layout.Column();
var mainLayout = dialog.Layout.AddColumn();
mainLayout.Margin = 16f;
var spectogramWidget = new SpectogramWidget(null);
mainLayout.Add(spectogramWidget, 1);
var controlsLayout = mainLayout.AddRow();
var splitButton = new Button.Primary("Split", "content_cut");
controlsLayout.Add(splitButton);
splitButton.Clicked = () =>
{
if (spectogramWidget.CurrentSound == null)
{
Log.Error("No sound loaded to split");
return;
}
spectogramWidget.SplitCurrentSound((soundFile) => {
soundEvent.Sounds.Add(soundFile);
EditorUtility.InspectorObject = null;
EditorUtility.InspectorObject = asset;
});
};
dialog.Show();
}
}
using Sandbox;
using Editor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace SFXR.Editor;
/// <summary>
/// A popup dialog to select an entity type
/// </summary>
internal partial class SFXREffectTypeSelector : PopupWidget
{
public Action<TypeDescription> OnSelect { get; set; }
List<EffectSelection> Panels { get; set; } = new();
int CurrentPanelId { get; set; } = 0;
Widget Main { get; set; }
string searchString;
const string NoCategoryName = "Uncategorized";
internal LineEdit Search { get; init; }
public SFXREffectTypeSelector( Widget parent ) : base( parent )
{
Layout = Layout.Column();
var head = Layout.Row();
head.Margin = 6;
Layout.Add( head );
Main = new Widget( this );
Main.Layout = Layout.Row();
Layout.Add( Main );
FixedWidth = 200;
MaximumHeight = 300;
DeleteOnClose = true;
Search = new LineEdit( this );
Search.MinimumHeight = 22;
Search.PlaceholderText = "Search..";
Search.TextEdited += ( t ) =>
{
searchString = t;
ResetSelection();
};
head.Add( Search );
ResetSelection();
Search.Focus();
}
/// <summary>
/// Pushes a new selection to the selector
/// </summary>
/// <param name="selection"></param>
void PushSelection( EffectSelection selection )
{
CurrentPanelId++;
// Do we have something at our new index, if so, kill it
if ( Panels.Count > CurrentPanelId && Panels.ElementAt( CurrentPanelId ) is var existingObj ) existingObj.Destroy();
Panels.Insert( CurrentPanelId, selection );
Main.Layout.Add( selection, 1 );
if ( !selection.IsManual )
{
UpdateSelection( selection );
}
AnimateSelection( true, Panels[CurrentPanelId - 1], selection );
selection.Focus();
}
/// <summary>
/// Pops the current selection off
/// </summary>
internal void PopSelection()
{
// Don't pop while empty
if ( CurrentPanelId == 0 ) return;
var currentIdx = Panels[CurrentPanelId];
CurrentPanelId--;
AnimateSelection( false, currentIdx, Panels[CurrentPanelId] );
Panels[CurrentPanelId].Focus();
}
/// <summary>
/// Runs an animation on the last selection, and the current selection.
/// I kinda hate this. A lot. But it's pretty.
/// </summary>
/// <param name="forward"></param>
/// <param name="prev"></param>
/// <param name="selection"></param>
void AnimateSelection( bool forward, EffectSelection prev, EffectSelection selection )
{
const string easing = "ease-out";
const float speed = 0.3f;
var distance = Width;
var prevFrom = prev.Position.x;
var prevTo = forward ? prev.Position.x - distance : prev.Position.x + distance;
var selectionFrom = forward ? selection.Position.x + distance : selection.Position.x;
var selectionTo = forward ? selection.Position.x : selection.Position.x + distance;
var func = ( EffectSelection a, float x ) =>
{
a.Position = a.Position.WithX( x );
OnMoved();
};
Animate.Add( prev, speed, prevFrom, prevTo, x => func( prev, x ), easing );
Animate.Add( selection, speed, selectionFrom, selectionTo, x => func( selection, x ), easing );
}
/// <summary>
/// Resets the current selection, useful when setting up / searching
/// </summary>
protected void ResetSelection()
{
Main.Layout.Clear( true );
Panels.Clear();
var selection = new EffectSelection( this, this );
CurrentPanelId = 0;
UpdateSelection( selection );
Panels.Add( selection );
Main.Layout.Add( selection );
}
protected override void OnPaint()
{
Paint.Antialiasing = true;
Paint.SetPen( Theme.WidgetBackground.Darken( 0.4f ), 1 );
Paint.SetBrush( Theme.WidgetBackground );
Paint.DrawRect( LocalRect.Shrink( 1 ), 3 );
}
/// <summary>
/// Called when a category is selected
/// </summary>
/// <param name="category"></param>
void OnCategorySelected( string category )
{
// Push this as a new selection
PushSelection( new EffectSelection( this, this, category ) );
}
/// <summary>
/// Called when an individual effect is selected
/// </summary>
/// <param name="type"></param>
void OnEffectSelected( TypeDescription type )
{
OnSelect( type );
Destroy();
}
protected override void OnKeyRelease( KeyEvent e )
{
if ( e.Key == KeyCode.Down )
{
var selection = Panels[CurrentPanelId];
if ( selection.ItemList.FirstOrDefault() != null )
{
selection.Focus();
selection.PostKeyEvent( KeyCode.Down );
e.Accepted = true;
}
}
}
/// <summary>
/// Updates any selection
/// </summary>
/// <param name="selection"></param>
void UpdateSelection( EffectSelection selection )
{
selection.Clear();
selection.ItemList.Add( selection.CategoryHeader );
// entity components
var types = EditorTypeLibrary.GetTypes<SFXREffect>().Where( x => !x.IsAbstract );
if ( !string.IsNullOrWhiteSpace( searchString ) )
{
var searchWords = searchString.Split( ' ', StringSplitOptions.RemoveEmptyEntries );
var query = types.Where( x =>
searchWords.All( word => x.Title.Contains( word, StringComparison.OrdinalIgnoreCase ) )
);
foreach ( var type in query )
{
selection.AddEntry( new EffectEntry( selection, type ) { MouseClick = () => OnEffectSelected( type ) } );
}
selection.AddStretchCell();
return;
}
if ( selection.Category == null )
{
var categories = types.Select( x => string.IsNullOrWhiteSpace( x.Group ) ? NoCategoryName : x.Group ).Distinct().OrderBy( x => x ).ToArray();
if ( categories.Length > 1 )
{
foreach ( var category in categories )
{
selection.AddEntry( new EffectCategory( selection )
{
Category = category,
MouseClick = () => OnCategorySelected( category ),
} );
}
selection.AddStretchCell();
return;
}
}
else
{
types = types.Where( x => selection.Category == NoCategoryName ? x.Group == null : x.Group == selection.Category ).OrderBy( x => x.Title );
foreach ( var type in types )
{
selection.AddEntry( new EffectEntry( selection, type ) { MouseClick = () => OnEffectSelected( type ) } );
}
selection.AddStretchCell();
}
}
/// <summary>
/// A widget that contains a given selection - we hold this in a class because more than one can exist.
/// </summary>
partial class EffectSelection : Widget
{
internal string Category { get; init; }
internal Widget CategoryHeader { get; init; }
ScrollArea Scroller { get; init; }
SFXREffectTypeSelector Selector { get; set; }
internal List<Widget> ItemList { get; private set; } = new();
internal int CurrentItemId { get; private set; } = 0;
internal Widget CurrentItem { get; private set; }
internal bool IsManual { get; set; }
internal EffectSelection( Widget parent, SFXREffectTypeSelector selector, string categoryName = null ) : base( parent )
{
Selector = selector;
Category = categoryName;
FixedWidth = 200;
MinimumHeight = 220;
Layout = Layout.Column();
CategoryHeader = new Widget( this );
CategoryHeader.FixedHeight = 24;
CategoryHeader.OnPaintOverride = PaintHeader;
CategoryHeader.MouseClick = Selector.PopSelection;
Layout.Add( CategoryHeader );
Scroller = new ScrollArea( this );
Scroller.Layout = Layout.Column();
Scroller.FocusMode = FocusMode.None;
Layout.Add( Scroller, 1 );
Scroller.Canvas = new Widget( Scroller );
Scroller.Canvas.Layout = Layout.Column();
}
protected bool SelectMoveRow( int delta )
{
var selection = Selector.Panels[Selector.CurrentPanelId];
if ( delta == 1 && selection.ItemList.Count - 1 > selection.CurrentItemId )
{
selection.CurrentItem = selection.ItemList[++selection.CurrentItemId];
selection.Update();
if ( selection.CurrentItem != null )
{
Scroller.MakeVisible( selection.CurrentItem );
}
return true;
}
else if ( delta == -1 )
{
if ( selection.CurrentItemId > 0 )
{
selection.CurrentItem = selection.ItemList[--selection.CurrentItemId];
selection.Update();
if ( selection.CurrentItem != null )
{
Scroller.MakeVisible( selection.CurrentItem );
}
return true;
}
else
{
selection.Selector.Search.Focus();
selection.CurrentItem = null;
selection.Update();
return true;
}
}
return false;
}
protected bool Enter()
{
var selection = Selector.Panels[Selector.CurrentPanelId];
if ( selection.ItemList[selection.CurrentItemId] is Widget entry )
{
entry.MouseClick?.Invoke();
return true;
}
return false;
}
protected override void OnKeyRelease( KeyEvent e )
{
// Move down
if ( e.Key == KeyCode.Down )
{
e.Accepted = true;
SelectMoveRow( 1 );
return;
}
// Move up
if ( e.Key == KeyCode.Up )
{
e.Accepted = true;
SelectMoveRow( -1 );
return;
}
// Back button while in any selection, goes to previous selction.
if ( e.Key == KeyCode.Left )
{
e.Accepted = true;
Selector.PopSelection();
return;
}
// Moving right, or hitting the enter key assumes you're trying to select something
if ( (e.Key == KeyCode.Return || e.Key == KeyCode.Right) && Enter() )
{
e.Accepted = true;
return;
}
}
internal bool PaintHeader()
{
var c = CategoryHeader;
var selected = c.IsUnderMouse || CurrentItem == c;
Paint.ClearPen();
Paint.SetBrush( selected ? Theme.Selection : Theme.WidgetBackground.WithAlpha( selected ? 0.7f : 0.4f ) );
Paint.DrawRect( c.LocalRect );
var r = c.LocalRect.Shrink( 12, 2 );
Paint.SetPen( Theme.ControlText );
if ( Selector.CurrentPanelId > 0 )
{
Paint.DrawIcon( r, "arrow_back", 14, TextFlag.LeftCenter );
}
Paint.SetDefaultFont( 8 );
Paint.DrawText( r, string.IsNullOrEmpty( Category ) ? "Effect" : Category, TextFlag.Center );
return true;
}
/// <summary>
/// Adds a new entry to the current selection.
/// </summary>
/// <param name="entry"></param>
internal Widget AddEntry( Widget entry )
{
var layoutWidget = Scroller.Canvas.Layout.Add( entry );
ItemList.Add( entry );
if ( entry is EffectEntry e ) e.Selector = this;
return layoutWidget;
}
/// <summary>
/// Adds a stretch cell to the bottom of the selection - good to call this when you know you're done adding entries.
/// </summary>
internal void AddStretchCell()
{
Scroller.Canvas.Layout.AddStretchCell( 1 );
Update();
}
/// <summary>
/// Adds a separator cell.
/// </summary>
internal void AddSeparator()
{
Scroller.Canvas.Layout.AddSeparator( true );
Update();
}
/// <summary>
/// Clears the current selection
/// </summary>
internal void Clear()
{
Scroller.Canvas.Layout.Clear( true );
ItemList.Clear();
}
protected override void OnPaint()
{
Paint.Antialiasing = true;
Paint.SetPen( Theme.WidgetBackground.Darken( 0.8f ), 1 );
Paint.SetBrush( Theme.WidgetBackground.Darken( 0.2f ) );
Paint.DrawRect( LocalRect.Shrink( 0 ), 3 );
}
}
/// <summary>
/// An effect entry
/// </summary>
class EffectEntry : Widget
{
public string Text { get; set; } = "My Effect";
public string Icon { get; set; } = "note_add";
public bool IsSelected { get; set; } = false;
internal EffectSelection Selector { get; set; }
public TypeDescription Type { get; init; }
internal EffectEntry( Widget parent, TypeDescription type = null ) : base( parent )
{
FixedHeight = 24;
Type = type;
if ( type is not null )
{
Text = type.Title;
Icon = type.Icon;
}
}
protected override void OnPaint()
{
var r = LocalRect.Shrink( 12, 2 );
var selected = IsUnderMouse || Selector.CurrentItem == this;
var opacity = selected ? 1.0f : 0.7f;
if ( selected )
{
Paint.ClearPen();
Paint.SetBrush( Theme.Selection );
Paint.DrawRect( LocalRect );
}
if ( Type is not null && !string.IsNullOrEmpty( Type.Icon ) )
{
Type.PaintComponentIcon( new Rect( r.Position, r.Height ).Shrink( 2 ), opacity );
}
else
{
Paint.SetPen( Theme.Green.WithAlpha( opacity ) );
var icon = !string.IsNullOrEmpty( Icon ) ? Icon : "note_add";
Paint.DrawIcon( new Rect( r.Position, r.Height ).Shrink( 2 ), icon, r.Height, TextFlag.Center );
}
r.Left += r.Height + 6;
Paint.SetDefaultFont( 8 );
Paint.SetPen( Theme.ControlText.WithAlpha( selected ? 1.0f : 0.5f ) );
Paint.DrawText( r, Text, TextFlag.LeftCenter );
}
}
/// <summary>
/// A category effect entry
/// </summary>
class EffectCategory : EffectEntry
{
public string Category { get; set; }
public EffectCategory( Widget parent ) : base( parent ) { }
protected override void OnPaint()
{
var selected = IsUnderMouse || Selector.CurrentItem == this;
if ( selected )
{
Paint.ClearPen();
Paint.SetBrush( Theme.Selection );
Paint.DrawRect( LocalRect );
}
var r = LocalRect.Shrink( 12, 2 );
Paint.SetPen( Theme.ControlText.WithAlpha( selected ? 1.0f : 0.5f ) );
Paint.SetDefaultFont( 8 );
Paint.DrawText( r, Category, TextFlag.LeftCenter );
Paint.DrawIcon( r, "arrow_forward", 14, TextFlag.RightCenter );
}
}
}
using Editor;
using System;
using System.IO;
namespace Editor.LibraryManager;
[Dock( "Editor", "Library Importer", "extension" )]
public class LibraryImporterDock : Widget
{
internal Action<string> OnValueChanged;
public LibraryImporterDock( Widget parent ) : base( parent )
{
Layout = Layout.Row();
Layout.Margin = 4;
Layout.Spacing = 4;
FocusMode = FocusMode.TabOrClickOrWheel;
}
[EditorEvent.Hotload]
void Rebuild()
{
Layout.Clear( true );
Layout.Add( new InstalledLibrariesWidgetProxy( this ) );
}
static void CopyFolder( string sourceDir, string targetDir, string libraryName, string libraryFolder )
{
if ( sourceDir.Contains( "\\.", StringComparison.OrdinalIgnoreCase ) )
{
return;
}
System.IO.Directory.CreateDirectory( targetDir );
foreach ( var file in Directory.GetFiles( sourceDir ) )
{
CopyAndProcessFile( file, targetDir, libraryName, libraryFolder );
}
foreach ( var directory in Directory.GetDirectories( sourceDir ) )
{
CopyFolder( directory, Path.Combine( targetDir, Path.GetFileName( directory ) ), libraryName, libraryFolder );
}
}
static void CopyAndProcessFile( string file, string targetDir, string libraryName, string libraryFolder )
{
var targetname = Path.Combine( targetDir, Path.GetFileName( file ) );
// Replace $ident with our ident in file name
targetname = targetname.Replace( "$title", libraryName );
targetname = targetname.Replace( "$ident", libraryFolder );
if ( file.EndsWith( ".cs" ) || file.EndsWith( ".json" ) || file.EndsWith( ".sbproj" ) )
{
var txt = System.IO.File.ReadAllText( file );
txt = txt.Replace( "$title", libraryName );
txt = txt.Replace( "$ident", libraryFolder );
System.IO.File.WriteAllText( targetname, txt );
}
else
{
File.Copy( file, targetname );
}
}
[EditorEvent.Frame]
public void Frame()
{
if ( SetContentHash( ContentHash, 0.5f ) )
{
Rebuild();
}
}
int ContentHash() => HashCode.Combine( 0 );
}
public class InstalledLibrariesWidgetProxy : Widget
{
Layout ContentLayout;
public InstalledLibrariesWidgetProxy( LibraryImporterDock manager )
{
Layout = Layout.Row();
var left = new LibraryListProxy( null )
{
ShowInstalled = true
};
Layout.Add( left, 1 );
ContentLayout = Layout.AddColumn();
left.OnLibrarySelected = ( library ) =>
{
ContentLayout.Clear( true );
ContentLayout.Add( new LibraryDetailProxy( library ) );
};
manager.OnValueChanged = ( txt ) =>
{
left.Filter = txt;
};
}
}