Editor/LevelDesignerDock.cs
using System;
using System.Buffers.Text;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using Editor;
using Sandbox;
using Sandbox.UI;
using Button = Editor.Button;
using Label = Editor.Label;
namespace DesignerBrowser.Editor;
[Dock("Editor", "Designer Browser", "local_fire_department")]
public class LevelDesignerDock : Widget
{
/// <summary>
/// Asset has been clicked
/// </summary>
public Action<Asset> OnAssetHighlight;
/// <summary>
/// Asset has been clicked
/// </summary>
public Action<Asset[]> OnAssetsHighlight;
/// <summary>
/// Item has been selected
/// </summary>
public Action<object> OnHighlight;
private NavigationView View;
public LevelDesignerDock( Widget parent ) : base( parent )
{
View = new NavigationView( this );
Layout = Layout.Column();
Layout.Margin = 4;
Layout.Spacing = 4;
Layout.Add( View, 1 );
OnAssetHighlight = a => EditorUtility.InspectorObject = a;
OnAssetsHighlight = a => EditorUtility.InspectorObject = a;
//OnAssetSelected = a => a.OpenInEditor();
Rebuild();
}
private string cachedLevelDesignTag;
private string LevelDesignTag
{
get
{
if (cachedLevelDesignTag is not null)
{
return cachedLevelDesignTag;
}
var cookie = ProjectCookie.Get<string>("leveldesigner.assettag", null);
if (cookie is null)
return null;
cachedLevelDesignTag = cookie;
return cachedLevelDesignTag;
}
}
private List<string> superCategoriesCache;
private IEnumerable<string> SuperCategories
{
get
{
if (superCategoriesCache is not null)
{
return superCategoriesCache;
}
var cookie = ProjectCookie.Get<string>("leveldesigner.supercategories", null);
if (cookie is null)
return Enumerable.Empty<string>();;
if (string.IsNullOrWhiteSpace(cookie) || !Base64.IsValid(cookie))
return Enumerable.Empty<string>();;
var tags = cookie.Base64Decode().Split('/', StringSplitOptions.RemoveEmptyEntries);
superCategoriesCache = tags.ToList();
return superCategoriesCache;
}
}
private List<string> categoriesCache;
private IEnumerable<string> Categories
{
get
{
if (categoriesCache is not null)
{
return categoriesCache;
}
var cookie = ProjectCookie.Get<string>("leveldesigner.categories", null);
if (cookie is null)
return Enumerable.Empty<string>();;
if (string.IsNullOrWhiteSpace(cookie) || !Base64.IsValid(cookie))
return Enumerable.Empty<string>();;
var tags = cookie.Base64Decode().Split('/', StringSplitOptions.RemoveEmptyEntries);
categoriesCache = tags.ToList();
return categoriesCache;
}
}
[EditorEvent.Hotload]
public void Rebuild()
{
superCategoriesCache = null;
cachedLevelDesignTag = null;
categoriesCache = null;
View.ClearPages();
View.AddSectionHeader("Super Categories");
View.PageContents.Margin = new Margin(2f, 0, 0, 0);
foreach ( var category in SuperCategories )
{
var option = new NavigationView.Option( category.ToTitleCase(), "" );
option.CreatePage = () =>
{
var view = new NavigationView( null );
view.AddSectionHeader(category.ToTitleCase());
CreateContentForLevelCategory( view, category );
return view;
};
View.AddPage( option );
}
var uncategorizedOption = new NavigationView.Option( "Uncategorized", "" );
uncategorizedOption.CreatePage = () =>
{
var view = new NavigationView( null );
view.AddSectionHeader("Uncategorized");
CreateContentForUncategorized( view );
return view;
};
View.AddPage( uncategorizedOption );
var categoryEditor = new NavigationView.Option( "Category Editor", "" );
categoryEditor.CreatePage = () =>
{
var scroll = new ScrollArea( null );
scroll.Canvas = new Widget( scroll );
scroll.Canvas.Layout = Layout.Column();
scroll.Canvas.Layout.Margin = 6f;
scroll.Canvas.Layout.Spacing = 6f;
scroll.Canvas.VerticalSizeMode = SizeMode.CanShrink;
CreateCategoryEditor(scroll.Canvas.Layout);
return scroll;
};
View.AddPage( categoryEditor );
}
[Event( "assetsystem.changes" )]
void OnAssetSystemChanges()
{
//Rebuild();
}
[Event( "content.changed" )]
void OnContentChanged( string file )
{
//Rebuild();
}
private void SaveLevelAssetTag(string tag)
{
ProjectCookie.Set("leveldesigner.assettag", tag);
}
private void CreateCategoryEditor(Layout layout)
{
var levelAssetTag = layout.Add(new Widget(null));
levelAssetTag.FixedHeight = 30f;
levelAssetTag.Layout = Layout.Row();
levelAssetTag.Layout.Spacing = 5f;
levelAssetTag.ToolTip = "Assets tagged with this tag will appear in the asset browser";
var label = levelAssetTag.Layout.Add(new Label("Level Asset Tag"));
label.FixedWidth = 90f;
label.Alignment = TextFlag.RightCenter;
var levelAssetTagButton = levelAssetTag.Layout.Add(new TagButton());
levelAssetTagButton.FixedWidth = 90f;
levelAssetTagButton.Clicked = () =>
{
var selector = new PopupWidget(levelAssetTagButton);
selector.IsPopup = true;
var grid = Layout.Grid();
grid.Spacing = 4f;
grid.Margin = 8f;
grid.SetColumnStretch(1, 1, 1, 1, 1);
//grid.SetColumnStretch(0);
selector.Layout = grid;
var gridColumns = 4;
var count = 0;
foreach (var tag in AssetTagSystem.All.Where(x => !x.AutoTag))
{
var column = count % gridColumns;
var row = (count / gridColumns);
var button = grid.AddCell(column, row, new TagButton()
{
Text = tag.Title,
Icon = tag.IconPixmap,
InternalName = tag.Tag,
} );
button.Clicked = () =>
{
levelAssetTagButton.Text = tag.Title;
levelAssetTagButton.Icon = tag.IconPixmap;
levelAssetTagButton.InternalName = tag.Tag;
SaveLevelAssetTag(tag.Tag);
};
count++;
}
var newColumn = count % gridColumns;
var newRow = (count / gridColumns);
var tagAdd = grid.AddCell(newColumn, newRow, new LineEdit());
tagAdd.PlaceholderText = "Add New Tag..";
tagAdd.MinimumHeight = 32 - 8;
tagAdd.ReturnPressed += () =>
{
var tagList = tagAdd.Text.Split( new[] { ',', ' ' } );
foreach ( var tag in tagList )
{
var method = typeof(AssetTagSystem).GetMethod("EnsureRegistered", BindingFlags.Static | BindingFlags.NonPublic);
method?.Invoke(null, new object[]{tag});
//AssetTagSystem.EnsureRegistered(tag);
var tagOption = TagsPicker.FindTag(tag);
if (tagOption is not null)
{
levelAssetTagButton.Text = tagOption.Value.Title;
levelAssetTagButton.Icon = tagOption.Value.IconPixmap;
levelAssetTagButton.InternalName = tagOption.Value.Tag;
SaveLevelAssetTag(tag);
}
selector.Close();
}
};
selector.FixedWidth = 360;
selector.Show();
selector.Position = levelAssetTagButton.ScreenRect.BottomRight;
selector.ConstrainToScreen();
};
var assetTag = TagsPicker.FindTag(ProjectCookie.Get<string>("leveldesigner.assettag", null));
if (assetTag is not null)
{
levelAssetTagButton.Text = assetTag.Value.Title;
levelAssetTagButton.Icon = assetTag.Value.IconPixmap;
levelAssetTagButton.InternalName = assetTag.Value.Tag;
}
levelAssetTag.Layout.AddStretchCell();
/*levelAssetTagButton.Updated += () =>
{
ProjectCookie.Set("leveldesigner.blacklisted", blacklistedTagsPicker.Serialize());
};
if (ProjectCookie.TryGetString("leveldesigner.blacklisted", out var blacklisted))
{
blacklistedTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(blacklisted));
}*/
var superCategoriesWidget = layout.Add(new Widget(null));
superCategoriesWidget.FixedHeight = 30f;
superCategoriesWidget.Layout = Layout.Row();
superCategoriesWidget.Layout.Spacing = 5f;
label = superCategoriesWidget.Layout.Add(new Label("Super Categories"));
label.FixedWidth = 90f;
label.Alignment = TextFlag.RightCenter;
var superCategoriesTagsPicker = superCategoriesWidget.Layout.Add(new TagsPicker());
superCategoriesTagsPicker.SetStyles($"background-color:{Theme.ControlBackground.Lighten(0.35f).Hex}");
superCategoriesTagsPicker.Updated += () =>
{
ProjectCookie.Set("leveldesigner.supercategories", superCategoriesTagsPicker.Serialize());
superCategoriesCache = null;
};
superCategoriesTagsPicker.Filter = definition =>
{
if (Categories.Contains(definition.Tag))
return false;
return LevelDesignTag != definition.Tag;
};
if (ProjectCookie.TryGetString("leveldesigner.supercategories", out var superCategories))
{
superCategoriesTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(superCategories));
}
var categoriesWidget = layout.Add(new Widget(null));
categoriesWidget.FixedHeight = 30f;
categoriesWidget.Layout = Layout.Row();
categoriesWidget.Layout.Spacing = 5f;
label = categoriesWidget.Layout.Add(new Label("Categories"));
label.FixedWidth = 90f;
label.Alignment = TextFlag.RightCenter;
var categoriesTagsPicker = categoriesWidget.Layout.Add(new TagsPicker());
categoriesTagsPicker.SetStyles($"background-color:{Theme.ControlBackground.Lighten(0.35f).Hex}");
categoriesTagsPicker.Updated += () =>
{
ProjectCookie.Set("leveldesigner.categories", categoriesTagsPicker.Serialize());
categoriesCache = null;
};
if (ProjectCookie.TryGetString("leveldesigner.categories", out var categories))
{
categoriesTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(categories));
}
categoriesTagsPicker.Filter = definition =>
{
if (SuperCategories.Contains(definition.Tag))
return false;
return LevelDesignTag != definition.Tag;
};
/*var blacklistedTags = layout.Add(new Widget(null));
blacklistedTags.FixedHeight = 30f;
blacklistedTags.Layout = Layout.Row();
blacklistedTags.Layout.Spacing = 5f;
label = blacklistedTags.Layout.Add(new Label("Blacklisted Tags"));
label.FixedWidth = 90f;
label.Alignment = TextFlag.RightCenter;
var blacklistedTagsPicker = blacklistedTags.Layout.Add(new TagsPicker(null));
blacklistedTagsPicker.SetStyles($"background-color:{Theme.ControlBackground.Lighten(0.35f).Hex}");
blacklistedTagsPicker.Updated += () =>
{
ProjectCookie.Set("leveldesigner.blacklisted", blacklistedTagsPicker.Serialize());
};
if (ProjectCookie.TryGetString("leveldesigner.blacklisted", out var blacklisted))
{
blacklistedTagsPicker.Deserialize(JsonSerializer.Deserialize<string>(blacklisted));
}*/
var button = layout.Add( new Button.Primary("Save and Reload", "save"));
button.Clicked = () =>
{
Rebuild();
};
}
private void CreateContent(NavigationView view, IEnumerable<Asset> assets)
{
var subCategories = Categories;
//also store all the asset entries we need for each subcategory
var subCategoryEntries = new Dictionary<string, List<AssetEntry>>();
var entries = assets
.Select(x => new AssetEntry(new FileInfo( x.Path ), x));
foreach (var assetEntry in entries)
{
var subcategory = assetEntry.Asset.Tags.FirstOrDefault(x => subCategories.Contains(x));
if (string.IsNullOrWhiteSpace(subcategory))
subcategory = "Uncategorized";
if (!subCategoryEntries.ContainsKey(subcategory))
{
subCategoryEntries.Add(subcategory, new List<AssetEntry>());
}
subCategoryEntries[subcategory].Add(assetEntry);
}
foreach ( var subCategory in subCategoryEntries.Keys.OrderBy(x => x == "Uncategorized") )
{
var option = new NavigationView.Option( subCategory.ToTitleCase(), "" );
option.CreatePage = () =>
{
var scroll = new ScrollArea( null );
scroll.Canvas = new Widget( scroll );
scroll.Canvas.Layout = Layout.Column();
scroll.Canvas.Layout.Margin = 16f;
scroll.Canvas.Layout.Spacing = 5f;
scroll.Canvas.VerticalSizeMode = SizeMode.CanGrow;
var assetList = scroll.Canvas.Layout.Add(new AssetList(null), 1);
assetList.ViewMode = AssetListViewMode.LargeIcons;
assetList.OnHighlight = ( o ) =>
{
if ( o is object[] object_list )
{
var entries = object_list.OfType<AssetEntry>().ToArray();
if ( entries.Length > 0 && entries.Length == object_list.Length )
{
OnAssetsHighlight?.Invoke( entries.Select( x => x.Asset ).ToArray() );
return;
}
}
if ( o is AssetEntry ae )
{
OnAssetHighlight?.Invoke( ae.Asset );
}
else
{
OnHighlight?.Invoke( o );
}
};
assetList.SetItems(subCategoryEntries[subCategory]);
return scroll;
};
view.AddPage( option );
}
}
private void CreateContentForUncategorized(NavigationView view)
{
var assets = AssetSystem.All.Where(x => x.Tags.Contains(LevelDesignTag))
.Where(x =>
{
foreach (var levelCategory in SuperCategories)
{
if (x.Tags.Contains(levelCategory))
return false;
}
return true;
});
CreateContent(view, assets);
}
private void CreateContentForLevelCategory(NavigationView view, string levelCategory)
{
//Thesse are all the assets that belong to our level category
var assets = AssetSystem.All.Where(x => x.Tags.Contains(LevelDesignTag) && x.Tags.Contains(levelCategory));
CreateContent(view, assets);
}
}