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 + +![Aspid.FastTools.ProfilerMarkers.png](Images/Aspid.FastTools.ProfilerMarkers.png) + +--- + +## 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); + } +} +``` +![Aspid.FastTools.SerializableType.png](Images/Aspid.FastTools.SerializableType.png) +### 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 + +![Aspid.FastTools.TypeSelectorWindow.png](Images/Aspid.FastTools.TypeSelectorWindow.png) +--- + +## 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 + +![Aspid.FastTools.VisualElement.png](Images/Aspid.FastTools.VisualElement.png) + +--- + +## 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(); + } +} +``` + +### Результат + +![Aspid.FastTools.ProfilerMarkers.png](Images/Aspid.FastTools.ProfilerMarkers.png) + +--- + +## Система сериализуемых типов + +Позволяет сериализовать ссылку на `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); + } +} +``` +![Aspid.FastTools.SerializableType.png](Images/Aspid.FastTools.SerializableType.png) +### 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) +- Историю навигации (кнопка «назад») +- Разрешение неоднозначности для типов с одинаковыми именами из разных сборок + +![Aspid.FastTools.TypeSelectorWindow.png](Images/Aspid.FastTools.TypeSelectorWindow.png) +--- + +## Система перечислений + +Предоставляет сериализуемые отображения 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)); + } +} +``` + +### Результат + +![Aspid.FastTools.VisualElement.png](Images/Aspid.FastTools.VisualElement.png) + +--- + +## Вспомогательные расширения для редактора + +Утилитарные методы для получения отображаемых имён объектов 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)field.GetValue(current))[index]; + return (type, classInstance); + } + } + + return field?.GetValue(current); + } + + FieldInfo FindField(Type type, string name) + { + return type?.GetMembersInfosIncludingBaseClasses(BindingFlags) + .OfType() + .FirstOrDefault(field => field.Name == name); + } + } + } +} \ No newline at end of file diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.Reflection.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.Reflection.cs.meta new file mode 100644 index 0000000..8bc0011 --- /dev/null +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.Reflection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 073ab35cc6ad4b87819de40a82ad9210 +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/SerializedProperties/SerializePropertyExtensions.SetValue.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.SetValue.cs new file mode 100644 index 0000000..72f1c21 --- /dev/null +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.SetValue.cs @@ -0,0 +1,1157 @@ +using UnityEditor; +using UnityEngine; + +// ReSharper disable once CheckNamespace +namespace Aspid.FastTools.Editors +{ + public static partial class SerializePropertyExtensions + { + #region Int + /// + public static T SetValue(this T property, int value) + where T : SerializedProperty + { + return property.SetInt(value); + } + + /// + public static T SetValueAndApply(this T property, int value) + where T : SerializedProperty + { + return property.SetIntAndApply(value); + } + + /// Sets and returns the property for chaining. + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetInt(this T property, int value) + where T : SerializedProperty + { + property.intValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetIntAndApply(this T property, int value) + where T : SerializedProperty + { + return property.SetInt(value).ApplyModifiedProperties(); + } + #endregion + + #region Uint + /// + public static T SetValue(this T property, uint value) + where T : SerializedProperty + { + return property.SetUint(value); + } + + /// + public static T SetValueAndApply(this T property, uint value) + where T : SerializedProperty + { + return property.SetUintAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetUint(this T property, uint value) + where T : SerializedProperty + { + property.uintValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetUintAndApply(this T property, uint value) + where T : SerializedProperty + { + return property.SetUint(value).ApplyModifiedProperties(); + } + #endregion + + #region Long + /// + public static T SetValue(this T property, long value) + where T : SerializedProperty + { + return property.SetLong(value); + } + + /// + public static T SetValueAndApply(this T property, long value) + where T : SerializedProperty + { + return property.SetLongAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetLong(this T property, long value) + where T : SerializedProperty + { + property.longValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetLongAndApply(this T property, long value) + where T : SerializedProperty + { + return property.SetLong(value).ApplyModifiedProperties(); + } + #endregion + + #region Ulong + /// + public static T SetValue(this T property, ulong value) + where T : SerializedProperty + { + return property.SetUlong(value); + } + + /// + public static T SetValueAndApply(this T property, ulong value) + where T : SerializedProperty + { + return property.SetUlongAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetUlong(this T property, ulong value) + where T : SerializedProperty + { + property.ulongValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetUlongAndApply(this T property, ulong value) + where T : SerializedProperty + { + return property.SetUlong(value).ApplyModifiedProperties(); + } + #endregion + + #region Float + /// + public static T SetValue(this T property, float value) + where T : SerializedProperty + { + return property.SetFloat(value); + } + + /// + public static T SetValueAndApply(this T property, float value) + where T : SerializedProperty + { + return property.SetFloatAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetFloat(this T property, float value) + where T : SerializedProperty + { + property.floatValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetFloatAndApply(this T property, float value) + where T : SerializedProperty + { + return property.SetFloat(value).ApplyModifiedProperties(); + } + #endregion + + #region Double + /// + public static T SetValue(this T property, double value) + where T : SerializedProperty + { + return property.SetDouble(value); + } + + /// + public static T SetValueAndApply(this T property, double value) + where T : SerializedProperty + { + return property.SetDoubleAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetDouble(this T property, double value) + where T : SerializedProperty + { + property.doubleValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetDoubleAndApply(this T property, double value) + where T : SerializedProperty + { + return property.SetDouble(value).ApplyModifiedProperties(); + } + #endregion + + #region EnumIndex + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Flag value to assign. + /// The same instance. + public static T SetEnumFlag(this T property, int value) + where T : SerializedProperty + { + property.enumValueFlag = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetEnumFlagAndApply(this T property, int value) + where T : SerializedProperty + { + return property.SetEnumFlag(value).ApplyModifiedProperties(); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Index value to assign. + /// The same instance. + public static T SetEnumIndex(this T property, int value) + where T : SerializedProperty + { + property.enumValueIndex = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetEnumIndexAndApply(this T property, int value) + where T : SerializedProperty + { + return property.SetEnumIndex(value).ApplyModifiedProperties(); + } + #endregion + + #region Bool + /// + public static T SetValue(this T property, bool value) + where T : SerializedProperty + { + return property.SetBool(value); + } + + /// + public static T SetValueAndApply(this T property, bool value) + where T : SerializedProperty + { + return property.SetBoolAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetBool(this T property, bool value) + where T : SerializedProperty + { + property.boolValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetBoolAndApply(this T property, bool value) + where T : SerializedProperty + { + return property.SetBool(value).ApplyModifiedProperties(); + } + #endregion + + #region Rect + /// + public static T SetValue(this T property, Rect value) + where T : SerializedProperty + { + return property.SetRect(value); + } + + /// + public static T SetValueAndApply(this T property, Rect value) + where T : SerializedProperty + { + return property.SetRectAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetRect(this T property, Rect value) + where T : SerializedProperty + { + property.rectValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetRectAndApply(this T property, Rect value) + where T : SerializedProperty + { + return property.SetRect(value).ApplyModifiedProperties(); + } + #endregion + + #region RectInt + /// + public static T SetValue(this T property, RectInt value) + where T : SerializedProperty + { + return property.SetRectInt(value); + } + + /// + public static T SetValueAndApply(this T property, RectInt value) + where T : SerializedProperty + { + return property.SetRectIntAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetRectInt(this T property, RectInt value) + where T : SerializedProperty + { + property.rectIntValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetRectIntAndApply(this T property, RectInt value) + where T : SerializedProperty + { + return property.SetRectInt(value).ApplyModifiedProperties(); + } + #endregion + + #region Bounds + /// + public static T SetValue(this T property, Bounds value) + where T : SerializedProperty + { + return property.SetBounds(value); + } + + /// + public static T SetValueAndApply(this T property, Bounds value) + where T : SerializedProperty + { + return property.SetBoundsAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetBounds(this T property, Bounds value) + where T : SerializedProperty + { + property.boundsValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetBoundsAndApply(this T property, Bounds value) + where T : SerializedProperty + { + return property.SetBounds(value).ApplyModifiedProperties(); + } + #endregion + + #region BoundsInt + /// + public static T SetValue(this T property, BoundsInt value) + where T : SerializedProperty + { + return property.SetBoundsInt(value); + } + + /// + public static T SetValueAndApply(this T property, BoundsInt value) + where T : SerializedProperty + { + return property.SetBoundsIntAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetBoundsInt(this T property, BoundsInt value) + where T : SerializedProperty + { + property.boundsIntValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetBoundsIntAndApply(this T property, BoundsInt value) + where T : SerializedProperty + { + return property.SetBoundsInt(value).ApplyModifiedProperties(); + } + #endregion + + #region Color + /// + public static T SetValue(this T property, Color value) + where T : SerializedProperty + { + return property.SetColor(value); + } + + /// + public static T SetValueAndApply(this T property, Color value) + where T : SerializedProperty + { + return property.SetColorAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetColor(this T property, Color value) + where T : SerializedProperty + { + property.colorValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetColorAndApply(this T property, Color value) + where T : SerializedProperty + { + return property.SetColor(value).ApplyModifiedProperties(); + } + #endregion + + #region Gradient + /// + public static T SetValue(this T property, Gradient value) + where T : SerializedProperty + { + return property.SetGradient(value); + } + + /// + public static T SetValueAndApply(this T property, Gradient value) + where T : SerializedProperty + { + return property.SetGradientAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetGradient(this T property, Gradient value) + where T : SerializedProperty + { + property.gradientValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetGradientAndApply(this T property, Gradient value) + where T : SerializedProperty + { + return property.SetGradient(value).ApplyModifiedProperties(); + } + #endregion + + #region Hash128 + /// + public static T SetValue(this T property, Hash128 value) + where T : SerializedProperty + { + return property.SetHash128(value); + } + + /// + public static T SetValueAndApply(this T property, Hash128 value) + where T : SerializedProperty + { + return property.SetHash128AndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetHash128(this T property, Hash128 value) + where T : SerializedProperty + { + property.hash128Value = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetHash128AndApply(this T property, Hash128 value) + where T : SerializedProperty + { + return property.SetHash128(value).ApplyModifiedProperties(); + } + #endregion + + #region Vector4 + /// + public static T SetValue(this T property, Vector4 value) + where T : SerializedProperty + { + return property.SetVector4(value); + } + + /// + public static T SetValueAndApply(this T property, Vector4 value) + where T : SerializedProperty + { + return property.SetVector4AndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetVector4(this T property, Vector4 value) + where T : SerializedProperty + { + property.vector4Value = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetVector4AndApply(this T property, Vector4 value) + where T : SerializedProperty + { + return property.SetVector4(value).ApplyModifiedProperties(); + } + #endregion + + #region Vector3 + /// + public static T SetValue(this T property, Vector3 value) + where T : SerializedProperty + { + return property.SetVector3(value); + } + + /// + public static T SetValueAndApply(this T property, Vector3 value) + where T : SerializedProperty + { + return property.SetVector3AndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetVector3(this T property, Vector3 value) + where T : SerializedProperty + { + property.vector3Value = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetVector3AndApply(this T property, Vector3 value) + where T : SerializedProperty + { + return property.SetVector3(value).ApplyModifiedProperties(); + } + #endregion + + #region Vector3Int + /// + public static T SetValue(this T property, Vector3Int value) + where T : SerializedProperty + { + return property.SetVector3Int(value); + } + + /// + public static T SetValueAndApply(this T property, Vector3Int value) + where T : SerializedProperty + { + return property.SetVector3IntAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetVector3Int(this T property, Vector3Int value) + where T : SerializedProperty + { + property.vector3IntValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetVector3IntAndApply(this T property, Vector3Int value) + where T : SerializedProperty + { + return property.SetVector3Int(value).ApplyModifiedProperties(); + } + #endregion + + #region Vector2 + /// + public static T SetValue(this T property, Vector2 value) + where T : SerializedProperty + { + return property.SetVector2(value); + } + + /// + public static T SetValueAndApply(this T property, Vector2 value) + where T : SerializedProperty + { + return property.SetVector2AndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetVector2(this T property, Vector2 value) + where T : SerializedProperty + { + property.vector2Value = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetVector2AndApply(this T property, Vector2 value) + where T : SerializedProperty + { + return property.SetVector2(value).ApplyModifiedProperties(); + } + #endregion + + #region Vector2Int + /// + public static T SetValue(this T property, Vector2Int value) + where T : SerializedProperty + { + return property.SetVector2Int(value); + } + + /// + public static T SetValueAndApply(this T property, Vector2Int value) + where T : SerializedProperty + { + return property.SetVector2IntAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetVector2Int(this T property, Vector2Int value) + where T : SerializedProperty + { + property.vector2Value = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetVector2IntAndApply(this T property, Vector2Int value) + where T : SerializedProperty + { + return property.SetVector2Int(value).ApplyModifiedProperties(); + } + #endregion + + #region Quaternion + /// + public static T SetValue(this T property, Quaternion value) + where T : SerializedProperty + { + return property.SetQuaternion(value); + } + + /// + public static T SetValueAndApply(this T property, Quaternion value) + where T : SerializedProperty + { + return property.SetQuaternionAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetQuaternion(this T property, Quaternion value) + where T : SerializedProperty + { + property.quaternionValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetQuaternionAndApply(this T property, Quaternion value) + where T : SerializedProperty + { + return property.SetQuaternion(value).ApplyModifiedProperties(); + } + #endregion + + #region String + /// + public static T SetValue(this T property, string value) + where T : SerializedProperty + { + return property.SetString(value); + } + + /// + public static T SetValueAndApply(this T property, string value) + where T : SerializedProperty + { + return property.SetStringAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetString(this T property, string value) + where T : SerializedProperty + { + property.stringValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetStringAndApply(this T property, string value) + where T : SerializedProperty + { + return property.SetString(value).ApplyModifiedProperties(); + } + #endregion + + #region AnimationCurve + /// + public static T SetValue(this T property, AnimationCurve value) + where T : SerializedProperty + { + return property.SetAnimationCurve(value); + } + + /// + public static T SetValueAndApply(this T property, AnimationCurve value) + where T : SerializedProperty + { + return property.SetAnimationCurveAndApply(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetAnimationCurve(this T property, AnimationCurve value) + where T : SerializedProperty + { + property.animationCurveValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetAnimationCurveAndApply(this T property, AnimationCurve value) + where T : SerializedProperty + { + return property.SetAnimationCurve(value).ApplyModifiedProperties(); + } + #endregion + + #region ArraySize + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target array property. + /// New array size. + /// The same instance. + public static T SetArraySize(this T property, int size) + where T : SerializedProperty + { + property.arraySize = size; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetArraySizeAndApply(this T property, int size) + where T : SerializedProperty + { + return property.SetArraySize(size).ApplyModifiedProperties(); + } + + /// + /// Increases by and returns the property for chaining. + /// + /// Concrete type. + /// Target array property. + /// Amount to add to the current array size. + /// The same instance. + public static T AddArraySize(this T property, int value = 1) + where T : SerializedProperty + { + return SetArraySize(property, size: property.arraySize + value); + } + + /// + /// Increases by then applies modified properties. + /// + /// + public static T AddArraySizeAndApply(this T property, int value = 1) + where T : SerializedProperty + { + return SetArraySizeAndApply(property, size: property.arraySize + value); + } + + /// + /// Decreases by and returns the property for chaining. + /// + /// Concrete type. + /// Target array property. + /// Amount to subtract from the current array size. + /// The same instance. + public static T RemoveArraySize(this T property, int value = 1) + where T : SerializedProperty + { + return SetArraySize(property, size: property.arraySize - value); + } + + /// + /// Decreases by then applies modified properties. + /// + /// + public static T RemoveArraySizeAndApply(this T property, int value = 1) + where T : SerializedProperty + { + return SetArraySizeAndApply(property, size: property.arraySize - value); + } + #endregion + + #region ManagedReference + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property (must be a [SerializeReference] field). + /// Managed reference value to assign. + /// The same instance. + public static T SetManagedReference(this T property, object value) + where T : SerializedProperty + { + property.managedReferenceValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetManagedReferenceAndApply(this T property, object value) + where T : SerializedProperty + { + return property.SetManagedReference(value).ApplyModifiedProperties(); + } + #endregion + + #region ObjectReference + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// reference to assign. + /// The same instance. + public static T SetObjectReference(this T property, Object value) + where T : SerializedProperty + { + property.objectReferenceValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetObjectReferenceAndApply(this T property, Object value) + where T : SerializedProperty + { + return property.SetObjectReference(value).ApplyModifiedProperties(); + } + #endregion + + #region ExposedReference + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// exposed reference to assign. + /// The same instance. + public static T SetExposedReference(this T property, Object value) + where T : SerializedProperty + { + property.exposedReferenceValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetExposedReferenceAndApply(this T property, Object value) + where T : SerializedProperty + { + return property.SetExposedReference(value).ApplyModifiedProperties(); + } + #endregion + +#if UNITY_6000_0_OR_NEWER + #region Boxed + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Boxed value to assign. + /// The same instance. + public static T SetBoxed(this T property, object value) + where T : SerializedProperty + { + property.boxedValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetBoxedAndApply(this T property, object value) + where T : SerializedProperty + { + return property.SetBoxed(value).ApplyModifiedProperties(); + } + #endregion +#endif + +#if UNITY_6000_2_OR_NEWER + #region EntityId + /// + public static T SetValue(this T property, EntityId value) + where T : SerializedProperty + { + return property.SetEntityId(value); + } + + /// + public static T SetValueAndApply(this T property, EntityId value) + where T : SerializedProperty + { + return property.SetEntityId(value); + } + + /// + /// Sets and returns the property for chaining. + /// + /// Concrete type. + /// Target property. + /// Value to assign. + /// The same instance. + public static T SetEntityId(this T property, EntityId value) + where T : SerializedProperty + { + property.entityIdValue = value; + return property; + } + + /// + /// Sets then applies modified properties. + /// + /// + public static T SetEntityIdApply(this T property, EntityId value) + where T : SerializedProperty + { + return property.SetEntityId(value).ApplyModifiedProperties(); + } + #endregion +#endif + } +} \ No newline at end of file diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties/SerializePropertyExtensions.SetValue.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.SetValue.cs.meta similarity index 78% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties/SerializePropertyExtensions.SetValue.cs.meta rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.SetValue.cs.meta index 6a8e63d..e409c89 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties/SerializePropertyExtensions.SetValue.cs.meta +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.SetValue.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/SerializedProperties/SerializePropertyExtensions.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.cs new file mode 100644 index 0000000..ebe826c --- /dev/null +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.cs @@ -0,0 +1,51 @@ +using UnityEditor; + +// ReSharper disable once CheckNamespace +namespace Aspid.FastTools.Editors +{ + /// + /// Fluent extension methods for providing chainable wrappers + /// around synchronization and typed value setters. + /// + public static partial class SerializePropertyExtensions + { + /// + /// Calls on the property's serialized object and returns the property for chaining. + /// + /// Concrete type. + /// The property whose serialized object should be updated. + /// The same instance. + public static T Update(this T property) + where T : SerializedProperty + { + property.serializedObject.Update(); + return property; + } + + /// + /// Calls on the property's serialized object and returns the property for chaining. + /// + /// Concrete type. + /// The property whose serialized object should be conditionally updated. + /// The same instance. + public static T UpdateIfRequiredOrScript(this T property) + where T : SerializedProperty + { + property.serializedObject.UpdateIfRequiredOrScript(); + return property; + } + + /// + /// Calls on the property's serialized object and returns the property for chaining. + /// + /// Concrete type. + /// The property whose serialized object changes should be applied. + /// The same instance. + public static T ApplyModifiedProperties(this T property) + where T : SerializedProperty + { + property.serializedObject.ApplyModifiedProperties(); + return property; + } + } +} \ No newline at end of file diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties/SerializePropertyExtensions.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.cs.meta similarity index 78% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties/SerializePropertyExtensions.cs.meta rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.cs.meta index 23676da..6fb27c1 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/SerializedProperties/SerializePropertyExtensions.cs.meta +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/SerializedProperties/SerializePropertyExtensions.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/Types.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types.meta similarity index 100% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types.meta rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types.meta diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/ComponentTypeSelectorPropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/ComponentTypeSelectorPropertyDrawer.cs new file mode 100644 index 0000000..228df96 --- /dev/null +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/ComponentTypeSelectorPropertyDrawer.cs @@ -0,0 +1,79 @@ +using System; +using UnityEngine; +using UnityEditor; +using UnityEngine.UIElements; +using Aspid.FastTools.Editors; + +// ReSharper disable once CheckNamespace +namespace Aspid.FastTools.Types +{ + [CustomPropertyDrawer(typeof(ComponentTypeSelector))] + internal sealed class ComponentTypeSelectorPropertyDrawer : PropertyDrawer + { + private const string StyleSheetPath = "Styles/Aspid-FastTools-ComponentTypeSelector"; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var currentType = property.serializedObject.targetObject.GetType(); + var buttonRow = new Rect(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + + if (GUI.Button(buttonRow, new GUIContent(currentType.Name), EditorStyles.popup)) + { + TypeSelectorWindow.Show + ( + GUIUtility.GUIToScreenRect(buttonRow), + types: new[] { fieldInfo.DeclaringType }, + currentType.AssemblyQualifiedName, + onSelected: aqn => OnTypeSelected(property, aqn, currentType) + ); + } + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) => + EditorGUIUtility.singleLineHeight; + + public override VisualElement CreatePropertyGUI(SerializedProperty property) + { + var currentType = property.serializedObject.targetObject.GetType(); + + var button = new Button() + .SetText(currentType.Name) + .AddStyleSheetsFromResource(StyleSheetPath); + + button.clicked += () => + { + var window = EditorWindow.focusedWindow; + var worldBound = button.worldBound; + var screenRect = new Rect(window.position.x + worldBound.xMin, window.position.y + worldBound.yMin, worldBound.width, worldBound.height); + var currentT = property.serializedObject.targetObject.GetType(); + + TypeSelectorWindow.Show + ( + screenRect: screenRect, + types: new[] { fieldInfo.DeclaringType }, + currentAqn: currentT.AssemblyQualifiedName, + onSelected: aqn => OnTypeSelected(property, aqn, currentT) + ); + }; + + return button; + } + + private static void OnTypeSelected(SerializedProperty property, string aqn, Type currentType) + { + var newType = Type.GetType(aqn); + if (newType is null || newType == currentType) return; + + var script = newType.FindMonoScript(); + + if (script is null) + { + Debug.LogWarning($"[SubclassDropdown] MonoScript not found for type: {aqn}"); + return; + } + + EditorApplication.delayCall += () => + property.serializedObject.FindProperty("m_Script").SetObjectReferenceAndApply(script); + } + } +} \ No newline at end of file diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/ComponentTypeSelectorPropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/ComponentTypeSelectorPropertyDrawer.cs.meta new file mode 100644 index 0000000..7811ba8 --- /dev/null +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/ComponentTypeSelectorPropertyDrawer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 00e5268f974244f24aa3a35526d3a1b6 +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/Types/SerializableTypeDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeDrawer.cs new file mode 100644 index 0000000..1aae5a8 --- /dev/null +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeDrawer.cs @@ -0,0 +1,178 @@ +using System; +using UnityEditor; +using UnityEngine; +using UnityEditor.UIElements; +using UnityEngine.UIElements; + +// ReSharper disable once CheckNamespace +namespace Aspid.FastTools.Editors +{ + internal static class SerializableTypeDrawer + { + private const string NoneOption = ""; + private const string MissingOption = ""; + + private const string OpenButtonText = "Open"; + + private const string StyleSheetPath = "Styles/Aspid-FastTools-SerializableType"; + private const string RootClass = "aspid-fasttools-serializable-type"; + private const string ButtonsClass = "aspid-fasttools-serializable-type-buttons"; + private const string OpenButtonClass = "aspid-fasttools-serializable-type-open-button"; + + internal static void DrawIMGUI( + Rect position, + SerializedProperty property, + GUIContent label, + Type[] types, + bool allowAbstract, + bool allowInterfaces) + { + const float openButtonWidth = 50f; + + if (!string.IsNullOrWhiteSpace(label.text)) + { + EditorGUI.LabelField(position, label); + position.x += EditorGUIUtility.labelWidth; + position.width -= EditorGUIUtility.labelWidth; + } + + var dropdownRect = position; + var currentType = GetType(property.stringValue); + var hasValidType = currentType is not null; + + if (hasValidType) + dropdownRect.width -= openButtonWidth + 2f; + + var caption = GetCaption(property.stringValue); + if (EditorGUI.DropdownButton(dropdownRect, new GUIContent(caption), FocusType.Passive)) + { + var current = property.stringValue ?? string.Empty; + var screenPosition = GUIUtility.GUIToScreenPoint(new Vector2(dropdownRect.x, dropdownRect.y)); + var screenRect = new Rect(screenPosition.x, screenPosition.y, dropdownRect.width, dropdownRect.height); + + TypeSelectorWindow.Show( + screenRect: screenRect, + types: types, + currentAqn: current, + allowAbstract: allowAbstract, + allowInterface: allowInterfaces, + onSelected: assemblyQualifiedName => + { + property.SetStringAndApply(assemblyQualifiedName ?? string.Empty); + }); + } + + if (!hasValidType) return; + + var openButtonRect = new Rect(dropdownRect.xMax + 2f, position.y, openButtonWidth, position.height); + if (GUI.Button(openButtonRect, OpenButtonText)) + OpenScript(currentType); + } + + internal static VisualElement DrawUIToolkit( + SerializedProperty property, + string label, + Type[] types, + bool allowAbstract, + bool allowInterfaces) + { + var typeSelector = new VisualElement() + .AddClass(RootClass) + .AddStyleSheetsFromResource(StyleSheetPath) + .AddChild(new PropertyField(property).SetDisplay(DisplayStyle.None)); + + var button = new Button() + .SetText(GetCaption(property.stringValue)) + .SetTooltip(GetTooltip(property.stringValue)); + + var propertyPath = property.propertyPath; + var serializedObject = property.serializedObject; + + var openButton = new Button() + .SetText(OpenButtonText) + .SetDisplay(DisplayStyle.None) + .AddClass(OpenButtonClass); + + button.clicked += () => + { + var window = EditorWindow.focusedWindow; + var worldBound = button.worldBound; + var screenRect = new Rect(window.position.x + worldBound.xMin, window.position.y + worldBound.yMin, worldBound.width, worldBound.height); + + var current = serializedObject.FindProperty(propertyPath).stringValue ?? string.Empty; + + TypeSelectorWindow.Show( + screenRect: screenRect, + types: types, + currentAqn: current, + allowAbstract: allowAbstract, + allowInterface: allowInterfaces, + onSelected: assemblyQualifiedName => + { + var currentProperty = serializedObject.FindProperty(propertyPath); + currentProperty.SetStringAndApply(assemblyQualifiedName ?? string.Empty); + + button + .SetText(GetCaption(currentProperty.stringValue)) + .SetTooltip(GetTooltip(currentProperty.stringValue)); + + UpdateOpenButtonVisibility(openButton, currentProperty.stringValue); + }); + }; + + openButton.clicked += () => + { + var currentProperty = serializedObject.FindProperty(propertyPath); + var currentType = GetType(currentProperty.stringValue); + + if (currentType is not null) + OpenScript(currentType); + }; + + if (!string.IsNullOrEmpty(label)) + { + typeSelector.AddChild(new Label(label)); + } + + var buttons = new VisualElement() + .AddChild(button) + .AddChild(openButton) + .AddClass(ButtonsClass); + + UpdateOpenButtonVisibility(openButton, property.stringValue); + return typeSelector.AddChild(buttons); + } + + private static void UpdateOpenButtonVisibility(Button openButton, string assemblyQualifiedName) + { + var hasValidType = !string.IsNullOrWhiteSpace(assemblyQualifiedName) && GetType(assemblyQualifiedName) is not null; + openButton.SetDisplay(hasValidType ? DisplayStyle.Flex : DisplayStyle.None); + } + + private static void OpenScript(Type type) + { + var (monoScript, lineNumber) = type.FindMonoScriptWithLine(); + + if (monoScript is not null) + AssetDatabase.OpenAsset(monoScript, lineNumber); + } + + private static string GetTooltip(string assemblyQualifiedName) + { + // TODO Aspid.FastTools – Add Tooltip for missing types + var type = GetType(assemblyQualifiedName); + return type is null ? string.Empty : type.FullName; + } + + private static string GetCaption(string assemblyQualifiedName) + { + if (string.IsNullOrEmpty(assemblyQualifiedName)) return NoneOption; + + var type = GetType(assemblyQualifiedName); + return type is null ? MissingOption : type.Name; + } + + private static Type GetType(string assemblyQualifiedName) => + Type.GetType(assemblyQualifiedName, throwOnError: false); + } +} \ No newline at end of file diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeDrawer.cs.meta similarity index 78% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeDrawer.cs.meta rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeDrawer.cs.meta index 6ac8f8a..b50a17a 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeDrawer.cs.meta +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeDrawer.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/Types/SerializableTypePropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypePropertyDrawer.cs similarity index 68% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypePropertyDrawer.cs rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypePropertyDrawer.cs index bd97e31..4720ad1 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypePropertyDrawer.cs +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypePropertyDrawer.cs @@ -4,7 +4,7 @@ using UnityEngine.UIElements; // ReSharper disable once CheckNamespace -namespace Aspid.UnityFastTools +namespace Aspid.FastTools.Editors { [CustomPropertyDrawer(typeof(SerializableType))] [CustomPropertyDrawer(typeof(SerializableType<>))] @@ -13,13 +13,26 @@ internal sealed class SerializableTypePropertyDrawer : PropertyDrawer public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { var type = GetTypeFromFieldType(); - SerializableTypeDrawer.DrawIMGUI(position, GetProperty(property), label, type); + + SerializableTypeDrawer.DrawIMGUI( + position, + GetProperty(property), + label, + new[] { type }, + true, + true); } public override VisualElement CreatePropertyGUI(SerializedProperty property) { var type = GetTypeFromFieldType(); - return SerializableTypeDrawer.DrawUIToolkit(GetProperty(property), preferredLabel, type); + + return SerializableTypeDrawer.DrawUIToolkit( + GetProperty(property), + preferredLabel, + new[] { type }, + true, + true); } private static SerializedProperty GetProperty(SerializedProperty property) => diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypePropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypePropertyDrawer.cs.meta similarity index 78% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypePropertyDrawer.cs.meta rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypePropertyDrawer.cs.meta index 231aad9..49df607 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypePropertyDrawer.cs.meta +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypePropertyDrawer.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/Types/SerializableTypeUtility.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeUtility.cs similarity index 86% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeUtility.cs rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeUtility.cs index 7bc9f99..c53a238 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeUtility.cs +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeUtility.cs @@ -3,11 +3,11 @@ using System.Collections.Generic; // ReSharper disable once CheckNamespace -namespace Aspid.UnityFastTools +namespace Aspid.FastTools.Editors { internal static class SerializableTypeUtility { - internal static Type GetGenericArgumentFromFieldType(FieldInfo field, out bool isGeneric) + public static Type GetGenericArgumentFromFieldType(FieldInfo field, out bool isGeneric) { var type = field.FieldType; diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeUtility.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeUtility.cs.meta similarity index 78% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeUtility.cs.meta rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeUtility.cs.meta index 38dc2e4..9ec1ed5 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeUtility.cs.meta +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/SerializableTypeUtility.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/Types/TypeSelectorPropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorPropertyDrawer.cs new file mode 100644 index 0000000..b0d9160 --- /dev/null +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorPropertyDrawer.cs @@ -0,0 +1,135 @@ +using System; +using System.Linq; +using UnityEditor; +using UnityEngine; +using System.Reflection; +using UnityEngine.UIElements; +using System.Collections.Generic; + +// ReSharper disable once CheckNamespace +namespace Aspid.FastTools.Editors +{ + [CustomPropertyDrawer(typeof(TypeSelectorAttribute))] + internal sealed class TypeSelectorPropertyDrawer : PropertyDrawer + { + private const BindingFlags BindingAttr = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly; + + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) + { + var typeSelectorAttribute = (TypeSelectorAttribute)attribute; + var types = GetTypesFromAttribute(typeSelectorAttribute, property); + + SerializableTypeDrawer.DrawIMGUI( + position, + property, + label, + types, + typeSelectorAttribute.AllowAbstractTypes, + typeSelectorAttribute.AllowInterfaces); + } + + public override VisualElement CreatePropertyGUI(SerializedProperty property) + { + var typeSelectorAttribute = (TypeSelectorAttribute)attribute; + var types = GetTypesFromAttribute(typeSelectorAttribute, property); + + return SerializableTypeDrawer.DrawUIToolkit( + property, + preferredLabel, + types, + typeSelectorAttribute.AllowAbstractTypes, + typeSelectorAttribute.AllowInterfaces); + } + + private static Type[] GetTypesFromAttribute(TypeSelectorAttribute typeSelectorAttribute, SerializedProperty property) + { + var assemblyQualifiedNames = typeSelectorAttribute.AssemblyQualifiedNames + .Where(assemblyQualifiedName => !string.IsNullOrWhiteSpace(assemblyQualifiedName)) + .ToArray(); + + if (assemblyQualifiedNames.Length is 0) + return Array.Empty(); + + var targetObject = property.serializedObject.targetObject; + var targetType = targetObject.GetType(); + var types = new List(); + + foreach (var name in assemblyQualifiedNames) + { + var member = GetMemberFromHierarchy(targetType, name); + + if (member is not null) + { + AddTypesFromMember(types, member, targetObject); + } + else + { + var type = Type.GetType(name, throwOnError: false); + + if (type is not null) + types.Add(type); + } + } + + return types.ToArray(); + } + + private static MemberInfo GetMemberFromHierarchy(Type type, string memberName) + { + var currentType = type; + while (currentType is not null) + { + var members = currentType.GetMember(memberName, BindingAttr); + if (members.Length > 0) + return members[0]; + + currentType = currentType.BaseType; + } + return null; + } + + private static void AddTypesFromMember(List types, MemberInfo member, object targetObject) + { + var value = member switch + { + FieldInfo fieldInfo => fieldInfo.GetValue(targetObject), + PropertyInfo propertyInfo => propertyInfo.GetValue(targetObject), + _ => null + }; + + switch (value) + { + case null: return; + + case Type type: + types.Add(type); + return; + + case Type[] typeArray: + types.AddRange(typeArray); + return; + + case string assemblyQualifiedName: + { + var type = Type.GetType(assemblyQualifiedName, throwOnError: false); + if (type is not null) + types.Add(type); + return; + } + + case string[] assemblyQualifiedNames: + { + foreach (var assemblyQualifiedName in assemblyQualifiedNames) + { + if (string.IsNullOrWhiteSpace(assemblyQualifiedName)) continue; + + var type = Type.GetType(assemblyQualifiedName, throwOnError: false); + if (type is not null) + types.Add(type); + } + return; + } + } + } + } +} \ No newline at end of file diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/TypeSelectorPropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorPropertyDrawer.cs.meta similarity index 78% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/TypeSelectorPropertyDrawer.cs.meta rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorPropertyDrawer.cs.meta index 089036f..cc3ae52 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/TypeSelectorPropertyDrawer.cs.meta +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorPropertyDrawer.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/Types/TypeSelectorWindow.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorWindow.cs similarity index 80% rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/TypeSelectorWindow.cs rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorWindow.cs index f90c513..1f3ef2e 100644 --- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/TypeSelectorWindow.cs +++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Types/TypeSelectorWindow.cs @@ -8,15 +8,24 @@ using System.Runtime.CompilerServices; // ReSharper disable once CheckNamespace -namespace Aspid.UnityFastTools +namespace Aspid.FastTools.Editors { - // TODO Aspid.UnityFastTools - Refactor - // TODO Aspid.UnityFastTools - Write summary + /// + /// Editor window that displays a hierarchical type selector dropdown, allowing the user to browse and select a from a filtered list. + /// public sealed class TypeSelectorWindow : EditorWindow { private const string NoneOption = ""; private const string GlobalNamespace = ""; + private const string StylesheetPath = "Styles/Aspid-FastTools-TypeSelectorWindow"; + private const string ContainerClass = "aspid-fasttools-type-selector-container"; + private const string HeaderClass = "aspid-fasttools-type-selector-header"; + + private const string ItemClass = "aspid-fasttools-type-selector-item"; + private const string ItemTitleClass = "aspid-fasttools-type-selector-item-title"; + private const string ItemArrowClass = "aspid-fasttools-type-selector-item-arrow"; + private Label _titleLabel; private Button _backButton; private ListView _listView; @@ -26,21 +35,48 @@ public sealed class TypeSelectorWindow : EditorWindow private Action _onSelected; private string _currentAqn = string.Empty; - public static void Show(Type type, Rect screenRect, string currentAqn, Action onSelected) + /// + /// Opens the type selector window as a dropdown anchored to . + /// + /// The screen-space rectangle the dropdown is anchored to. + /// Base types used to filter which concrete types are shown. Only types assignable to all entries are listed. + /// Assembly-qualified name of the currently selected type, used to pre-navigate to that type's location. Pass null or empty to start at the root. + /// Whether abstract types are included in the list. Defaults to false. + /// Whether interface types are included in the list. Defaults to false. + /// Callback invoked with the assembly-qualified name of the selected type, or null if the user chose <None>. + public static void Show( + Rect screenRect, + Type[] types = null, + string currentAqn = "", + bool allowAbstract = false, + bool allowInterface = false, + Action onSelected = null) { var window = CreateInstance(); - window.Initialize(type, screenRect, currentAqn, onSelected); + window.Initialize( + screenRect, + types, + currentAqn, + allowAbstract, + allowInterface, + onSelected); } #region Initialization - private void Initialize(Type type, Rect screenRect, string currentAqn, Action onSelected) + private void Initialize( + Rect screenRect, + Type[] types, + string currentAqn, + bool allowAbstract, + bool allowInterface, + Action onSelected) { _onSelected = onSelected; _currentAqn = currentAqn ?? string.Empty; BuildUI(); - - var hierarchy = HierarchyBuilder.Build(type); + + var hierarchy = HierarchyBuilder.Build(types, allowAbstract, allowInterface); InitializeNavigation(hierarchy, _currentAqn); RefreshView(); @@ -53,60 +89,33 @@ private void Initialize(Type type, Rect screenRect, string currentAqn, Action(HandleKeyDown, TrickleDown.TrickleDown); + _searchField = CreateSearchField(); + _listView = CreateListView(); - return root; - } - + rootVisualElement + .AddStyleSheetsFromResource(StylesheetPath) + .AddClass(ContainerClass) + .AddChild(CreateHeader()) + .AddChild(_searchField) + .AddChild(_listView); + + rootVisualElement.RegisterCallback(HandleKeyDown, TrickleDown.TrickleDown); + return; + VisualElement CreateHeader() { - _backButton = new Button(NavigateBack) - .SetText("←") - .SetMargin(right: 4) - .SetSize(width: 26, height: 20) - .SetBackgroundColor(Color.clear) - .SetBorderWidth(top: 0, bottom: 0, left: 0, right: 0); - - _titleLabel = new Label("Select ViewModel") - .SetFlexGrow(1) - .SetUnityFontStyleAndWeight(FontStyle.Bold); + _titleLabel = new Label(string.Empty); + _backButton = new Button(NavigateBack).SetText("←"); return new VisualElement() - .SetSize(height: 20) - .SetMinSize(height: 20) - .SetMargin(top: -3, left: -3, right: -3) - .SetBackgroundColor(new Color(0.149f, 0.149f, 0.149f)) - .SetAlignItems(Align.Center) - .SetFlexDirection(FlexDirection.Row) - .SetMargin(bottom: 4) + .AddClass(HeaderClass) .AddChild(_backButton) .AddChild(_titleLabel); } ToolbarSearchField CreateSearchField() { - var field = new ToolbarSearchField() - .SetMargin(bottom: 4) - .SetPadding(right: 4) - .SetSize(width: Length.Auto()); + var field = new ToolbarSearchField(); field.RegisterValueChangedCallback(e => HandleSearchChanged(e.newValue ?? string.Empty)); field.RegisterCallback(e => @@ -136,20 +145,15 @@ ListView CreateListView() VisualElement CreateListItem() { var label = new Label() - .SetName("title") - .SetFlexGrow(1); + .AddClass(ItemTitleClass); var arrow = new Label("›") - .SetName("arrow"); - arrow.style.opacity = 0.6f; + .AddClass(ItemArrowClass); return new VisualElement() + .AddClass(ItemClass) .AddChild(label) - .AddChild(arrow) - .SetSize(height: 20) - .SetAlignItems(Align.Center) - .SetPadding(left: 6, right: 6) - .SetFlexDirection(FlexDirection.Row); + .AddChild(arrow); } void BindListItem(VisualElement element, int index) @@ -160,11 +164,11 @@ void BindListItem(VisualElement element, int index) if (index < 0 || index >= items.Count) return; var node = items[index]; - element.Q