Editor/DefaultProviders.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Crux.Editor.Common;
using Editor;
using Sandbox;
public class AssetSearchProvider : ISearchProvider
{
private string GetIcon( AssetType assetType )
{
if ( assetType == AssetType.Model )
{
return "view_in_ar";
}
if ( assetType == AssetType.Material )
{
return "palette";
}
if ( assetType == AssetType.Animation )
{
return "play_arrow";
}
return "insert_drive_file";
}
public IEnumerable<SearchResult> Search( string query )
{
foreach ( var asset in AssetSystem.All )
{
if ( asset.AssetType == AssetType.Texture || asset.AssetType == AssetType.ImageFile ) continue;
if ( !FuzzyMatcher.FuzzyMatch( query, asset.Name, out int score ) )
continue;
if ( asset.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
score += 100;
if ( asset.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
score += 50;
yield return new SearchResult
{
Title = asset.Name,
Subtitle = asset.RelativePath,
Icon = GetIcon( asset.AssetType ),
Score = score,
OnSelect = () => asset.OpenInEditor(),
Type = asset.AssetType.ToString()
};
}
}
}
public class SceneObjectsProvider : ISearchProvider
{
public IEnumerable<SearchResult> Search( string query )
{
var scene = SceneEditorSession.Active?.Scene;
if ( scene is null ) yield break;
foreach ( var go in scene.GetAllObjects( true ) )
{
if ( !FuzzyMatcher.FuzzyMatch( query, go.Name, out int score ) )
{
continue;
}
if ( go.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
score += 100;
if ( go.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
score += 50;
yield return new SearchResult
{
Title = go.Name,
Subtitle = string.Join( ", ", go.Components.GetAll().Select( c => c.GetType().Name ) ),
Icon = "gamepad",
Score = score,
OnSelect = () => SceneEditorSession.Active.Selection.Set( go ),
Type = "SceneObject"
};
}
}
}
public class ComponentSearchProvider : ISearchProvider
{
public IEnumerable<SearchResult> Search( string query )
{
foreach ( var type in TypeLibrary.GetTypes<Component>() )
{
if ( !FuzzyMatcher.FuzzyMatch( query, type.Name, out int score ) )
continue;
if ( !string.IsNullOrEmpty( type.Namespace ) && type.Namespace.Contains( "Sandbox" ) ) continue;
if ( type.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
score += 100;
if ( type.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
score += 50;
yield return new SearchResult
{
Title = type.Name,
Subtitle = type.Namespace ?? "",
Icon = "code",
Score = score,
OnSelect = () =>
{
CodeEditor.OpenFile( type.SourceFile, type.SourceLine );
},
Type = "Component"
};
}
}
}
public class EditorActionProvider : ISearchProvider
{
public IEnumerable<SearchResult> Search( string query )
{
var seen = new HashSet<string>();
foreach ( var entry in EditorShortcuts.Entries )
{
if ( !seen.Add( entry.Identifier ) )
continue;
if ( !FuzzyMatcher.FuzzyMatch( query, entry.Name, out int score ) )
continue;
if ( entry.Name.Contains( query, StringComparison.OrdinalIgnoreCase ) )
score += 100;
if ( entry.Name.StartsWith( query, StringComparison.OrdinalIgnoreCase ) )
score += 50;
yield return new SearchResult
{
Title = entry.Name,
Subtitle = entry.DisplayKeys,
Type = entry.Group,
Icon = "keyboard_command_key",
Score = score,
OnSelect = () =>
{
var id = entry.Identifier;
IndexerPopup.PendingAction = () =>
{
foreach ( var e in EditorShortcuts.Entries )
{
if ( e.Identifier != id ) continue;
if ( e.Invoke() ) break;
}
};
}
};
}
}
}
public class MathProvider : ISearchProvider
{
public IEnumerable<SearchResult> Search( string query )
{
if ( !query.Any( char.IsDigit ) )
return Enumerable.Empty<SearchResult>();
var result = TryEvaluate( query );
if ( result == null )
return Enumerable.Empty<SearchResult>();
var text = result.Value.ToString( "G" );
return new[]
{
new SearchResult
{
Title = $"{query} = {text}",
Subtitle = "Copy to clipboard",
Type = "Math",
Icon = "calculate",
Score = 10000,
OnSelect = () => EditorUtility.Clipboard.Copy( text )
}
};
}
// Note: Unfortunately System.Data.DataTable seems to be blocked in s&box (only when I tried to pull from the library manager)
// So this requires some manual labor :(
private double? TryEvaluate( string expr )
{
try
{
expr = expr.Replace( " ", "" );
var pos = 0;
var result = ParseExpression( expr, ref pos );
return pos == expr.Length ? result : null;
}
catch
{
return null;
}
}
private double ParseExpression( string expr, ref int pos )
{
var left = ParseTerm( expr, ref pos );
while ( pos < expr.Length && (expr[pos] == '+' || expr[pos] == '-') )
{
var op = expr[pos++];
var right = ParseTerm( expr, ref pos );
left = op == '+' ? left + right : left - right;
}
return left;
}
private double ParseTerm( string expr, ref int pos )
{
var left = ParseFactor( expr, ref pos );
while ( pos < expr.Length && (expr[pos] == '*' || expr[pos] == '/') )
{
var op = expr[pos++];
var right = ParseFactor( expr, ref pos );
left = op == '*' ? left * right : left / right;
}
return left;
}
private double ParseFactor( string expr, ref int pos )
{
if ( pos < expr.Length && expr[pos] == '(' )
{
pos++;
var result = ParseExpression( expr, ref pos );
if ( pos < expr.Length && expr[pos] == ')' ) pos++;
return result;
}
if ( pos < expr.Length && expr[pos] == '-' )
{
pos++;
return -ParseFactor( expr, ref pos );
}
var start = pos;
while ( pos < expr.Length && (char.IsDigit( expr[pos] ) || expr[pos] == '.') )
pos++;
if ( pos == start )
throw new Exception( "Expected number" );
return double.Parse( expr.Substring( start, pos - start ), System.Globalization.CultureInfo.InvariantCulture );
}
}
public class ColorProvider : ISearchProvider
{
public IEnumerable<SearchResult> Search( string query )
{
var color = TryParseColor( query.Trim() );
if ( color == null )
return Enumerable.Empty<SearchResult>();
var c = color.Value;
var hex = $"#{(int)(c.r * 255):X2}{(int)(c.g * 255):X2}{(int)(c.b * 255):X2}";
var rgb = $"rgb({(int)(c.r * 255)}, {(int)(c.g * 255)}, {(int)(c.b * 255)})";
var vec = $"new Color({c.r:F2}f, {c.g:F2}f, {c.b:F2}f)";
var results = new List<SearchResult>();
results.Add( new SearchResult
{
Title = hex,
Subtitle = "Copy hex",
Type = "Color",
Icon = "palette",
Score = 10000,
OnSelect = () => EditorUtility.Clipboard.Copy( hex )
} );
results.Add( new SearchResult
{
Title = rgb,
Subtitle = "Copy rgb",
Type = "Color",
Icon = "palette",
Score = 9999,
OnSelect = () => EditorUtility.Clipboard.Copy( rgb )
} );
results.Add( new SearchResult
{
Title = vec,
Subtitle = "Copy C#",
Type = "Color",
Icon = "palette",
Score = 9998,
OnSelect = () => EditorUtility.Clipboard.Copy( vec )
} );
return results;
}
private Color? TryParseColor( string input )
{
if ( string.IsNullOrEmpty( input ) ) return null;
if ( input.Length == 4 && input[0] == '#' )
{
var r = Convert.ToInt32( new string( input[1], 2 ), 16 );
var g = Convert.ToInt32( new string( input[2], 2 ), 16 );
var b = Convert.ToInt32( new string( input[3], 2 ), 16 );
return new Color( r / 255f, g / 255f, b / 255f );
}
if ( (input.Length == 7 || input.Length == 9) && input[0] == '#' )
return Color.Parse( input );
if ( input.StartsWith( "rgb(", StringComparison.OrdinalIgnoreCase ) && input.EndsWith( ")" ) )
{
var inner = input.Substring( 4, input.Length - 5 );
var parts = inner.Split( ',' );
if ( parts.Length == 3
&& int.TryParse( parts[0].Trim(), out int r )
&& int.TryParse( parts[1].Trim(), out int g )
&& int.TryParse( parts[2].Trim(), out int b ) )
{
return new Color( r / 255f, g / 255f, b / 255f );
}
}
return Color.Parse( input );
}
}