Editor/Tileset/TilesetResourceEditor.cs

Editor widget that inspects and edits a TilesetResource asset. It builds UI controls for tileset properties, global tags, rect ordering, provides buttons to generate/clear tiles, add/delete rects, respond to hotloads, and save changes back to the asset.

File Access
using Editor;
using Editor.Assets;
using Saandy.Tilemapper;
using Sandbox;
using static Editor.Inspectors.AssetInspector;

namespace Saandy.Editor.Tilemapper;

[CanEdit( "asset:tileset" )]
public sealed class TilesetResourceEditor : Widget, IAssetInspector
{
	private Asset _asset;
	private TilesetResource _tileset;

	private ControlSheet _sheet;
	private TilesetGlobalTagsWidget _globalTagsWidget;
	private TilesetRectOrderWidget _rectWidget;

	public TilesetResourceEditor( Widget parent ) : base( parent )
	{
		Layout = Layout.Column();
		Layout.Margin = 8;
		Layout.Spacing = 8;

		MinimumHeight = 1000;
		MinimumWidth = 600;
		VerticalSizeMode = SizeMode.CanGrow;
		HorizontalSizeMode = SizeMode.Flexible;
	}

	public void SetAssetPreview( AssetPreview preview )
	{
	}

	public void SetAsset( Asset asset )
	{
		_asset = asset;
		_tileset = asset?.LoadResource<TilesetResource>();

		Rebuild();
	}

	[EditorEvent.Hotload]
	private void OnHotload()
	{
		if ( _asset != null )
			_tileset = _asset.LoadResource<TilesetResource>();

		Rebuild();
	}

	private void Rebuild()
	{
		if ( Layout == null )
			return;

		Layout.Clear( true );

		if ( _tileset == null )
		{
			Layout.Add( new Label( "No TilesetResource loaded.", this ) );
			return;
		}

		var title = new Label( "Tileset Editor", this );
		title.SetStyles( "font-size: 22px; font-weight: 700; margin-bottom: 8px;" );
		Layout.Add( title );

		var brushTemplate = TileBrushEditorTemplates.Get( _tileset.BrushType );

		var brushInfo = new Label(
			brushTemplate != null
				? $"{brushTemplate.Title}: {brushTemplate.Description}"
				: "No brush template selected.",
			this
		);
		brushInfo.WordWrap = true;
		brushInfo.SetStyles( "color: #ddd; margin-bottom: 6px;" );
		Layout.Add( brushInfo );

		var serializedObject = _tileset.GetSerialized();

		_sheet = new ControlSheet();
		_sheet.AddObject( serializedObject, prop =>
		{
			if ( prop.HasAttribute<HideAttribute>() )
				return false;

			if ( prop.Name == nameof( TilesetResource.Tiles ) )
				return false;

			if ( prop.Name == nameof( TilesetResource.GlobalTags ) )
				return false;

			return prop.HasAttribute<PropertyAttribute>();
		} );

		serializedObject.OnPropertyChanged += prop =>
		{
			if ( prop != null && prop.Name == nameof( TilesetResource.FilePath ) )
			{
				_tileset.ClearTextureCache();
				_rectWidget?.Refresh();
				MarkChanged();
				return;
			}

			if ( prop != null && prop.Name == nameof( TilesetResource.BrushType ) )
			{
				MarkChanged();
				Rebuild();
				return;
			}

			MarkChanged();
		};

		Layout.Add( _sheet );

		_globalTagsWidget = new TilesetGlobalTagsWidget( this, _tileset, MarkChanged )
		{
			MinimumWidth = 560
		};
		Layout.Add( _globalTagsWidget );

		_rectWidget = new TilesetRectOrderWidget( _tileset, brushTemplate, MarkChanged )
		{
			MinimumWidth = 560
		};

		var buttonRow = Layout.Add( Layout.Row() );
		buttonRow.Spacing = 6;

		var generateButton = new Button( "Generate Grid Rects", "grid_on", this );
		generateButton.Clicked = () =>
		{
			_tileset.GenerateTilesFromGrid();

			MarkChanged();

			_rectWidget?.Refresh();
			_rectWidget?.LoadTemplateFromCurrentOrder();
		};
		buttonRow.Add( generateButton );

		var clearButton = new Button( "Clear Tiles", "delete", this );
		clearButton.Clicked = () =>
		{
			_tileset.ClearTiles();

			MarkChanged();

			_rectWidget?.Refresh();
			_rectWidget?.ClearTemplate();
		};
		buttonRow.Add( clearButton );

		var rectActionRow = Layout.Add( Layout.Row() );
		rectActionRow.Spacing = 6;

		var addRectButton = new Button( "Add Rect", "add", this );
		addRectButton.Clicked = () => _rectWidget?.AddPendingRect();
		rectActionRow.Add( addRectButton );

		var deleteTileButton = new Button( "Delete Selected Rect", "delete", this );
		deleteTileButton.Clicked = () => _rectWidget?.DeleteSelectedTile();
		rectActionRow.Add( deleteTileButton );

		if ( _rectWidget.HasTemplate )
		{
			var loadOrderButton = new Button( "Load Current Order", "refresh", this );
			loadOrderButton.Clicked = () => _rectWidget?.LoadTemplateFromCurrentOrder();
			rectActionRow.Add( loadOrderButton );

			var applyOrderButton = new Button( "Apply Template Order", "done", this );
			applyOrderButton.Clicked = () => _rectWidget?.ApplyTemplateOrder();
			rectActionRow.Add( applyOrderButton );
		}

		Layout.Add( _rectWidget );

		Layout.AddStretchCell();
	}

	private void MarkChanged()
	{
		if ( _tileset == null )
			return;

		_tileset.InternalUpdateTiles();
		_tileset.StateHasChanged();

		SaveAsset();

		Update();
	}

	private void SaveAsset()
	{
		if ( _asset == null || _tileset == null )
			return;

		_asset.SaveToDisk( _tileset );
	}
}