diff --git a/.claude/agents/code-reviewer.md b/.claude/agents/code-reviewer.md
new file mode 100644
index 0000000..b34e171
--- /dev/null
+++ b/.claude/agents/code-reviewer.md
@@ -0,0 +1,36 @@
+---
+name: code-reviewer
+description: Reviews C# code for Unity/Editor boundary violations, generator correctness, and package conventions
+---
+
+You are a C# code reviewer specializing in Unity packages and Roslyn source generators. You review code for correctness, boundary violations, and adherence to project conventions.
+
+## Project Context
+
+This is **Aspid.FastTools** — a Unity package (`com.aspid.fasttools`) with two separate projects:
+- `Aspid.FastTools/` — Unity project (Runtime + Editor assemblies)
+- `Aspid.FastTools.Generators/` — .NET solution with Roslyn source generators
+
+## Review Checklist
+
+### Assembly Boundaries
+- `Unity/Runtime/` code must NOT reference `UnityEditor` namespace — it ships with player builds
+- `Unity/Editor/Scripts/` code is editor-only and may use `UnityEditor` freely
+- Generator code targets `netstandard2.0` and must NOT reference any Unity assemblies
+
+### Generators (`Aspid.FastTools.Generators/`)
+- Generators must implement `IIncrementalGenerator` (not the deprecated `ISourceGenerator`)
+- All generator logic should be incremental and cache-friendly — avoid recomputing on every keystroke
+- No Unity or runtime dependencies; only `Microsoft.CodeAnalysis.CSharp` and `Aspid.Generators.Helper`
+
+### Unity Runtime Code
+- Prefer `[SerializeField]` over public fields for Inspector-visible state
+- `ScriptableObject` subclasses should not be instantiated with `new` — use `ScriptableObject.CreateInstance`
+- Extension methods on `VisualElement` should follow the fluent pattern already established in `VisualElementExtensions.*`
+
+### General C# Quality
+- Nullable annotations must be consistent — the project has `enable`
+- Avoid boxing of value types in hot paths (ProfilerMarkers, EnumValues iteration)
+- Partial classes must all reside in files named consistently with the partial suffix pattern used elsewhere
+
+Report issues grouped by severity: **Error** (breaks compilation or runtime), **Warning** (likely bug or convention violation), **Info** (minor improvement).
diff --git a/.claude/skills/build-generator/SKILL.md b/.claude/skills/build-generator/SKILL.md
new file mode 100644
index 0000000..a2763a1
--- /dev/null
+++ b/.claude/skills/build-generator/SKILL.md
@@ -0,0 +1,13 @@
+---
+name: build-generator
+description: Build Roslyn source generators and deploy the resulting DLL into the Unity package
+user-invocable: true
+---
+
+Build the Aspid.FastTools source generators and deploy to Unity:
+
+1. Run `dotnet build Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj -c Release` from the repository root
+2. Copy `Aspid.FastTools.Generators/Aspid.FastTools.Generators/bin/Release/netstandard2.0/Aspid.FastTools.Generators.dll` to `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Aspid.FastTools.Generators.dll`
+3. Report the result: build output, any errors, and confirm the DLL was copied successfully
+
+Arguments: $ARGUMENTS (optional: pass `Debug` to build in Debug configuration instead of Release)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d6b130c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.claude/settings.local.json
diff --git a/.gitmodules b/.gitmodules
index e69de29..1a23456 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "Aspid.FastTools/Assets/Plugins/Aspid/Internal/Unity"]
+ path = Aspid.FastTools/Assets/Plugins/Aspid/Internal/Unity
+ url = git@github.com:VPDPersonal/Aspid.Internal.Unity.git
diff --git a/Aspid.UnityFastTools.Generators/.gitignore b/Aspid.FastTools.Generators/.gitignore
similarity index 100%
rename from Aspid.UnityFastTools.Generators/.gitignore
rename to Aspid.FastTools.Generators/.gitignore
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Sample/Aspid.UnityFastTools.Generators.Sample.csproj b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Sample/Aspid.FastTools.Generators.Sample.csproj
similarity index 61%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Sample/Aspid.UnityFastTools.Generators.Sample.csproj
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators.Sample/Aspid.FastTools.Generators.Sample.csproj
index c7d1c6e..c971c51 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Sample/Aspid.UnityFastTools.Generators.Sample.csproj
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Sample/Aspid.FastTools.Generators.Sample.csproj
@@ -3,13 +3,13 @@
net6.0
enable
- UnityFastToolsGenerators.Sample
+ Aspid.FastTools.Sample
6000.2.7f2
9
-
+
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Tests/Aspid.UnityFastTools.Generators.Tests.csproj b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Aspid.FastTools.Generators.Tests.csproj
similarity index 81%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Tests/Aspid.UnityFastTools.Generators.Tests.csproj
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Aspid.FastTools.Generators.Tests.csproj
index 963f078..e4d8ac9 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators.Tests/Aspid.UnityFastTools.Generators.Tests.csproj
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/Aspid.FastTools.Generators.Tests.csproj
@@ -6,7 +6,7 @@
false
- UnityFastToolsGenerators.Tests
+ Aspid.FastTools.Tests
@@ -20,7 +20,7 @@
-
+
diff --git a/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.sln b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.sln
similarity index 64%
rename from Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.sln
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators.sln
index aa25b9e..29d5927 100644
--- a/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.sln
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.sln
@@ -1,10 +1,10 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.UnityFastTools.Generators", "UnityFastToolsGenerators\Aspid.UnityFastTools.Generators\Aspid.UnityFastTools.Generators.csproj", "{CB9D8D51-7D86-4B84-A0DB-73E418962DA7}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.FastTools.Generators", "Aspid.FastTools.Generators\Aspid.FastTools.Generators.csproj", "{CB9D8D51-7D86-4B84-A0DB-73E418962DA7}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.UnityFastTools.Generators.Sample", "UnityFastToolsGenerators\Aspid.UnityFastTools.Generators.Sample\Aspid.UnityFastTools.Generators.Sample.csproj", "{2835DD81-D105-4C2E-AE03-BC7D064C29D1}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.FastTools.Generators.Sample", "Aspid.FastTools.Generators.Sample\Aspid.FastTools.Generators.Sample.csproj", "{2835DD81-D105-4C2E-AE03-BC7D064C29D1}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.UnityFastTools.Generators.Tests", "UnityFastToolsGenerators\Aspid.UnityFastTools.Generators.Tests\Aspid.UnityFastTools.Generators.Tests.csproj", "{F4953608-2F14-4B2E-B91C-B3FDFC81B180}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspid.FastTools.Generators.Tests", "Aspid.FastTools.Generators.Tests\Aspid.FastTools.Generators.Tests.csproj", "{F4953608-2F14-4B2E-B91C-B3FDFC81B180}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.csproj b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj
similarity index 91%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.csproj
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj
index 74888a7..e04492e 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Aspid.UnityFastTools.Generators.csproj
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj
@@ -3,9 +3,9 @@
netstandard2.0
enable
- 13
+ latest
true
- UnityFastToolsGenerators
+ Aspid.FastTools
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Descriptions/General.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Descriptions/General.cs
new file mode 100644
index 0000000..201eb71
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Descriptions/General.cs
@@ -0,0 +1,8 @@
+using Aspid.Generators.Helper;
+
+namespace Aspid.FastTools.Descriptions;
+
+public static class General
+{
+ public static readonly string ProfilerMarkerGeneratedCode = $"""{Classes.GeneratedCodeAttribute}("Aspid.FastTools.Generators.ProfilerMarkersGenerator", "1.0.0")""";
+}
\ No newline at end of file
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Bodies/IdStructBody.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Bodies/IdStructBody.cs
new file mode 100644
index 0000000..748f048
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Bodies/IdStructBody.cs
@@ -0,0 +1,33 @@
+using Microsoft.CodeAnalysis;
+using Aspid.Generators.Helper;
+using Aspid.FastTools.Generators.IdStruct.Data;
+
+namespace Aspid.FastTools.Generators.IdStruct.Bodies;
+
+public static class IdStructBody
+{
+ public static void GenerateCode(in SourceProductionContext context, in IdStructData data)
+ {
+ var hasNamespace = data.Namespace != null;
+
+ var code = new CodeWriter()
+ .AppendLine("// ")
+ .AppendLine("#nullable enable")
+ .AppendLine()
+ .AppendLineIf(hasNamespace, $"namespace {data.Namespace}")
+ .BeginBlockIf(hasNamespace)
+ .AppendLine($"partial struct {data.StructName}")
+ .BeginBlock()
+ .AppendLine("#if UNITY_EDITOR")
+ .AppendLine("[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]")
+ .AppendLine("[global::UnityEngine.SerializeField] private string __stringId;")
+ .AppendLine("#endif")
+ .AppendLine("[global::UnityEngine.SerializeField] private int _id;")
+ .AppendLine("public int Id => _id;")
+ .EndBlock()
+ .EndBlockIf(hasNamespace);
+
+ var hintName = $"{data.StructName}.IId.g.cs";
+ context.AddSource(hintName, code.GetSourceText());
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructData.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructData.cs
new file mode 100644
index 0000000..b8f659d
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructData.cs
@@ -0,0 +1,12 @@
+using Microsoft.CodeAnalysis;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+public readonly struct IdStructData(INamedTypeSymbol symbol)
+{
+ public readonly INamedTypeSymbol Symbol = symbol;
+ public readonly string StructName = symbol.Name;
+ public readonly string? Namespace = symbol.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : symbol.ContainingNamespace.ToDisplayString();
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/IdStructGenerator.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/IdStructGenerator.cs
new file mode 100644
index 0000000..0cafcb2
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/IdStructGenerator.cs
@@ -0,0 +1,50 @@
+using System.Threading;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Aspid.FastTools.Generators.IdStruct.Data;
+using Aspid.FastTools.Generators.IdStruct.Bodies;
+
+namespace Aspid.FastTools.Generators.IdStruct;
+
+[Generator(LanguageNames.CSharp)]
+public class IdStructGenerator : IIncrementalGenerator
+{
+ private const string IIdFullName = "Aspid.FastTools.IId";
+
+ public void Initialize(IncrementalGeneratorInitializationContext context)
+ {
+ var provider = context.SyntaxProvider
+ .CreateSyntaxProvider(Predicate, Transform)
+ .Where(static d => d.HasValue)
+ .Select(static (d, _) => d!.Value);
+
+ context.RegisterSourceOutput(provider, static (ctx, data) => IdStructBody.GenerateCode(ctx, data));
+ }
+
+ private static bool Predicate(SyntaxNode node, CancellationToken _)
+ {
+ if (node is not StructDeclarationSyntax structDecl) return false;
+ if (!structDecl.Modifiers.Any(SyntaxKind.PartialKeyword)) return false;
+ return structDecl.BaseList is { Types.Count: > 0 };
+ }
+
+ private static IdStructData? Transform(GeneratorSyntaxContext context, CancellationToken ct)
+ {
+ var structDecl = (StructDeclarationSyntax)context.Node;
+
+ if (context.SemanticModel.GetDeclaredSymbol(structDecl, ct) is not INamedTypeSymbol symbol)
+ return null;
+
+ var iidInterface = context.SemanticModel.Compilation.GetTypeByMetadataName(IIdFullName);
+ if (iidInterface == null) return null;
+
+ foreach (var iface in symbol.AllInterfaces)
+ {
+ if (SymbolEqualityComparer.Default.Equals(iface, iidInterface))
+ return new IdStructData(symbol);
+ }
+
+ return null;
+ }
+}
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
similarity index 81%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
index 1939a0e..12b874c 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
@@ -2,12 +2,12 @@
using Microsoft.CodeAnalysis;
using Aspid.Generators.Helper;
using System.Collections.Immutable;
-using UnityFastToolsGenerators.Generators.ProfilerMarkers.Data;
+using Aspid.FastTools.Generators.ProfilerMarkers.Data;
using static Aspid.Generators.Helper.Classes;
+using static Aspid.FastTools.Descriptions.General;
using static Aspid.Generators.Helper.Unity.UnityClasses;
-using static UnityFastToolsGenerators.Descriptions.General;
-namespace UnityFastToolsGenerators.Generators.ProfilerMarkers.Bodies;
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Bodies;
public static class ExtensionClassBody
{
@@ -15,7 +15,7 @@ public static void GenerateCode(in SourceProductionContext context, in Immutable
{
var markerCallTypes = markerCalls
.GroupBy(markerCall => markerCall.NamedTypeSymbol, SymbolEqualityComparer.Default)
- .Select(group => new MarkerCallType(group.Key!, group.ToImmutableArray()));
+ .Select(group => new MarkerCallType((INamedTypeSymbol)group.Key!, group.ToImmutableArray()));
foreach (var markerCallType in markerCallTypes)
GenerateCode(context, markerCallType);
@@ -28,13 +28,14 @@ private static void GenerateCode(in SourceProductionContext context, in MarkerCa
var hasNamespaceName = !symbol.ContainingNamespace.IsGlobalNamespace;
var namespaceName = hasNamespaceName ? symbol.ContainingNamespace.ToDisplayString() : null;
-
+
var markerCallMembers = type.MarkerCalls
.GroupBy(markerCall => markerCall.MethodSymbol, SymbolEqualityComparer.Default)
.Select(grouping => new MarkerCallMember((IMethodSymbol)grouping.Key!, grouping.ToImmutableArray()))
.ToImmutableArray();
- var className = $"__{typeName}ProfilerMarkerExtensions";
+ var arityPart = symbol.Arity > 0 ? $"_{symbol.Arity}" : string.Empty;
+ var className = $"__{typeName}{arityPart}ProfilerMarkerExtensions";
var code = new CodeWriter()
.AppendLine("// ")
@@ -76,11 +77,16 @@ private static CodeWriter AppendProfilerMarkers(this CodeWriter code, ImmutableA
private static CodeWriter AppendWithoutMessage(
this CodeWriter code,
- ISymbol symbol,
+ INamedTypeSymbol symbol,
ImmutableArray markerCallMembers)
{
+ var typeParams = symbol.TypeParameters;
+ var typeParamList = typeParams.Length > 0
+ ? $"<{string.Join(", ", typeParams.Select(p => p.Name))}>"
+ : string.Empty;
+
code.AppendLine($"[{ProfilerMarkerGeneratedCode}]")
- .AppendLine($"public static {ProfilerMarker}.AutoScope Marker(this {symbol.ToDisplayStringGlobal()} _, [{CallerLineNumberAttribute}] int line = -1)")
+ .AppendLine($"public static {ProfilerMarker}.AutoScope Marker{typeParamList}(this {symbol.ToDisplayStringGlobal()} _, [{CallerLineNumberAttribute}] int line = -1)")
.BeginBlock();
foreach (var markerCall in markerCallMembers.SelectMany(member => member.MarkerCalls))
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs
similarity index 88%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs
index dd3ea86..9801370 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCall.cs
@@ -1,6 +1,6 @@
using Microsoft.CodeAnalysis;
-namespace UnityFastToolsGenerators.Generators.ProfilerMarkers.Data;
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Data;
public readonly struct MarkerCall(
INamedTypeSymbol namedTypeSymbol,
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallMember.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallMember.cs
similarity index 82%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallMember.cs
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallMember.cs
index f0a01d7..8f3b9b5 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallMember.cs
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallMember.cs
@@ -1,7 +1,7 @@
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
-namespace UnityFastToolsGenerators.Generators.ProfilerMarkers.Data;
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Data;
public readonly struct MarkerCallMember(
IMethodSymbol methodSymbol,
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallType.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallType.cs
similarity index 61%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallType.cs
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallType.cs
index af2d3c7..b268bff 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallType.cs
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Data/MarkerCallType.cs
@@ -1,12 +1,12 @@
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
-namespace UnityFastToolsGenerators.Generators.ProfilerMarkers.Data;
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Data;
public readonly struct MarkerCallType(
- ISymbol symbol,
+ INamedTypeSymbol symbol,
ImmutableArray markerCalls)
{
- public readonly ISymbol Symbol = symbol;
+ public readonly INamedTypeSymbol Symbol = symbol;
public readonly ImmutableArray MarkerCalls = markerCalls;
}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs
similarity index 94%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs
index 846c545..00e2233 100644
--- a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/ProfilerMarkersGenerator.cs
@@ -2,10 +2,10 @@
using Microsoft.CodeAnalysis;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using UnityFastToolsGenerators.Generators.ProfilerMarkers.Data;
-using UnityFastToolsGenerators.Generators.ProfilerMarkers.Bodies;
+using Aspid.FastTools.Generators.ProfilerMarkers.Data;
+using Aspid.FastTools.Generators.ProfilerMarkers.Bodies;
-namespace UnityFastToolsGenerators.Generators.ProfilerMarkers;
+namespace Aspid.FastTools.Generators.ProfilerMarkers;
[Generator(LanguageNames.CSharp)]
public class ProfilerMarkersGenerator : IIncrementalGenerator
diff --git a/Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Properties/launchSettings.json b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Properties/launchSettings.json
similarity index 100%
rename from Aspid.UnityFastTools.Generators/UnityFastToolsGenerators/Aspid.UnityFastTools.Generators/Properties/launchSettings.json
rename to Aspid.FastTools.Generators/Aspid.FastTools.Generators/Properties/launchSettings.json
diff --git a/Aspid.FastTools.Generators/Directory.Build.targets b/Aspid.FastTools.Generators/Directory.Build.targets
new file mode 100644
index 0000000..f176b58
--- /dev/null
+++ b/Aspid.FastTools.Generators/Directory.Build.targets
@@ -0,0 +1,11 @@
+
+
+
+
+ <_UnityDestination>$(MSBuildThisFileDirectory)../Aspid.FastTools/Assets/Plugins/Aspid/FastTools/
+
+
+
+
+
+
diff --git a/Aspid.UnityFastTools/.gitignore b/Aspid.FastTools/.gitignore
similarity index 100%
rename from Aspid.UnityFastTools/.gitignore
rename to Aspid.FastTools/.gitignore
diff --git a/Aspid.UnityFastTools/Assets/Plugins.meta b/Aspid.FastTools/Assets/Plugins.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins.meta
rename to Aspid.FastTools/Assets/Plugins.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid.meta b/Aspid.FastTools/Assets/Plugins/Aspid.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Aspid.FastTools.Generators.dll
similarity index 99%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Aspid.FastTools.Generators.dll
index 95a75d7..1fe643d 100644
Binary files a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Aspid.FastTools.Generators.dll differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Aspid.FastTools.Generators.dll.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Aspid.UnityFastTools.Generators.dll.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Aspid.FastTools.Generators.dll.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.ProfilerMarkers.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.ProfilerMarkers.png
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.ProfilerMarkers.png
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.ProfilerMarkers.png
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.ProfilerMarkers.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.ProfilerMarkers.png.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.ProfilerMarkers.png.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.ProfilerMarkers.png.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png
new file mode 100644
index 0000000..a332f4e
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Resources/Editor/Aspid.UnityFastTools Icon.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png.meta
similarity index 95%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Resources/Editor/Aspid.UnityFastTools Icon.png.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png.meta
index 91a7f78..683f8d2 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Resources/Editor/Aspid.UnityFastTools Icon.png.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png.meta
@@ -1,12 +1,12 @@
fileFormatVersion: 2
-guid: daa63609570f146cfa1b178f6d6c8bda
+guid: b0d82cbe15d4842dd9d7c31cb4040e88
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
- enableMipMap: 0
+ enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
@@ -37,10 +37,10 @@ TextureImporter:
filterMode: 1
aniso: 1
mipBias: 0
- wrapU: 1
- wrapV: 1
+ wrapU: 0
+ wrapV: 0
wrapW: 0
- nPOTScale: 0
+ nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
@@ -52,9 +52,9 @@ TextureImporter:
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
- alphaIsTransparency: 1
+ alphaIsTransparency: 0
spriteTessellationDetail: -1
- textureType: 2
+ textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.TypeSelectorWindow.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.TypeSelectorWindow.png
new file mode 100644
index 0000000..5c97940
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.TypeSelectorWindow.png differ
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.TypeSelectorWindow.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.TypeSelectorWindow.png.meta
new file mode 100644
index 0000000..7553ff5
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.TypeSelectorWindow.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 648f40dc17f7a46a99e35e8dcf5318e7
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 0
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 2048
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.VisualElement.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.VisualElement.png
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.VisualElement.png
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.VisualElement.png
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.VisualElement.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.VisualElement.png.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/Images/Aspid.UnityFastTools.VisualElement.png.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.VisualElement.png.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md
new file mode 100644
index 0000000..0b09f20
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md
@@ -0,0 +1,873 @@
+# Aspid.FastTools
+
+**Aspid.FastTools** is a set of tools designed to minimize routine code writing in Unity.
+
+## Source Code
+
+[[Aspid.FastTools](https://github.com/VPDPersonal/Aspid.FastTools)]
+
+
+---
+
+## Integration
+
+Install Aspid.FastTools using one of the following methods:
+
+- **Download .unitypackage** — Visit the [Release page on GitHub](https://github.com/VPDPersonal/Aspid.FastTools/releases) and download the latest version, `Aspid.FastTools.X.X.X.unitypackage`. Import it into your project.
+- **Via UPM** (Unity Package Manager) integrate the following packages:
+ - `https://github.com/VPDPersonal/Aspid.Internal.Unity.git`
+ - `https://github.com/VPDPersonal/Aspid.FastTools.git?path=Aspid.FastTools/Assets/Plugins/Aspid/FastTools`
+
+---
+
+## Namespaces
+
+| Namespace | Description |
+|-----------|-------------|
+| `Aspid.FastTools` | Runtime API — types, VisualElement extensions |
+| `Aspid.FastTools.Editors` | Editor-only API — property drawers, IMGUI scopes, editor extensions |
+
+---
+
+## ProfilerMarker
+
+Provides source-generated `ProfilerMarker` registration. The generator creates a static marker per call-site, identified by the calling method and line number.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public class MyBehaviour : MonoBehaviour
+{
+ private void Update()
+ {
+ DoSomething1();
+ DoSomething2();
+ }
+
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Some code
+ }
+
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Some code
+ using var _ = this.Marker().WithName("Calculate");
+ // Some code
+ }
+ }
+}
+```
+
+### Generated code
+
+```csharp
+using System;
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_line_13 = new("MyBehaviour.DoSomething1 (13)");
+ private static readonly ProfilerMarker DoSomething2_line_19 = new("MyBehaviour.DoSomething2 (19)");
+ private static readonly ProfilerMarker DoSomething2_line_22 = new("MyBehaviour.Calculate (22)");
+
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+ if (line is 13) return DoSomething1_line_13.Auto();
+ if (line is 19) return DoSomething2_line_19.Auto();
+ if (line is 22) return DoSomething2_line_22.Auto();
+
+ throw new Exception();
+ }
+}
+```
+
+### Result
+
+
+
+---
+
+## Serializable Type System
+
+Allows serializing a `System.Type` reference in the Unity Inspector. The selected type is stored as an assembly-qualified name and resolved lazily on first access.
+
+### SerializableType
+
+Two variants are available:
+
+- **`SerializableType`** — stores any type (base type is `object`)
+- **`SerializableType`** — stores a type constrained to `T` or its subclasses
+
+Both support implicit conversion to `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [SerializeField] private SerializableType _anyType;
+ [SerializeField] private SerializableType _behaviourType;
+
+ private void Start()
+ {
+ Type type1 = _anyType; // implicit operator
+ Type type2 = _behaviourType.Type; // explicit property
+
+ var instance = (MonoBehaviour)gameObject.AddComponent(type2);
+ }
+}
+```
+
+### ComponentTypeSelector
+
+A serializable struct that renders a type-switching dropdown in the Inspector. Add it as a field to a base class — picking a subtype rewrites `m_Script` on the `SerializedObject`, effectively changing the component or ScriptableObject to the chosen subtype.
+
+The dropdown is automatically constrained to subtypes of the class that declares the field. No additional configuration is required.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public abstract class BaseEnemy : MonoBehaviour
+{
+ [SerializeField] private ComponentTypeSelector _typeSelector;
+}
+
+public class FastEnemy : BaseEnemy { }
+public class TankEnemy : BaseEnemy { }
+```
+
+---
+
+### TypeSelectorAttribute
+
+An editor-only `PropertyAttribute` that restricts the type selection popup to specific base types. Applied to `string` fields that store assembly-qualified type names.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class TypeSelectorAttribute : PropertyAttribute
+{
+ public TypeSelectorAttribute() // base type: object
+ public TypeSelectorAttribute(Type type)
+ public TypeSelectorAttribute(params Type[] types)
+ public TypeSelectorAttribute(string assemblyQualifiedName)
+ public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
+
+ public bool AllowAbstractTypes { get; set; } // default: false
+ public bool AllowInterfaces { get; set; } // default: false
+}
+```
+
+| Property | Description |
+|----------|-------------|
+| `AllowAbstractTypes` | Includes abstract classes in the picker. Default: `false` |
+| `AllowInterfaces` | Includes interface types in the picker. Default: `false` |
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [TypeSelector(typeof(IMyInterface))]
+ [SerializeField] private string _typeName;
+
+ // Include abstract types and interfaces in the picker
+ [TypeSelector(typeof(object), AllowAbstractTypes = true, AllowInterfaces = true)]
+ [SerializeField] private string _anyType;
+}
+```
+
+### Type Selector Window
+
+The Inspector shows a button that opens a searchable popup window with:
+
+- Hierarchical namespace organization
+- Text search with filtering
+- Keyboard navigation (Arrow keys, Enter, Escape)
+- Navigation history (back button)
+- Assembly disambiguation for types with identical names
+
+
+---
+
+## Enum System
+
+Provides serializable enum-to-value mappings configurable from the Inspector.
+
+### EnumValues\
+
+A serializable collection of `EnumValue` entries with a configurable default value. Implements `IEnumerable>`.
+
+```csharp
+[Serializable]
+public sealed class EnumValues : IEnumerable>
+```
+
+| Member | Description |
+|--------|-------------|
+| `TValue GetValue(Enum enumValue)` | Returns the mapped value, or `_defaultValue` if not found |
+| `bool Equals(Enum, Enum)` | Equality check with proper `[Flags]` support |
+
+Supports `[Flags]` enums: `Equals` uses `HasFlag` and treats `0`-valued members correctly.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public enum Direction { Left, Right, Up, Down }
+
+public class MyBehaviour : MonoBehaviour
+{
+ [SerializeField] private EnumValues _directionSprites;
+
+ private void SetIcon(Direction dir)
+ {
+ var sprite = _directionSprites.GetValue(dir);
+ _image.sprite = sprite;
+ }
+}
+```
+
+In the Inspector, select the enum type in the `EnumValues` header, then assign a value for each enum member.
+
+---
+
+## String ID System
+
+Maps string names to stable integer IDs. Designed for data-driven setups where assets need a reliable, editor-assignable identifier that can be safely used in switch statements and dictionaries.
+
+### Setup
+
+**1.** Declare a `partial struct` implementing `IId`. The source generator adds the required fields and property automatically:
+
+```csharp
+using Aspid.FastTools;
+
+public partial struct EnemyId : IId { }
+```
+
+Generated code:
+
+```csharp
+public partial struct EnemyId
+{
+ [SerializeField] private string __stringId;
+ [SerializeField] private int _id;
+
+ public int Id => _id;
+}
+```
+
+**2.** Create an `IdRegistry` asset via *Assets → Create → Aspid → FastTools → String Id Registry* and bind it to the struct type in the Inspector.
+
+**3.** Use the struct as a serialized field. The Inspector shows a dropdown of registered names with a **Create** button to add new entries inline:
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+[CreateAssetMenu]
+public class EnemyDefinition : ScriptableObject
+{
+ [UniqueId] [SerializeField] private EnemyId _id;
+}
+```
+
+```csharp
+public class EnemySpawner : MonoBehaviour
+{
+ [SerializeField] private EnemyId _targetEnemy;
+
+ private void Spawn()
+ {
+ int id = _targetEnemy.Id; // stable integer, safe for switch / Dictionary
+ }
+}
+```
+
+### UniqueIdAttribute
+
+Marks a field as requiring a unique value across all assets of the declaring type. The Inspector shows a warning if two assets share the same ID.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class UniqueIdAttribute : PropertyAttribute { }
+```
+
+### IdRegistry
+
+A `ScriptableObject` that stores name ↔ integer pairs. Each name is assigned a stable, auto-incrementing ID that never changes even when other entries are added or removed.
+
+| Member | Description |
+|--------|-------------|
+| `bool Contains(string name)` | Returns whether a name is registered |
+| `int Add(string name)` | Registers a name and returns its ID; returns the existing ID if already registered |
+| `void Remove(string name)` | Removes an entry by name |
+| `void Rename(string oldName, string newName)` | Renames an entry |
+| `int GetId(string name)` | Returns the ID for a name, or `0` if not found |
+| `string? GetName(int id)` | Returns the name for an ID, or `null` if not found |
+
+---
+
+## SerializedProperty Extensions
+
+Chainable extension methods on `SerializedProperty` for setting values and applying changes.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+### Update and Apply
+
+```csharp
+property.Update();
+property.UpdateIfRequiredOrScript();
+property.ApplyModifiedProperties();
+```
+
+All methods return `SerializedProperty`, enabling chaining.
+
+### SetValue / SetXxx
+
+For each supported type there are two methods:
+
+- `SetXxx(value)` — sets the value, returns `property` for chaining
+- `SetXxxAndApply(value)` — sets the value and immediately calls `ApplyModifiedProperties()`
+
+`SetValue(value)` dispatches to the appropriate typed setter automatically.
+
+| Method family | Unity type |
+|---------------|-----------|
+| `SetInt` / `SetUint` / `SetLong` / `SetUlong` | Integer types |
+| `SetFloat` / `SetDouble` | Float types |
+| `SetBool` | `bool` |
+| `SetString` | `string` |
+| `SetColor` | `Color` |
+| `SetRect` / `SetRectInt` | `Rect` / `RectInt` |
+| `SetBounds` / `SetBoundsInt` | `Bounds` / `BoundsInt` |
+| `SetVector2` / `SetVector2Int` | `Vector2` / `Vector2Int` |
+| `SetVector3` / `SetVector3Int` | `Vector3` / `Vector3Int` |
+| `SetVector4` | `Vector4` |
+| `SetQuaternion` | `Quaternion` |
+| `SetGradient` | `Gradient` |
+| `SetHash128` | `Hash128` |
+| `SetAnimationCurveValue` | `AnimationCurve` |
+| `SetEnumFlag` / `SetEnumIndex` | Enum flag/index |
+| `SetArraySize` | Array size |
+| `SetManagedReference` | Managed reference |
+| `SetObjectReference` | `UnityEngine.Object` |
+| `SetExposedReference` | Exposed reference |
+| `SetBoxed` | Boxed value *(Unity 6+)* |
+| `SetEntityId` | Entity ID *(Unity 6.2+)* |
+
+```csharp
+SerializedProperty property = GetProperty();
+
+// Simple apply
+property.ApplyModifiedProperties();
+
+// Set and apply — equivalent forms
+property.SetValue(10).ApplyModifiedProperties();
+property.SetValueAndApply(10);
+property.SetInt(10).ApplyModifiedProperties();
+property.SetIntAndApply(10);
+
+// Chain multiple setters
+property.SetVector3(Vector3.up).SetBool(true).ApplyModifiedProperties();
+```
+
+---
+
+## IMGUI Layout Scopes
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+Three scope types are available: `VerticalScope`, `HorizontalScope`, `ScrollViewScope`. Each exposes a `Rect` property and calls the matching `EditorGUILayout.End*` method on `Dispose`.
+
+### Usage via AspidEditorGUILayout
+
+```csharp
+using (AspidEditorGUILayout.BeginVertical())
+{
+ EditorGUILayout.LabelField("Item 1");
+ EditorGUILayout.LabelField("Item 2");
+}
+
+using (AspidEditorGUILayout.BeginHorizontal())
+{
+ EditorGUILayout.LabelField("Left");
+ EditorGUILayout.LabelField("Right");
+}
+
+var scrollPos = Vector2.zero;
+using (AspidEditorGUILayout.BeginScrollView(ref scrollPos))
+{
+ EditorGUILayout.LabelField("Scrollable content");
+}
+```
+
+### Usage via scope structs directly
+
+```csharp
+using (VerticalScope.Begin()) { /* ... */ }
+using (HorizontalScope.Begin()) { /* ... */ }
+using (ScrollViewScope.Begin(ref scrollPos)) { /* ... */ }
+```
+
+All `Begin` overloads match the corresponding `EditorGUILayout.Begin*` signatures (with optional `GUIStyle`, `GUILayoutOption[]`, scroll view options, etc.).
+
+---
+
+## VisualElement Extensions
+
+Fluent extension methods for building UIToolkit trees in code. All methods return `T` (the element itself) for chaining.
+
+```csharp
+using Aspid.FastTools; // runtime extensions
+using Aspid.FastTools.Editors; // editor-only extensions
+```
+
+### Core element operations
+
+```csharp
+element
+ .SetName("MyElement")
+ .SetVisible(true)
+ .SetTooltip("Tooltip text")
+ .AddChild(new Label("Hello"))
+ .AddChildren(child1, child2, child3);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetName(string)` | Sets `element.name` |
+| `SetVisible(bool)` | Sets `element.visible` |
+| `SetTooltip(string)` | Sets `element.tooltip` |
+| `SetUserData(object)` | Sets `element.userData` |
+| `SetEnabledSelf(bool)` | Sets `element.enabledSelf` |
+| `SetPickingMode(PickingMode)` | Sets `element.pickingMode` |
+| `SetUsageHints(UsageHints)` | Sets `element.usageHints` |
+| `SetViewDataKey(string)` | Sets `element.viewDataKey` |
+| `SetLanguageDirection(LanguageDirection)` | Sets `element.languageDirection` |
+| `SetDisablePlayModeTint(bool)` | Sets `element.disablePlayModeTint` |
+| `SetDataSource(object)` | Sets `element.dataSource` |
+| `SetDataSourceType(Type)` | Sets `element.dataSourceType` |
+| `SetDataSourcePath(PropertyPath)` | Sets `element.dataSourcePath` |
+| `AddChild(VisualElement)` | Appends a child, returns the parent |
+| `AddChildren(params VisualElement[])` | Appends multiple children |
+| `AddChildren(IEnumerable)` | Appends from a sequence |
+| `AddChildren(List)` | Appends from a list |
+| `AddChildren(Span)` | Appends from a span |
+| `AddChildren(ReadOnlySpan)` | Appends from a read-only span |
+
+> `RegisterCallbackOnce` and `RegisterCallbackOnce` are available on all Unity versions (polyfill included for versions prior to 2023.1).
+
+### Focusable
+
+| Method | Description |
+|--------|-------------|
+| `SetFocus()` | Attempts to give focus to the element |
+| `SetBlur()` | Tells the element to release focus |
+| `IsFocus()` | Returns whether the element currently has keyboard focus |
+| `SetTabIndex(int)` | Sets `element.tabIndex` |
+| `SetFocusable(bool)` | Sets `element.focusable` |
+| `SetDelegatesFocus(bool)` | Sets `element.delegatesFocus` |
+
+### USS & class operations
+
+| Method | Description |
+|--------|-------------|
+| `AddClass(string)` | Adds a USS class |
+| `RemoveClass(string)` | Removes a USS class |
+| `ClearClasses()` | Removes all USS classes |
+| `ToggleInClass(string)` | Toggles a USS class on/off |
+| `EnableInClass(string, bool)` | Adds or removes a USS class based on a condition |
+| `AddStyleSheets(StyleSheet)` | Adds a `StyleSheet` |
+| `RemoveStyleSheets(StyleSheet)` | Removes a `StyleSheet` |
+| `AddStyleSheetsFromResource(string)` | Adds a stylesheet loaded via `Resources.Load` |
+| `RemoveStyleSheetsFromResource(string)` | Removes a stylesheet loaded via `Resources.Load` |
+
+### Style extensions — by category
+
+All style methods are also available on `IStyle` directly (same method names, operate on the style object).
+
+#### Layout
+
+| Method | Style property |
+|--------|---------------|
+| `SetFlexBasis(StyleLength)` | `flexBasis` |
+| `SetFlexGrow(StyleFloat)` | `flexGrow` |
+| `SetFlexShrink(StyleFloat)` | `flexShrink` |
+| `SetFlexWrap(StyleEnum)` | `flexWrap` |
+| `SetFlexDirection(FlexDirection)` | `flexDirection` |
+| `SetAlignSelf(StyleEnum)` | `alignSelf` |
+| `SetAlignItems(StyleEnum)` | `alignItems` |
+| `SetAlignContent(StyleEnum)` | `alignContent` |
+| `SetJustifyContent(StyleEnum)` | `justifyContent` |
+| `SetPosition(StyleEnum)` | `position` |
+
+#### Size
+
+| Method | Description |
+|--------|-------------|
+| `SetSize(StyleLength)` | Sets both width and height |
+| `SetSize(width?, height?)` | Sets width and/or height independently |
+| `SetMinSize(StyleLength)` | Sets both minWidth and minHeight |
+| `SetMinSize(width?, height?)` | |
+| `SetMaxSize(StyleLength)` | Sets both maxWidth and maxHeight |
+| `SetMaxSize(width?, height?)` | |
+
+#### Spacing
+
+All spacing methods have a uniform-value overload and a per-side overload (`top`, `bottom`, `left`, `right`).
+
+| Method | Style properties |
+|--------|----------------------------------------------------|
+| `SetMargin(…)` | `Top/Bottom/Left/Right` |
+| `SetPadding(…)` | `Top/Bottom/Left/Right` |
+| `SetDistance(…)` | `Top/Bottom/Left/Right` (absolute position offset) |
+
+#### Font
+
+| Method | Style property |
+|--------|---------------|
+| `SetUnityFont(StyleFont)` | `unityFont` |
+| `SetFontSize(StyleLength)` | `fontSize` |
+| `SetUnityFontDefinition(StyleFontDefinition)` | `unityFontDefinition` |
+| `SetUnityFontStyleAndWeight(StyleEnum)` | `unityFontStyleAndWeight` |
+
+#### Font style presets
+
+Convenience methods for toggling bold / italic without overwriting the other flag:
+
+| Method | Description |
+|--------|-------------|
+| `SetNormalUnityFontStyleAndWeight()` | Resets to `FontStyle.Normal` |
+| `AddBoldUnityFontStyleAndWeight()` | Adds bold, preserving italic |
+| `RemoveBoldUnityFontStyleAndWeight()` | Removes bold, preserving italic |
+| `AddItalicUnityFontStyleAndWeight()` | Adds italic, preserving bold |
+| `RemoveItalicUnityFontStyleAndWeight()` | Removes italic, preserving bold |
+
+#### Text
+
+| Method | Style property | Notes |
+|--------|---------------|-------|
+| `SetWorldSpacing(StyleLength)` | `wordSpacing` | |
+| `SetLetterSpacing(StyleLength)` | `letterSpacing` | |
+| `SetUnityTextAlign(TextAnchor)` | `unityTextAlign` | |
+| `SetTextShadow(StyleTextShadow)` | `textShadow` | |
+| `SetUnityTextOutlineColor(StyleColor)` | `unityTextOutlineColor` | |
+| `SetUnityTextOutlineWidth(StyleFloat)` | `unityTextOutlineWidth` | |
+| `SetUnityParagraphSpacing(StyleLength)` | `unityParagraphSpacing` | |
+| `SetTextOverflow(StyleEnum)` | `textOverflow` | |
+| `SetUnityTextOverflowPosition(TextOverflowPosition)` | `unityTextOverflowPosition` | |
+| `SetUnityTextGenerator(TextGeneratorType)` | `unityTextGenerator` | Unity 6+ |
+| `SetUnityEditorTextRenderingMode(EditorTextRenderingMode)` | `unityEditorTextRenderingMode` | Unity 6+ |
+| `SetUnityTextAutoSize(StyleTextAutoSize)` | `unityTextAutoSize` | Unity 6.2+ |
+| `SetWhiteSpace(StyleEnum)` | `whiteSpace` | |
+
+#### Color & Opacity
+
+| Method | Style property |
+|--------|---------------|
+| `SetColor(StyleColor)` | `color` |
+| `SetOpacity(StyleFloat)` | `opacity` |
+
+#### Border
+
+| Method | Description |
+|--------|-------------|
+| `SetBorderColor(StyleColor)` | All sides |
+| `SetBorderColor(top?, bottom?, left?, right?)` | Per side |
+| `SetBorderRadius(StyleLength)` | All corners |
+| `SetBorderRadius(topLeft?, topRight?, bottomLeft?, bottomRight?)` | Per corner |
+| `SetBorderWidth(StyleFloat)` | All sides |
+| `SetBorderWidth(top?, bottom?, left?, right?)` | Per side |
+
+#### Background
+
+| Method | Style property |
+|--------|---------------|
+| `SetBackgroundColor(StyleColor)` | `backgroundColor` |
+| `SetBackgroundImage(StyleBackground)` | `backgroundImage` |
+| `SetBackgroundSize(StyleBackgroundSize)` | `backgroundSize` |
+| `SetBackgroundRepeat(StyleBackgroundRepeat)` | `backgroundRepeat` |
+| `SetBackgroundPosition(StyleBackgroundPosition)` | Both X and Y |
+| `SetBackgroundPosition(x?, y?)` | Independently |
+| `SetUnityBackgroundImageTintColor(StyleColor)` | `unityBackgroundImageTintColor` |
+
+#### Transform
+
+| Method | Style property |
+|--------|---------------|
+| `SetScale(StyleScale)` | `scale` |
+| `SetRotate(StyleRotate)` | `rotate` |
+| `SetTranslate(StyleTranslate)` | `translate` |
+| `SetTransformOrigin(StyleTransformOrigin)` | `transformOrigin` |
+
+#### Transition
+
+| Method | Style property |
+|--------|---------------|
+| `SetTransitionDelay(StyleList)` | `transitionDelay` |
+| `SetTransitionDuration(StyleList)` | `transitionDuration` |
+| `SetTransitionProperty(StyleList)` | `transitionProperty` |
+| `SetTransitionTimingFunction(StyleList)` | `transitionTimingFunction` |
+
+#### Overflow & Visibility
+
+| Method | Style property |
+|--------|---------------|
+| `SetOverflow(StyleEnum)` | `overflow` |
+| `SetUnityOverflowClipBox(StyleEnum)` | `unityOverflowClipBox` |
+| `SetVisibility(StyleEnum)` | `visibility` |
+| `SetDisplay(DisplayStyle)` | `display` |
+
+#### Unity Slice
+
+| Method | Description |
+|--------|-------------|
+| `SetUnitySlice(StyleInt)` | All sides |
+| `SetUnitySlice(top?, bottom?, left?, right?)` | Per side |
+| `SetUnitySliceType(StyleEnum)` | Unity 6+ |
+
+#### Cursor
+
+| Method | Style property |
+|--------|---------------|
+| `SetCursor(StyleCursor)` | `cursor` |
+
+### Specialized element extensions
+
+#### TextElement
+
+```csharp
+label.SetText("Hello World");
+```
+
+#### BaseField\
+
+```csharp
+field.SetLabel("My Field");
+field.SetValue(42);
+```
+
+#### INotifyValueChanged\
+
+```csharp
+field.SetValue(42, notify: false); // sets value without raising ChangeEvent
+field.AddValueChanged(evt => Debug.Log(evt.newValue));
+field.RemoveValueChanged(myCallback);
+```
+
+Typed overloads are provided for `int`, `uint`, `long`, `ulong`, `short`, `ushort`, `byte`, `sbyte`, `float`, `double`, `string`, `bool`, `Color`, `Vector2/3/4`, `Vector2Int/3Int`, `Rect/RectInt`, `Bounds/BoundsInt`, `Hash128`, `Enum`, `Object`, and more.
+
+#### IMixedValueSupport
+
+```csharp
+field.SetShowMixedValue(true); // shows the mixed-value indicator
+```
+
+#### Button
+
+```csharp
+button
+ .AddClicked(() => Debug.Log("Clicked"))
+ .SetClickable(new Clickable(() => { }))
+ .SetIconImage(myBackground);
+```
+
+| Method | Description |
+|--------|-------------|
+| `AddClicked(Action)` | Subscribes to `Button.clicked` |
+| `RemoveClicked(Action)` | Unsubscribes from `Button.clicked` |
+| `SetClickable(Clickable)` | Sets `Button.clickable` |
+| `SetIconImage(Background)` | Sets `Button.iconImage` |
+
+#### Slider / BaseSlider\
+
+```csharp
+slider
+ .SetLowValue(0f)
+ .SetHighValue(100f)
+ .SetShowInputField(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetLowValue(TValue)` | Sets the minimum slider value |
+| `SetHighValue(TValue)` | Sets the maximum slider value |
+| `SetFill(bool)` | Whether the track is filled up to the current value |
+| `SetInverted(bool)` | Reverses the slider direction |
+| `SetPageSize(float)` | Controls how much the value changes per page step |
+| `SetShowInputField(bool)` | Shows a numeric input field alongside the slider |
+| `SetDirection(SliderDirection)` | Sets the slider orientation |
+
+#### ProgressBar
+
+```csharp
+progressBar.SetTitle("Loading...").SetLowValue(0f).SetHighValue(100f);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetTitle(string)` | Sets the title displayed in the center |
+| `SetLowValue(float)` | Sets the minimum value |
+| `SetHighValue(float)` | Sets the maximum value |
+
+#### HelpBox
+
+```csharp
+helpBox.SetHelpBoxFontSize(14);
+helpBox.SetMessageType(14, HelpBoxMessageType.Warning);
+```
+
+#### Foldout
+
+```csharp
+foldout.SetText("Section Title");
+foldout.SetValue(true);
+```
+
+#### Image
+
+```csharp
+image.SetImage(myTexture);
+image.SetImageFromResource("Editor/MyIcon"); // loads via Resources.Load
+```
+
+#### IMGUIContainer
+
+```csharp
+container
+ .SetOnGUIHandler(() => GUILayout.Label("IMGUI"))
+ .SetCullingEnabled(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetOnGUIHandler(Action)` | Replaces the `onGUIHandler` callback |
+| `AddOnGUIHandler(Action)` | Subscribes to `onGUIHandler` |
+| `RemoveOnGUIHandler(Action)` | Unsubscribes from `onGUIHandler` |
+| `SetCullingEnabled(bool)` | Skips `onGUIHandler` when the element is offscreen |
+| `SetContextType(ContextType)` | Sets the IMGUI context type |
+
+#### ListView / CollectionView
+
+| Method | Description | Notes |
+|--------|-------------|-------|
+| `SetBindItem(Action)` | Item binding callback | |
+| `SetMakeItem(Func)` | Item factory | |
+| `SetMakeFooter(Func)` | Footer factory | Unity 6+ |
+| `SetMakeHeader(Func)` | Header factory | Unity 6+ |
+| `SetMakeNoneElement(Func)` | Empty state element factory | Unity 6+ |
+
+### Editor commands (editor-only)
+
+```csharp
+using Aspid.FastTools.Editors;
+
+image.AddOpenScriptCommand(target);
+// Double-clicking the element opens the script for 'target' in the IDE
+```
+
+### Full example
+
+```csharp
+using UnityEditor;
+using UnityEngine;
+using Aspid.FastTools;
+using Aspid.FastTools.Editors;
+using UnityEngine.UIElements;
+
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ const string iconPath = "Editor/MyIcon";
+
+ var scriptName = target.GetScriptName();
+ var dark = new Color(0.15f, 0.15f, 0.15f);
+ var light = new Color(0.75f, 0.75f, 0.75f);
+
+ return new VisualElement()
+ .SetName("Header")
+ .SetBackgroundColor(dark)
+ .SetFlexDirection(FlexDirection.Row)
+ .SetPadding(top: 5, bottom: 5, left: 10, right: 10)
+ .SetBorderRadius(topLeft: 10, topRight: 10, bottomLeft: 10, bottomRight: 10)
+ .AddChild(new Image()
+ .SetName("Icon")
+ .AddOpenScriptCommand(target)
+ .SetImageFromResource(iconPath)
+ .SetSize(width: 40, height: 40))
+ .AddChild(new Label(scriptName)
+ .SetName("Title")
+ .SetFlexGrow(1)
+ .SetFontSize(16)
+ .SetMargin(left: 10)
+ .SetColor(light)
+ .SetAlignSelf(Align.Center)
+ .SetOverflow(Overflow.Hidden)
+ .SetWhiteSpace(WhiteSpace.NoWrap)
+ .SetTextOverflow(TextOverflow.Ellipsis)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold));
+ }
+}
+```
+
+### Result
+
+
+
+---
+
+## Editor Helper Extensions
+
+Utility methods for getting display names of Unity objects in custom editors.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+```csharp
+public static string GetScriptName(this Object obj)
+```
+
+Returns the display name of a Unity object:
+- If the type has `[AddComponentMenu]`, returns `ObjectNames.GetInspectorTitle(obj)`
+- Otherwise returns `ObjectNames.NicifyVariableName(typeName)`
+
+```csharp
+public static string GetScriptNameWithIndex(this Component targetComponent)
+```
+
+Returns the display name with a count suffix when multiple components of the same type exist on the same GameObject. For example, if two `AudioSource` components are attached, the second returns `"Audio Source (2)"`.
+
+```csharp
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ // "My Behaviour" — or "Custom Name" if [AddComponentMenu("Custom Name")] is present
+ var name = target.GetScriptName();
+
+ // "My Behaviour (2)" when a second component of the same type exists
+ var nameWithIndex = ((Component)target).GetScriptNameWithIndex();
+
+ return new Label(name);
+ }
+}
+```
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/README.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/README.md.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md
new file mode 100644
index 0000000..beab0b9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md
@@ -0,0 +1,869 @@
+# Aspid.FastTools
+
+**Aspid.FastTools** — набор инструментов, предназначенных для минимизации рутинного написания кода в Unity.
+
+## Исходный код
+
+[[Aspid.FastTools](https://github.com/VPDPersonal/Aspid.FastTools)]
+
+
+Установите Aspid.FastTools одним из следующих способов:
+
+- **Скачать .unitypackage** — Перейдите на [страницу релизов GitHub](https://github.com/VPDPersonal/Aspid.FastTools/releases) и скачайте последнюю версию `Aspid.FastTools.X.X.X.unitypackage`. Импортируйте его в проект.
+- **Через UPM** (Unity Package Manager) подключите следующие пакеты:
+ - `https://github.com/VPDPersonal/Aspid.Internal.Unity.git`
+ - `https://github.com/VPDPersonal/Aspid.FastTools.git?path=Aspid.FastTools/Assets/Plugins/Aspid/FastTools`
+
+---
+
+## Пространства имён
+
+| Пространство имён | Описание |
+|-------------------|----------|
+| `Aspid.FastTools` | Runtime API — типы, расширения VisualElement |
+| `Aspid.FastTools.Editors` | Editor-only API — property drawers, IMGUI-области, расширения редактора |
+
+---
+
+## ProfilerMarker
+
+Предоставляет регистрацию `ProfilerMarker` через source generation. Генератор создаёт статический маркер для каждого места вызова, идентифицируемый по вызывающему методу и номеру строки.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public class MyBehaviour : MonoBehaviour
+{
+ private void Update()
+ {
+ DoSomething1();
+ DoSomething2();
+ }
+
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Некоторый код
+ }
+
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Некоторый код
+ using var _ = this.Marker().WithName("Calculate");
+ // Некоторый код
+ }
+ }
+}
+```
+
+### Сгенерированный код
+
+```csharp
+using System;
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_line_13 = new("MyBehaviour.DoSomething1 (13)");
+ private static readonly ProfilerMarker DoSomething2_line_19 = new("MyBehaviour.DoSomething2 (19)");
+ private static readonly ProfilerMarker DoSomething2_line_22 = new("MyBehaviour.Calculate (22)");
+
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+ if (line is 13) return DoSomething1_line_13.Auto();
+ if (line is 19) return DoSomething2_line_19.Auto();
+ if (line is 22) return DoSomething2_line_22.Auto();
+
+ throw new Exception();
+ }
+}
+```
+
+### Результат
+
+
+
+---
+
+## Система сериализуемых типов
+
+Позволяет сериализовать ссылку на `System.Type` в Unity Inspector. Выбранный тип хранится как assembly-qualified name и разрешается лениво при первом обращении.
+
+### SerializableType
+
+Доступны два варианта:
+
+- **`SerializableType`** — хранит любой тип (базовый тип — `object`)
+- **`SerializableType`** — хранит тип, ограниченный `T` или его подклассами
+
+Оба поддерживают неявное преобразование в `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [SerializeField] private SerializableType _anyType;
+ [SerializeField] private SerializableType _behaviourType;
+
+ private void Start()
+ {
+ Type type1 = _anyType; // неявный оператор
+ Type type2 = _behaviourType.Type; // явное свойство
+
+ var instance = (MonoBehaviour)gameObject.AddComponent(type2);
+ }
+}
+```
+
+### ComponentTypeSelector
+
+Сериализуемая структура, добавляющая в Inspector выпадающий список для смены типа объекта. Добавьте её как поле в базовый класс — при выборе подтипа редактор перезаписывает `m_Script` на `SerializedObject`, фактически превращая компонент или ScriptableObject в выбранный подтип.
+
+Список автоматически ограничивается подтипами класса, в котором объявлено поле. Дополнительная настройка не требуется.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public abstract class BaseEnemy : MonoBehaviour
+{
+ [SerializeField] private ComponentTypeSelector _typeSelector;
+}
+
+public class FastEnemy : BaseEnemy { }
+public class TankEnemy : BaseEnemy { }
+```
+
+---
+
+### TypeSelectorAttribute
+
+Атрибут `PropertyAttribute`, доступный только в редакторе, ограничивающий всплывающее окно выбора типа конкретными базовыми типами. Применяется к полям `string`, хранящим assembly-qualified имена типов.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class TypeSelectorAttribute : PropertyAttribute
+{
+ public TypeSelectorAttribute() // базовый тип: object
+ public TypeSelectorAttribute(Type type)
+ public TypeSelectorAttribute(params Type[] types)
+ public TypeSelectorAttribute(string assemblyQualifiedName)
+ public TypeSelectorAttribute(params string[] assemblyQualifiedNames)
+
+ public bool AllowAbstractTypes { get; set; } // по умолчанию: false
+ public bool AllowInterfaces { get; set; } // по умолчанию: false
+}
+```
+
+| Свойство | Описание |
+|----------|----------|
+| `AllowAbstractTypes` | Включает абстрактные классы в список выбора. По умолчанию: `false` |
+| `AllowInterfaces` | Включает интерфейсы в список выбора. По умолчанию: `false` |
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [TypeSelector(typeof(IMyInterface))]
+ [SerializeField] private string _typeName;
+
+ // Включить абстрактные типы и интерфейсы в список выбора
+ [TypeSelector(typeof(object), AllowAbstractTypes = true, AllowInterfaces = true)]
+ [SerializeField] private string _anyType;
+}
+```
+
+### Окно выбора типа
+
+В Inspector отображается кнопка, открывающая всплывающее окно с поиском, которое включает:
+
+- Иерархическую организацию по пространствам имён
+- Текстовый поиск с фильтрацией
+- Навигацию с клавиатуры (стрелки, Enter, Escape)
+- Историю навигации (кнопка «назад»)
+- Разрешение неоднозначности для типов с одинаковыми именами из разных сборок
+
+
+---
+
+## Система перечислений
+
+Предоставляет сериализуемые отображения enum → значение, настраиваемые через Inspector.
+
+### EnumValues\
+
+Сериализуемая коллекция записей `EnumValue` с настраиваемым значением по умолчанию. Реализует `IEnumerable>`.
+
+```csharp
+[Serializable]
+public sealed class EnumValues : IEnumerable>
+```
+
+| Член | Описание |
+|------|----------|
+| `TValue GetValue(Enum enumValue)` | Возвращает сопоставленное значение или `_defaultValue`, если не найдено |
+| `bool Equals(Enum, Enum)` | Проверка равенства с поддержкой `[Flags]` |
+
+Поддерживает `[Flags]`-перечисления: `Equals` использует `HasFlag` и корректно обрабатывает члены со значением `0`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+public enum Direction { Left, Right, Up, Down }
+
+public class MyBehaviour : MonoBehaviour
+{
+ [SerializeField] private EnumValues _directionSprites;
+
+ private void SetIcon(Direction dir)
+ {
+ var sprite = _directionSprites.GetValue(dir);
+ _image.sprite = sprite;
+ }
+}
+```
+
+В Inspector выберите тип перечисления в заголовке `EnumValues`, затем назначьте значение для каждого члена перечисления.
+
+---
+
+## Система строковых ID
+
+Отображает строковые имена в стабильные целочисленные ID. Предназначена для data-driven проектов, где ассетам нужен надёжный, назначаемый из редактора идентификатор, безопасный для использования в `switch` и `Dictionary`.
+
+### Использование
+
+**1.** Объявите `partial struct`, реализующий `IId`. Генератор исходников автоматически добавит необходимые поля и свойство:
+
+```csharp
+using Aspid.FastTools;
+
+public partial struct EnemyId : IId { }
+```
+
+Сгенерированный код:
+
+```csharp
+public partial struct EnemyId
+{
+ [SerializeField] private string __stringId;
+ [SerializeField] private int _id;
+
+ public int Id => _id;
+}
+```
+
+**2.** Создайте ассет `IdRegistry` через меню *Assets → Create → Aspid → FastTools → String Id Registry* и привяжите его к вашему типу структуры в Inspector.
+
+**3.** Используйте структуру как сериализуемое поле. В Inspector отображается выпадающий список зарегистрированных имён с кнопкой **Create** для добавления новых записей прямо там:
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools;
+
+[CreateAssetMenu]
+public class EnemyDefinition : ScriptableObject
+{
+ [UniqueId] [SerializeField] private EnemyId _id;
+}
+```
+
+```csharp
+public class EnemySpawner : MonoBehaviour
+{
+ [SerializeField] private EnemyId _targetEnemy;
+
+ private void Spawn()
+ {
+ int id = _targetEnemy.Id; // стабильный integer, безопасен для switch / Dictionary
+ }
+}
+```
+
+### UniqueIdAttribute
+
+Помечает поле как требующее уникального значения среди всех ассетов объявляющего типа. Inspector показывает предупреждение, если два ассета используют одинаковый ID.
+
+```csharp
+[Conditional("UNITY_EDITOR")]
+public sealed class UniqueIdAttribute : PropertyAttribute { }
+```
+
+### IdRegistry
+
+`ScriptableObject`, хранящий пары имя ↔ целое число. Каждому имени назначается стабильный, автоинкрементный ID, который не изменяется даже при добавлении или удалении других записей.
+
+| Член | Описание |
+|------|----------|
+| `bool Contains(string name)` | Возвращает, зарегистрировано ли имя |
+| `int Add(string name)` | Регистрирует имя и возвращает ID; если имя уже есть — возвращает существующий ID |
+| `void Remove(string name)` | Удаляет запись по имени |
+| `void Rename(string oldName, string newName)` | Переименовывает запись |
+| `int GetId(string name)` | Возвращает ID для имени или `0`, если не найдено |
+| `string? GetName(int id)` | Возвращает имя для ID или `null`, если не найдено |
+
+---
+
+## Расширения SerializedProperty
+
+Цепочечные методы расширения для `SerializedProperty`, позволяющие задавать значения и применять изменения.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+### Update и Apply
+
+```csharp
+property.Update();
+property.UpdateIfRequiredOrScript();
+property.ApplyModifiedProperties();
+```
+
+Все методы возвращают `SerializedProperty`, что позволяет строить цепочки вызовов.
+
+### SetValue / SetXxx
+
+Для каждого поддерживаемого типа доступны два метода:
+
+- `SetXxx(value)` — устанавливает значение, возвращает `property` для цепочки
+- `SetXxxAndApply(value)` — устанавливает значение и сразу вызывает `ApplyModifiedProperties()`
+
+`SetValue(value)` автоматически направляет вызов к соответствующему типизированному сеттеру.
+
+| Семейство методов | Тип Unity |
+|-------------------|-----------|
+| `SetInt` / `SetUint` / `SetLong` / `SetUlong` | Целочисленные типы |
+| `SetFloat` / `SetDouble` | Вещественные типы |
+| `SetBool` | `bool` |
+| `SetString` | `string` |
+| `SetColor` | `Color` |
+| `SetRect` / `SetRectInt` | `Rect` / `RectInt` |
+| `SetBounds` / `SetBoundsInt` | `Bounds` / `BoundsInt` |
+| `SetVector2` / `SetVector2Int` | `Vector2` / `Vector2Int` |
+| `SetVector3` / `SetVector3Int` | `Vector3` / `Vector3Int` |
+| `SetVector4` | `Vector4` |
+| `SetQuaternion` | `Quaternion` |
+| `SetGradient` | `Gradient` |
+| `SetHash128` | `Hash128` |
+| `SetAnimationCurveValue` | `AnimationCurve` |
+| `SetEnumFlag` / `SetEnumIndex` | Флаг/индекс перечисления |
+| `SetArraySize` | Размер массива |
+| `SetManagedReference` | Управляемая ссылка |
+| `SetObjectReference` | `UnityEngine.Object` |
+| `SetExposedReference` | Exposed reference |
+| `SetBoxed` | Упакованное значение *(Unity 6+)* |
+| `SetEntityId` | Entity ID *(Unity 6.2+)* |
+
+```csharp
+SerializedProperty property = GetProperty();
+
+// Простое применение
+property.ApplyModifiedProperties();
+
+// Установка и применение — эквивалентные формы
+property.SetValue(10).ApplyModifiedProperties();
+property.SetValueAndApply(10);
+property.SetInt(10).ApplyModifiedProperties();
+property.SetIntAndApply(10);
+
+// Цепочка нескольких сеттеров
+property.SetVector3(Vector3.up).SetBool(true).ApplyModifiedProperties();
+```
+
+---
+
+## IMGUI-области разметки
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+Доступны три типа областей: `VerticalScope`, `HorizontalScope`, `ScrollViewScope`. Каждая предоставляет свойство `Rect` и вызывает соответствующий метод `EditorGUILayout.End*` в `Dispose`.
+
+### Использование через AspidEditorGUILayout
+
+```csharp
+using (AspidEditorGUILayout.BeginVertical())
+{
+ EditorGUILayout.LabelField("Item 1");
+ EditorGUILayout.LabelField("Item 2");
+}
+
+using (AspidEditorGUILayout.BeginHorizontal())
+{
+ EditorGUILayout.LabelField("Left");
+ EditorGUILayout.LabelField("Right");
+}
+
+var scrollPos = Vector2.zero;
+using (AspidEditorGUILayout.BeginScrollView(ref scrollPos))
+{
+ EditorGUILayout.LabelField("Scrollable content");
+}
+```
+
+### Использование через структуры областей напрямую
+
+```csharp
+using (VerticalScope.Begin()) { /* ... */ }
+using (HorizontalScope.Begin()) { /* ... */ }
+using (ScrollViewScope.Begin(ref scrollPos)) { /* ... */ }
+```
+
+Все перегрузки `Begin` соответствуют сигнатурам `EditorGUILayout.Begin*` (с опциональными `GUIStyle`, `GUILayoutOption[]`, параметрами scroll view и т.д.).
+
+---
+
+## Расширения VisualElement
+
+Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
+
+```csharp
+using Aspid.FastTools; // runtime-расширения
+using Aspid.FastTools.Editors; // editor-only расширения
+```
+
+### Основные операции с элементами
+
+```csharp
+element
+ .SetName("MyElement")
+ .SetVisible(true)
+ .SetTooltip("Текст подсказки")
+ .AddChild(new Label("Hello"))
+ .AddChildren(child1, child2, child3);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetName(string)` | Устанавливает `element.name` |
+| `SetVisible(bool)` | Устанавливает `element.visible` |
+| `SetTooltip(string)` | Устанавливает `element.tooltip` |
+| `SetUserData(object)` | Устанавливает `element.userData` |
+| `SetEnabledSelf(bool)` | Устанавливает `element.enabledSelf` |
+| `SetPickingMode(PickingMode)` | Устанавливает `element.pickingMode` |
+| `SetUsageHints(UsageHints)` | Устанавливает `element.usageHints` |
+| `SetViewDataKey(string)` | Устанавливает `element.viewDataKey` |
+| `SetLanguageDirection(LanguageDirection)` | Устанавливает `element.languageDirection` |
+| `SetDisablePlayModeTint(bool)` | Устанавливает `element.disablePlayModeTint` |
+| `SetDataSource(object)` | Устанавливает `element.dataSource` |
+| `SetDataSourceType(Type)` | Устанавливает `element.dataSourceType` |
+| `SetDataSourcePath(PropertyPath)` | Устанавливает `element.dataSourcePath` |
+| `AddChild(VisualElement)` | Добавляет дочерний элемент, возвращает родителя |
+| `AddChildren(params VisualElement[])` | Добавляет несколько дочерних элементов |
+| `AddChildren(IEnumerable)` | Добавляет из последовательности |
+| `AddChildren(List)` | Добавляет из списка |
+| `AddChildren(Span)` | Добавляет из span |
+| `AddChildren(ReadOnlySpan)` | Добавляет из read-only span |
+
+> `RegisterCallbackOnce` и `RegisterCallbackOnce` доступны на всех версиях Unity (пакет содержит polyfill для версий до 2023.1).
+
+### Focusable
+
+| Метод | Описание |
+|-------|----------|
+| `SetFocus()` | Устанавливает фокус на элемент |
+| `SetBlur()` | Снимает фокус с элемента |
+| `IsFocus()` | Возвращает, находится ли элемент в фокусе |
+| `SetTabIndex(int)` | Устанавливает `element.tabIndex` |
+| `SetFocusable(bool)` | Устанавливает `element.focusable` |
+| `SetDelegatesFocus(bool)` | Устанавливает `element.delegatesFocus` |
+
+### USS и операции с классами
+
+| Метод | Описание |
+|-------|----------|
+| `AddClass(string)` | Добавляет USS-класс |
+| `RemoveClass(string)` | Удаляет USS-класс |
+| `ClearClasses()` | Удаляет все USS-классы |
+| `ToggleInClass(string)` | Переключает USS-класс вкл/выкл |
+| `EnableInClass(string, bool)` | Добавляет или удаляет USS-класс по условию |
+| `AddStyleSheets(StyleSheet)` | Добавляет `StyleSheet` |
+| `RemoveStyleSheets(StyleSheet)` | Удаляет `StyleSheet` |
+| `AddStyleSheetsFromResource(string)` | Добавляет таблицу стилей через `Resources.Load` |
+| `RemoveStyleSheetsFromResource(string)` | Удаляет таблицу стилей, загруженную через `Resources.Load` |
+
+### Расширения стилей — по категориям
+
+Все методы стилей также доступны напрямую на `IStyle` (те же имена методов, работают с объектом стиля).
+
+#### Разметка
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetFlexBasis(StyleLength)` | `flexBasis` |
+| `SetFlexGrow(StyleFloat)` | `flexGrow` |
+| `SetFlexShrink(StyleFloat)` | `flexShrink` |
+| `SetFlexWrap(StyleEnum)` | `flexWrap` |
+| `SetFlexDirection(FlexDirection)` | `flexDirection` |
+| `SetAlignSelf(StyleEnum)` | `alignSelf` |
+| `SetAlignItems(StyleEnum)` | `alignItems` |
+| `SetAlignContent(StyleEnum)` | `alignContent` |
+| `SetJustifyContent(StyleEnum)` | `justifyContent` |
+| `SetPosition(StyleEnum)` | `position` |
+
+#### Размер
+
+| Метод | Описание |
+|-------|----------|
+| `SetSize(StyleLength)` | Устанавливает ширину и высоту одновременно |
+| `SetSize(width?, height?)` | Устанавливает ширину и/или высоту независимо |
+| `SetMinSize(StyleLength)` | Устанавливает minWidth и minHeight одновременно |
+| `SetMinSize(width?, height?)` | |
+| `SetMaxSize(StyleLength)` | Устанавливает maxWidth и maxHeight одновременно |
+| `SetMaxSize(width?, height?)` | |
+
+#### Отступы
+
+Все методы отступов имеют перегрузку с единым значением и перегрузку по сторонам (`top`, `bottom`, `left`, `right`).
+
+| Метод | Свойства стиля |
+|-------|----------------|
+| `SetMargin(…)` | `Top/Bottom/Left/Right` |
+| `SetPadding(…)` | `Top/Bottom/Left/Right` |
+| `SetDistance(…)` | `Top/Bottom/Left/Right` (смещение для абсолютного позиционирования) |
+
+#### Шрифт
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetUnityFont(StyleFont)` | `unityFont` |
+| `SetFontSize(StyleLength)` | `fontSize` |
+| `SetUnityFontDefinition(StyleFontDefinition)` | `unityFontDefinition` |
+| `SetUnityFontStyleAndWeight(StyleEnum)` | `unityFontStyleAndWeight` |
+
+#### Пресеты стиля шрифта
+
+Удобные методы для переключения bold / italic без перезаписи другого флага:
+
+| Метод | Описание |
+|-------|----------|
+| `SetNormalUnityFontStyleAndWeight()` | Сбрасывает в `FontStyle.Normal` |
+| `AddBoldUnityFontStyleAndWeight()` | Добавляет bold, сохраняя italic |
+| `RemoveBoldUnityFontStyleAndWeight()` | Убирает bold, сохраняя italic |
+| `AddItalicUnityFontStyleAndWeight()` | Добавляет italic, сохраняя bold |
+| `RemoveItalicUnityFontStyleAndWeight()` | Убирает italic, сохраняя bold |
+
+#### Текст
+
+| Метод | Свойство стиля | Примечания |
+|-------|---------------|------------|
+| `SetWorldSpacing(StyleLength)` | `wordSpacing` | |
+| `SetLetterSpacing(StyleLength)` | `letterSpacing` | |
+| `SetUnityTextAlign(TextAnchor)` | `unityTextAlign` | |
+| `SetTextShadow(StyleTextShadow)` | `textShadow` | |
+| `SetUnityTextOutlineColor(StyleColor)` | `unityTextOutlineColor` | |
+| `SetUnityTextOutlineWidth(StyleFloat)` | `unityTextOutlineWidth` | |
+| `SetUnityParagraphSpacing(StyleLength)` | `unityParagraphSpacing` | |
+| `SetTextOverflow(StyleEnum)` | `textOverflow` | |
+| `SetUnityTextOverflowPosition(TextOverflowPosition)` | `unityTextOverflowPosition` | |
+| `SetUnityTextGenerator(TextGeneratorType)` | `unityTextGenerator` | Unity 6+ |
+| `SetUnityEditorTextRenderingMode(EditorTextRenderingMode)` | `unityEditorTextRenderingMode` | Unity 6+ |
+| `SetUnityTextAutoSize(StyleTextAutoSize)` | `unityTextAutoSize` | Unity 6.2+ |
+| `SetWhiteSpace(StyleEnum)` | `whiteSpace` | |
+
+#### Цвет и прозрачность
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetColor(StyleColor)` | `color` |
+| `SetOpacity(StyleFloat)` | `opacity` |
+
+#### Рамка
+
+| Метод | Описание |
+|-------|----------|
+| `SetBorderColor(StyleColor)` | Все стороны |
+| `SetBorderColor(top?, bottom?, left?, right?)` | По стороне |
+| `SetBorderRadius(StyleLength)` | Все углы |
+| `SetBorderRadius(topLeft?, topRight?, bottomLeft?, bottomRight?)` | По углу |
+| `SetBorderWidth(StyleFloat)` | Все стороны |
+| `SetBorderWidth(top?, bottom?, left?, right?)` | По стороне |
+
+#### Фон
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetBackgroundColor(StyleColor)` | `backgroundColor` |
+| `SetBackgroundImage(StyleBackground)` | `backgroundImage` |
+| `SetBackgroundSize(StyleBackgroundSize)` | `backgroundSize` |
+| `SetBackgroundRepeat(StyleBackgroundRepeat)` | `backgroundRepeat` |
+| `SetBackgroundPosition(StyleBackgroundPosition)` | X и Y одновременно |
+| `SetBackgroundPosition(x?, y?)` | Независимо |
+| `SetUnityBackgroundImageTintColor(StyleColor)` | `unityBackgroundImageTintColor` |
+
+#### Трансформации
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetScale(StyleScale)` | `scale` |
+| `SetRotate(StyleRotate)` | `rotate` |
+| `SetTranslate(StyleTranslate)` | `translate` |
+| `SetTransformOrigin(StyleTransformOrigin)` | `transformOrigin` |
+
+#### Анимации переходов
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetTransitionDelay(StyleList)` | `transitionDelay` |
+| `SetTransitionDuration(StyleList)` | `transitionDuration` |
+| `SetTransitionProperty(StyleList)` | `transitionProperty` |
+| `SetTransitionTimingFunction(StyleList)` | `transitionTimingFunction` |
+
+#### Переполнение и видимость
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetOverflow(StyleEnum)` | `overflow` |
+| `SetUnityOverflowClipBox(StyleEnum)` | `unityOverflowClipBox` |
+| `SetVisibility(StyleEnum)` | `visibility` |
+| `SetDisplay(DisplayStyle)` | `display` |
+
+#### Unity Slice
+
+| Метод | Описание |
+|-------|----------|
+| `SetUnitySlice(StyleInt)` | Все стороны |
+| `SetUnitySlice(top?, bottom?, left?, right?)` | По стороне |
+| `SetUnitySliceType(StyleEnum)` | Unity 6+ |
+
+#### Курсор
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetCursor(StyleCursor)` | `cursor` |
+
+### Расширения для специализированных элементов
+
+#### TextElement
+
+```csharp
+label.SetText("Hello World");
+```
+
+#### BaseField\
+
+```csharp
+field.SetLabel("My Field");
+field.SetValue(42);
+```
+
+#### INotifyValueChanged\
+
+```csharp
+field.SetValue(42, notify: false); // устанавливает значение без генерации ChangeEvent
+field.AddValueChanged(evt => Debug.Log(evt.newValue));
+field.RemoveValueChanged(myCallback);
+```
+
+Типизированные перегрузки доступны для `int`, `uint`, `long`, `ulong`, `short`, `ushort`, `byte`, `sbyte`, `float`, `double`, `string`, `bool`, `Color`, `Vector2/3/4`, `Vector2Int/3Int`, `Rect/RectInt`, `Bounds/BoundsInt`, `Hash128`, `Enum`, `Object` и других типов.
+
+#### IMixedValueSupport
+
+```csharp
+field.SetShowMixedValue(true); // показывает индикатор смешанного значения
+```
+
+#### Button
+
+```csharp
+button
+ .AddClicked(() => Debug.Log("Clicked"))
+ .SetClickable(new Clickable(() => { }))
+ .SetIconImage(myBackground);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `AddClicked(Action)` | Подписка на `Button.clicked` |
+| `RemoveClicked(Action)` | Отписка от `Button.clicked` |
+| `SetClickable(Clickable)` | Устанавливает `Button.clickable` |
+| `SetIconImage(Background)` | Устанавливает `Button.iconImage` |
+
+#### Slider / BaseSlider\
+
+```csharp
+slider
+ .SetLowValue(0f)
+ .SetHighValue(100f)
+ .SetShowInputField(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetLowValue(TValue)` | Устанавливает минимальное значение слайдера |
+| `SetHighValue(TValue)` | Устанавливает максимальное значение слайдера |
+| `SetFill(bool)` | Заполнение трека до текущего значения |
+| `SetInverted(bool)` | Инвертирует направление слайдера |
+| `SetPageSize(float)` | Шаг изменения значения при постраничной навигации |
+| `SetShowInputField(bool)` | Показывает числовое поле ввода рядом со слайдером |
+| `SetDirection(SliderDirection)` | Устанавливает ориентацию слайдера |
+
+#### ProgressBar
+
+```csharp
+progressBar.SetTitle("Загрузка...").SetLowValue(0f).SetHighValue(100f);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetTitle(string)` | Устанавливает заголовок, отображаемый в центре |
+| `SetLowValue(float)` | Устанавливает минимальное значение |
+| `SetHighValue(float)` | Устанавливает максимальное значение |
+
+#### HelpBox
+
+```csharp
+helpBox.SetHelpBoxFontSize(14);
+helpBox.SetMessageType(14, HelpBoxMessageType.Warning);
+```
+
+#### Foldout
+
+```csharp
+foldout.SetText("Section Title");
+foldout.SetValue(true);
+```
+
+#### Image
+
+```csharp
+image.SetImage(myTexture);
+image.SetImageFromResource("Editor/MyIcon"); // загрузка через Resources.Load
+```
+
+#### IMGUIContainer
+
+```csharp
+container
+ .SetOnGUIHandler(() => GUILayout.Label("IMGUI"))
+ .SetCullingEnabled(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetOnGUIHandler(Action)` | Заменяет коллбэк `onGUIHandler` |
+| `AddOnGUIHandler(Action)` | Подписка на `onGUIHandler` |
+| `RemoveOnGUIHandler(Action)` | Отписка от `onGUIHandler` |
+| `SetCullingEnabled(bool)` | Пропускает `onGUIHandler`, когда элемент за пределами экрана |
+| `SetContextType(ContextType)` | Устанавливает тип контекста IMGUI |
+
+#### ListView / CollectionView
+
+| Метод | Описание | Примечания |
+|-------|----------|------------|
+| `SetBindItem(Action)` | Коллбэк привязки элемента | |
+| `SetMakeItem(Func)` | Фабрика элементов | |
+| `SetMakeFooter(Func)` | Фабрика подвала | Unity 6+ |
+| `SetMakeHeader(Func)` | Фабрика заголовка | Unity 6+ |
+| `SetMakeNoneElement(Func)` | Фабрика элемента пустого состояния | Unity 6+ |
+
+### Команды редактора (только для редактора)
+
+```csharp
+using Aspid.FastTools.Editors;
+
+image.AddOpenScriptCommand(target);
+// Двойной клик на элемент открывает скрипт 'target' в IDE
+```
+
+### Полный пример
+
+```csharp
+using UnityEditor;
+using UnityEngine;
+using Aspid.FastTools;
+using Aspid.FastTools.Editors;
+using UnityEngine.UIElements;
+
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ const string iconPath = "Editor/MyIcon";
+
+ var scriptName = target.GetScriptName();
+ var dark = new Color(0.15f, 0.15f, 0.15f);
+ var light = new Color(0.75f, 0.75f, 0.75f);
+
+ return new VisualElement()
+ .SetName("Header")
+ .SetBackgroundColor(dark)
+ .SetFlexDirection(FlexDirection.Row)
+ .SetPadding(top: 5, bottom: 5, left: 10, right: 10)
+ .SetBorderRadius(topLeft: 10, topRight: 10, bottomLeft: 10, bottomRight: 10)
+ .AddChild(new Image()
+ .SetName("Icon")
+ .AddOpenScriptCommand(target)
+ .SetImageFromResource(iconPath)
+ .SetSize(width: 40, height: 40))
+ .AddChild(new Label(scriptName)
+ .SetName("Title")
+ .SetFlexGrow(1)
+ .SetFontSize(16)
+ .SetMargin(left: 10)
+ .SetColor(light)
+ .SetAlignSelf(Align.Center)
+ .SetOverflow(Overflow.Hidden)
+ .SetWhiteSpace(WhiteSpace.NoWrap)
+ .SetTextOverflow(TextOverflow.Ellipsis)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold));
+ }
+}
+```
+
+### Результат
+
+
+
+---
+
+## Вспомогательные расширения для редактора
+
+Утилитарные методы для получения отображаемых имён объектов Unity в пользовательских редакторах.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+```csharp
+public static string GetScriptName(this Object obj)
+```
+
+Возвращает отображаемое имя объекта Unity:
+- Если тип имеет `[AddComponentMenu]`, возвращает `ObjectNames.GetInspectorTitle(obj)`
+- В противном случае возвращает `ObjectNames.NicifyVariableName(typeName)`
+
+```csharp
+public static string GetScriptNameWithIndex(this Component targetComponent)
+```
+
+Возвращает отображаемое имя с числовым суффиксом, если на одном GameObject присутствует несколько компонентов одного типа. Например, если прикреплены два компонента `AudioSource`, второй вернёт `"Audio Source (2)"`.
+
+```csharp
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ // "My Behaviour" — или "Custom Name", если присутствует [AddComponentMenu("Custom Name")]
+ var name = target.GetScriptName();
+
+ // "My Behaviour (2)" при наличии второго компонента того же типа
+ var nameWithIndex = ((Component)target).GetScriptNameWithIndex();
+
+ return new Label(name);
+ }
+}
+```
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Editor/Aspid.UnityFastTools.VisualElements.Editor.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md.meta
similarity index 59%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Editor/Aspid.UnityFastTools.VisualElements.Editor.asmdef.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md.meta
index 13dfc76..a1ba8c4 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Editor/Aspid.UnityFastTools.VisualElements.Editor.asmdef.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md.meta
@@ -1,6 +1,6 @@
fileFormatVersion: 2
-guid: 3000aedd14c05428fa373199e8a0cac2
-AssemblyDefinitionImporter:
+guid: 9b6c3feaf2078463b98fab80105c4ed8
+TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples.meta
similarity index 77%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples.meta
index a72d9f7..bbc47e1 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c6490710e847c4157a38f045d5205648
+guid: 7612e8e013bd64a248031119249e8eb4
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/.DS_Store b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/.DS_Store
new file mode 100644
index 0000000..cee7546
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/.DS_Store differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/.DS_Store b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/.DS_Store
new file mode 100644
index 0000000..a834d4f
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/.DS_Store differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/Profiler Markers.unity
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/Profiler Markers.unity
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/Profiler Markers.unity.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/Profiler Markers.unity.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.ProfilerMarkers.asmdef
similarity index 87%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.ProfilerMarkers.asmdef
index 5199cfd..4e65422 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.ProfilerMarkers.asmdef
@@ -1,5 +1,5 @@
{
- "name": "Aspid.UnityFastTools.ProfilerMarkers",
+ "name": "Aspid.FastTools.ProfilerMarkers",
"rootNamespace": "",
"references": [
"GUID:7c010b89992542508a6b6189977e64d4"
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.ProfilerMarkers.asmdef.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.ProfilerMarkers.asmdef.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/MarkerTest.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/MarkerTest.cs
similarity index 93%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/MarkerTest.cs
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/MarkerTest.cs
index 9671e5a..795dce8 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/MarkerTest.cs
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/MarkerTest.cs
@@ -1,7 +1,7 @@
using UnityEngine;
// ReSharper disable once CheckNamespace
-namespace Aspid.UnityFastTools.Samples.ProfilerMarkers
+namespace Aspid.FastTools.Samples.ProfilerMarkers
{
public class MarkerTest : MonoBehaviour
{
@@ -38,4 +38,4 @@ private static void Load()
}
}
}
-}
+}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/MarkerTest.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/MarkerTest.cs.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/MarkerTest.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/MarkerTest.cs.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/.DS_Store b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/.DS_Store
new file mode 100644
index 0000000..bbf7471
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/.DS_Store differ
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/TypeSelectorTest.prefab
similarity index 68%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/TypeSelectorTest.prefab
index f67d442..65a2696 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/TypeSelectorTest.prefab
@@ -43,13 +43,13 @@ MonoBehaviour:
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 16ae92dcd533401a91fe53810fd25d08, type: 3}
m_Name:
- m_EditorClassIdentifier: Assembly-CSharp-firstpass::Aspid.UnityFastTools.Samples.Types.TypeSelectorTest
- _typeSelector: System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
- _typeSelectorWithFilterEnum: UnityEngine.Space, UnityEngine.CoreModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
- _serializableType:
- _assemblyQualifiedName: System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral,
+ m_EditorClassIdentifier: Assembly-CSharp-firstpass::Aspid.FastTools.Samples.Types.TypeSelectorTest
+ _componentType:
+ _assemblyQualifiedName: Aspid.FastTools.Samples.Types.FastEnemy, Assembly-CSharp-firstpass,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _behaviourType:
+ _assemblyQualifiedName: Aspid.FastTools.Samples.Types.TankEnemy, Assembly-CSharp-firstpass,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _enumType:
+ _assemblyQualifiedName: System.DayOfWeek, mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
- _serializableTypeWithEnumFilter:
- _assemblyQualifiedName: UnityEngine.ForceMode, UnityEngine.PhysicsModule, Version=0.0.0.0,
- Culture=neutral, PublicKeyToken=null
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/TypeSelectorTest.prefab.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/TypeSelectorTest.prefab.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Scripts.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Scripts.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/TypeSelectorTest.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/TypeSelectorTest.cs
new file mode 100644
index 0000000..ba71170
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/TypeSelectorTest.cs
@@ -0,0 +1,40 @@
+using System;
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ ///
+ /// Demonstrates and :
+ /// serializable wrappers around that are assignable in the Inspector.
+ ///
+ public class TypeSelectorTest : MonoBehaviour
+ {
+ [Header("Any type — resolved at runtime via implicit conversion")]
+ [SerializeField] private SerializableType _componentType;
+
+ [Header("Constrained to MonoBehaviour subtypes — safe AddComponent at runtime")]
+ [SerializeField] private SerializableType _behaviourType;
+
+ [Header("Constrained to Enum subtypes — string stored, filtered picker in Inspector")]
+ [SerializeField] private SerializableType _enumType;
+
+ private void Start()
+ {
+ // Implicit conversion to System.Type
+ Type componentType = _componentType;
+ if (componentType is not null)
+ Debug.Log($"Selected type: {componentType.FullName}");
+
+ // Safe AddComponent — picker guarantees the type is a MonoBehaviour subtype
+ Type behaviourType = _behaviourType;
+ if (behaviourType is not null)
+ gameObject.AddComponent(behaviourType);
+
+ // Use the resolved enum type for reflection
+ Type enumType = _enumType;
+ if (enumType is not null)
+ Debug.Log($"Enum values: {string.Join(", ", Enum.GetNames(enumType))}");
+ }
+ }
+}
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Scripts/TypeSelectorTest.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/TypeSelectorTest.cs.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Scripts/TypeSelectorTest.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/TypeSelectorTest.cs.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source.meta
new file mode 100644
index 0000000..cd7ea7a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: c1865534b470439a979710a6ee77f7dc
+timeCreated: 1772957434
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef
new file mode 100644
index 0000000..d15c03b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef
@@ -0,0 +1,3 @@
+{
+ "name": "Aspid.FastTools"
+}
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Aspid.UnityFastTools.VisualElements.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef.meta
similarity index 76%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Aspid.UnityFastTools.VisualElements.asmdef.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef.meta
index ece2aa7..ea49f73 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Aspid.UnityFastTools.VisualElements.asmdef.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 0acbb74fa21d2442393a327f6c8d5639
+guid: c8f809693df904cca816d5d7a67dff48
AssemblyDefinitionImporter:
externalObjects: {}
userData:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions.meta
new file mode 100644
index 0000000..64fdaf8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b03005925b304e1a90f79d4f1bda93bf
+timeCreated: 1772957451
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions/TypeExtensions.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions/TypeExtensions.cs
new file mode 100644
index 0000000..1adcf7c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions/TypeExtensions.cs
@@ -0,0 +1,37 @@
+#nullable enable
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools
+{
+ public static class TypeExtensions
+ {
+ public static IReadOnlyList GetMembersInfosIncludingBaseClasses(this Type type, BindingFlags bindingFlags, Type? stopAt = null)
+ {
+ var currentType = type;
+ var typeChain = new List();
+
+ while (currentType != stopAt)
+ {
+ if (currentType is null) break;
+
+ typeChain.Add(currentType);
+ currentType = currentType.BaseType;
+ }
+
+ // Iterate base → derived so members appear in declaration order (matches Unity inspector)
+ typeChain.Reverse();
+
+ if (!bindingFlags.HasFlag(BindingFlags.DeclaredOnly))
+ bindingFlags |= BindingFlags.DeclaredOnly;
+
+ var members = new List();
+ foreach (var t in typeChain)
+ members.AddRange(t.GetMembers(bindingFlags));
+
+ return members;
+ }
+ }
+}
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/AspidEditorGUILayout.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions/TypeExtensions.cs.meta
similarity index 64%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/AspidEditorGUILayout.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions/TypeExtensions.cs.meta
index 13fe3e3..fddc207 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/AspidEditorGUILayout.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions/TypeExtensions.cs.meta
@@ -1,11 +1,11 @@
fileFormatVersion: 2
-guid: f7b5767a90834361a58536b70283e74b
+guid: 6dcae9cf24b04569bd40b0f0224b715c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Aspid.UnityFastTools.Editor.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Aspid.FastTools.Unity.Editor.asmdef
similarity index 80%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Aspid.UnityFastTools.Editor.asmdef
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Aspid.FastTools.Unity.Editor.asmdef
index c948561..df044f8 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Aspid.UnityFastTools.Editor.asmdef
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Aspid.FastTools.Unity.Editor.asmdef
@@ -1,7 +1,8 @@
{
- "name": "Aspid.UnityFastTools.Editor",
+ "name": "Aspid.FastTools.Unity.Editor",
"rootNamespace": "",
"references": [
+ "GUID:c8f809693df904cca816d5d7a67dff48",
"GUID:7c010b89992542508a6b6189977e64d4"
],
"includePlatforms": [
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Aspid.UnityFastTools.Editor.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Aspid.FastTools.Unity.Editor.asmdef.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Aspid.UnityFastTools.Editor.asmdef.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Aspid.FastTools.Unity.Editor.asmdef.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources.meta
similarity index 77%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources.meta
index 78aeed3..915c6e0 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c5527f6f042e7408ebabfc82d150433b
+guid: 4c03e99f7975a44b6ae45192a73b498e
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles.meta
similarity index 67%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles.meta
index c1b162f..de6fbe1 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles.meta
@@ -1,5 +1,6 @@
fileFormatVersion: 2
-guid: 7f88981b89c7a48f196d36c976c31f20
+guid: 99e16817ee21d470aa0c1a6a286115c8
+folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-ComponentTypeSelector.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-ComponentTypeSelector.uss
new file mode 100644
index 0000000..2ccf062
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-ComponentTypeSelector.uss
@@ -0,0 +1,4 @@
+Button
+{
+ flex-grow: 1;
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-ComponentTypeSelector.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-ComponentTypeSelector.uss.meta
new file mode 100644
index 0000000..b72f14f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-ComponentTypeSelector.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 4e1660368ce1e4a7d98814a517d5ea5f
+ScriptedImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+ script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
+ disableValidation: 0
+ unsupportedSelectorAction: 0
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-EnumValues.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-EnumValues.uss
new file mode 100644
index 0000000..3c78c20
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-EnumValues.uss
@@ -0,0 +1,55 @@
+.aspid-fasttools-enum-values
+{
+ margin: 2px 0 2px 3px;
+}
+
+.aspid-fasttools-enum-values .aspid-fasttools-serializable-type
+{
+ margin: 0;
+}
+
+.aspid-fasttools-enum-values .unity-foldout__toggle--inspector
+{
+ margin-left: 0;
+}
+
+.aspid-fasttools-enum-values-header
+{
+ padding: 2px;
+ border-width: 1px;
+ border-radius: 5px 5px 0 0;
+ border-color: rgb(32, 32, 32);
+}
+
+.aspid-fasttools-enum-values-container
+{
+ border-top-width: 0;
+ border-left-width: 1px;
+ border-right-width: 1px;
+ border-bottom-width: 1px;
+ border-radius: 0 0 5px 5px;
+ border-color: rgb(32, 32, 32);
+ background-color: rgb(70, 70, 70);
+}
+
+.aspid-fasttools-enum-values-values
+{
+ margin: 5px 5px 2px 10px;
+}
+
+.aspid-fasttools-enum-values-values Foldout
+{
+ margin-left: 1px;
+}
+
+.aspid-fasttools-enum-values-default-value
+{
+ margin-left: 7px;
+ margin-right: 7px;
+ margin-bottom: 5px;
+}
+
+.aspid-fasttools-enum-values-default-value > Foldout
+{
+ margin-left: 3px;
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-EnumValues.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-EnumValues.uss.meta
new file mode 100644
index 0000000..c39aa7b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-EnumValues.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: dddaadecf670a48939d44c30e5460a21
+ScriptedImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+ script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
+ disableValidation: 0
+ unsupportedSelectorAction: 0
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-Id.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-Id.uss
new file mode 100644
index 0000000..570f01a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-Id.uss
@@ -0,0 +1,168 @@
+.aspid-fasttools-id-drawer
+{
+ flex-direction: column;
+ margin: 1px -2px 1px 3px;
+}
+
+.aspid-fasttools-id-drawer-main-row
+{
+ flex-direction: row;
+}
+
+.aspid-fasttools-id-drawer-label
+{
+ width: 40%;
+ flex-grow: 1;
+ max-width: 40%;
+ min-width: 40%;
+ flex-shrink: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -unity-text-align: middle-left;
+}
+
+.aspid-fasttools-id-drawer-dropdown
+{
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin: 0;
+ overflow: hidden;
+ white-space: nowrap;
+ -unity-text-align: middle-left;
+ text-overflow: ellipsis;
+}
+
+.aspid-fasttools-id-drawer-create-button
+{
+ margin-left: 4px;
+}
+
+.aspid-fasttools-id-drawer-create-row
+{
+ display: none;
+ flex-direction: row;
+ margin-top: 2px;
+}
+
+.aspid-fasttools-id-drawer-input
+{
+ flex-grow: 1;
+ flex-shrink: 1;
+}
+
+.aspid-fasttools-id-drawer-add-button
+{
+ margin-left: 4px;
+}
+
+.aspid-fasttools-id-drawer-cancel-button
+{
+ margin-left: 2px;
+}
+
+.aspid-fasttools-id-drawer-error
+{
+ display: none;
+ margin-top: 2px;
+ color: rgb(255, 102, 102);
+ font-size: 10px;
+}
+
+/* StringIdRegistryEditor */
+
+.aspid-fasttools-id-registry
+{
+ margin: 2px 0;
+}
+
+.aspid-fasttools-id-registry-entry
+{
+ flex-direction: column;
+ margin-bottom: 1px;
+}
+
+.aspid-fasttools-id-registry-row
+{
+ flex-direction: row;
+}
+
+.aspid-fasttools-id-registry-name
+{
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin: 0;
+}
+
+.aspid-fasttools-id-registry-name--duplicate
+{
+ border-color: rgb(255, 100, 100);
+}
+
+.aspid-fasttools-id-registry-duplicate-label
+{
+ -unity-text-align: middle-left;
+ width: 72px;
+ margin: 0 2px;
+ flex-shrink: 0;
+}
+
+.aspid-fasttools-id-registry-id
+{
+ width: 50px;
+ flex-shrink: 0;
+ margin: 0 2px;
+}
+
+.aspid-fasttools-id-registry-delete
+{
+ width: 20px;
+ flex-shrink: 0;
+ margin: 0;
+ padding: 0;
+}
+
+.aspid-fasttools-id-registry-add-row
+{
+ flex-direction: row;
+ margin-top: 4px;
+}
+
+.aspid-fasttools-id-registry-add-input
+{
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin: 0;
+}
+
+.aspid-fasttools-id-registry-add-button
+{
+ width: 20px;
+ flex-shrink: 0;
+ margin: 0;
+ margin-left: 2px;
+ padding: 0;
+}
+
+/* StringIdSelectorWindow */
+
+.aspid-fasttools-id-selector-container
+{
+ padding: 4px;
+ flex-grow: 1;
+ flex-direction: column;
+}
+
+ToolbarSearchField
+{
+ width: auto;
+ margin-bottom: 4px;
+ padding-right: 4px;
+}
+
+.aspid-fasttools-id-selector-item
+{
+ height: 22px;
+ padding-left: 6px;
+ padding-right: 6px;
+ -unity-text-align: middle-left;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-Id.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-Id.uss.meta
new file mode 100644
index 0000000..bf87743
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-Id.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 276e8a9dbed204c3580b5e6e9e07d50a
+ScriptedImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+ script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
+ disableValidation: 0
+ unsupportedSelectorAction: 0
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-SerializableType.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-SerializableType.uss
new file mode 100644
index 0000000..16097d5
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-SerializableType.uss
@@ -0,0 +1,38 @@
+VisualElement > Label
+{
+ width: 40%;
+ flex-grow: 1;
+ max-width: 40%;
+ min-width: 40%;
+ flex-shrink: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.aspid-fasttools-serializable-type
+{
+ margin: 1px -2px 1px 3px;
+ flex-direction: row;
+}
+
+.aspid-fasttools-serializable-type-buttons
+{
+ flex-grow: 1;
+ flex-direction: row;
+}
+
+Button
+{
+ margin: 0;
+ flex-grow: 1;
+ flex-shrink: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -unity-text-align: middle-left;
+}
+
+.aspid-fasttools-serializable-type-open-button
+{
+ flex-grow: 0;
+ margin-left: 1px;
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-SerializableType.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-SerializableType.uss.meta
new file mode 100644
index 0000000..90e5964
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-SerializableType.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 29cdf5b29244a4657a84f50b65824d71
+ScriptedImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+ script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
+ disableValidation: 0
+ unsupportedSelectorAction: 0
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-TypeSelectorWindow.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-TypeSelectorWindow.uss
new file mode 100644
index 0000000..eabe6c4
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-TypeSelectorWindow.uss
@@ -0,0 +1,62 @@
+.aspid-fasttools-type-selector-container
+{
+ padding: 4px;
+ flex-grow: 1;
+ flex-direction: column;
+}
+
+.aspid-fasttools-type-selector-header
+{
+ height: 20px;
+ min-height: 20px;
+ align-items: center;
+ flex-direction: row;
+ margin: -3px -3px 4px;
+ background-color: rgb(38, 38, 38);
+}
+
+/* BackButton */
+VisualElement > VisualElement > Button
+{
+ width: 26px;
+ height: 20px;
+ margin-right: 4px;
+ border-top-width: 0;
+ border-left-width: 0;
+ border-right-width: 0;
+ border-bottom-width: 0;
+ background-color: rgba(0, 0, 0, 0);
+}
+
+/* Title*/
+VisualElement > VisualElement > Label
+{
+ flex-grow: 1;
+ -unity-font-style: bold;
+}
+
+ToolbarSearchField
+{
+ width: auto;
+ margin-bottom: 4px;
+ padding-right: 4px;
+}
+
+.aspid-fasttools-type-selector-item
+{
+ height: 20px;
+ padding-left: 6px;
+ padding-right: 6px;
+ flex-direction: row;
+ align-items: center;
+}
+
+.aspid-fasttools-type-selector-item-title
+{
+ flex-grow: 1;
+}
+
+.aspid-fasttools-type-selector-item-arrow
+{
+ opacity: 0.6;
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-TypeSelectorWindow.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-TypeSelectorWindow.uss.meta
new file mode 100644
index 0000000..59cd700
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Styles/Aspid-FastTools-TypeSelectorWindow.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 09741cdf2939046f08938a85bc78f4ef
+ScriptedImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+ script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
+ disableValidation: 0
+ unsupportedSelectorAction: 0
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts.meta
new file mode 100644
index 0000000..9560390
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3f21b2a15e5724419a3fa476e9ac8f68
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums.meta
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuePropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs
similarity index 89%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuePropertyDrawer.cs
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs
index 7d83058..2b0256f 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuePropertyDrawer.cs
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs
@@ -4,11 +4,10 @@
using UnityEngine.UIElements;
// ReSharper disable once CheckNamespace
-namespace Aspid.UnityFastTools
+namespace Aspid.FastTools.Editors
{
- // TODO Aspid.UnityFastTools – Refactor
[CustomPropertyDrawer(typeof(EnumValue<>))]
- public class EnumValuePropertyDrawer : PropertyDrawer
+ internal sealed class EnumValuePropertyDrawer : PropertyDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
@@ -19,6 +18,7 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property)
var enumTypeField = new PropertyField(enumTypeProperty, label: string.Empty).SetDisplay(DisplayStyle.None);
var keyField = new PropertyField(keyProperty, label: string.Empty).SetDisplay(DisplayStyle.None);
+
var keyEnumField = new EnumField(label: string.Empty).SetDisplay(DisplayStyle.None);
var keyEnumFlagField = new EnumFlagsField(label: string.Empty).SetDisplay(DisplayStyle.None);
@@ -36,15 +36,13 @@ public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
keyProperty.SetStringAndApply(e.newValue.ToString());
});
-
- container
+
+ return container
.AddChild(enumTypeField)
.AddChild(keyField)
.AddChild(keyEnumField)
- .AddChild(keyEnumFlagField);
-
- container.AddChild(new PropertyField(property.FindPropertyRelative("_value"), string.Empty));
- return container;
+ .AddChild(keyEnumFlagField)
+ .AddChild(new PropertyField(property.FindPropertyRelative("_value"), label: string.Empty));
void UpdateValue()
{
@@ -70,7 +68,7 @@ void UpdateValue()
{
try
{
- enumValue = (Enum)Enum.GetValues(enumType).GetValue(0);
+ enumValue = (Enum)Enum.GetValues(enumType).GetValue(index: 0);
}
catch
{
@@ -84,6 +82,8 @@ void UpdateValue()
if (enumType.IsDefined(typeof(FlagsAttribute), false))
{
keyEnumFlagField.SetDisplay(DisplayStyle.Flex);
+
+ keyEnumFlagField.value = null;
keyEnumFlagField.Init(enumValue);
}
else
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuePropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs.meta
similarity index 78%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuePropertyDrawer.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs.meta
index adb9c1f..8abadc7 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuePropertyDrawer.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs.meta
@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawer.cs
new file mode 100644
index 0000000..c9226dc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawer.cs
@@ -0,0 +1,61 @@
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ [CustomPropertyDrawer(typeof(EnumValues<>))]
+ internal sealed class EnumValuesPropertyDrawer : PropertyDrawer
+ {
+ private const string StylesheetPath = "Styles/Aspid-FastTools-EnumValues";
+ private const string RootClass = "aspid-fasttools-enum-values";
+ private const string HeaderClass = "aspid-fasttools-enum-values-header";
+ private const string ContainerClass = "aspid-fasttools-enum-values-container";
+ private const string ValuesClass = "aspid-fasttools-enum-values-values";
+ private const string DefaultValueClass = "aspid-fasttools-enum-values-default-value";
+
+ public override VisualElement CreatePropertyGUI(SerializedProperty property)
+ {
+ var root = new VisualElement()
+ .AddStyleSheetsFromResource(StylesheetPath)
+ .AddClass(RootClass);
+
+ var values = property.FindPropertyRelative("_values");
+ var enumType = property.FindPropertyRelative("_enumType");
+ var defaultValueProperty = property.FindPropertyRelative("_defaultValue");
+
+ var enumTypeField = new PropertyField(enumType, label: string.Empty);
+ enumTypeField.RegisterValueChangeCallback(_ =>
+ {
+ UpdateValues();
+ });
+
+ var valuesField = new PropertyField(values).AddClass(ValuesClass);
+ valuesField.RegisterValueChangeCallback(_ => UpdateValues());
+
+ return root
+ .AddChild(new VisualElement()
+ .AddClass(HeaderClass)
+ .AddChild(new Label(property.displayName))
+ .AddChild(enumTypeField)
+ )
+ .AddChild(new VisualElement()
+ .AddClass(ContainerClass)
+ .AddChild(valuesField)
+ .AddChild(new PropertyField(defaultValueProperty)
+ .AddClass(DefaultValueClass)
+ )
+ );
+
+ void UpdateValues()
+ {
+ for (var i = 0; i < values.arraySize; i++)
+ {
+ var element = values.GetArrayElementAtIndex(i);
+ element.FindPropertyRelative("_enumType").SetStringAndApply(enumType.stringValue);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuesPropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawer.cs.meta
similarity index 78%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuesPropertyDrawer.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawer.cs.meta
index b916aa2..6d819b6 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Enums/EnumValuesPropertyDrawer.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawer.cs.meta
@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Extensions.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Extensions.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/EditorExtensions.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/EditorExtensions.cs
new file mode 100644
index 0000000..abd36be
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/EditorExtensions.cs
@@ -0,0 +1,79 @@
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+
+// ReSharper disable CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ ///
+ /// Editor-side extension methods for and
+ /// that resolve human-readable script names, respecting the attribute.
+ ///
+ public static class EditorExtensions
+ {
+ ///
+ /// Returns a human-readable display name for the given Unity object.
+ /// If the object's type is decorated with , the name is taken from
+ /// ; otherwise it falls back to
+ /// applied to the type name.
+ ///
+ /// The object whose display name should be resolved.
+ ///
+ /// The display name string, or if is .
+ ///
+ public static string GetScriptName(this Object obj)
+ {
+ if (!obj) return string.Empty;
+
+ var targetType = obj.GetType();
+ var attributes = targetType.GetCustomAttributes(false);
+
+ return attributes.Any(attribute => attribute is AddComponentMenu)
+ ? ObjectNames.GetInspectorTitle(obj)
+ : ObjectNames.NicifyVariableName(targetType.Name);
+ }
+
+ ///
+ /// Returns the display name of a component with a numeric suffix appended when multiple components
+ /// of the same type exist on the same .
+ /// For example, the second AudioSource would be returned as "Audio Source (2)".
+ ///
+ /// The component whose indexed display name should be resolved.
+ ///
+ /// The display name with an index suffix if duplicates exist on the same object,
+ /// the plain display name if there is only one such component,
+ /// or if is .
+ ///
+ public static string GetScriptNameWithIndex(this Component targetComponent)
+ {
+ if (targetComponent is null) return null;
+
+ var type = targetComponent.GetType();
+ var components = targetComponent
+ .GetComponents(type)
+ .Where(component => component.GetType() == targetComponent.GetType())
+ .ToArray();
+
+ switch (components.Length)
+ {
+ case 0:
+ case 1: return targetComponent.GetScriptName();
+ default:
+ {
+ var index = 0;
+
+ foreach (var component in components)
+ {
+ if (component.GetType() != type) continue;
+
+ index++;
+ if (component == targetComponent)
+ return $"{targetComponent.GetScriptName()} ({index})";
+ }
+
+ return targetComponent.GetScriptName();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Extensions/EditorExtensions.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/EditorExtensions.cs.meta
similarity index 78%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Extensions/EditorExtensions.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/EditorExtensions.cs.meta
index 5f9b16e..70e938b 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Extensions/EditorExtensions.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/EditorExtensions.cs.meta
@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/TypeExtensions.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/TypeExtensions.cs
new file mode 100644
index 0000000..355e5b1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/TypeExtensions.cs
@@ -0,0 +1,110 @@
+using System;
+using System.Linq;
+using UnityEditor;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ ///
+ /// Editor-side extension methods for that locate the
+ /// asset corresponding to a given type using the Asset Database.
+ ///
+ public static class TypeExtensions
+ {
+ private static readonly Dictionary _regexCache = new();
+
+ ///
+ /// Searches the Asset Database for the that defines the given type.
+ /// The search first tries an exact match via , then falls back
+ /// to a regex match against the script text, checking the namespace and the type declaration keyword
+ /// (class, struct, record, or enum).
+ ///
+ /// The type to locate a script asset for.
+ ///
+ /// The matching asset, or if none is found.
+ ///
+ public static MonoScript FindMonoScript(this Type type)
+ {
+ var isEnum = type.IsEnum;
+ var typeName = type.Name;
+ var typeNamespace = type.Namespace;
+
+ var scripts = AssetDatabase.FindAssets(filter: $"t:MonoScript {typeName}")
+ .Select(AssetDatabase.GUIDToAssetPath)
+ .Select(AssetDatabase.LoadAssetAtPath)
+ .Where(script => script is not null)
+ .ToArray();
+
+ var regex = GetRegex(isEnum, typeName);
+
+ foreach (var script in scripts)
+ {
+ if (script.GetClass() != type) continue;
+ return script;
+ }
+
+ foreach (var script in scripts)
+ {
+ var text = script.text;
+ if (string.IsNullOrWhiteSpace(text)) continue;
+ if (!string.IsNullOrWhiteSpace(typeNamespace) && !text.Contains($"namespace {typeNamespace}")) continue;
+ if (!regex.IsMatch(text)) continue;
+
+ return script;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Searches the Asset Database for the that defines the given type
+ /// and also determines the 1-based line number of the type declaration within that script.
+ ///
+ /// The type to locate.
+ ///
+ /// A tuple of the matched and the 1-based line number of the type declaration.
+ /// If no script is found, returns (null, 0).
+ ///
+ public static (MonoScript script, int line) FindMonoScriptWithLine(this Type type)
+ {
+ var script = type.FindMonoScript();
+ if (script is null) return (script: null, line: 0);
+
+ var line = FindTypeLineNumber(script.text, type.Name, type.IsEnum);
+ return (script, line);
+ }
+
+ private static int FindTypeLineNumber(string text, string typeName, bool isEnum)
+ {
+ if (string.IsNullOrEmpty(text)) return 1;
+
+ var regex = GetRegex(isEnum, typeName);
+ var lines = text.Split('\n');
+
+ for (var i = 0; i < lines.Length; i++)
+ {
+ if (regex.IsMatch(lines[i]))
+ return i + 1;
+ }
+
+ return 1;
+ }
+
+ private static string GetPattern(bool isEnum, string typeName) => isEnum
+ ? $@"\benum\s+{Regex.Escape(typeName)}\b"
+ : $@"\b(class|struct|record)\s+{Regex.Escape(typeName)}\b";
+
+ private static Regex GetRegex(bool isEnum, string typeName)
+ {
+ var key = $"{isEnum}:{typeName}";
+ if (_regexCache.TryGetValue(key, out var cached))
+ return cached;
+
+ var regex = new Regex(GetPattern(isEnum, typeName), RegexOptions.Compiled);
+ _regexCache[key] = regex;
+ return regex;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/VisualElements/Extensions/VisualElementExtensions.Focus.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/TypeExtensions.cs.meta
similarity index 64%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/VisualElements/Extensions/VisualElementExtensions.Focus.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/TypeExtensions.cs.meta
index 71dae8b..b57b300 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/VisualElements/Extensions/VisualElementExtensions.Focus.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/TypeExtensions.cs.meta
@@ -1,11 +1,11 @@
fileFormatVersion: 2
-guid: 31af21071e07470bb8503dc09cd608f0
+guid: 338f2589964a40118e510350f9e07667
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/HorizontalScope.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/HorizontalScope.cs
new file mode 100644
index 0000000..2f2adfd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/HorizontalScope.cs
@@ -0,0 +1,72 @@
+using UnityEditor;
+using UnityEngine;
+
+// ReSharper disable CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ ///
+ /// Disposable ref struct wrapper around /
+ /// that exposes the resulting .
+ /// Use in a using statement to automatically close the horizontal group.
+ ///
+ public readonly ref struct HorizontalScope
+ {
+ ///
+ /// The returned by for this group.
+ ///
+ public readonly Rect Rect;
+
+ private HorizontalScope(Rect rect)
+ {
+ Rect = rect;
+ }
+
+ ///
+ /// Begins a horizontal layout group with the given layout options.
+ ///
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static HorizontalScope Begin(params GUILayoutOption[] options) =>
+ new(EditorGUILayout.BeginHorizontal(options));
+
+ ///
+ /// Begins a horizontal layout group with a specific and layout options.
+ ///
+ /// The style to apply to the horizontal group.
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static HorizontalScope Begin(GUIStyle style, params GUILayoutOption[] options) =>
+ new(EditorGUILayout.BeginHorizontal(style, options));
+
+ ///
+ /// Begins a horizontal layout group and outputs the resulting rect via an out parameter.
+ ///
+ /// Receives the of the horizontal group.
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static HorizontalScope Begin(out Rect rect, params GUILayoutOption[] options)
+ {
+ rect = EditorGUILayout.BeginHorizontal(options);
+ return new HorizontalScope(rect);
+ }
+
+ ///
+ /// Begins a horizontal layout group with a specific and outputs the resulting rect via an out parameter.
+ ///
+ /// Receives the of the horizontal group.
+ /// The style to apply to the horizontal group.
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static HorizontalScope Begin(out Rect rect, GUIStyle style, params GUILayoutOption[] options)
+ {
+ rect = EditorGUILayout.BeginHorizontal(style, options);
+ return new HorizontalScope(rect);
+ }
+
+ ///
+ /// Ends the horizontal layout group by calling .
+ ///
+ public void Dispose() =>
+ EditorGUILayout.EndHorizontal();
+ }
+}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/HorizontalScope.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/HorizontalScope.cs.meta
similarity index 78%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/HorizontalScope.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/HorizontalScope.cs.meta
index 517c805..620036b 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/HorizontalScope.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/HorizontalScope.cs.meta
@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/ScrollViewScope.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/ScrollViewScope.cs
new file mode 100644
index 0000000..8c266fc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/ScrollViewScope.cs
@@ -0,0 +1,231 @@
+using UnityEditor;
+using UnityEngine;
+
+// ReSharper disable CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ ///
+ /// Disposable ref struct wrapper around /
+ /// that exposes the updated .
+ /// Use in a using statement to automatically close the scroll view.
+ /// Overloads accepting ref Vector2 update the caller's variable in place.
+ ///
+ public readonly ref struct ScrollViewScope
+ {
+ ///
+ /// The updated scroll position returned by .
+ ///
+ public readonly Vector2 ScrollPosition;
+
+ private ScrollViewScope(Vector2 scrollPosition)
+ {
+ ScrollPosition = scrollPosition;
+ }
+
+ ///
+ /// Begins a scroll view with the given scroll position and layout options.
+ ///
+ /// The current scroll position.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ Vector2 scrollPosition,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with explicit scrollbar visibility flags.
+ ///
+ /// The current scroll position.
+ /// Always show the horizontal scrollbar.
+ /// Always show the vertical scrollbar.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ Vector2 scrollPosition,
+ bool alwaysShowHorizontal,
+ bool alwaysShowVertical,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with custom scrollbar styles.
+ ///
+ /// The current scroll position.
+ /// Style for the horizontal scrollbar.
+ /// Style for the vertical scrollbar.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ Vector2 scrollPosition,
+ GUIStyle horizontalScrollbar,
+ GUIStyle verticalScrollbar,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, horizontalScrollbar, verticalScrollbar, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with a single applied to it.
+ ///
+ /// The current scroll position.
+ /// Style applied to the scroll view.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ Vector2 scrollPosition,
+ GUIStyle style,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, style, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with full control over scrollbar visibility, styles, and background.
+ ///
+ /// The current scroll position.
+ /// Always show the horizontal scrollbar.
+ /// Always show the vertical scrollbar.
+ /// Style for the horizontal scrollbar.
+ /// Style for the vertical scrollbar.
+ /// Background style for the scroll view.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ Vector2 scrollPosition,
+ bool alwaysShowHorizontal,
+ bool alwaysShowVertical,
+ GUIStyle horizontalScrollbar,
+ GUIStyle verticalScrollbar,
+ GUIStyle background,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, background, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view and updates the caller's scroll position variable in place.
+ ///
+ /// Reference to the current scroll position; updated to the new value.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ ref Vector2 scrollPosition,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with explicit scrollbar visibility flags, updating the caller's variable in place.
+ ///
+ /// Reference to the current scroll position; updated to the new value.
+ /// Always show the horizontal scrollbar.
+ /// Always show the vertical scrollbar.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ ref Vector2 scrollPosition,
+ bool alwaysShowHorizontal,
+ bool alwaysShowVertical,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with custom scrollbar styles, updating the caller's variable in place.
+ ///
+ /// Reference to the current scroll position; updated to the new value.
+ /// Style for the horizontal scrollbar.
+ /// Style for the vertical scrollbar.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ ref Vector2 scrollPosition,
+ GUIStyle horizontalScrollbar,
+ GUIStyle verticalScrollbar,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, horizontalScrollbar, verticalScrollbar, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with a single style, updating the caller's variable in place.
+ ///
+ /// Reference to the current scroll position; updated to the new value.
+ /// Style applied to the scroll view.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ ref Vector2 scrollPosition,
+ GUIStyle style,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, style, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Begins a scroll view with full control over scrollbar visibility, styles, and background,
+ /// updating the caller's variable in place.
+ ///
+ /// Reference to the current scroll position; updated to the new value.
+ /// Always show the horizontal scrollbar.
+ /// Always show the vertical scrollbar.
+ /// Style for the horizontal scrollbar.
+ /// Style for the vertical scrollbar.
+ /// Background style for the scroll view.
+ /// Optional layout options.
+ ///
+ /// A new with the updated .
+ ///
+ public static ScrollViewScope Begin(
+ ref Vector2 scrollPosition,
+ bool alwaysShowHorizontal,
+ bool alwaysShowVertical,
+ GUIStyle horizontalScrollbar,
+ GUIStyle verticalScrollbar,
+ GUIStyle background,
+ params GUILayoutOption[] options)
+ {
+ scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition, alwaysShowHorizontal, alwaysShowVertical, horizontalScrollbar, verticalScrollbar, background, options);
+ return new ScrollViewScope(scrollPosition);
+ }
+
+ ///
+ /// Ends the scroll view by calling .
+ ///
+ public void Dispose() => EditorGUILayout.EndScrollView();
+ }
+}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/ScrollViewScope.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/ScrollViewScope.cs.meta
similarity index 78%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/ScrollViewScope.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/ScrollViewScope.cs.meta
index 80e38f3..d714d41 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/ScrollViewScope.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/ScrollViewScope.cs.meta
@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/VerticalScope.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/VerticalScope.cs
new file mode 100644
index 0000000..f98e07c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/VerticalScope.cs
@@ -0,0 +1,72 @@
+using UnityEditor;
+using UnityEngine;
+
+// ReSharper disable CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ ///
+ /// Disposable ref struct wrapper around /
+ /// that exposes the resulting .
+ /// Use in a using statement to automatically close the vertical group.
+ ///
+ public readonly ref struct VerticalScope
+ {
+ ///
+ /// The returned by for this group.
+ ///
+ public readonly Rect Rect;
+
+ private VerticalScope(Rect rect)
+ {
+ Rect = rect;
+ }
+
+ ///
+ /// Begins a vertical layout group with the given layout options.
+ ///
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static VerticalScope Begin(params GUILayoutOption[] options) =>
+ new(EditorGUILayout.BeginVertical(options));
+
+ ///
+ /// Begins a vertical layout group with a specific and layout options.
+ ///
+ /// The style to apply to the vertical group.
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static VerticalScope Begin(GUIStyle style, params GUILayoutOption[] options) =>
+ new(EditorGUILayout.BeginVertical(style, options));
+
+ ///
+ /// Begins a vertical layout group and outputs the resulting rect via an out parameter.
+ ///
+ /// Receives the of the vertical group.
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static VerticalScope Begin(out Rect rect, params GUILayoutOption[] options)
+ {
+ rect = EditorGUILayout.BeginVertical(options);
+ return new VerticalScope(rect);
+ }
+
+ ///
+ /// Begins a vertical layout group with a specific and outputs the resulting rect via an out parameter.
+ ///
+ /// Receives the of the vertical group.
+ /// The style to apply to the vertical group.
+ /// Optional layout options passed to .
+ /// A new whose reflects the group bounds.
+ public static VerticalScope Begin(out Rect rect, GUIStyle style, params GUILayoutOption[] options)
+ {
+ rect = EditorGUILayout.BeginVertical(style, options);
+ return new VerticalScope(rect);
+ }
+
+ ///
+ /// Ends the vertical layout group by calling .
+ ///
+ public void Dispose() =>
+ EditorGUILayout.EndVertical();
+ }
+}
\ No newline at end of file
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/VerticalScope.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/VerticalScope.cs.meta
similarity index 78%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/VerticalScope.cs.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/VerticalScope.cs.meta
index 25f45cf..8a38afb 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/IMGUI/VerticalScope.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/VerticalScope.cs.meta
@@ -5,7 +5,7 @@ MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids.meta
new file mode 100644
index 0000000..3839b68
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bfe6a9a3efae7e74ab79401b710e95d0
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdRegisterEditorExtensions.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdRegisterEditorExtensions.cs
new file mode 100644
index 0000000..e478123
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdRegisterEditorExtensions.cs
@@ -0,0 +1,113 @@
+using UnityEditor;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ internal static class IdRegisterEditorExtensions
+ {
+ internal static int Add(this IdRegistry idRegistry, string nameId)
+ {
+ var registry = new Registry(idRegistry);
+ var entries = registry.Entries;
+
+ for (var i = 0; i < entries.Size; i++)
+ {
+ if (entries[i].Name != nameId) continue;
+ return entries[i].Id;
+ }
+
+ var id = registry.NextId;
+ registry.NextId++;
+
+ var lastIndex = entries.Size;
+ entries.Size++;
+
+ entries[lastIndex].Id = id;
+ entries[lastIndex].Name = nameId;
+
+ return id;
+ }
+
+ internal static void Rename(this IdRegistry idRegistry, string oldName, string newName)
+ {
+ var registry = new Registry(idRegistry);
+ var entries = registry.Entries;
+
+ for (var i = 0; i < entries.Size; i++)
+ {
+ if (entries[i].Name != oldName) continue;
+
+ entries[i].Name = newName;
+ return;
+ }
+ }
+
+ private class Registry
+ {
+ private readonly SerializedProperty _nextIdProperty;
+
+ public int NextId
+ {
+ get => _nextIdProperty.intValue;
+ set => _nextIdProperty.SetValueAndApply(value);
+ }
+
+ public Entries Entries { get; }
+
+ public Registry(IdRegistry registry)
+ {
+ var serializedObject = new SerializedObject(registry);
+
+ _nextIdProperty = serializedObject.FindProperty("_nextId");
+ Entries = new Entries(serializedObject);
+ }
+ }
+
+ private class Entries
+ {
+ private readonly SerializedProperty _property;
+
+ public int Size
+ {
+ get => _property.arraySize;
+ set => _property.SetArraySizeAndApply(value);
+ }
+
+ public EntryProperty this[int index] =>
+ EntryProperty.Create(_property.GetArrayElementAtIndex(index));
+
+ public Entries(SerializedObject serializedObject)
+ {
+ _property = serializedObject.FindProperty("_entries");
+ }
+ }
+
+ private class EntryProperty
+ {
+ private readonly SerializedProperty _id;
+ private readonly SerializedProperty _name;
+
+ public int Id
+ {
+ get => _id.intValue;
+ set => _id.SetValueAndApply(value);
+ }
+
+ public string Name
+ {
+ get => _name.stringValue;
+ set => _name.SetValueAndApply(value);
+ }
+
+ private EntryProperty(SerializedProperty id, SerializedProperty name)
+ {
+ _id = id;
+ _name = name;
+ }
+
+ public static EntryProperty Create(SerializedProperty property) => new(
+ id: property.FindPropertyRelative("Id"),
+ name: property.FindPropertyRelative("Name"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdRegisterEditorExtensions.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdRegisterEditorExtensions.cs.meta
new file mode 100644
index 0000000..5ed4415
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdRegisterEditorExtensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b3fe5f5ee2834f75b44f0700b33eb2f4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructDrawer.cs
new file mode 100644
index 0000000..b35cd1b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructDrawer.cs
@@ -0,0 +1,305 @@
+using System;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ internal static class IdStructDrawer
+ {
+ private const string NoneOption = "";
+ private const float CreateButtonWidth = 55f;
+ private const string StringIdFieldName = "__stringId";
+ private const string IntIdFieldName = "_id";
+
+ private const string StyleSheetPath = "Styles/Aspid-FastTools-Id";
+ private const string RootClass = "aspid-fasttools-id-drawer";
+ private const string MainRowClass = "aspid-fasttools-id-drawer-main-row";
+ private const string LabelClass = "aspid-fasttools-id-drawer-label";
+ private const string DropdownClass = "aspid-fasttools-id-drawer-dropdown";
+ private const string CreateButtonClass = "aspid-fasttools-id-drawer-create-button";
+ private const string CreateRowClass = "aspid-fasttools-id-drawer-create-row";
+ private const string InputClass = "aspid-fasttools-id-drawer-input";
+ private const string AddButtonClass = "aspid-fasttools-id-drawer-add-button";
+ private const string CancelButtonClass = "aspid-fasttools-id-drawer-cancel-button";
+ private const string ErrorClass = "aspid-fasttools-id-drawer-error";
+
+ // IMGUI per-property state: (isCreating, inputText)
+ private static readonly Dictionary _imguiState = new();
+
+ public static float GetIMGUIHeight(SerializedProperty property)
+ {
+ var h = EditorGUIUtility.singleLineHeight;
+ if (_imguiState.TryGetValue(PropertyKey(property), out var s) && s.creating)
+ h += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ return h;
+ }
+
+ public static void DrawIMGUI(Rect position, SerializedProperty property, GUIContent label, Type fieldType)
+ {
+ var stringIdProp = property.FindPropertyRelative(StringIdFieldName);
+ var intIdProp = property.FindPropertyRelative(IntIdFieldName);
+
+ var currentId = intIdProp?.intValue ?? 0;
+ if (currentId > 0 && stringIdProp != null)
+ {
+ var reg = StringIdRegistryHelper.FindRegistry(fieldType);
+ var registryName = reg?.GetNameId(currentId);
+ if (registryName != null && registryName != stringIdProp.stringValue)
+ {
+ stringIdProp.stringValue = registryName;
+ property.serializedObject.ApplyModifiedPropertiesWithoutUndo();
+ }
+ }
+
+ if (!string.IsNullOrWhiteSpace(label.text))
+ {
+ EditorGUI.LabelField(position, label);
+ position.x += EditorGUIUtility.labelWidth;
+ position.width -= EditorGUIUtility.labelWidth;
+ }
+
+ var key = PropertyKey(property);
+ _imguiState.TryGetValue(key, out var state);
+
+ var mainRect = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight);
+ var dropRect = new Rect(mainRect.x, mainRect.y, mainRect.width - CreateButtonWidth - 2f, mainRect.height);
+ var btnRect = new Rect(dropRect.xMax + 2f, mainRect.y, CreateButtonWidth, mainRect.height);
+
+ var currentName = stringIdProp?.stringValue ?? string.Empty;
+
+ if (EditorGUI.DropdownButton(dropRect, new GUIContent(Caption(currentName)), FocusType.Passive))
+ {
+ var reg = StringIdRegistryHelper.FindRegistry(fieldType);
+ var sp = GUIUtility.GUIToScreenPoint(new Vector2(dropRect.x, dropRect.y));
+ var sr = new Rect(sp.x, sp.y, dropRect.width, dropRect.height);
+ StringIdSelectorWindow.Show(reg?.Ids ?? Array.Empty(), sr, currentName,
+ selected => ApplySelection(property, stringIdProp, intIdProp, fieldType, selected));
+ }
+
+ if (GUI.Button(btnRect, state.creating ? "Cancel" : "Create"))
+ {
+ _imguiState[key] = state.creating ? (false, string.Empty) : (true, string.Empty);
+ state = _imguiState[key];
+ }
+
+ if (!state.creating) return;
+
+ const float gap = 2f;
+ const float addW = 40f;
+ const float cancelW = 22f;
+ var rowY = mainRect.yMax + EditorGUIUtility.standardVerticalSpacing;
+ var inputW = position.width - addW - cancelW - gap * 2f;
+
+ var inputRect = new Rect(position.x, rowY, inputW, EditorGUIUtility.singleLineHeight);
+ var addRect = new Rect(inputRect.xMax + gap, rowY, addW, EditorGUIUtility.singleLineHeight);
+ var cancelRect = new Rect(addRect.xMax + gap, rowY, cancelW, EditorGUIUtility.singleLineHeight);
+
+ var newInput = EditorGUI.TextField(inputRect, state.input ?? string.Empty);
+ if (newInput != state.input)
+ {
+ state.input = newInput;
+ _imguiState[key] = state;
+ }
+
+ var reg2 = StringIdRegistryHelper.FindRegistry(fieldType);
+ var trimmed = state.input?.Trim() ?? string.Empty;
+ var canAdd = !string.IsNullOrEmpty(trimmed) && (reg2 == null || !reg2.Contains(trimmed));
+
+ using (new EditorGUI.DisabledScope(!canAdd))
+ {
+ if (GUI.Button(addRect, "+"))
+ {
+ var registry = reg2 ?? StringIdRegistryHelper.CreateRegistry(fieldType);
+ var assignedId = registry.Add(trimmed);
+ EditorUtility.SetDirty(registry);
+ AssetDatabase.SaveAssetIfDirty(registry);
+ SetFields(property, stringIdProp, intIdProp, trimmed, assignedId);
+ _imguiState[key] = (false, string.Empty);
+ }
+ }
+
+ if (GUI.Button(cancelRect, "✗"))
+ _imguiState[key] = (false, string.Empty);
+ }
+
+ public static VisualElement DrawUIToolkit(SerializedProperty property, string label, Type fieldType)
+ {
+ var stringIdProp = property.FindPropertyRelative(StringIdFieldName);
+ var intIdProp = property.FindPropertyRelative(IntIdFieldName);
+
+ var root = new VisualElement()
+ .AddStyleSheetsFromResource(StyleSheetPath)
+ .AddClass(RootClass)
+ .AddChild(new PropertyField(property).SetDisplay(DisplayStyle.None));
+
+ var mainRow = new VisualElement().AddClass(MainRowClass);
+
+ var currentName = stringIdProp?.stringValue ?? string.Empty;
+
+ var dropdownButton = new Button().AddClass(DropdownClass).SetText(Caption(currentName));
+ var createToggleButton = new Button().AddClass(CreateButtonClass).SetText("Create");
+
+ var createRow = new VisualElement().AddClass(CreateRowClass);
+ var inputField = new TextField().AddClass(InputClass);
+ var addButton = new Button().AddClass(AddButtonClass).SetText("+");
+ var cancelRowButton = new Button().AddClass(CancelButtonClass).SetText("✗");
+ var errorLabel = new Label().AddClass(ErrorClass);
+
+ addButton.SetEnabled(false);
+
+ var propertyPath = property.propertyPath;
+ var serializedObject = property.serializedObject;
+
+ dropdownButton.schedule.Execute(SyncStringFromInt).StartingIn(0);
+ dropdownButton.TrackPropertyValue(intIdProp, _ => SyncStringFromInt());
+
+ inputField.RegisterValueChangedCallback(e =>
+ {
+ var val = e.newValue?.Trim() ?? string.Empty;
+ var reg = StringIdRegistryHelper.FindRegistry(fieldType);
+
+ if (string.IsNullOrEmpty(val))
+ {
+ errorLabel.SetDisplay(DisplayStyle.None);
+ addButton.SetEnabled(false);
+ return;
+ }
+
+ if (reg != null && reg.Contains(val))
+ {
+ errorLabel.text = "ID already exists";
+ errorLabel.SetDisplay(DisplayStyle.Flex);
+ addButton.SetEnabled(false);
+ return;
+ }
+
+ errorLabel.SetDisplay(DisplayStyle.None);
+ addButton.SetEnabled(true);
+ });
+
+ createToggleButton.clicked += () =>
+ {
+ var isVisible = createRow.style.display == DisplayStyle.Flex;
+ createRow.SetDisplay(isVisible ? DisplayStyle.None : DisplayStyle.Flex);
+ errorLabel.SetDisplay(DisplayStyle.None);
+ if (!isVisible)
+ {
+ inputField.value = string.Empty;
+ inputField.Focus();
+ }
+ };
+
+ addButton.clicked += () =>
+ {
+ var name = inputField.value?.Trim();
+ if (string.IsNullOrEmpty(name)) return;
+
+ var reg= StringIdRegistryHelper.FindRegistry(fieldType) ?? StringIdRegistryHelper.CreateRegistry(fieldType);
+ var assignedId = reg.Add(name);
+ EditorUtility.SetDirty(reg);
+ AssetDatabase.SaveAssetIfDirty(reg);
+
+ var p = serializedObject.FindProperty(propertyPath);
+ var strProp = p.FindPropertyRelative(StringIdFieldName);
+ var intProp = p.FindPropertyRelative(IntIdFieldName);
+ SetFields(p, strProp, intProp, name, assignedId);
+ dropdownButton.SetText(Caption(name));
+
+ inputField.value = string.Empty;
+ createRow.SetDisplay(DisplayStyle.None);
+ errorLabel.SetDisplay(DisplayStyle.None);
+ };
+
+ cancelRowButton.clicked += () =>
+ {
+ inputField.value = string.Empty;
+ createRow.SetDisplay(DisplayStyle.None);
+ errorLabel.SetDisplay(DisplayStyle.None);
+ };
+
+ dropdownButton.clicked += () =>
+ {
+ var reg = StringIdRegistryHelper.FindRegistry(fieldType);
+ var window = EditorWindow.focusedWindow;
+ var wb = dropdownButton.worldBound;
+ var sr = new Rect(window.position.x + wb.xMin, window.position.y + wb.yMin, wb.width, wb.height);
+
+ var p = serializedObject.FindProperty(propertyPath);
+ var strProp = p.FindPropertyRelative(StringIdFieldName);
+ var current = strProp?.stringValue ?? string.Empty;
+
+ StringIdSelectorWindow.Show(reg?.Ids ?? Array.Empty(), sr, current, selected =>
+ {
+ var p2 = serializedObject.FindProperty(propertyPath);
+ var strProp2 = p2.FindPropertyRelative(StringIdFieldName);
+ var intProp2 = p2.FindPropertyRelative(IntIdFieldName);
+ ApplySelection(p2, strProp2, intProp2, fieldType, selected);
+ dropdownButton.SetText(Caption(strProp2?.stringValue ?? string.Empty));
+ });
+ };
+
+ if (!string.IsNullOrEmpty(label))
+ mainRow.AddChild(new Label(label).AddClass(LabelClass));
+
+ mainRow.AddChild(dropdownButton).AddChild(createToggleButton);
+ createRow.AddChild(inputField).AddChild(addButton).AddChild(cancelRowButton);
+
+ return root.AddChild(mainRow).AddChild(createRow).AddChild(errorLabel);
+
+ void SyncStringFromInt()
+ {
+ var p = serializedObject.FindProperty(propertyPath);
+ var strProp = p?.FindPropertyRelative(StringIdFieldName);
+ var intProp = p?.FindPropertyRelative(IntIdFieldName);
+ if (intProp == null || strProp == null || intProp.intValue <= 0) return;
+
+ var reg = StringIdRegistryHelper.FindRegistry(fieldType);
+ var registryName = reg?.GetNameId(intProp.intValue);
+ if (registryName == null || registryName == strProp.stringValue) return;
+
+ strProp.stringValue = registryName;
+ serializedObject.ApplyModifiedPropertiesWithoutUndo();
+ dropdownButton.SetText(Caption(registryName));
+ }
+ }
+
+ private static void ApplySelection(
+ SerializedProperty property,
+ SerializedProperty stringIdProp,
+ SerializedProperty intIdProp,
+ Type fieldType,
+ string selected)
+ {
+ var name = selected ?? string.Empty;
+ var id = 0;
+ if (!string.IsNullOrEmpty(name))
+ {
+ var reg = StringIdRegistryHelper.FindRegistry(fieldType);
+ id = reg?.GetId(name) ?? 0;
+ }
+ SetFields(property, stringIdProp, intIdProp, name, id);
+ }
+
+ private static void SetFields(
+ SerializedProperty property,
+ SerializedProperty stringIdProp,
+ SerializedProperty intIdProp,
+ string name,
+ int id)
+ {
+ if (stringIdProp != null) stringIdProp.stringValue = name;
+ if (intIdProp != null) intIdProp.intValue = id;
+ property.serializedObject.ApplyModifiedProperties();
+ }
+
+ private static string Caption(string name) =>
+ string.IsNullOrEmpty(name) ? NoneOption : name;
+
+ private static string PropertyKey(SerializedProperty p) =>
+ $"{p.serializedObject.targetObject.GetInstanceID()}:{p.propertyPath}";
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructDrawer.cs.meta
new file mode 100644
index 0000000..534b6e9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 31aa14fabd960064489a422808195610
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructPropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructPropertyDrawer.cs
new file mode 100644
index 0000000..7d05c77
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructPropertyDrawer.cs
@@ -0,0 +1,121 @@
+#nullable enable
+using System;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ [CustomPropertyDrawer(typeof(IId), useForChildren: true)]
+ internal sealed class IdStructPropertyDrawer : PropertyDrawer
+ {
+ // Cache uniqueness per (objectId:propertyPath:value) → avoids AssetDatabase calls every frame
+ private static readonly System.Collections.Generic.Dictionary _cache = new();
+ private const double CacheLifetime = 2.0;
+
+ private bool? _isUnique;
+ private bool IsUnique => _isUnique ??= fieldInfo.GetCustomAttributes(typeof(UniqueIdAttribute), false).Length > 0;
+
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ var h = IdStructDrawer.GetIMGUIHeight(property);
+
+ if (IsUnique && !string.IsNullOrEmpty(property.FindPropertyRelative("__stringId")?.stringValue) && !GetIsUnique(property))
+ h += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+
+ return h;
+ }
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ var drawH = IdStructDrawer.GetIMGUIHeight(property);
+ var drawRect = new Rect(position.x, position.y, position.width, drawH);
+ IdStructDrawer.DrawIMGUI(drawRect, property, label, fieldInfo.FieldType);
+
+ if (!IsUnique) return;
+
+ var stringId = property.FindPropertyRelative("__stringId")?.stringValue;
+ if (string.IsNullOrEmpty(stringId) || GetIsUnique(property)) return;
+
+ var warnY = position.y + drawH + EditorGUIUtility.standardVerticalSpacing;
+ var warnRect = new Rect(position.x, warnY, position.width, EditorGUIUtility.singleLineHeight);
+ EditorGUI.HelpBox(warnRect, "ID is not unique among assets of this type", MessageType.Warning);
+ }
+
+ public override VisualElement CreatePropertyGUI(SerializedProperty property)
+ {
+ var mainElement = IdStructDrawer.DrawUIToolkit(property, preferredLabel, fieldInfo.FieldType);
+
+ if (!IsUnique)
+ return mainElement;
+
+ var warningLabel = new Label("⚠ ID is not unique among assets of this type")
+ .SetDisplay(DisplayStyle.None)
+ .SetMargin(top: 2)
+ .SetColor(new Color(1f, 0.75f, 0f))
+ .SetFontSize(11);
+
+ var declaringType = fieldInfo.DeclaringType;
+ var propPath = property.propertyPath;
+ var so = property.serializedObject;
+
+ void Refresh()
+ {
+ var p = so.FindProperty(propPath);
+ var stringId = p?.FindPropertyRelative("__stringId")?.stringValue;
+ var isUnique = string.IsNullOrEmpty(stringId) || CheckIsUnique(p!, stringId!, declaringType);
+ warningLabel.SetDisplay(isUnique ? DisplayStyle.None : DisplayStyle.Flex);
+ }
+
+ var idProp = property.FindPropertyRelative("__stringId");
+ if (idProp != null)
+ warningLabel.TrackPropertyValue(idProp, _ => Refresh());
+ warningLabel.schedule.Execute(Refresh).StartingIn(0);
+
+ return new VisualElement()
+ .SetFlexDirection(FlexDirection.Column)
+ .AddChild(mainElement)
+ .AddChild(warningLabel);
+ }
+
+ private bool GetIsUnique(SerializedProperty structProp)
+ {
+ var idProp = structProp.FindPropertyRelative("__stringId");
+ if (idProp == null) return true;
+
+ var key = $"{structProp.serializedObject.targetObject.GetInstanceID()}:{structProp.propertyPath}:{idProp.stringValue}";
+ var now = EditorApplication.timeSinceStartup;
+
+ if (_cache.TryGetValue(key, out var cached) && now - cached.time < CacheLifetime)
+ return cached.isUnique;
+
+ var result = CheckIsUnique(structProp, idProp.stringValue, fieldInfo.DeclaringType);
+ _cache[key] = (now, result);
+ return result;
+ }
+
+ private static bool CheckIsUnique(SerializedProperty structProp, string idValue, Type? assetType)
+ {
+ if (assetType == null || string.IsNullOrEmpty(idValue)) return true;
+
+ var currentObject = structProp.serializedObject.targetObject;
+ var fullPath = $"{structProp.propertyPath}.__stringId";
+ var guids = AssetDatabase.FindAssets($"t:{assetType.Name}");
+
+ foreach (var guid in guids)
+ {
+ var assetPath = AssetDatabase.GUIDToAssetPath(guid);
+ var asset = AssetDatabase.LoadAssetAtPath(assetPath, assetType);
+ if (asset == null || asset == currentObject) continue;
+
+ var otherSo = new SerializedObject(asset);
+ var otherValue = otherSo.FindProperty(fullPath)?.stringValue;
+ if (otherValue == idValue) return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructPropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructPropertyDrawer.cs.meta
new file mode 100644
index 0000000..b9f635b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/IdStructPropertyDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4a01d15a4e71aac45aa79852f3bf79a6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryEditor.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryEditor.cs
new file mode 100644
index 0000000..06c5e49
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryEditor.cs
@@ -0,0 +1,412 @@
+using System;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEditor.SceneManagement;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using UnityEngine.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ [CustomEditor(typeof(IdRegistry))]
+ internal sealed class StringIdRegistryEditor : Editor
+ {
+ private SerializedProperty _targetTypeProp;
+ private SerializedProperty _entriesProp;
+
+ private void OnEnable()
+ {
+ _targetTypeProp = serializedObject.FindProperty("_targetStructType");
+ _entriesProp = serializedObject.FindProperty("_entries");
+ }
+
+ private void OnDisable()
+ {
+ if (target == null) return;
+ CleanUpInvalid();
+ }
+
+ private const string StyleSheetPath = "Styles/Aspid-FastTools-Id";
+
+ public override VisualElement CreateInspectorGUI()
+ {
+ var root = new VisualElement()
+ .AddStyleSheetsFromResource(StyleSheetPath)
+ .AddClass("aspid-fasttools-id-registry");
+
+ root.Add(new PropertyField(_targetTypeProp, label: string.Empty));
+
+ var spacer = new VisualElement();
+ spacer.style.height = 8;
+ root.Add(spacer);
+
+ var idsLabel = new Label("IDs");
+ idsLabel.style.unityFontStyleAndWeight = FontStyle.Bold;
+ idsLabel.style.marginBottom = 2;
+ root.Add(idsLabel);
+
+ var entriesContainer = new VisualElement();
+ root.Add(entriesContainer);
+ root.Add(BuildRegistryAddRow());
+
+ root.TrackSerializedObjectValue(serializedObject, _ => RebuildEntries(entriesContainer));
+ RebuildEntries(entriesContainer);
+
+ return root;
+ }
+
+ private void RebuildEntries(VisualElement container)
+ {
+ container.Clear();
+ var duplicates = GetDuplicates();
+
+ for (int i = 0; i < _entriesProp.arraySize; i++)
+ {
+ var element = _entriesProp.GetArrayElementAtIndex(i);
+ var name = element.FindPropertyRelative("Name").stringValue;
+ var id = element.FindPropertyRelative("Id").intValue;
+ var isDuplicate = duplicates.Contains(name);
+ container.Add(BuildRegistryEntryRow(i, name, id, isDuplicate));
+ }
+ }
+
+ private VisualElement BuildRegistryEntryRow(int index, string name, int id, bool isDuplicate)
+ {
+ var container = new VisualElement().AddClass("aspid-fasttools-id-registry-entry");
+ var row = new VisualElement().AddClass("aspid-fasttools-id-registry-row");
+
+ var nameField = new TextField { value = name };
+ nameField.AddClass("aspid-fasttools-id-registry-name");
+ if (isDuplicate)
+ {
+ nameField.AddClass("aspid-fasttools-id-registry-name--duplicate");
+ row.Add(nameField);
+ row.Add(new Label("(duplicate)").AddClass("aspid-fasttools-id-registry-duplicate-label"));
+ }
+ else
+ {
+ row.Add(nameField);
+ }
+
+ var idField = new IntegerField(string.Empty) { value = id };
+ idField.SetEnabled(false);
+ idField.AddClass("aspid-fasttools-id-registry-id");
+ row.Add(idField);
+
+ var deleteButton = new Button { text = "×" };
+ deleteButton.AddClass("aspid-fasttools-id-registry-delete");
+ row.Add(deleteButton);
+
+ container.Add(row);
+
+ var errorLabel = new Label().AddClass("aspid-fasttools-id-drawer-error");
+ if (isDuplicate)
+ {
+ errorLabel.text = "Name already exists.";
+ errorLabel.SetDisplay(DisplayStyle.Flex);
+ }
+ container.Add(errorLabel);
+
+ nameField.RegisterCallback(_ =>
+ {
+ if (HasDuplicate(name))
+ {
+ errorLabel.text = "Name already exists.";
+ errorLabel.SetDisplay(DisplayStyle.Flex);
+ }
+ });
+
+ nameField.RegisterValueChangedCallback(e =>
+ {
+ var t = e.newValue?.Trim() ?? string.Empty;
+ var registry = (IdRegistry)target;
+ if (string.IsNullOrEmpty(t))
+ {
+ errorLabel.text = "Name cannot be empty.";
+ errorLabel.SetDisplay(DisplayStyle.Flex);
+ }
+ else if (HasDuplicate(t) || (t != name && registry.Contains(t)))
+ {
+ errorLabel.text = $"'{t}' already exists.";
+ errorLabel.SetDisplay(DisplayStyle.Flex);
+ }
+ else
+ {
+ errorLabel.SetDisplay(DisplayStyle.None);
+ }
+ });
+
+ nameField.RegisterCallback(_ =>
+ {
+ var t = nameField.value?.Trim() ?? string.Empty;
+ var registry = (IdRegistry)target;
+ if (!string.IsNullOrEmpty(t) && t != name && !registry.Contains(t))
+ {
+ serializedObject.ApplyModifiedProperties();
+ registry.Rename(name, t);
+ EditorUtility.SetDirty(registry);
+ serializedObject.Update();
+ errorLabel.SetDisplay(DisplayStyle.None);
+ }
+ else
+ {
+ nameField.SetValueWithoutNotify(name);
+ if (HasDuplicate(name))
+ {
+ errorLabel.text = "Name already exists.";
+ errorLabel.SetDisplay(DisplayStyle.Flex);
+ }
+ else
+ {
+ errorLabel.SetDisplay(DisplayStyle.None);
+ }
+ }
+ });
+
+ deleteButton.clicked += () =>
+ {
+ TryDeleteEntry(index);
+ serializedObject.ApplyModifiedProperties();
+ serializedObject.Update();
+ };
+
+ return container;
+ }
+
+ private VisualElement BuildRegistryAddRow()
+ {
+ var row = new VisualElement().AddClass("aspid-fasttools-id-registry-add-row");
+
+ var inputField = new TextField();
+ inputField.AddClass("aspid-fasttools-id-registry-add-input");
+
+ var addButton = new Button { text = "+" };
+ addButton.AddClass("aspid-fasttools-id-registry-add-button");
+ addButton.SetEnabled(false);
+
+ inputField.RegisterValueChangedCallback(e =>
+ {
+ var val = e.newValue?.Trim() ?? string.Empty;
+ var registry = (IdRegistry)target;
+ addButton.SetEnabled(!string.IsNullOrEmpty(val) && !registry.Contains(val));
+ });
+
+ addButton.clicked += () =>
+ {
+ var val = inputField.value?.Trim();
+ if (string.IsNullOrEmpty(val)) return;
+ var registry = (IdRegistry)target;
+ serializedObject.ApplyModifiedProperties();
+ registry.Add(val);
+ EditorUtility.SetDirty(registry);
+ serializedObject.Update();
+ inputField.SetValueWithoutNotify(string.Empty);
+ addButton.SetEnabled(false);
+ };
+
+ row.Add(inputField);
+ row.Add(addButton);
+ return row;
+ }
+
+ private void TryDeleteEntry(int index)
+ {
+ var nameProp = _entriesProp.GetArrayElementAtIndex(index).FindPropertyRelative("Name");
+ var nameToDelete = nameProp.stringValue;
+ var usageCount = CountUsages(nameToDelete);
+
+ var message = usageCount == 0
+ ? $"Delete '{nameToDelete}'?"
+ : $"'{nameToDelete}' is used in {usageCount} asset(s).\n\nFields referencing this ID will show after deletion.\n\nDelete anyway?";
+
+ if (EditorUtility.DisplayDialog("Delete ID", message, "Delete", "Cancel"))
+ _entriesProp.DeleteArrayElementAtIndex(index);
+ }
+
+ private int CountUsages(string name)
+ {
+ var structType = GetStructType();
+ var idFieldName = structType != null ? GetStringIdFieldName(structType) : null;
+ if (idFieldName == null) return 0;
+
+ try
+ {
+ return FindUsages(idFieldName, name, showProgress: true).Count;
+ }
+ finally
+ {
+ EditorUtility.ClearProgressBar();
+ }
+ }
+
+ private static List<(UnityEngine.Object asset, string path)> FindUsages(
+ string idFieldName, string name, bool showProgress = false)
+ {
+ var results = new List<(UnityEngine.Object, string)>();
+ ScanAssets("t:ScriptableObject", idFieldName, name, results, showProgress, "Scanning ScriptableObjects");
+ ScanPrefabs(idFieldName, name, results, showProgress);
+ ScanScenes(idFieldName, name, results, showProgress);
+ return results;
+ }
+
+ private static void ScanAssets(string filter, string idFieldName, string name,
+ List<(UnityEngine.Object, string)> results, bool showProgress, string progressTitle)
+ {
+ var guids = AssetDatabase.FindAssets(filter, new[] { "Assets" });
+ for (int i = 0; i < guids.Length; i++)
+ {
+ var assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
+
+ if (showProgress)
+ EditorUtility.DisplayProgressBar(progressTitle, assetPath, (float)i / guids.Length);
+
+ var asset = AssetDatabase.LoadAssetAtPath(assetPath);
+ if (asset == null) continue;
+
+ ScanObject(asset, idFieldName, name, results);
+ }
+ }
+
+ private static void ScanPrefabs(string idFieldName, string name,
+ List<(UnityEngine.Object, string)> results, bool showProgress)
+ {
+ var guids = AssetDatabase.FindAssets("t:Prefab", new[] { "Assets" });
+ for (int i = 0; i < guids.Length; i++)
+ {
+ var assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
+ if (showProgress)
+ EditorUtility.DisplayProgressBar("Scanning Prefabs", assetPath, (float)i / guids.Length);
+
+ var prefab = AssetDatabase.LoadAssetAtPath(assetPath);
+ if (prefab == null) continue;
+
+ foreach (var component in prefab.GetComponentsInChildren(includeInactive: true))
+ {
+ if (component != null)
+ ScanObject(component, idFieldName, name, results);
+ }
+ }
+ }
+
+ private static void ScanObject(UnityEngine.Object obj, string idFieldName, string name,
+ List<(UnityEngine.Object, string)> results)
+ {
+ var so = new SerializedObject(obj);
+ var iterator = so.GetIterator();
+
+ while (iterator.Next(enterChildren: true))
+ {
+ if (iterator.propertyType == SerializedPropertyType.String
+ && iterator.name == idFieldName
+ && iterator.stringValue == name)
+ {
+ results.Add((obj, iterator.propertyPath));
+ }
+ }
+ }
+
+ private static void ScanScenes(string idFieldName, string name,
+ List<(UnityEngine.Object asset, string path)> results, bool showProgress)
+ {
+ var guids = AssetDatabase.FindAssets("t:Scene", new[] { "Assets" });
+ for (int i = 0; i < guids.Length; i++)
+ {
+ var scenePath = AssetDatabase.GUIDToAssetPath(guids[i]);
+ if (showProgress)
+ EditorUtility.DisplayProgressBar("Scanning Scenes", scenePath, (float)i / guids.Length);
+
+ var alreadyOpen = false;
+ for (int s = 0; s < SceneManager.sceneCount; s++)
+ {
+ if (SceneManager.GetSceneAt(s).path == scenePath)
+ {
+ alreadyOpen = true;
+ break;
+ }
+ }
+
+ var scene = alreadyOpen
+ ? SceneManager.GetSceneByPath(scenePath)
+ : EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive);
+
+ if (!scene.IsValid())
+ {
+ if (!alreadyOpen) EditorSceneManager.CloseScene(scene, true);
+ continue;
+ }
+
+ foreach (var root in scene.GetRootGameObjects())
+ {
+ foreach (var component in root.GetComponentsInChildren(includeInactive: true))
+ {
+ if (component != null)
+ ScanObject(component, idFieldName, name, results);
+ }
+ }
+
+ if (!alreadyOpen)
+ EditorSceneManager.CloseScene(scene, true);
+ }
+ }
+
+ private void CleanUpInvalid()
+ {
+ var so = new SerializedObject(target);
+ var entries = so.FindProperty("_entries");
+ var seen = new HashSet();
+ var toRemove = new List();
+
+ for (int i = 0; i < entries.arraySize; i++)
+ {
+ var val = entries.GetArrayElementAtIndex(i).FindPropertyRelative("Name").stringValue;
+ if (string.IsNullOrEmpty(val) || !seen.Add(val))
+ toRemove.Add(i);
+ }
+
+ for (int i = toRemove.Count - 1; i >= 0; i--)
+ entries.DeleteArrayElementAtIndex(toRemove[i]);
+
+ if (toRemove.Count > 0)
+ so.ApplyModifiedPropertiesWithoutUndo();
+ }
+
+ private Type GetStructType()
+ {
+ var aqn = _targetTypeProp.stringValue;
+ return string.IsNullOrEmpty(aqn) ? null : Type.GetType(aqn, throwOnError: false);
+ }
+
+ private static string GetStringIdFieldName(Type structType)
+ {
+ // IId-generated structs use __stringId
+ return typeof(IId).IsAssignableFrom(structType)
+ ? "__stringId"
+ : null;
+ }
+
+ private HashSet GetDuplicates()
+ {
+ var seen = new HashSet();
+ var dupes = new HashSet();
+ for (int i = 0; i < _entriesProp.arraySize; i++)
+ {
+ var val = _entriesProp.GetArrayElementAtIndex(i).FindPropertyRelative("Name").stringValue;
+ if (!string.IsNullOrEmpty(val) && !seen.Add(val))
+ dupes.Add(val);
+ }
+ return dupes;
+ }
+
+ // Reads directly from the C# object — reliable in UIToolkit callbacks
+ // where _entriesProp may be stale.
+ private bool HasDuplicate(string entryName)
+ {
+ var count = 0;
+ foreach (var e in ((IdRegistry)target).Entries)
+ if (e.Name == entryName) count++;
+ return count > 1;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryEditor.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryEditor.cs.meta
new file mode 100644
index 0000000..a38f609
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f38578ffc72b42189bc5cbe4ef4fd9a1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryHelper.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryHelper.cs
new file mode 100644
index 0000000..4c9ae3d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryHelper.cs
@@ -0,0 +1,70 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ internal class StringIdRegistryCacheInvalidator : AssetPostprocessor
+ {
+ private static void OnPostprocessAllAssets(string[] imported, string[] deleted, string[] moved, string[] movedFrom)
+ {
+ if (imported.Length > 0 || deleted.Length > 0 || moved.Length > 0)
+ StringIdRegistryHelper.ClearCache();
+ }
+ }
+
+ internal static class StringIdRegistryHelper
+ {
+ private static readonly Dictionary _cache = new();
+
+ internal static void ClearCache() => _cache.Clear();
+
+ public static IdRegistry? FindRegistry(Type? declaringType)
+ {
+ if (declaringType == null) return null;
+
+ var aqn = declaringType.AssemblyQualifiedName ?? string.Empty;
+ if (_cache.TryGetValue(aqn, out var cached))
+ return cached;
+
+ var guids = AssetDatabase.FindAssets("t:IdRegistry");
+ foreach (var guid in guids)
+ {
+ var path = AssetDatabase.GUIDToAssetPath(guid);
+ var reg = AssetDatabase.LoadAssetAtPath(path);
+ if (reg != null && reg.TargetStructType == aqn)
+ {
+ _cache[aqn] = reg;
+ return reg;
+ }
+ }
+
+ _cache[aqn] = null;
+ return null;
+ }
+
+ public static IdRegistry CreateRegistry(Type? declaringType)
+ {
+ if (declaringType is null)
+ throw new ArgumentNullException(nameof(declaringType));
+
+ var path = AssetDatabase.GenerateUniqueAssetPath($"Assets/StringIdRegistry_{declaringType.Name}.asset");
+ var reg = ScriptableObject.CreateInstance();
+ AssetDatabase.CreateAsset(reg, path);
+
+ var so = new SerializedObject(reg);
+ so.FindProperty("_targetStructType").stringValue = declaringType.AssemblyQualifiedName ?? string.Empty;
+ so.ApplyModifiedPropertiesWithoutUndo();
+
+ AssetDatabase.SaveAssets();
+
+ var aqn = declaringType.AssemblyQualifiedName ?? string.Empty;
+ _cache[aqn] = reg;
+
+ return reg;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryHelper.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryHelper.cs.meta
new file mode 100644
index 0000000..dd56bc6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdRegistryHelper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d3e55dba10918834483ed242cde16ce1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdSelectorWindow.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdSelectorWindow.cs
new file mode 100644
index 0000000..1b3ff9c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdSelectorWindow.cs
@@ -0,0 +1,122 @@
+#nullable enable
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ internal sealed class StringIdSelectorWindow : EditorWindow
+ {
+ private const string NoneOption = "";
+ private const string StyleSheetPath = "Styles/Aspid-FastTools-Id";
+ private const string ContainerClass = "aspid-fasttools-id-selector-container";
+ private const string ItemClass = "aspid-fasttools-id-selector-item";
+
+ private ToolbarSearchField _searchField = null!;
+ private ListView _listView = null!;
+ private Action? _onSelected;
+ private string[] _allIds = Array.Empty();
+ private readonly List _filteredIds = new();
+ private string _current = string.Empty;
+
+ public static void Show(IReadOnlyList ids, Rect screenRect, string current, Action onSelected)
+ {
+ var window = CreateInstance();
+ window.Initialize(ids, screenRect, current, onSelected);
+ }
+
+ private void Initialize(IReadOnlyList ids, Rect screenRect, string current, Action onSelected)
+ {
+ _onSelected = onSelected;
+ _current = current ?? string.Empty;
+ _allIds = ids.ToArray();
+
+ BuildUI();
+ RefreshList(string.Empty);
+
+ var size = new Vector2(Mathf.Max(250, screenRect.width), 250);
+ ShowAsDropDown(screenRect, size);
+
+ _searchField.Focus();
+ }
+
+ private void BuildUI()
+ {
+ _searchField = new ToolbarSearchField();
+ _searchField.RegisterValueChangedCallback(e => RefreshList(e.newValue ?? string.Empty));
+ _searchField.RegisterCallback(e =>
+ {
+ if (e.direction == NavigationMoveEvent.Direction.Down)
+ _listView.Focus();
+ }, TrickleDown.TrickleDown);
+
+ _listView = new ListView
+ {
+ selectionType = SelectionType.Single,
+ virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight,
+ itemsSource = _filteredIds,
+ };
+
+ _listView.SetMakeItem(CreateItem);
+ _listView.SetBindItem(BindItem);
+ _listView.itemsChosen += items => SelectItem(items.OfType().FirstOrDefault());
+
+ var container = new VisualElement()
+ .AddStyleSheetsFromResource(StyleSheetPath)
+ .AddClass(ContainerClass)
+ .AddChild(_searchField)
+ .AddChild(_listView);
+
+ rootVisualElement.Add(container);
+ rootVisualElement.RegisterCallback(OnKeyDown, TrickleDown.TrickleDown);
+ }
+
+ private VisualElement CreateItem()
+ {
+ return new Label().AddClass(ItemClass);
+ }
+
+ private void BindItem(VisualElement element, int index)
+ {
+ if (index < 0 || index >= _filteredIds.Count) return;
+ var label = (Label)element;
+ var id = _filteredIds[index];
+ label.text = id;
+ label.style.unityFontStyleAndWeight = id == _current ? FontStyle.Bold : FontStyle.Normal;
+ }
+
+ private void RefreshList(string search)
+ {
+ _filteredIds.Clear();
+ _filteredIds.Add(NoneOption);
+
+ foreach (var id in _allIds)
+ {
+ if (string.IsNullOrEmpty(search) || id.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0)
+ _filteredIds.Add(id);
+ }
+
+ _listView.Rebuild();
+ }
+
+ private void SelectItem(string? item)
+ {
+ if (item == null) return;
+ _onSelected?.Invoke(item == NoneOption ? string.Empty : item);
+ Close();
+ }
+
+ private void OnKeyDown(KeyDownEvent e)
+ {
+ if (e.keyCode == KeyCode.Escape)
+ Close();
+ }
+
+ private void OnLostFocus() => Close();
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdSelectorWindow.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdSelectorWindow.cs.meta
new file mode 100644
index 0000000..c76c3ba
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/StringIdSelectorWindow.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f1db6054f201aae478cc31bbd3029ff6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 3f246f77b3b5a4b6ba0ab47ff519a0a9, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.Reflection.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.Reflection.cs
new file mode 100644
index 0000000..d6772d8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.Reflection.cs
@@ -0,0 +1,122 @@
+using System;
+using UnityEditor;
+using System.Linq;
+using System.Reflection;
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ public static partial class SerializePropertyExtensions
+ {
+ private const BindingFlags BindingFlags =
+ System.Reflection.BindingFlags.Instance
+ | System.Reflection.BindingFlags.Public
+ | System.Reflection.BindingFlags.NonPublic;
+
+ ///
+ /// Returns the of the field or property that backs this .
+ ///
+ /// The property to inspect.
+ ///
+ /// The or of the backing member,
+ /// or if the member cannot be resolved.
+ ///
+ public static Type GetPropertyType(this SerializedProperty serializedProperty) => GetMemberInfo(serializedProperty) switch
+ {
+ FieldInfo field => field.FieldType,
+ PropertyInfo property => property.PropertyType,
+ _ => null
+ };
+
+ ///
+ /// Uses reflection to find the (field or property) on the owning class
+ /// that corresponds to this .
+ ///
+ /// The property whose backing member should be located.
+ ///
+ /// The whose name matches ,
+ /// or if it cannot be found.
+ ///
+ public static MemberInfo GetMemberInfo(this SerializedProperty serializedProperty)
+ {
+ var type = serializedProperty.GetClassInstance().GetType();
+
+ return type
+ .GetMembersInfosIncludingBaseClasses(BindingFlags)
+ .FirstOrDefault(member => member.Name == serializedProperty.name);
+ }
+
+ ///
+ /// Traverses the to return the runtime object instance
+ /// that directly owns this property (i.e., the containing class instance, not the root target).
+ /// Supports nested objects, arrays, and generic fields.
+ ///
+ /// The property whose owning instance should be resolved.
+ /// The runtime object that contains the field represented by .
+ public static object GetClassInstance(this SerializedProperty property)
+ {
+ var path = property.propertyPath;
+ var target = property.serializedObject.targetObject;
+ var startRemoveIndex = path.Length - property.name.Length - 1;
+
+ if (startRemoveIndex < 0) return target;
+
+ path = path
+ .Remove(startRemoveIndex)
+ .Replace(".Array.data[", "[");
+
+ object current = target;
+
+ foreach (var part in path.Split('.'))
+ {
+ if (part.Contains("["))
+ {
+ var startPartIndex = part.IndexOf("[", StringComparison.Ordinal) + 1;
+ var length = part.IndexOf("]", StringComparison.Ordinal) - startPartIndex;
+
+ var index = int.Parse(part.Substring(startPartIndex, length));
+ current = FindInstance(part[..(startPartIndex - 1)], index);
+ }
+ else
+ {
+ current = FindInstance(part);
+ }
+ }
+
+ return current;
+
+ object FindInstance(string name, int index = -1)
+ {
+ var currentType = current.GetType();
+ var field = FindField(currentType, name);
+
+ if (index > -1)
+ {
+ if (field.FieldType.IsArray)
+ {
+ var type = field.FieldType.GetElementType();
+ var classInstance = ((object[])field.GetValue(current))[index];
+ return (type, classInstance);
+ }
+
+ if (field.FieldType.IsGenericType && field.FieldType.GetGenericTypeDefinition() == typeof(List<>))
+ {
+ var type = field.FieldType.GetGenericArguments()[0];
+ var classInstance = ((IReadOnlyList