Files
tooll3--t3/Editor/UiModel/EditableSymbolProject.cs
2026-07-13 13:13:17 +08:00

234 lines
8.7 KiB
C#

#nullable enable
using System.Diagnostics;
using System.IO;
using T3.Core.Compilation;
using T3.Core.Operator;
using T3.Core.Resource;
using T3.Core.Resource.Assets;
using T3.Core.SystemUi;
using T3.Core.Settings;
using T3.Editor.Compilation;
using T3.Editor.Gui.Interaction.Variations.Model;
using T3.Editor.Gui.UiHelpers.Thumbnails;
namespace T3.Editor.UiModel;
/// <summary>
/// EditableSymbolProject is an <see cref="EditorSymbolPackage"/> that contains runtime package manipulation logic.
/// </summary>
[DebuggerDisplay("{DisplayName}")]
internal sealed partial class EditableSymbolProject : EditorSymbolPackage
{
public override string DisplayName { get; }
/// <summary>
/// Create a new <see cref="EditableSymbolProject"/> using the given <see cref="CsProjectFile"/>.
/// </summary>
public EditableSymbolProject(CsProjectFile csProjectFile) :
base(assembly: AssemblyInformation.CreateUninitialized(), directory: csProjectFile.Directory, false)
{
AssemblyInformation.Initialize(csProjectFile.GetBuildTargetDirectory(), false);
CsProjectFile = csProjectFile;
//Log.Info($"Adding project {csProjectFile.Name}...");
_csFileWatcher = new CodeFileWatcher(this, OnFileChanged, OnCodeFileRenamed);
_csFileWatcher.EnableRaisingEvents = true;
DisplayName = $"{csProjectFile.Name} ({CsProjectFile.RootNamespace})";
SymbolUpdated += OnSymbolUpdated;
SymbolRemoved += OnSymbolRemoved;
InitializeAssets();
_allProjectsCache = null;
}
public void OpenProjectInCodeEditor()
{
CoreUi.Instance.OpenWithDefaultApplication(CsProjectFile.FullPath);
}
public bool TryOpenCSharpInEditor(Symbol symbol)
{
var guid = symbol.Id;
if (!FilePathHandlers.TryGetValue(guid, out var filePathHandler))
{
Log.Error($"No file path handler found for symbol {guid}");
return false;
}
var sourceCodePath = filePathHandler.SourceCodePath;
if (string.IsNullOrWhiteSpace(sourceCodePath))
{
Log.Error($"No source code path found for symbol {guid}");
return false;
}
OpenProjectInCodeEditor();
CoreUi.Instance.OpenWithDefaultApplication(sourceCodePath);
return true;
}
public void ReplaceSymbolUi(SymbolUi symbolUi)
{
var symbol = symbolUi.Symbol;
SymbolUiDict[symbol.Id] = symbolUi;
symbolUi.FlagAsModified();
symbolUi.ReadOnly = false;
SaveSymbolFile(symbolUi);
Log.Debug($"Replaced symbol ui for {symbol.Name}");
}
private void GiveSymbolToPackage(Guid id, EditableSymbolProject newDestinationProject)
{
SymbolDict.Remove(id, out var symbol);
SymbolUiDict.Remove(id, out var symbolUi);
FilePathHandlers.Remove(id, out var symbolPathHandler);
if(_pendingSource.Remove(id, out var source))
newDestinationProject._pendingSource.TryAdd(id, source);
Debug.Assert(symbol != null);
Debug.Assert(symbolUi != null);
Debug.Assert(symbolPathHandler != null);
symbol.SymbolPackage = newDestinationProject;
symbolUi.UpdateSymbolPackage(newDestinationProject);
newDestinationProject.SymbolDict.TryAdd(id, symbol);
newDestinationProject.SymbolUiDict.TryAdd(id, symbolUi);
newDestinationProject.FilePathHandlers.TryAdd(id, symbolPathHandler);
// Copy thumbnails...
{
var sourceFilePath = Path.Combine(Folder, FileLocations.MetaSubFolder, ThumbnailManager.ThumbnailsSubFolder, id + ".png");
if (File.Exists(sourceFilePath))
{
var targetFolder = Path.Combine(newDestinationProject.Folder, FileLocations.MetaSubFolder, ThumbnailManager.ThumbnailsSubFolder);
var targetFilePath = Path.Combine(targetFolder, id + ".png");
Directory.CreateDirectory(targetFolder);
try
{
File.Move(sourceFilePath, targetFilePath);
}
catch (Exception e)
{
Log.Warning("Can't move thumbnail " + e.Message);
}
}
}
// Move variations...
{
var sourceFilePath = SymbolVariationPool.GetVariationFilePathInPackage(Folder, id);
if (File.Exists(sourceFilePath))
{
var targetFilePath = SymbolVariationPool.GetVariationFilePathInPackage(newDestinationProject.Folder, id);
try
{
Directory.CreateDirectory(Path.GetDirectoryName(targetFilePath)!);
File.Move(sourceFilePath, targetFilePath);
}
catch (Exception e)
{
Log.Warning("Can't move variations " + e.Message);
}
}
}
// move files to new project - incorrect paths will be corrected by the loading process
symbolPathHandler.UpdateFromSymbol();
OnSymbolMoved?.Invoke(id, newDestinationProject);
symbolUi.FlagAsModified();
}
private static readonly string[] _folderExclusions = ["bin", "obj", "dependencies", FileLocations.ExportSubFolder];
protected override IEnumerable<string> SymbolUiSearchFiles => FindFilesOfType(SymbolUiExtension);
protected override IEnumerable<string> SymbolSearchFiles => FindFilesOfType(SymbolExtension);
protected override IEnumerable<string> SourceCodeSearchFiles => FindFilesOfType(SourceCodeExtension);
private IEnumerable<string> FindFilesOfType(string fileExtension)
{
var directoryInfo = new DirectoryInfo(Folder);
return directoryInfo.EnumerateDirectories()
.Where(x => !_folderExclusions.Contains(x.Name))
.SelectMany(x => x.EnumerateFiles($"*{fileExtension}", SearchOption.AllDirectories))
.Concat(directoryInfo.EnumerateFiles($"*{fileExtension}")).Select(x => x.FullName);
}
protected override void InitializeAssets()
{
base.InitializeAssets();
_resourceFileWatcher = new ResourceFileWatcher(AssetsFolder);
_resourceFileWatcher.FileCreated += (_, path) =>
{
var isDirectory = Directory.Exists(path);
FileSystemInfo info = isDirectory
? new DirectoryInfo(path)
: new FileInfo(path);
AssetRegistry.RegisterPackageEntry(info, this, isDirectory);
ResourceFileWatcher.FileStateChangeCounter++;
};
_resourceFileWatcher.FileRenamed += (oldPath, newPath) =>
{
AssetRegistry.UpdateMovedAsset(oldPath, newPath);
};
_resourceFileWatcher.FileDeleted += (_, path) =>
{
AssetRegistry.UnregisterAbsoluteFilePath(path, this);
};
}
public override void Dispose()
{
base.Dispose();
FileWatcher?.Dispose();
ProjectSetup.RemoveSymbolPackage(this, false);
_allProjectsCache = null;
}
/// <summary>
/// Event raised when a symbol is moved to a different project or deleted
/// </summary>
public event Action<Guid, EditableSymbolProject?>? OnSymbolMoved;
public readonly CsProjectFile CsProjectFile;
private ResourceFileWatcher? _resourceFileWatcher;
public override ResourceFileWatcher? FileWatcher => _resourceFileWatcher;
public override bool IsReadOnly => false;
private static IEnumerable<EditableSymbolProject>? _allProjectsCache;
public static IEnumerable<EditableSymbolProject> AllProjects
{
get
{
_allProjectsCache ??= ProjectSetup
.AllPackages
.Where(x => x is EditableSymbolProject)
.Cast<EditableSymbolProject>()
.OrderByDescending(x => x.CsProjectFile.ModifiedAt)
.ToList();
return _allProjectsCache;
}
}
// New helper to force reload internally
public void MarkCodeExternallyModified()
{
CodeExternallyModified = true;
}
}