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/agents/uss-bem-checker.md b/.claude/agents/uss-bem-checker.md
new file mode 100644
index 0000000..68d6283
--- /dev/null
+++ b/.claude/agents/uss-bem-checker.md
@@ -0,0 +1,80 @@
+---
+name: uss-bem-checker
+description: Reviews USS stylesheets and the C# strings that reference them against the Aspid.FastTools BEM grammar (class names) and the positional grammar (custom properties). Use after edits to any *.uss file or to any code holding USS class names / `--aspid-*` variables (Constants.cs, AspidStyles.cs, component .cs files).
+---
+
+You are a strict reviewer of UIToolkit USS conventions for the **Aspid.FastTools** Unity package. Both grammars below are mandatory and documented in the project root `CLAUDE.md`. Your only job is to verify that every USS class name and every custom property follows them, and to flag legacy forms.
+
+## Scope
+
+Files to review (only what was changed unless the user widens the scope):
+
+- `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/**/*.uss`
+- C# files that emit class strings or read custom properties:
+ - `Unity/Editor/Scripts/Ids/Constants.cs` (`Constants.Drawer.*`, `Constants.Registry.*`, `Constants.Selector.*`)
+ - `Unity/Editor/Scripts/VisualElements/Internal/Styles/AspidStyles.cs`
+ - Component `.cs` under `Unity/Editor/Scripts/VisualElements/Internal/Components/**/`
+- Anywhere a literal `aspid-fasttools-...` or `--aspid-...` appears.
+
+## Grammar #1 — USS class names (BEM)
+
+Format: `aspid-fasttools-{block}[__{element}][--{modifier}]`
+
+Rules to enforce:
+
+1. The prefix `aspid-fasttools-` is mandatory and joined to the block by a single `-`.
+2. Block — kebab-case (`id-registry`, `enum-values`, `serializable-type`).
+3. Element — joined to block with `__` (double underscore): `aspid-fasttools-id-drawer__add-button`.
+4. Modifier — joined with `--` (double dash): `aspid-fasttools-id-registry__warning--visible`, `aspid-fasttools-status--error`.
+5. Inside any segment use kebab-case only — never `camelCase`, never single `_`.
+6. Utility/state classes (`status`, `theme`) are blocks of their own: `aspid-fasttools-status--error`, `aspid-fasttools-theme--dark`.
+
+**Legacy form to flag and propose migrating:** classes that use a single `-` between block and element instead of `__` (e.g. `aspid-fasttools-id-drawer-add-button`). The CLAUDE.md says: migrate when touching surrounding code; new classes must follow BEM from the start. So:
+- If the diff *adds* a non-BEM class → reject.
+- If the diff *modifies code around* a legacy class → suggest migrating it as part of the change, but don't block.
+
+## Grammar #2 — USS custom properties (positional)
+
+Format: `--{prefix}-{group}-{role}[-{state}][-{tone}]`
+
+Rules to enforce:
+
+| Slot | Allowed values | Required |
+|---|---|---|
+| `prefix` | `aspid` (palette shared between Aspid packages) or `aspid-fasttools` (product-specific) | yes |
+| `group` | `colors`, `icons`, `metrics`, `prop` | yes |
+| `role` | `bg`, `shade`, `text`, `border`, `icon`, `status`, `gradient`, `label_size`, `line_size`, `theme`, … | yes |
+| `state` | `success`, `warning`, `error`, `info`, `hover`, `pressed`, … | optional |
+| `tone` | `darkness`, `dark`, `light`, `lightness` | optional |
+
+Additional rules:
+
+1. Slot separator is `-`. Compound words **inside one slot** use `_` (e.g. `label_size`, `line_size`) — never two independent concepts in one slot.
+2. Order is **state → tone**: `--aspid-colors-status-success-darkness`, never `darkness-success`.
+3. Color roles:
+ - `bg` — surface palette.
+ - `shade` — generic content palette (text/border/icon-tint share the same shade swatch when not specialised).
+ - `text` / `border` / `icon` — specialised, component-local roles.
+ - `status` — `success` / `warning` / `error` / `info`.
+4. `prop` group is for inline component parameters (e.g. `--aspid-fasttools-prop-theme`), not palette tokens.
+5. Palette variables are declared on `:root`. Component-scoped variables on the component's selector.
+
+The reference implementation is `Aspid-FastTools-Default-Dark.uss` — palette tokens there are the source of truth.
+
+## How to review
+
+For each USS file or code string in scope:
+
+1. Extract every class name (`.aspid-fasttools-...`) and every custom property (`--aspid-...`).
+2. For each, validate against the matching grammar above.
+3. Categorise findings as:
+ - **Block** — adds a new non-conforming name. Must be fixed before merge.
+ - **Migrate** — touches surrounding code that already contains a legacy form. Suggest the rewrite, don't block.
+ - **OK** — conforming.
+4. Report concisely:
+ - File path + line.
+ - The offending name.
+ - Which rule it breaks.
+ - The corrected form.
+
+Do not propose stylistic changes (colors, spacing, ordering). Stay narrowly inside the two grammars and the legacy-migration rule. If the change does not touch USS classes or custom properties, return "No USS naming issues found." in one line.
diff --git a/.claude/hooks/rebuild-generators-on-change.sh b/.claude/hooks/rebuild-generators-on-change.sh
new file mode 100755
index 0000000..ae0c9e8
--- /dev/null
+++ b/.claude/hooks/rebuild-generators-on-change.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# PostToolUse hook: rebuild Roslyn source generators after edits inside the
+# main generator project, then redeploy the DLL into the Unity package.
+#
+# Path-scoped on purpose:
+# - Triggers ONLY for *.cs under Aspid.FastTools.Generators/Aspid.FastTools.Generators/
+# - Skips Unity-side edits (Aspid.FastTools/Assets/...), tests, and the Sample project.
+# - Skipping Unity edits matches the rule "do not run dotnet build for Unity-only edits".
+#
+# Build success -> exit 0 (silent).
+# Path mismatch -> exit 0 (silent).
+# Build failure -> exit 2 with stderr piped through, so the assistant sees it.
+
+set -uo pipefail
+
+file_path=$(jq -r '.tool_input.file_path // empty' 2>/dev/null)
+
+case "$file_path" in
+ */Aspid.FastTools.Generators/Aspid.FastTools.Generators/*.cs) ;;
+ *) exit 0 ;;
+esac
+
+cd "$CLAUDE_PROJECT_DIR" || exit 0
+
+dotnet build \
+ Aspid.FastTools.Generators/Aspid.FastTools.Generators/Aspid.FastTools.Generators.csproj \
+ -c Release --nologo -v quiet 1>&2 || exit 2
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..548c507
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,26 @@
+{
+ "extraKnownMarketplaces": {
+ "unicli": {
+ "source": {
+ "source": "github",
+ "repo": "yucchiy/UniCli"
+ }
+ }
+ },
+ "enabledPlugins": {
+ "unicli@unicli": true
+ },
+ "hooks": {
+ "PostToolUse": [
+ {
+ "matcher": "Edit|Write",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/rebuild-generators-on-change.sh\""
+ }
+ ]
+ }
+ ]
+ }
+}
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/.claude/skills/sync-readmes/SKILL.md b/.claude/skills/sync-readmes/SKILL.md
new file mode 100644
index 0000000..15dd420
--- /dev/null
+++ b/.claude/skills/sync-readmes/SKILL.md
@@ -0,0 +1,81 @@
+---
+name: sync-readmes
+description: Verify and update Aspid.FastTools README files against the actual codebase — namespaces, public API, CreateAssetMenu paths — keeping EN/RU and root/Documentation copies in sync
+user-invocable: true
+---
+
+The package ships **eight** README files that drift from the code easily. Use this skill whenever the user asks to "check / update / sync READMEs", or after any change that touches: namespaces of public types, public API surface, `[CreateAssetMenu]` paths, source generator output, or sample structure.
+
+## Files in scope
+
+**Main READMEs (mirror each other 1:1 except for image paths and one heading):**
+
+| Path | Locale | Image base path |
+|---|---|---|
+| `README.md` | EN | `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/` |
+| `README_RU.md` | RU | `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/` |
+| `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md` | EN | `Images/` |
+| `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md` | RU | `Images/` |
+
+The Documentation copies have an extra `## Source Code` / `## Исходный код` block linking to the GitHub repo — the root copies don't. Otherwise the body is identical character-for-character.
+
+**Sample READMEs (one EN + one RU per sample):**
+
+- `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples/Types/`
+- `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples/Ids/`
+- `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples/EnumValues/`
+- `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples/ProfilerMarkers/`
+- `Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples/VisualElements/`
+
+## Workflow
+
+### 1. Verify against source before editing
+
+For every fact the README states, prove it from the code:
+
+| Claim | Verify with |
+|---|---|
+| Namespace of a public type | `grep -rn "namespace " --include="*.cs"` then locate the file declaring the type |
+| `[CreateAssetMenu]` menu path | `grep -rn "CreateAssetMenu\|menuName" --include="*.cs"` |
+| Public method signature / return value | Read the source file directly; do not infer from name |
+| Generated code shape (`IdStructGenerator`, `ProfilerMarkersGenerator`) | Read `Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/.../*Body.cs` |
+| Class actually exists | `find … -name ".cs"` — not all helper classes documented in old READMEs still exist (e.g. there is **no** `AspidEditorGUILayout`) |
+
+Common drift points discovered historically:
+
+- **Namespaces split per feature.** Public types live in `Aspid.FastTools` (root: `IId`, `UniqueIdAttribute`, `StringIdRegistry`), `Aspid.FastTools.Types`, `Aspid.FastTools.Enums`, `Aspid.FastTools.Ids`, `Aspid.FastTools.UIElements`. Editor helpers split similarly: `Aspid.FastTools.Editors` for `SerializedProperty` extensions / IMGUI scopes / `GetScriptName`, but per-feature editor code lives in `Aspid.FastTools.{Feature}.Editors`. A `using Aspid.FastTools;` line in a `SerializableType` example is wrong — it must be `using Aspid.FastTools.Types;`.
+- **Two ID registries.** `StringIdRegistry` (in `Aspid.FastTools`) keeps int↔string at runtime; `IdRegistry` (in `Aspid.FastTools.Ids`) is int-only at runtime with names stripped from player builds. Don't conflate them. Their menu paths differ: `Aspid/FastTools/String Id Registry` vs `Aspid/FastTools/Id Registry`. `StringIdRegistry.GetId` returns `-1` (not `0`) when not found; the lookup-by-id method is `GetNameId(int)`, not `GetName(int)`. Neither registry exposes public `Add`, `Remove`, or `Rename` — those live behind the registry inspector / `RegistryEditorCore`.
+- **Sample asset menu order.** Samples use `Aspid/Samples/FastTools/` (Samples first), not `Aspid/FastTools/Samples/`. Always re-grep `[CreateAssetMenu]` instead of trusting the existing README.
+
+### 2. Apply edits to all matching files
+
+Most edits hit all four main READMEs (EN root, EN Documentation, RU root, RU Documentation). Apply the same change to each — they must stay textually identical inside their respective body except for the image paths and the `## Source Code` heading.
+
+For RU edits, follow the existing RU translation conventions in the file: `Namespace` → `Пространство имён`, `Description` → `Описание`, code identifiers and English technical terms like `runtime`, `partial struct`, `Inspector` stay in English.
+
+When updating sample READMEs, edit both the EN and RU copy in the same sample folder.
+
+### 3. Sanity-check after editing
+
+Run these commands from the repo root and skim the output:
+
+```bash
+# All using statements in samples — these are ground truth for namespaces
+grep -rn "^using Aspid" Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples --include="*.cs" | sort -u
+
+# All CreateAssetMenu paths in the package
+grep -rn "menuName" Aspid.FastTools/Assets/Plugins/Aspid/FastTools --include="*.cs"
+
+# Confirm both READMEs of a pair stay aligned (count sections)
+grep -c "^## " README.md Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md
+grep -c "^## " README_RU.md Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md
+```
+
+Then visually diff each pair of matched files (EN root vs EN Documentation; RU root vs RU Documentation) — only image paths and the `## Source Code` block should differ.
+
+## Arguments
+
+`$ARGUMENTS` (optional):
+- empty — full audit and update of all eight READMEs;
+- `--check` — audit only, report findings without editing;
+- a feature name (`ids`, `types`, `enums`, `visualelements`, `profilermarkers`, `imgui`, `serializedproperty`) — narrow the audit/update to that section.
diff --git a/.github/workflows/claude-code-review.yml b/.github/workflows/claude-code-review.yml
new file mode 100644
index 0000000..b5e8cfd
--- /dev/null
+++ b/.github/workflows/claude-code-review.yml
@@ -0,0 +1,44 @@
+name: Claude Code Review
+
+on:
+ pull_request:
+ types: [opened, synchronize, ready_for_review, reopened]
+ # Optional: Only run on specific file changes
+ # paths:
+ # - "src/**/*.ts"
+ # - "src/**/*.tsx"
+ # - "src/**/*.js"
+ # - "src/**/*.jsx"
+
+jobs:
+ claude-review:
+ # Optional: Filter by PR author
+ # if: |
+ # github.event.pull_request.user.login == 'external-contributor' ||
+ # github.event.pull_request.user.login == 'new-developer' ||
+ # github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
+
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+ id-token: write
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code Review
+ id: claude-review
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+ plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
+ plugins: 'code-review@claude-code-plugins'
+ prompt: '/code-review:code-review ${{ github.repository }}/pull/${{ github.event.pull_request.number }}'
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+
diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml
new file mode 100644
index 0000000..6b15fac
--- /dev/null
+++ b/.github/workflows/claude.yml
@@ -0,0 +1,50 @@
+name: Claude Code
+
+on:
+ issue_comment:
+ types: [created]
+ pull_request_review_comment:
+ types: [created]
+ issues:
+ types: [opened, assigned]
+ pull_request_review:
+ types: [submitted]
+
+jobs:
+ claude:
+ if: |
+ (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) ||
+ (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) ||
+ (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')))
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pull-requests: read
+ issues: read
+ id-token: write
+ actions: read # Required for Claude to read CI results on PRs
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 1
+
+ - name: Run Claude Code
+ id: claude
+ uses: anthropics/claude-code-action@v1
+ with:
+ claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
+
+ # This is an optional setting that allows Claude to read CI results on PRs
+ additional_permissions: |
+ actions: read
+
+ # Optional: Give a custom prompt to Claude. If this is not specified, Claude will perform the instructions specified in the comment that tagged it.
+ # prompt: 'Update the pull request description to include a summary of changes.'
+
+ # Optional: Add claude_args to customize behavior and configuration
+ # See https://github.com/anthropics/claude-code-action/blob/main/docs/usage.md
+ # or https://code.claude.com/docs/en/cli-reference for available options
+ # claude_args: '--allowed-tools Bash(gh pr *)'
+
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ca6f4bb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+.claude/settings.local.json
+.worktrees/
+
+# UPM convention: `Samples~` is hidden from Unity importer but must be tracked.
+# Override global `*~` ignore.
+!Samples~/
+!Samples~/**
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.FastTools.Generators/Aspid.FastTools.Generators.Tests/IdStructGeneratorTests.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IdStructGeneratorTests.cs
new file mode 100644
index 0000000..4f8fe98
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators.Tests/IdStructGeneratorTests.cs
@@ -0,0 +1,147 @@
+using Xunit;
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
+using Aspid.FastTools.Generators.IdStruct;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Generators.Tests
+{
+ public class IdStructGeneratorTests
+ {
+ private const string IIdDefinition = "namespace Aspid.FastTools.Ids { public interface IId { int Id { get; } } }";
+
+ private static GeneratorDriverRunResult RunGenerator(string source)
+ {
+ var syntaxTree = CSharpSyntaxTree.ParseText(source);
+ var iidTree = CSharpSyntaxTree.ParseText(IIdDefinition);
+
+ var references = new[]
+ {
+ MetadataReference.CreateFromFile(typeof(object).Assembly.Location)
+ };
+
+ var compilation = CSharpCompilation.Create("TestCompilation",
+ syntaxTrees: new[] { syntaxTree, iidTree },
+ references: references,
+ options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));
+
+ var generator = new IdStructGenerator();
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(generator);
+ driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _);
+
+ return driver.GetRunResult();
+ }
+
+ [Fact]
+ public void Test_Generator_DoesNotCrash_OnEmptySource()
+ {
+ var result = RunGenerator("namespace Test { }");
+
+ Assert.Empty(result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error));
+ Assert.Empty(result.Results[0].GeneratedSources);
+ }
+
+ [Fact]
+ public void Struct_WithIId_InNamespace_GeneratesIdFieldAndProperty()
+ {
+ const string source = @"
+namespace Sample
+{
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+}
+";
+ var result = RunGenerator(source);
+ var generated = result.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var source0 = generated[0];
+
+ Assert.EndsWith(".IId.g.cs", source0.HintName);
+ var text = source0.SourceText.ToString();
+ Assert.Contains("partial struct Foo", text);
+ Assert.Contains("private int _id;", text);
+ Assert.Contains("public int Id =>", text);
+ }
+
+ [Fact]
+ public void Struct_WithoutPartial_NotGenerated()
+ {
+ const string source = @"
+namespace Sample
+{
+ public struct Foo : global::Aspid.FastTools.Ids.IId
+ {
+ public int Id => 0;
+ }
+}
+";
+ var result = RunGenerator(source);
+ var generated = result.Results[0].GeneratedSources;
+
+ Assert.Empty(generated);
+ }
+
+ [Fact]
+ public void Struct_WithoutIId_NotGenerated()
+ {
+ const string source = @"
+namespace Sample
+{
+ public interface IMarker { }
+ public partial struct Foo : IMarker { }
+}
+";
+ var result = RunGenerator(source);
+ var generated = result.Results[0].GeneratedSources;
+
+ Assert.Empty(generated);
+ }
+
+ [Fact]
+ public void TwoStructsSameName_DifferentNamespaces_NoHintNameCollision()
+ {
+ const string source = @"
+namespace SampleA
+{
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+}
+
+namespace SampleB
+{
+ public partial struct Foo : global::Aspid.FastTools.Ids.IId { }
+}
+";
+ var result = RunGenerator(source);
+ var generated = result.Results[0].GeneratedSources;
+
+ Assert.Equal(2, generated.Length);
+
+ var hintNames = generated.Select(s => s.HintName).ToArray();
+ Assert.NotEqual(hintNames[0], hintNames[1]);
+ Assert.Contains(hintNames, h => h.Contains("SampleA"));
+ Assert.Contains(hintNames, h => h.Contains("SampleB"));
+ }
+
+ [Fact]
+ public void NestedStruct_WrappedInPartialOuterClass()
+ {
+ const string source = @"
+namespace Sample
+{
+ public partial class Outer
+ {
+ public partial struct Inner : global::Aspid.FastTools.Ids.IId { }
+ }
+}
+";
+ var result = RunGenerator(source);
+ var generated = result.Results[0].GeneratedSources;
+
+ Assert.Single(generated);
+ var text = generated[0].SourceText.ToString();
+ Assert.Contains("partial class Outer", text);
+ Assert.Contains("partial struct Inner", text);
+ }
+ }
+}
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..42b61ad
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Bodies/IdStructBody.cs
@@ -0,0 +1,50 @@
+using System.Text;
+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 nestedDepth = data.ContainingTypes.Length;
+
+ var code = new CodeWriter()
+ .AppendLine("// ")
+ .AppendLine()
+ .AppendLineIf(hasNamespace, $"namespace {data.Namespace}")
+ .BeginBlockIf(hasNamespace);
+
+ var nestedNames = new StringBuilder();
+ foreach (var containing in data.ContainingTypes)
+ {
+ code.AppendLine($"partial {containing.Keyword} {containing.Name}")
+ .BeginBlock();
+ nestedNames.Append(containing.Name).Append('.');
+ }
+
+ code.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();
+
+ for (var i = 0; i < nestedDepth; i++)
+ code.EndBlock();
+
+ code.EndBlockIf(hasNamespace);
+
+ var hintName = hasNamespace
+ ? $"{data.Namespace}.{nestedNames}{data.StructName}.IId.g.cs"
+ : $"{nestedNames}{data.StructName}.IId.g.cs";
+
+ context.AddSource(hintName, code.GetSourceText());
+ }
+}
diff --git a/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/ContainingTypeInfo.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/ContainingTypeInfo.cs
new file mode 100644
index 0000000..d04dc9a
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/ContainingTypeInfo.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+public readonly struct ContainingTypeInfo(string name, string keyword) : IEquatable
+{
+ public readonly string Name = name;
+ public readonly string Keyword = keyword;
+
+ public bool Equals(ContainingTypeInfo other) =>
+ Name == other.Name && Keyword == other.Keyword;
+
+ public override bool Equals(object? obj) =>
+ obj is ContainingTypeInfo other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ return (Name.GetHashCode() * 397) ^ Keyword.GetHashCode();
+ }
+ }
+}
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..9719f2e
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/IdStruct/Data/IdStructData.cs
@@ -0,0 +1,82 @@
+using System;
+using Microsoft.CodeAnalysis;
+using System.Collections.Immutable;
+
+namespace Aspid.FastTools.Generators.IdStruct.Data;
+
+public readonly struct IdStructData : IEquatable
+{
+ public readonly string StructName;
+ public readonly string? Namespace;
+ public readonly ImmutableArray ContainingTypes;
+
+ public IdStructData(INamedTypeSymbol symbol)
+ {
+ StructName = symbol.Name;
+ Namespace = symbol.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : symbol.ContainingNamespace.ToDisplayString();
+
+ if (symbol.ContainingType is null)
+ {
+ ContainingTypes = ImmutableArray.Empty;
+ return;
+ }
+
+ var builder = ImmutableArray.CreateBuilder();
+ var current = symbol.ContainingType;
+ while (current is not null)
+ {
+ builder.Add(new ContainingTypeInfo(current.Name, GetKeyword(current)));
+ current = current.ContainingType;
+ }
+
+ builder.Reverse();
+ ContainingTypes = builder.ToImmutable();
+ }
+
+ private static string GetKeyword(INamedTypeSymbol symbol)
+ {
+ if (symbol.IsRecord)
+ return symbol.IsValueType ? "record struct" : "record";
+
+ return symbol.TypeKind switch
+ {
+ TypeKind.Class => "class",
+ TypeKind.Struct => "struct",
+ TypeKind.Interface => "interface",
+ _ => throw new InvalidOperationException($"Unsupported containing type kind: {symbol.TypeKind}"),
+ };
+ }
+
+ public bool Equals(IdStructData other)
+ {
+ if (StructName != other.StructName) return false;
+ if (Namespace != other.Namespace) return false;
+ if (ContainingTypes.Length != other.ContainingTypes.Length) return false;
+
+ for (var i = 0; i < ContainingTypes.Length; i++)
+ {
+ if (!ContainingTypes[i].Equals(other.ContainingTypes[i])) return false;
+ }
+
+ return true;
+ }
+
+ public override bool Equals(object? obj) =>
+ obj is IdStructData other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ unchecked
+ {
+ var hash = StructName.GetHashCode();
+ hash = (hash * 397) ^ (Namespace?.GetHashCode() ?? 0);
+
+ foreach (var ct in ContainingTypes)
+ hash = (hash * 397) ^ ct.GetHashCode();
+
+ return hash;
+ }
+ }
+}
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..e690f23
--- /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.Ids.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.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
new file mode 100644
index 0000000..f3b935b
--- /dev/null
+++ b/Aspid.FastTools.Generators/Aspid.FastTools.Generators/Generators/ProfilerMarkers/Bodies/ExtensionClassBody.cs
@@ -0,0 +1,161 @@
+using System.Linq;
+using Microsoft.CodeAnalysis;
+using Aspid.Generators.Helper;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+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;
+
+namespace Aspid.FastTools.Generators.ProfilerMarkers.Bodies;
+
+public static class ExtensionClassBody
+{
+ public static void GenerateCode(in SourceProductionContext context, in ImmutableArray markerCalls)
+ {
+ var markerCallTypes = markerCalls
+ .GroupBy(markerCall => markerCall.NamedTypeSymbol, SymbolEqualityComparer.Default)
+ .Select(group => new MarkerCallType((INamedTypeSymbol)group.Key!, group.ToImmutableArray()));
+
+ foreach (var markerCallType in markerCallTypes)
+ GenerateCode(context, markerCallType);
+ }
+
+ private static void GenerateCode(in SourceProductionContext context, in MarkerCallType type)
+ {
+ var symbol = (INamedTypeSymbol)type.Symbol;
+ var typeName = symbol.Name;
+
+ 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 arityPart = symbol.Arity > 0 ? $"_{symbol.Arity}" : string.Empty;
+ var className = $"__{typeName}{arityPart}ProfilerMarkerExtensions";
+ var typeParameters = symbol.TypeParameters;
+ var isGeneric = typeParameters.Length > 0;
+ var typeParamList = isGeneric
+ ? "<" + string.Join(", ", typeParameters.Select(p => p.Name)) + ">"
+ : string.Empty;
+ var constraintsClause = BuildConstraintsClause(typeParameters);
+
+ var code = new CodeWriter()
+ .AppendLine("// ")
+ .AppendLine()
+ .AppendLineIf(hasNamespaceName, $"namespace {namespaceName}")
+ .BeginBlockIf(hasNamespaceName)
+ .AppendLine($"[{ProfilerMarkerGeneratedCode}]")
+ .AppendLine($"internal static class {className}")
+ .BeginBlock()
+ .AppendProfilerMarkers(symbol, markerCallMembers, typeParamList, constraintsClause, isGeneric)
+ .AppendWithoutMessage(symbol, markerCallMembers, typeParamList, constraintsClause, isGeneric)
+ .EndBlock()
+ .EndBlockIf(hasNamespaceName);
+
+ var hintName = $"{className}.g.cs";
+ context.AddSource(hintName, code.GetSourceText());
+ }
+
+ private static CodeWriter AppendProfilerMarkers(
+ this CodeWriter code,
+ INamedTypeSymbol symbol,
+ ImmutableArray markerCallMembers,
+ string typeParamList,
+ string constraintsClause,
+ bool isGeneric)
+ {
+ if (isGeneric)
+ {
+ code.AppendLine($"private static class Markers{typeParamList}{constraintsClause}")
+ .BeginBlock();
+ }
+
+ var fieldVisibility = isGeneric ? "public" : "private";
+
+ foreach (var markerCallMember in markerCallMembers)
+ {
+ var orderedMarkerCalls = markerCallMember.MarkerCalls
+ .OrderBy(markerCall => markerCall.Line);
+
+ foreach (var markerCall in orderedMarkerCalls)
+ {
+ var markerValueExpression = BuildMarkerValueExpression(symbol, markerCall);
+ code.AppendMultiline(
+ $"""
+ [{ProfilerMarkerGeneratedCode}]
+ {fieldVisibility} static readonly {ProfilerMarker} {markerCall.MarkerName} = new({markerValueExpression});
+
+ """);
+ }
+ }
+
+ if (isGeneric)
+ code.EndBlock().AppendLine();
+
+ return code;
+ }
+
+ private static CodeWriter AppendWithoutMessage(
+ this CodeWriter code,
+ INamedTypeSymbol symbol,
+ ImmutableArray markerCallMembers,
+ string typeParamList,
+ string constraintsClause,
+ bool isGeneric)
+ {
+ code.AppendLine($"[{ProfilerMarkerGeneratedCode}]")
+ .AppendLine($"public static {ProfilerMarker}.AutoScope Marker{typeParamList}(this {symbol.ToDisplayStringGlobal()} _, [{CallerLineNumberAttribute}] int line = -1){constraintsClause}")
+ .BeginBlock();
+
+ var prefix = isGeneric ? $"Markers{typeParamList}." : string.Empty;
+ foreach (var markerCall in markerCallMembers.SelectMany(member => member.MarkerCalls))
+ code.AppendLine($"if (line is {markerCall.Line}) return {prefix}{markerCall.MarkerName}.Auto();");
+
+ code.AppendLine()
+ .AppendLine($"throw new {Exception}();")
+ .EndBlock();
+
+ return code;
+ }
+
+ private static string BuildMarkerValueExpression(INamedTypeSymbol symbol, MarkerCall markerCall)
+ {
+ if (symbol.TypeParameters.Length is 0)
+ return $"\"{symbol.Name}.{markerCall.Label} ({markerCall.Line})\"";
+
+ var typeArgs = string.Join(", ", symbol.TypeParameters.Select(p => $"{{typeof({p.Name}).Name}}"));
+ return $"$\"{symbol.Name}<{typeArgs}>.{markerCall.Label} ({markerCall.Line})\"";
+ }
+
+ private static string BuildConstraintsClause(ImmutableArray typeParameters)
+ {
+ if (typeParameters.Length is 0) return string.Empty;
+
+ var clauses = new List();
+ foreach (var tp in typeParameters)
+ {
+ var constraints = new List();
+
+ if (tp.HasReferenceTypeConstraint) constraints.Add("class");
+ else if (tp.HasUnmanagedTypeConstraint) constraints.Add("unmanaged");
+ else if (tp.HasValueTypeConstraint) constraints.Add("struct");
+
+ if (tp.HasNotNullConstraint) constraints.Add("notnull");
+
+ foreach (var ct in tp.ConstraintTypes)
+ constraints.Add(ct.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
+
+ if (tp.HasConstructorConstraint) constraints.Add("new()");
+
+ if (constraints.Count > 0)
+ clauses.Add($"where {tp.Name} : {string.Join(", ", constraints)}");
+ }
+
+ return clauses.Count is 0 ? string.Empty : " " + string.Join(" ", clauses);
+ }
+}
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 61%
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..e713d1f 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,
@@ -12,7 +12,7 @@ public readonly struct MarkerCall(
public readonly int Line = line;
public readonly IMethodSymbol MethodSymbol = methodSymbol;
public readonly INamedTypeSymbol NamedTypeSymbol = namedTypeSymbol;
-
- public readonly string MarkerName = markerName + "_line_" + line;
- public readonly string MarkerValue = $"{namedTypeSymbol.Name}.{markerValue} ({line})";
-}
\ No newline at end of file
+
+ public readonly string MarkerName = markerName + "_Marker_Line_" + line;
+ public readonly string Label = markerValue;
+}
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 76%
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..da074a5 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
@@ -25,7 +25,7 @@ private static bool Predicate(SyntaxNode node, CancellationToken _)
{
if (node is not InvocationExpressionSyntax invocation) return false;
if (invocation.Expression is not MemberAccessExpressionSyntax memberAccessExpression) return false;
-
+
return memberAccessExpression.Name is IdentifierNameSyntax
{
Identifier.ValueText: "Marker"
@@ -39,35 +39,47 @@ private static bool Predicate(SyntaxNode node, CancellationToken _)
if (invocation.Expression is not MemberAccessExpressionSyntax memberAccessExpression) return null;
if (memberAccessExpression.Name is not IdentifierNameSyntax idName || idName.Identifier.ValueText is not "Marker") return null;
if (context.SemanticModel.GetEnclosingSymbol(invocation.SpanStart) is not IMethodSymbol enclosing) return null;
-
+
var namedTypeSymbol = enclosing.ContainingType;
if (namedTypeSymbol is null) return null;
- var markerName = enclosing.AssociatedSymbol is IPropertySymbol property
- ? property.Name
- : enclosing.MethodKind is MethodKind.Constructor
- ? "Ctor"
- : enclosing.Name;
-
+ var markerName = ResolveMarkerName(enclosing);
var markerValue = markerName;
-
- if (invocation.Parent is MemberAccessExpressionSyntax memberAccessExpressionWithName
+
+ if (invocation.Parent is MemberAccessExpressionSyntax memberAccessExpressionWithName
&& memberAccessExpressionWithName.Name is IdentifierNameSyntax { Identifier.ValueText: "WithName" }
&& memberAccessExpressionWithName.Parent is InvocationExpressionSyntax invocationExpressionWithName
&& invocationExpressionWithName.ArgumentList.Arguments.FirstOrDefault()?.Expression is LiteralExpressionSyntax literalExpressionWithName)
{
markerValue = literalExpressionWithName.Token.ValueText;
}
-
+
var lineSpan = invocation.GetLocation().GetLineSpan();
var lineNumber = lineSpan.StartLinePosition.Line + 1;
-
+
return new MarkerCall(namedTypeSymbol, enclosing, lineNumber, markerName, markerValue);
}
+ private static string ResolveMarkerName(IMethodSymbol enclosing)
+ {
+ if (enclosing.AssociatedSymbol is IPropertySymbol property)
+ {
+ return property.ExplicitInterfaceImplementations.Length > 0
+ ? property.ExplicitInterfaceImplementations[0].Name
+ : property.Name;
+ }
+
+ if (enclosing.MethodKind is MethodKind.Constructor)
+ return "Ctor";
+
+ return enclosing.ExplicitInterfaceImplementations.Length > 0
+ ? enclosing.ExplicitInterfaceImplementations[0].Name
+ : enclosing.Name;
+ }
+
private static void GenerateCode(SourceProductionContext context, ImmutableArray markerCalls)
{
if (markerCalls.Length is 0) return;
ExtensionClassBody.GenerateCode(context, markerCalls);
}
-}
\ No newline at end of file
+}
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/CLAUDE.md b/Aspid.FastTools.Generators/CLAUDE.md
new file mode 100644
index 0000000..23beb6c
--- /dev/null
+++ b/Aspid.FastTools.Generators/CLAUDE.md
@@ -0,0 +1,122 @@
+# Aspid.FastTools.Generators
+
+Standalone .NET solution containing Roslyn source generators for the `com.aspid.fasttools` Unity package.
+
+## Commands
+
+```bash
+# Build and auto-deploy DLL into Unity package
+dotnet build -c Release
+
+# Run generator unit tests
+dotnet test
+```
+
+`Directory.Build.targets` copies the compiled DLL to `../Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Aspid.FastTools.Generators.dll` on build.
+
+A repo-level PostToolUse hook (`.claude/hooks/rebuild-generators-on-change.sh`) also runs `dotnet build` automatically after any `Edit`/`Write` to `*.cs` under `Aspid.FastTools.Generators/Aspid.FastTools.Generators/`. The hook intentionally **does not** trigger for tests, the Sample project, or Unity-side edits — keep that scope if you modify it.
+
+## Solution Structure
+
+```
+Aspid.FastTools.Generators/ ← generator implementation
+Aspid.FastTools.Generators.Tests/ ← unit tests (Roslyn test helpers)
+Aspid.FastTools.Generators.Sample/ ← manual smoke-test project
+Aspid.FastTools.Generators.sln
+Directory.Build.targets
+```
+
+## Target Framework
+
+`netstandard2.0` — required by Roslyn. No Unity assemblies, no runtime packages.
+
+## Dependencies
+
+| Package | Role |
+|---|---|
+| `Microsoft.CodeAnalysis.CSharp` 4.3.0 | Roslyn semantic model and syntax |
+| `Aspid.Generators.Helper` | `CodeWriter` utility for emitting source |
+| `Aspid.Generators.Helper.Unity` | Unity-specific analysis helpers |
+| `SourceGenerator.Foundations` 2.0.13 | Incremental generator infrastructure |
+
+## Generator Implementation Pattern
+
+All generators implement `IIncrementalGenerator` (never the deprecated `ISourceGenerator`).
+
+### Three-Stage Pipeline
+
+```
+Predicate (SyntaxNode) → Transform (SemanticModel) → GenerateCode (SourceProductionContext)
+```
+
+**1. Predicate** — cheap syntax-only filter, no semantic model:
+```csharp
+private static bool Predicate(SyntaxNode node, CancellationToken _)
+{
+ if (node is not StructDeclarationSyntax s) return false;
+ if (!s.Modifiers.Any(SyntaxKind.PartialKeyword)) return false;
+ return s.BaseList is { Types.Count: > 0 };
+}
+```
+
+**2. Transform** — semantic extraction, returns a readonly struct or `null`:
+```csharp
+private static IdStructData? Transform(GeneratorSyntaxContext ctx, CancellationToken _)
+{
+ var symbol = ctx.SemanticModel.GetDeclaredSymbol(...);
+ // resolve IId by metadata name, check AllInterfaces
+ return new IdStructData(symbol);
+}
+```
+
+**3. GenerateCode** — emit source using `CodeWriter`:
+```csharp
+public static void GenerateCode(in SourceProductionContext context, in IdStructData data)
+{
+ var code = new CodeWriter()
+ .AppendLine("// ")
+ ...
+ .ToString();
+ context.AddSource($"{data.StructName}.IId.g.cs", code);
+}
+```
+
+### Data Structures
+
+Carry semantic results between pipeline stages as `readonly struct` — they must be value-comparable for Roslyn's caching to work correctly.
+
+```csharp
+public readonly struct IdStructData(INamedTypeSymbol symbol)
+{
+ public readonly string StructName = symbol.Name;
+ public readonly string? Namespace = symbol.ContainingNamespace.IsGlobalNamespace
+ ? null
+ : symbol.ContainingNamespace.ToDisplayString();
+}
+```
+
+### Generated File Naming
+
+| Generator | Pattern |
+|---|---|
+| `IdStructGenerator` | `{StructName}.IId.g.cs` |
+| `ProfilerMarkersGenerator` | `__{TypeName}{Arity}ProfilerMarkerExtensions.g.cs` |
+
+All generated files begin with `// `.
+
+## Existing Generators
+
+### ProfilerMarkersGenerator
+
+Finds every `.Marker()` call site, extracts enclosing type + method + line number, and generates `private static readonly ProfilerMarker` fields unique to each call site. Marker name: `"{markerName}_line_{line}"`. Marker display value: `"{TypeName}.{member} ({line})"`. Supports `.WithName(string)` override.
+
+### IdStructGenerator
+
+Finds `partial struct` types implementing `Aspid.FastTools.Ids.IId` and generates boilerplate: serialized `_id` field, `Id` property, editor-only `__stringId` field. Supports nested types — generated code is wrapped in matching `partial class`/`partial struct`/`partial record` containing-type declarations.
+
+## Conventions
+
+- Generator class → `[Generator(LanguageNames.CSharp)]` attribute
+- Generated members → `[ProfilerMarkerGeneratedCode]` or equivalent visibility attribute from `Descriptions/General.cs`
+- Namespace handling → always check `IsGlobalNamespace` before emitting `namespace` block
+- Avoid LINQ in hot Predicate/Transform paths — allocations defeat incremental caching
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..74791c1 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.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png.meta
new file mode 100644
index 0000000..683f8d2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/Images/Aspid.FastTools.SerializableType.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: b0d82cbe15d4842dd9d7c31cb4040e88
+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.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..7cf7985
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md
@@ -0,0 +1,525 @@
+# 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.Types` | `SerializableType`, `SerializableType`, `ComponentTypeSelector`, `TypeSelectorAttribute` |
+| `Aspid.FastTools.Enums` | `EnumValues` |
+| `Aspid.FastTools.Ids` | `IId`, `UniqueIdAttribute`, `IdRegistry` |
+| `Aspid.FastTools.UIElements` | Runtime `VisualElement` fluent extensions |
+| `Aspid.FastTools.Editors` | Editor helpers — `SerializedProperty` extensions, IMGUI scopes, `GetScriptName` |
+| `Aspid.FastTools.Types.Editors` · `.Enums.Editors` · `.Ids.Editors` · `.UIElements.Editors` | Per-feature editor code (property drawers, registry inspector, editor-only `VisualElement` 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;
+
+public class MyBehaviour : MonoBehaviour
+{
+ private void Update()
+ {
+ DoSomething1();
+ DoSomething2();
+ }
+
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Some code
+ }
+
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Some code
+ using var _ = this.Marker().WithName("Calculate");
+ // Some code
+ }
+ }
+}
+```
+
+### Generated code
+
+```csharp
+using System;
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_line_13 = new("MyBehaviour.DoSomething1 (13)");
+ private static readonly ProfilerMarker DoSomething2_line_19 = new("MyBehaviour.DoSomething2 (19)");
+ private static readonly ProfilerMarker DoSomething2_line_22 = new("MyBehaviour.Calculate (22)");
+
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+ if (line is 13) return DoSomething1_line_13.Auto();
+ if (line is 19) return DoSomething2_line_19.Auto();
+ if (line is 22) return DoSomething2_line_22.Auto();
+
+ throw new Exception();
+ }
+}
+```
+
+### Result
+
+
+
+---
+
+## Serializable Type System
+
+Allows serializing a `System.Type` reference in the Unity Inspector. The selected type is stored as an assembly-qualified name and resolved lazily on first access.
+
+### SerializableType
+
+Two variants are available:
+
+- **`SerializableType`** — stores any type (base type is `object`)
+- **`SerializableType`** — stores a type constrained to `T` or its subclasses
+
+Both support implicit conversion to `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [SerializeField] private SerializableType _anyType;
+ [SerializeField] private SerializableType _behaviourType;
+
+ private void Start()
+ {
+ Type type1 = _anyType; // implicit operator
+ Type type2 = _behaviourType.Type; // explicit property
+
+ var instance = (MonoBehaviour)gameObject.AddComponent(type2);
+ }
+}
+```
+
+### ComponentTypeSelector
+
+A serializable struct that renders a type-switching dropdown in the Inspector. Add it as a field to a base class — picking a subtype rewrites `m_Script` on the `SerializedObject`, effectively changing the component or ScriptableObject to the chosen subtype.
+
+The dropdown is automatically constrained to subtypes of the class that declares the field. No additional configuration is required.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+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 TypeAllow Allow { get; set; } // default: TypeAllow.None
+}
+
+[Flags]
+public enum TypeAllow
+{
+ None = 0,
+ Abstract = 1,
+ Interface = 2,
+ All = Abstract | Interface
+}
+```
+
+| Property | Description |
+|----------|-------------|
+| `Allow` | Which special type categories (abstract classes, interfaces) the picker includes in addition to plain concrete classes. Default: `TypeAllow.None` |
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [TypeSelector(typeof(IMyInterface))]
+ [SerializeField] private string _typeName;
+
+ // Include abstract types and interfaces in the picker
+ [TypeSelector(typeof(object), Allow = TypeAllow.All)]
+ [SerializeField] private string _anyType;
+}
+```
+
+### Type Selector Window
+
+The Inspector shows a button that opens a searchable popup window with:
+
+- Hierarchical namespace organization
+- Text search with filtering
+- Keyboard navigation (Arrow keys, Enter, Escape)
+- Navigation history (back button)
+- Assembly disambiguation for types with identical names
+
+
+---
+
+## Enum System
+
+Provides serializable enum-to-value mappings configurable from the Inspector.
+
+### EnumValues\
+
+A serializable collection of `EnumValue` entries with a configurable default value. Implements `IEnumerable>`.
+
+```csharp
+[Serializable]
+public sealed class EnumValues : IEnumerable>
+```
+
+| Member | Description |
+|--------|-------------|
+| `TValue GetValue(Enum enumValue)` | Returns the mapped value, or `_defaultValue` if not found |
+| `bool Equals(Enum, Enum)` | Equality check with proper `[Flags]` support |
+
+Supports `[Flags]` enums: `Equals` uses `HasFlag` and treats `0`-valued members correctly.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Enums;
+
+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. Right-click the property to open a context menu with **Populate Missing Enum Members** — it appends an entry for every enum member not yet in the list, seeded with the current Default Value.
+
+---
+
+## ID System
+
+> **Beta:** the ID System is currently in beta. The public API, generated code layout and editor workflow may change in future releases.
+
+Maps an asset-assignable name to a stable integer ID. Use the resulting `int` in `switch` statements and `Dictionary` keys without paying for string lookups at runtime.
+
+A single `IdRegistry` ScriptableObject maps string names to stable integer IDs and provides full `int ↔ string` lookups at runtime.
+
+### Setup
+
+**1.** Declare a `partial struct` implementing `IId`. The source generator adds the required fields and property automatically:
+
+```csharp
+using Aspid.FastTools.Ids;
+
+public partial struct EnemyId : IId { }
+```
+
+Generated code:
+
+```csharp
+public partial struct EnemyId
+{
+ [SerializeField] private string __stringId; // editor-only field, stripped from player builds
+ [SerializeField] private int _id;
+
+ public int Id => _id;
+}
+```
+
+**2.** Create the registry asset and bind it to the struct type in its Inspector:
+- `Assets → Create → Aspid → Id Registry`
+
+**3.** Use the struct as a serialized field. The Inspector shows a dropdown of registered names; the selector window also lets you create new entries on the fly:
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+[CreateAssetMenu]
+public class EnemyDefinition : ScriptableObject
+{
+ [UniqueId] [SerializeField] private EnemyId _id;
+}
+```
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+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
+
+`ScriptableObject` in `Aspid.FastTools.Ids` that stores `(int, string)` entries and keeps the lookup tables available at runtime. Each name is assigned a stable, auto-incrementing ID that never changes when other entries are added or removed.
+
+| Member | Description |
+|--------|-------------|
+| `bool TryGetId(string name, out int id)` | Returns `true` and the ID when found; otherwise `false` |
+| `bool TryGetName(int id, out string name)` | Returns `true` and the name when found; otherwise `false` and `string.Empty` |
+| `bool Contains(int id)` | Whether an ID is registered |
+| `bool Contains(string name)` | Whether a name is registered |
+| `int Count` | Number of entries |
+| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Registered IDs / names, in registration order |
+| `IEnumerator> GetEnumerator()` | Iterate `(id, name)` pairs |
+
+The registry derives from `ScriptableObject` directly and exposes a generic counterpart `IdRegistry` (with `T : struct, IId`) that adds typed `Contains(T)` and `TryGetName(T, out string)` overloads. Edits — adding, renaming, removing entries — happen through the registry inspector and `RegistryEditorCore`, not via a public runtime API.
+
+---
+
+## SerializedProperty Extensions
+
+Chainable extensions on `SerializedProperty` for synchronizing the owning `SerializedObject`, writing typed values, and reflecting on the underlying field.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+```csharp
+property
+ .Update()
+ .SetVector3(Vector3.up)
+ .SetBool(true)
+ .ApplyModifiedProperties();
+```
+
+The package covers:
+
+- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
+- **Typed setters** — `SetValue` (generic dispatch) and `SetXxx` for `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (and `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). Each comes with a paired `SetXxxAndApply` variant.
+- **Enum setters** — `SetEnumFlag` and `SetEnumIndex` (each + `AndApply`).
+- **Arrays** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (each + `AndApply`).
+- **References** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, and `SetBoxed` (Unity 6+).
+- **Reflection helpers** — `GetPropertyType`, `GetMemberInfo`, `GetClassInstance` for resolving the C# member and runtime instance behind a property.
+
+> Full method-by-method reference: [SerializedPropertyExtensions.md](SerializedPropertyExtensions.md)
+
+---
+
+## IMGUI Layout Scopes
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+Three `ref struct` scopes — `VerticalScope`, `HorizontalScope`, `ScrollViewScope` — wrap `EditorGUILayout.Begin*` / `End*`. Each exposes a `Rect` property and calls the matching `End*` method on `Dispose`:
+
+```csharp
+using (VerticalScope.Begin())
+{
+ EditorGUILayout.LabelField("Item 1");
+ EditorGUILayout.LabelField("Item 2");
+}
+
+using (HorizontalScope.Begin())
+{
+ EditorGUILayout.LabelField("Left");
+ EditorGUILayout.LabelField("Right");
+}
+
+var scrollPos = Vector2.zero;
+using (ScrollViewScope.Begin(ref scrollPos))
+{
+ EditorGUILayout.LabelField("Scrollable content");
+}
+```
+
+Capture the group rect with the `out`-overload when needed:
+
+```csharp
+using (VerticalScope.Begin(out var rect, GUI.skin.box))
+{
+ EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
+ EditorGUILayout.LabelField("Boxed content");
+}
+```
+
+All `Begin` overloads match the corresponding `EditorGUILayout.Begin*` signatures (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.UIElements; // runtime extensions
+using Aspid.FastTools.UIElements.Editors; // editor-only extensions (e.g. AddOpenScriptCommand)
+```
+
+### Quick reference
+
+The package covers:
+
+- **Core element operations** — name, visibility, tooltip, user data, picking mode, data source, and `AddChild`/`InsertChild` helpers.
+- **Focus** — `SetFocus`, `SetBlur`, `SetTabIndex`, `SetFocusable`.
+- **USS** — `AddClass`/`RemoveClass`/`ToggleInClass`/`EnableInClass`, `AddStyleSheets[FromResource]`.
+- **Styles** — every `IStyle` property: layout, size, spacing, font, text, color, border, background, transform (incl. Unity 6.3+ aspect/filter/material), transition, overflow, slice, cursor.
+- **Specialized elements** — `TextElement`, `ITextEdition`, `ITextSelection`, `BaseField`, `BaseBoolField` (Toggle), `INotifyValueChanged` (with optional `Unity.Mathematics` types), `IMixedValueSupport`, `Button`, `Slider`/`BaseSlider`, `ProgressBar`, `HelpBox`, `Foldout`, `Image`, `IMGUIContainer`, plus the full `ListView`/`TreeView`/`MultiColumn*` surface.
+- **Editor-only commands** — `AddOpenScriptCommand`, `BindTo`/`BindPropertyTo`, `EnumField`/`EnumFlagsField` `Initialize`, and `PropertyField` value-change subscriptions.
+- **USS custom-style helpers** — `ICustomStyle.TryGetByEnum` for parsing string USS properties as enums.
+
+> Full method-by-method reference: [VisualElementExtensions.md](VisualElementExtensions.md)
+
+### Full example
+
+```csharp
+using UnityEditor;
+using UnityEngine;
+using Aspid.FastTools.Editors; // GetScriptName
+using Aspid.FastTools.UIElements; // runtime VisualElement extensions
+using Aspid.FastTools.UIElements.Editors; // AddOpenScriptCommand
+using UnityEngine.UIElements;
+
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ const string iconPath = "Editor/MyIcon";
+
+ var scriptName = target.GetScriptName();
+ var dark = new Color(0.15f, 0.15f, 0.15f);
+ var light = new Color(0.75f, 0.75f, 0.75f);
+
+ return new VisualElement()
+ .SetName("Header")
+ .SetBackgroundColor(dark)
+ .SetFlexDirection(FlexDirection.Row)
+ .SetPadding(top: 5, bottom: 5, left: 10, right: 10)
+ .SetBorderRadius(topLeft: 10, topRight: 10, bottomLeft: 10, bottomRight: 10)
+ .AddChild(new Image()
+ .SetName("Icon")
+ .AddOpenScriptCommand(target)
+ .SetImageFromResource(iconPath)
+ .SetSize(width: 40, height: 40))
+ .AddChild(new Label(scriptName)
+ .SetName("Title")
+ .SetFlexGrow(1)
+ .SetFontSize(16)
+ .SetMargin(left: 10)
+ .SetColor(light)
+ .SetAlignSelf(Align.Center)
+ .SetOverflow(Overflow.Hidden)
+ .SetWhiteSpace(WhiteSpace.NoWrap)
+ .SetTextOverflow(TextOverflow.Ellipsis)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold));
+ }
+}
+```
+
+### Result
+
+
+
+---
+
+## Editor Helper Extensions
+
+Utility methods for getting display names of Unity objects in custom editors.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+```csharp
+public static string GetScriptName(this Object obj)
+```
+
+Returns the display name of a Unity object:
+- If the type has `[AddComponentMenu]`, returns `ObjectNames.GetInspectorTitle(obj)`
+- Otherwise returns `ObjectNames.NicifyVariableName(typeName)`
+
+```csharp
+public static string GetScriptNameWithIndex(this Component targetComponent)
+```
+
+Returns the display name with a count suffix when multiple components of the same type exist on the same GameObject. For example, if two `AudioSource` components are attached, the second returns `"Audio Source (2)"`.
+
+```csharp
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ // "My Behaviour" — or "Custom Name" if [AddComponentMenu("Custom Name")] is present
+ var name = target.GetScriptName();
+
+ // "My Behaviour (2)" when a second component of the same type exists
+ var nameWithIndex = ((Component)target).GetScriptNameWithIndex();
+
+ return new Label(name);
+ }
+}
+```
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/README.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md.meta
similarity index 100%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Documentation/README.md.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README.md.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md
new file mode 100644
index 0000000..53748bb
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md
@@ -0,0 +1,525 @@
+# 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.Types` | `SerializableType`, `SerializableType`, `ComponentTypeSelector`, `TypeSelectorAttribute` |
+| `Aspid.FastTools.Enums` | `EnumValues` |
+| `Aspid.FastTools.Ids` | `IId`, `UniqueIdAttribute`, `IdRegistry` |
+| `Aspid.FastTools.UIElements` | Runtime fluent-расширения `VisualElement` |
+| `Aspid.FastTools.Editors` | Редакторские утилиты — расширения `SerializedProperty`, IMGUI-области, `GetScriptName` |
+| `Aspid.FastTools.Types.Editors` · `.Enums.Editors` · `.Ids.Editors` · `.UIElements.Editors` | Редакторский код по фичам (property drawers, инспектор реестров, editor-only расширения `VisualElement`) |
+
+---
+
+## ProfilerMarker
+
+Предоставляет регистрацию `ProfilerMarker` через source generation. Генератор создаёт статический маркер для каждого места вызова, идентифицируемый по вызывающему методу и номеру строки.
+
+```csharp
+using UnityEngine;
+
+public class MyBehaviour : MonoBehaviour
+{
+ private void Update()
+ {
+ DoSomething1();
+ DoSomething2();
+ }
+
+ private void DoSomething1()
+ {
+ using var _ = this.Marker();
+ // Некоторый код
+ }
+
+ private void DoSomething2()
+ {
+ using (this.Marker())
+ {
+ // Некоторый код
+ using var _ = this.Marker().WithName("Calculate");
+ // Некоторый код
+ }
+ }
+}
+```
+
+### Сгенерированный код
+
+```csharp
+using System;
+using Unity.Profiling;
+using System.Runtime.CompilerServices;
+
+internal static class __MyBehaviourProfilerMarkerExtensions
+{
+ private static readonly ProfilerMarker DoSomething1_line_13 = new("MyBehaviour.DoSomething1 (13)");
+ private static readonly ProfilerMarker DoSomething2_line_19 = new("MyBehaviour.DoSomething2 (19)");
+ private static readonly ProfilerMarker DoSomething2_line_22 = new("MyBehaviour.Calculate (22)");
+
+ public static ProfilerMarker.AutoScope Marker(this MyBehaviour _, [CallerLineNumberAttribute] int line = -1)
+ {
+ if (line is 13) return DoSomething1_line_13.Auto();
+ if (line is 19) return DoSomething2_line_19.Auto();
+ if (line is 22) return DoSomething2_line_22.Auto();
+
+ throw new Exception();
+ }
+}
+```
+
+### Результат
+
+
+
+---
+
+## Система сериализуемых типов
+
+Позволяет сериализовать ссылку на `System.Type` в Unity Inspector. Выбранный тип хранится как assembly-qualified name и разрешается лениво при первом обращении.
+
+### SerializableType
+
+Доступны два варианта:
+
+- **`SerializableType`** — хранит любой тип (базовый тип — `object`)
+- **`SerializableType`** — хранит тип, ограниченный `T` или его подклассами
+
+Оба поддерживают неявное преобразование в `System.Type`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [SerializeField] private SerializableType _anyType;
+ [SerializeField] private SerializableType _behaviourType;
+
+ private void Start()
+ {
+ Type type1 = _anyType; // неявный оператор
+ Type type2 = _behaviourType.Type; // явное свойство
+
+ var instance = (MonoBehaviour)gameObject.AddComponent(type2);
+ }
+}
+```
+
+### ComponentTypeSelector
+
+Сериализуемая структура, добавляющая в Inspector выпадающий список для смены типа объекта. Добавьте её как поле в базовый класс — при выборе подтипа редактор перезаписывает `m_Script` на `SerializedObject`, фактически превращая компонент или ScriptableObject в выбранный подтип.
+
+Список автоматически ограничивается подтипами класса, в котором объявлено поле. Дополнительная настройка не требуется.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+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 TypeAllow Allow { get; set; } // по умолчанию: TypeAllow.None
+}
+
+[Flags]
+public enum TypeAllow
+{
+ None = 0,
+ Abstract = 1,
+ Interface = 2,
+ All = Abstract | Interface
+}
+```
+
+| Свойство | Описание |
+|----------|----------|
+| `Allow` | Какие специальные категории типов (абстрактные классы, интерфейсы) включаются в список выбора в дополнение к обычным конкретным классам. По умолчанию: `TypeAllow.None` |
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+public class MyBehaviour : MonoBehaviour
+{
+ [TypeSelector(typeof(IMyInterface))]
+ [SerializeField] private string _typeName;
+
+ // Включить абстрактные типы и интерфейсы в список выбора
+ [TypeSelector(typeof(object), Allow = TypeAllow.All)]
+ [SerializeField] private string _anyType;
+}
+```
+
+### Окно выбора типа
+
+В Inspector отображается кнопка, открывающая всплывающее окно с поиском, которое включает:
+
+- Иерархическую организацию по пространствам имён
+- Текстовый поиск с фильтрацией
+- Навигацию с клавиатуры (стрелки, Enter, Escape)
+- Историю навигации (кнопка «назад»)
+- Разрешение неоднозначности для типов с одинаковыми именами из разных сборок
+
+
+---
+
+## Система перечислений
+
+Предоставляет сериализуемые отображения enum → значение, настраиваемые через Inspector.
+
+### EnumValues\
+
+Сериализуемая коллекция записей `EnumValue` с настраиваемым значением по умолчанию. Реализует `IEnumerable>`.
+
+```csharp
+[Serializable]
+public sealed class EnumValues : IEnumerable>
+```
+
+| Член | Описание |
+|------|----------|
+| `TValue GetValue(Enum enumValue)` | Возвращает сопоставленное значение или `_defaultValue`, если не найдено |
+| `bool Equals(Enum, Enum)` | Проверка равенства с поддержкой `[Flags]` |
+
+Поддерживает `[Flags]`-перечисления: `Equals` использует `HasFlag` и корректно обрабатывает члены со значением `0`.
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Enums;
+
+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`, затем назначьте значение для каждого члена перечисления. Нажмите правой кнопкой мыши по свойству, чтобы открыть контекстное меню с пунктом **Populate Missing Enum Members** — он добавит записи для всех отсутствующих членов перечисления, используя текущее Default Value как начальное значение.
+
+---
+
+## Система ID
+
+> **Бета:** Система ID находится в бета-версии. Публичный API, структура генерируемого кода и редакторский UX могут измениться в будущих релизах.
+
+Сопоставляет имя, назначаемое в активе, со стабильным целочисленным ID. Получившийся `int` подходит для `switch` и ключей `Dictionary` без затрат на строковые поиски в рантайме.
+
+Единственный ScriptableObject `IdRegistry` сопоставляет строковые имена стабильным целочисленным ID и предоставляет полные `int ↔ string` поиски в рантайме.
+
+### Использование
+
+**1.** Объявите `partial struct`, реализующий `IId`. Генератор исходников автоматически добавит необходимые поля и свойство:
+
+```csharp
+using Aspid.FastTools.Ids;
+
+public partial struct EnemyId : IId { }
+```
+
+Сгенерированный код:
+
+```csharp
+public partial struct EnemyId
+{
+ [SerializeField] private string __stringId; // editor-only поле, вырезается из player-сборок
+ [SerializeField] private int _id;
+
+ public int Id => _id;
+}
+```
+
+**2.** Создайте ассет реестра и привяжите его к вашему типу структуры в Inspector:
+- `Assets → Create → Aspid → Id Registry`
+
+**3.** Используйте структуру как сериализуемое поле. В Inspector отображается выпадающий список зарегистрированных имён; окно селектора также позволяет создавать новые записи на лету:
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+[CreateAssetMenu]
+public class EnemyDefinition : ScriptableObject
+{
+ [UniqueId] [SerializeField] private EnemyId _id;
+}
+```
+
+```csharp
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+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` из `Aspid.FastTools.Ids`, хранящий записи `(int, string)` и поддерживающий таблицы поиска доступными во рантайме. Каждому имени назначается стабильный, автоинкрементный ID, который не изменяется даже при добавлении или удалении других записей.
+
+| Член | Описание |
+|------|----------|
+| `bool TryGetId(string name, out int id)` | Возвращает `true` и найденный ID; иначе `false` |
+| `bool TryGetName(int id, out string name)` | Возвращает `true` и найденное имя; иначе `false` и `string.Empty` |
+| `bool Contains(int id)` | Зарегистрирован ли ID |
+| `bool Contains(string name)` | Зарегистрировано ли имя |
+| `int Count` | Количество записей |
+| `IReadOnlyList Ids` · `IReadOnlyList IdNames` | Зарегистрированные ID / имена в порядке регистрации |
+| `IEnumerator> GetEnumerator()` | Итерация по парам `(id, name)` |
+
+Реестр наследуется напрямую от `ScriptableObject` и предоставляет генерик-аналог `IdRegistry` (с `T : struct, IId`), добавляющий типизированные перегрузки `Contains(T)` и `TryGetName(T, out string)`. Редактирование — добавление, переименование, удаление записей — выполняется через инспектор реестра и `RegistryEditorCore`, а не через публичный runtime API.
+
+---
+
+## Расширения SerializedProperty
+
+Цепочные расширения над `SerializedProperty` для синхронизации владеющего `SerializedObject`, записи типизированных значений и рефлексии над полем-источником.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+```csharp
+property
+ .Update()
+ .SetVector3(Vector3.up)
+ .SetBool(true)
+ .ApplyModifiedProperties();
+```
+
+Пакет покрывает:
+
+- **Update / Apply** — `Update`, `UpdateIfRequiredOrScript`, `ApplyModifiedProperties`.
+- **Типизированные сеттеры** — `SetValue` (обобщённый диспетчер) и `SetXxx` для `int`/`uint`/`long`/`ulong`/`float`/`double`/`bool`/`string`/`Color`/`Gradient`/`Hash128`/`Rect`/`RectInt`/`Bounds`/`BoundsInt`/`Vector2..4` (и `Vector2/3Int`)/`Quaternion`/`AnimationCurve`/`EntityId` (Unity 6.2+). К каждому идёт парный вариант `SetXxxAndApply`.
+- **Enum-сеттеры** — `SetEnumFlag` и `SetEnumIndex` (каждый + `AndApply`).
+- **Массивы** — `SetArraySize`, `AddArraySize`, `RemoveArraySize` (каждый + `AndApply`).
+- **Ссылки** — `SetManagedReference`, `SetObjectReference`, `SetExposedReference`, а также `SetBoxed` (Unity 6+).
+- **Рефлексионные хелперы** — `GetPropertyType`, `GetMemberInfo`, `GetClassInstance` для разрешения C#-члена и runtime-экземпляра, стоящих за property.
+
+> Полный справочник по методам: [SerializedPropertyExtensions_RU.md](SerializedPropertyExtensions_RU.md)
+
+---
+
+## IMGUI-области разметки
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+Три `ref struct`-области — `VerticalScope`, `HorizontalScope`, `ScrollViewScope` — оборачивают `EditorGUILayout.Begin*` / `End*`. Каждая предоставляет свойство `Rect` и вызывает соответствующий метод `End*` в `Dispose`:
+
+```csharp
+using (VerticalScope.Begin())
+{
+ EditorGUILayout.LabelField("Item 1");
+ EditorGUILayout.LabelField("Item 2");
+}
+
+using (HorizontalScope.Begin())
+{
+ EditorGUILayout.LabelField("Left");
+ EditorGUILayout.LabelField("Right");
+}
+
+var scrollPos = Vector2.zero;
+using (ScrollViewScope.Begin(ref scrollPos))
+{
+ EditorGUILayout.LabelField("Scrollable content");
+}
+```
+
+Получить rect области через перегрузку с `out`-параметром:
+
+```csharp
+using (VerticalScope.Begin(out var rect, GUI.skin.box))
+{
+ EditorGUI.DrawRect(rect, new Color(0, 0, 0, 0.1f));
+ EditorGUILayout.LabelField("Boxed content");
+}
+```
+
+Все перегрузки `Begin` соответствуют сигнатурам `EditorGUILayout.Begin*` (опциональные `GUIStyle`, `GUILayoutOption[]`, параметры scroll view и т.д.).
+
+---
+
+## Расширения VisualElement
+
+Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
+
+```csharp
+using Aspid.FastTools.UIElements; // runtime-расширения
+using Aspid.FastTools.UIElements.Editors; // editor-only расширения (например, AddOpenScriptCommand)
+```
+
+### Краткий справочник
+
+Пакет покрывает:
+
+- **Основные операции с элементом** — имя, видимость, tooltip, user data, picking mode, data source, а также хелперы `AddChild`/`InsertChild`.
+- **Фокус** — `SetFocus`, `SetBlur`, `SetTabIndex`, `SetFocusable`.
+- **USS** — `AddClass`/`RemoveClass`/`ToggleInClass`/`EnableInClass`, `AddStyleSheets[FromResource]`.
+- **Стили** — все свойства `IStyle`: разметка, размер, отступы, шрифт, текст, цвет, рамка, фон, трансформации (вкл. aspect/filter/material с Unity 6.3+), переходы, overflow, slice, cursor.
+- **Специализированные элементы** — `TextElement`, `ITextEdition`, `ITextSelection`, `BaseField`, `BaseBoolField` (Toggle), `INotifyValueChanged` (с опциональными типами `Unity.Mathematics`), `IMixedValueSupport`, `Button`, `Slider`/`BaseSlider`, `ProgressBar`, `HelpBox`, `Foldout`, `Image`, `IMGUIContainer`, а также полная поверхность `ListView`/`TreeView`/`MultiColumn*`.
+- **Editor-only команды** — `AddOpenScriptCommand`, `BindTo`/`BindPropertyTo`, `Initialize` для `EnumField`/`EnumFlagsField`, подписка на изменения у `PropertyField`.
+- **USS custom-style helpers** — `ICustomStyle.TryGetByEnum` для парсинга строковых USS-свойств в enum.
+
+> Полный справочник по методам: [VisualElementExtensions_RU.md](VisualElementExtensions_RU.md)
+
+### Полный пример
+
+```csharp
+using UnityEditor;
+using UnityEngine;
+using Aspid.FastTools.Editors; // GetScriptName
+using Aspid.FastTools.UIElements; // runtime-расширения VisualElement
+using Aspid.FastTools.UIElements.Editors; // AddOpenScriptCommand
+using UnityEngine.UIElements;
+
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ const string iconPath = "Editor/MyIcon";
+
+ var scriptName = target.GetScriptName();
+ var dark = new Color(0.15f, 0.15f, 0.15f);
+ var light = new Color(0.75f, 0.75f, 0.75f);
+
+ return new VisualElement()
+ .SetName("Header")
+ .SetBackgroundColor(dark)
+ .SetFlexDirection(FlexDirection.Row)
+ .SetPadding(top: 5, bottom: 5, left: 10, right: 10)
+ .SetBorderRadius(topLeft: 10, topRight: 10, bottomLeft: 10, bottomRight: 10)
+ .AddChild(new Image()
+ .SetName("Icon")
+ .AddOpenScriptCommand(target)
+ .SetImageFromResource(iconPath)
+ .SetSize(width: 40, height: 40))
+ .AddChild(new Label(scriptName)
+ .SetName("Title")
+ .SetFlexGrow(1)
+ .SetFontSize(16)
+ .SetMargin(left: 10)
+ .SetColor(light)
+ .SetAlignSelf(Align.Center)
+ .SetOverflow(Overflow.Hidden)
+ .SetWhiteSpace(WhiteSpace.NoWrap)
+ .SetTextOverflow(TextOverflow.Ellipsis)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold));
+ }
+}
+```
+
+### Результат
+
+
+
+---
+
+## Вспомогательные расширения для редактора
+
+Утилитарные методы для получения отображаемых имён объектов Unity в пользовательских редакторах.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+```csharp
+public static string GetScriptName(this Object obj)
+```
+
+Возвращает отображаемое имя объекта Unity:
+- Если тип имеет `[AddComponentMenu]`, возвращает `ObjectNames.GetInspectorTitle(obj)`
+- В противном случае возвращает `ObjectNames.NicifyVariableName(typeName)`
+
+```csharp
+public static string GetScriptNameWithIndex(this Component targetComponent)
+```
+
+Возвращает отображаемое имя с числовым суффиксом, если на одном GameObject присутствует несколько компонентов одного типа. Например, если прикреплены два компонента `AudioSource`, второй вернёт `"Audio Source (2)"`.
+
+```csharp
+[CustomEditor(typeof(MyBehaviour))]
+public class MyBehaviourEditor : Editor
+{
+ public override VisualElement CreateInspectorGUI()
+ {
+ // "My Behaviour" — или "Custom Name", если присутствует [AddComponentMenu("Custom Name")]
+ var name = target.GetScriptName();
+
+ // "My Behaviour (2)" при наличии второго компонента того же типа
+ var nameWithIndex = ((Component)target).GetScriptNameWithIndex();
+
+ return new Label(name);
+ }
+}
+```
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md.meta
new file mode 100644
index 0000000..a1ba8c4
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9b6c3feaf2078463b98fab80105c4ed8
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions.md
new file mode 100644
index 0000000..c22b34c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions.md
@@ -0,0 +1,124 @@
+# SerializedProperty Extensions — full reference
+
+Chainable extension methods on `SerializedProperty` for synchronizing the owning `SerializedObject`, setting values, and reflecting on the underlying field.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+All extensions are generic over `T : SerializedProperty` and return the same property instance, so calls can be chained freely.
+
+## Update / Apply
+
+Thin wrappers around the matching `SerializedObject` methods on `property.serializedObject`.
+
+```csharp
+property
+ .Update()
+ .SetInt(42)
+ .ApplyModifiedProperties();
+```
+
+| Method | Description |
+|--------|-------------|
+| `Update()` | Calls `serializedObject.Update()` |
+| `UpdateIfRequiredOrScript()` | Calls `serializedObject.UpdateIfRequiredOrScript()` |
+| `ApplyModifiedProperties()` | Calls `serializedObject.ApplyModifiedProperties()` |
+
+## SetValue / SetXxx — typed setters
+
+For each supported type four variants exist:
+
+| Variant | Behavior |
+|---------|----------|
+| `SetValue(value)` | Generic dispatch — picks the right typed setter based on the value's runtime type, returns `property` |
+| `SetValueAndApply(value)` | `SetValue(value)` followed by `ApplyModifiedProperties()` |
+| `SetXxx(value)` | Typed setter (e.g. `SetInt`) that writes to the matching `SerializedProperty.xxxValue` field |
+| `SetXxxAndApply(value)` | `SetXxx(value)` followed by `ApplyModifiedProperties()` |
+
+### Supported types
+
+| Method family | Unity type | Notes |
+|---------------|------------|-------|
+| `SetInt` | `int` | |
+| `SetUint` | `uint` | |
+| `SetLong` | `long` | |
+| `SetUlong` | `ulong` | |
+| `SetFloat` | `float` | |
+| `SetDouble` | `double` | |
+| `SetBool` | `bool` | |
+| `SetString` | `string` | |
+| `SetColor` | `Color` | |
+| `SetGradient` | `Gradient` | |
+| `SetHash128` | `Hash128` | |
+| `SetRect` / `SetRectInt` | `Rect` / `RectInt` | |
+| `SetBounds` / `SetBoundsInt` | `Bounds` / `BoundsInt` | |
+| `SetVector2` / `SetVector2Int` | `Vector2` / `Vector2Int` | |
+| `SetVector3` / `SetVector3Int` | `Vector3` / `Vector3Int` | |
+| `SetVector4` | `Vector4` | |
+| `SetQuaternion` | `Quaternion` | |
+| `SetAnimationCurve` | `AnimationCurve` | |
+| `SetEntityId` | `Unity.Entities.EntityId` | Unity 6.2+. The apply-variant is named `SetEntityIdApply` *(method name preserves the source typo: missing `And`)* |
+
+### Enum setters
+
+Enum values do not flow through `SetValue` — use the explicit pair below depending on whether the field is a `[Flags]` enum:
+
+| Method | Description |
+|--------|-------------|
+| `SetEnumFlag(int)` / `SetEnumFlagAndApply(int)` | Writes to `enumValueFlag` |
+| `SetEnumIndex(int)` / `SetEnumIndexAndApply(int)` | Writes to `enumValueIndex` |
+
+### Example
+
+```csharp
+SerializedProperty property = GetProperty();
+
+// 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();
+```
+
+## Array operations
+
+| Method | Description |
+|--------|-------------|
+| `SetArraySize(int)` / `SetArraySizeAndApply(int)` | Sets `property.arraySize` |
+| `AddArraySize(int = 1)` / `AddArraySizeAndApply(int = 1)` | Increases `arraySize` by the given amount (default `1`) |
+| `RemoveArraySize(int = 1)` / `RemoveArraySizeAndApply(int = 1)` | Decreases `arraySize` by the given amount (default `1`) |
+
+## Reference setters
+
+| Method | Description | Notes |
+|--------|-------------|-------|
+| `SetManagedReference(object)` / `SetManagedReferenceAndApply(object)` | Writes to `managedReferenceValue` (target must be a `[SerializeReference]` field) | |
+| `SetObjectReference(Object)` / `SetObjectReferenceAndApply(Object)` | Writes to `objectReferenceValue` | |
+| `SetExposedReference(Object)` / `SetExposedReferenceAndApply(Object)` | Writes to `exposedReferenceValue` | |
+| `SetBoxed(object)` / `SetBoxedAndApply(object)` | Writes to `boxedValue` | Unity 6+ |
+
+## Reflection helpers
+
+For drawer / inspector code that needs to inspect the runtime type or instance behind a property:
+
+| Method | Returns | Description |
+|--------|---------|-------------|
+| `GetPropertyType()` | `Type` or `null` | Returns the `FieldType` / `PropertyType` of the C# member that backs the property. `null` if the member can't be resolved. |
+| `GetMemberInfo()` | `MemberInfo` or `null` | Locates the field/property on the owning class whose name matches `SerializedProperty.name`. Walks base classes via `TypeExtensions.GetMembersInfosIncludingBaseClasses`. |
+| `GetClassInstance()` | `object` | Walks `propertyPath` from the root `targetObject` and returns the runtime instance that directly contains this property. Supports nested objects, arrays, and `List` fields. |
+
+```csharp
+public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
+{
+ var declaringType = property.GetPropertyType();
+ var owner = property.GetClassInstance();
+ // …
+}
+```
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions.md.meta
new file mode 100644
index 0000000..78b1558
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: d89b4f8675a09411280afbba6c1aaf33
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions_RU.md
new file mode 100644
index 0000000..f97bf98
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions_RU.md
@@ -0,0 +1,124 @@
+# Расширения SerializedProperty — полный справочник
+
+Цепочные методы расширения над `SerializedProperty` для синхронизации владеющего `SerializedObject`, установки значений и рефлексии над полем-источником.
+
+```csharp
+using Aspid.FastTools.Editors;
+```
+
+Все расширения обобщены по `T : SerializedProperty` и возвращают тот же экземпляр, поэтому вызовы можно свободно объединять в цепочки.
+
+## Update / Apply
+
+Тонкие обёртки над одноимёнными методами `SerializedObject` у `property.serializedObject`.
+
+```csharp
+property
+ .Update()
+ .SetInt(42)
+ .ApplyModifiedProperties();
+```
+
+| Метод | Описание |
+|-------|----------|
+| `Update()` | Вызывает `serializedObject.Update()` |
+| `UpdateIfRequiredOrScript()` | Вызывает `serializedObject.UpdateIfRequiredOrScript()` |
+| `ApplyModifiedProperties()` | Вызывает `serializedObject.ApplyModifiedProperties()` |
+
+## SetValue / SetXxx — типизированные сеттеры
+
+Для каждого поддерживаемого типа существуют четыре варианта:
+
+| Вариант | Поведение |
+|---------|-----------|
+| `SetValue(value)` | Обобщённый диспетчер — выбирает нужный типизированный сеттер по runtime-типу значения, возвращает `property` |
+| `SetValueAndApply(value)` | `SetValue(value)` плюс `ApplyModifiedProperties()` |
+| `SetXxx(value)` | Типизированный сеттер (например, `SetInt`), пишущий в соответствующее поле `SerializedProperty.xxxValue` |
+| `SetXxxAndApply(value)` | `SetXxx(value)` плюс `ApplyModifiedProperties()` |
+
+### Поддерживаемые типы
+
+| Семейство методов | Unity-тип | Примечания |
+|-------------------|-----------|------------|
+| `SetInt` | `int` | |
+| `SetUint` | `uint` | |
+| `SetLong` | `long` | |
+| `SetUlong` | `ulong` | |
+| `SetFloat` | `float` | |
+| `SetDouble` | `double` | |
+| `SetBool` | `bool` | |
+| `SetString` | `string` | |
+| `SetColor` | `Color` | |
+| `SetGradient` | `Gradient` | |
+| `SetHash128` | `Hash128` | |
+| `SetRect` / `SetRectInt` | `Rect` / `RectInt` | |
+| `SetBounds` / `SetBoundsInt` | `Bounds` / `BoundsInt` | |
+| `SetVector2` / `SetVector2Int` | `Vector2` / `Vector2Int` | |
+| `SetVector3` / `SetVector3Int` | `Vector3` / `Vector3Int` | |
+| `SetVector4` | `Vector4` | |
+| `SetQuaternion` | `Quaternion` | |
+| `SetAnimationCurve` | `AnimationCurve` | |
+| `SetEntityId` | `Unity.Entities.EntityId` | Unity 6.2+. Apply-вариант называется `SetEntityIdApply` *(имя метода сохраняет опечатку из исходника: пропущено `And`)* |
+
+### Enum-сеттеры
+
+Значения enum не идут через `SetValue` — используйте явную пару ниже в зависимости от того, является ли поле `[Flags]`-перечислением:
+
+| Метод | Описание |
+|-------|----------|
+| `SetEnumFlag(int)` / `SetEnumFlagAndApply(int)` | Пишет в `enumValueFlag` |
+| `SetEnumIndex(int)` / `SetEnumIndexAndApply(int)` | Пишет в `enumValueIndex` |
+
+### Пример
+
+```csharp
+SerializedProperty property = GetProperty();
+
+// Эквивалентные формы
+property.SetValue(10).ApplyModifiedProperties();
+property.SetValueAndApply(10);
+property.SetInt(10).ApplyModifiedProperties();
+property.SetIntAndApply(10);
+
+// Цепочка из нескольких сеттеров
+property
+ .SetVector3(Vector3.up)
+ .SetBool(true)
+ .ApplyModifiedProperties();
+```
+
+## Операции с массивами
+
+| Метод | Описание |
+|-------|----------|
+| `SetArraySize(int)` / `SetArraySizeAndApply(int)` | Устанавливает `property.arraySize` |
+| `AddArraySize(int = 1)` / `AddArraySizeAndApply(int = 1)` | Увеличивает `arraySize` на указанное количество (по умолчанию `1`) |
+| `RemoveArraySize(int = 1)` / `RemoveArraySizeAndApply(int = 1)` | Уменьшает `arraySize` на указанное количество (по умолчанию `1`) |
+
+## Сеттеры ссылок
+
+| Метод | Описание | Примечания |
+|-------|----------|------------|
+| `SetManagedReference(object)` / `SetManagedReferenceAndApply(object)` | Пишет в `managedReferenceValue` (поле должно быть помечено `[SerializeReference]`) | |
+| `SetObjectReference(Object)` / `SetObjectReferenceAndApply(Object)` | Пишет в `objectReferenceValue` | |
+| `SetExposedReference(Object)` / `SetExposedReferenceAndApply(Object)` | Пишет в `exposedReferenceValue` | |
+| `SetBoxed(object)` / `SetBoxedAndApply(object)` | Пишет в `boxedValue` | Unity 6+ |
+
+## Рефлексионные хелперы
+
+Для drawer-/inspector-кода, которому нужно получить runtime-тип или экземпляр, стоящий за property:
+
+| Метод | Возвращает | Описание |
+|-------|------------|----------|
+| `GetPropertyType()` | `Type` или `null` | Возвращает `FieldType` / `PropertyType` C#-члена, стоящего за property. `null`, если член не удаётся разрешить. |
+| `GetMemberInfo()` | `MemberInfo` или `null` | Находит field/property на классе-владельце, имя которого совпадает с `SerializedProperty.name`. Обходит базовые классы через `TypeExtensions.GetMembersInfosIncludingBaseClasses`. |
+| `GetClassInstance()` | `object` | Идёт по `propertyPath` от корневого `targetObject` и возвращает runtime-экземпляр, который непосредственно содержит это property. Поддерживает вложенные объекты, массивы и `List`-поля. |
+
+```csharp
+public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label)
+{
+ var declaringType = property.GetPropertyType();
+ var owner = property.GetClassInstance();
+ // …
+}
+```
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions_RU.md.meta
new file mode 100644
index 0000000..76e5ed2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/SerializedPropertyExtensions_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: bf2b6a386bf354961a0a7bb99ff06b10
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions.md
new file mode 100644
index 0000000..c6a44b5
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions.md
@@ -0,0 +1,600 @@
+# VisualElement Extensions — full reference
+
+Fluent extension methods for building UIToolkit trees in code. All methods return `T` (the element itself) for chaining.
+
+```csharp
+using Aspid.FastTools.UIElements; // runtime extensions
+using Aspid.FastTools.UIElements.Editors; // editor-only extensions (e.g. AddOpenScriptCommand)
+```
+
+## 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 |
+| `InsertChild(int, VisualElement)` | Inserts a child at the specified index |
+| `InsertChildren(int, params VisualElement[])` | Inserts multiple children starting at an index |
+| `InsertChildren(int, IEnumerable)` | Inserts from a sequence |
+| `InsertChildren(int, List)` | Inserts from a list |
+| `InsertChildren(int, Span)` | Inserts from a span |
+| `InsertChildren(int, ReadOnlySpan)` | Inserts 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?)` | |
+| `SetWidth(StyleLength)` | `width` |
+| `SetMinWidth(StyleLength)` | `minWidth` |
+| `SetMaxWidth(StyleLength)` | `maxWidth` |
+| `SetHeight(StyleLength)` | `height` |
+| `SetMinHeight(StyleLength)` | `minHeight` |
+| `SetMaxHeight(StyleLength)` | `maxHeight` |
+
+### Spacing
+
+All spacing methods have a uniform-value overload, a per-side overload (`top`, `right`, `bottom`, `left`), single-side setters, and X/Y-axis pair setters.
+
+| Method | Style properties |
+|--------|------------------|
+| `SetMargin(…)` / `SetPadding(…)` / `SetDistance(…)` | `Top/Right/Bottom/Left` (uniform or per-side) |
+| `SetMarginX/Y` · `SetPaddingX/Y` · `SetDistanceX/Y` | Sets the horizontal (X = `Left`+`Right`) or vertical (Y = `Top`+`Bottom`) pair |
+| `SetMarginTop/Right/Bottom/Left` | Single-side margin |
+| `SetPaddingTop/Right/Bottom/Left` | Single-side padding |
+| `SetDistanceTop/Right/Bottom/Left` *(via `SetTop` / `SetRight` / `SetBottom` / `SetLeft`)* | Single-side absolute offset (`top` / `right` / `bottom` / `left` style properties) |
+
+> `SetDistance` is the wrapper for the four `top`/`right`/`bottom`/`left` style properties used by absolute positioning. `SetTop`, `SetRight`, `SetBottom`, `SetLeft` are direct single-property aliases.
+
+### 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` |
+| `SetColor(string)` | `color` parsed from an HTML string (`"#RRGGBB"` or a named color) |
+| `SetOpacity(StyleFloat)` | `opacity` |
+
+### Border
+
+| Method | Description |
+|--------|-------------|
+| `SetBorderColor(StyleColor)` | All sides |
+| `SetBorderColor(top?, right?, bottom?, left?)` | Per side |
+| `SetBorderColorX(StyleColor)` · `SetBorderColorY(StyleColor)` | Horizontal (left + right) or vertical (top + bottom) pair |
+| `SetBorderColorTop/Right/Bottom/Left(StyleColor)` | Single side |
+| `SetBorderRadius(StyleLength)` | All corners |
+| `SetBorderRadius(topLeft?, topRight?, bottomLeft?, bottomRight?)` | Per corner |
+| `SetBorderRadiusTop(StyleLength)` · `SetBorderRadiusBottom(StyleLength)` | Top or bottom corner pair |
+| `SetBorderRadiusTopLeft/TopRight/BottomLeft/BottomRight(StyleLength)` | Single corner |
+| `SetBorderWidth(StyleFloat)` | All sides |
+| `SetBorderWidth(top?, right?, bottom?, left?)` | Per side |
+| `SetBorderWidthX(StyleFloat)` · `SetBorderWidthY(StyleFloat)` | Horizontal or vertical pair |
+| `SetBorderWidthTop/Right/Bottom/Left(StyleFloat)` | Single side |
+
+### Background
+
+| Method | Style property |
+|--------|---------------|
+| `SetBackgroundColor(StyleColor)` | `backgroundColor` |
+| `SetBackgroundColor(string)` | `backgroundColor` parsed from an HTML string (`"#RRGGBB"` or a named color) |
+| `SetBackgroundImage(StyleBackground)` | `backgroundImage` |
+| `SetBackgroundImageFromResource(string)` | Loads a `Texture2D` via `Resources.Load` and assigns it to `backgroundImage` |
+| `SetBackgroundSize(StyleBackgroundSize)` | `backgroundSize` |
+| `SetBackgroundRepeat(StyleBackgroundRepeat)` | `backgroundRepeat` |
+| `SetBackgroundPosition(StyleBackgroundPosition)` | Both X and Y |
+| `SetBackgroundPosition(x?, y?)` | Independently |
+| `SetBackgroundPositionX(StyleBackgroundPosition)` | `backgroundPositionX` |
+| `SetBackgroundPositionY(StyleBackgroundPosition)` | `backgroundPositionY` |
+| `SetUnityBackgroundImageTintColor(StyleColor)` | `unityBackgroundImageTintColor` |
+
+### Transform
+
+| Method | Style property |
+|--------|---------------|
+| `SetScale(StyleScale)` | `scale` |
+| `SetRotate(StyleRotate)` | `rotate` |
+| `SetTranslate(StyleTranslate)` | `translate` |
+| `SetTransformOrigin(StyleTransformOrigin)` | `transformOrigin` |
+
+### Aspect, Filter & Material
+
+Available on Unity 6000.3+.
+
+| Method | Style property |
+|--------|---------------|
+| `SetAspectRation(StyleRatio)` | `aspectRatio` *(method name preserves the source typo)* |
+| `SetFilter(StyleList)` | `filter` |
+| `SetUnityMaterial(StyleMaterialDefinition)` | `unityMaterial` |
+
+### 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?, right?, bottom?, left?)` | Per side |
+| `SetUnitySliceX(StyleInt)` · `SetUnitySliceY(StyleInt)` | Horizontal (left + right) or vertical (top + bottom) pair |
+| `SetUnitySliceTop/Right/Bottom/Left(StyleInt)` | Single side |
+| `SetUnitySliceScale(StyleFloat)` | `unitySliceScale` |
+| `SetUnitySliceType(StyleEnum)` | Unity 6+ |
+
+### Cursor
+
+| Method | Style property |
+|--------|---------------|
+| `SetCursor(StyleCursor)` | `cursor` |
+
+## Specialized element extensions
+
+### TextElement
+
+```csharp
+label
+ .SetText("Hello World")
+ .SetEnableRichText(true)
+ .SetParseEscapeSequences(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the displayed text |
+| `SetEnableRichText(bool)` | Enables rich-text tag parsing |
+| `SetEmojiFallbackSupport(bool)` | Enables emoji fallback rendering |
+| `SetParseEscapeSequences(bool)` | Whether escape sequences (e.g. `\n`) are parsed |
+| `SetDisplayTooltipWhenElided(bool)` | Shows the elided text in a tooltip on hover |
+
+### ITextEdition (TextField, IntegerField, …)
+
+```csharp
+textField
+ .SetPlaceholder("Search…")
+ .SetMaxLength(64)
+ .SetIsDelayed(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetMaxLength(int)` | Maximum number of characters |
+| `SetMaskChar(char)` | Character used to mask password input |
+| `SetIsDelayed(bool)` | Defers value change until focus loss / Enter |
+| `SetIsReadOnly(bool)` | Disables editing |
+| `SetIsPassword(bool)` | Toggles password mode (uses mask char) |
+| `SetPlaceholder(string)` | Placeholder text shown when empty |
+| `SetAutoCorrection(bool)` | Enables auto-correction (mobile) |
+| `SetHideMobileInput(bool)` | Hides the mobile soft input |
+| `SetHideSoftKeyboard(bool)` | Hides the on-screen soft keyboard |
+| `SetHidePlaceholderOnFocus(bool)` | Removes the placeholder on focus |
+| `SetKeyboardType(TouchScreenKeyboardType)` | Sets the touch-screen keyboard type |
+
+### ITextSelection
+
+```csharp
+textField
+ .SetIsSelectable(true)
+ .SetSelectAllOnFocus(true)
+ .AddOnCursorIndexChange(() => Debug.Log(textField.cursorIndex));
+```
+
+| Method | Description |
+|--------|-------------|
+| `AddOnCursorIndexChange(Action)` / `RemoveOnCursorIndexChange(Action)` | Cursor-index change subscription |
+| `AddOnSelectIndexChange(Action)` / `RemoveOnSelectIndexChange(Action)` | Selection-index change subscription |
+| `SetCursorIndex(int)` | Sets the current cursor index |
+| `SetSelectIndex(int)` | Sets the current selection anchor |
+| `SetIsSelectable(bool)` | Whether text can be selected |
+| `SetSelectAllOnFocus(bool)` | Selects all text on focus |
+| `SetSelectAllOnMouseUp(bool)` | Selects all text on mouse release |
+| `SetDoubleClickSelectsWord(bool)` | Double-click selects the word under cursor |
+| `SetTripleClickSelectsLine(bool)` | Triple-click selects the line under cursor |
+
+### BaseField\
+
+```csharp
+field.SetLabel("My Field");
+field.SetValue(42);
+```
+
+### BaseBoolField (Toggle)
+
+```csharp
+toggle
+ .SetLabel("Enabled")
+ .SetText("Show advanced settings")
+ .SetToggleOnLabelClick(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the label next to the toggle box |
+| `SetLabel(string)` | Sets the field-level label |
+| `SetToggleOnLabelClick(bool)` | Whether clicking the label toggles the value |
+
+### 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`, `nint`, `nuint`, `long`, `ulong`, `short`, `ushort`, `byte`, `sbyte`, `float`, `double`, `decimal`, `char`, `string`, `bool`, `Color`, `Vector2/3/4`, `Vector2Int/3Int`, `Rect/RectInt`, `Bounds/BoundsInt`, `Hash128`, `GUID`, `Quaternion`, `Matrix4x4`, `Gradient`, `AnimationCurve`, `Delegate`, `Enum`, `Object`, `object`, plus a generic `SetValue` fallback.
+
+> When the `com.unity.mathematics` package is installed, the `ASPID_FASTTOOLS_UNITY_MATHEMATICS_INTEGRATION` define is set automatically and adds `SetValue` / `AddValueChanged` / `RemoveValueChanged` overloads for `int2/3/4` (and `intMxN`), `float2/3/4` (and `floatMxN`), `bool2/3/4` (and `boolMxN`), and `quaternion`.
+
+### 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
+ .SetText("Something went wrong")
+ .SetMessageType(HelpBoxMessageType.Warning);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the help-box message text |
+| `SetMessageType(HelpBoxMessageType)` | Sets the icon / severity (`None` / `Info` / `Warning` / `Error`) |
+
+### Foldout
+
+```csharp
+foldout
+ .SetText("Section Title")
+ .SetToggleOnLabelClick(true)
+ .SetValue(true);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetText(string)` | Sets the foldout title |
+| `SetToggleOnLabelClick(bool)` | Whether clicking the title toggles expansion |
+
+### Image
+
+```csharp
+image
+ .SetImage(myTexture)
+ .SetTintColor(Color.white)
+ .SetScaleMode(ScaleMode.ScaleToFit);
+```
+
+| Method | Description |
+|--------|-------------|
+| `SetImage(Texture)` | Sets `Image.image` |
+| `SetImageFromResource(string)` | Loads a texture via `Resources.Load` |
+| `SetSprite(Sprite)` | Sets `Image.sprite` |
+| `SetSpriteFromResource(string)` | Loads a sprite via `Resources.Load` |
+| `SetVectorImage(VectorImage)` | Sets `Image.vectorImage` |
+| `SetVectorImageFromResource(string)` | Loads a vector image via `Resources.Load` |
+| `SetUv(Rect)` | Sets the UV rect |
+| `SetSourceRect(Rect)` | Sets the source rect |
+| `SetTintColor(Color)` | Sets the image tint |
+| `SetScaleMode(ScaleMode)` | Sets the scale mode |
+
+### 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 |
+
+### Collection views (ListView, TreeView, MultiColumn variants)
+
+Common methods are spread across multiple targeted extensions:
+
+- `BaseVerticalCollectionViewExtensions` — applies to **all** collection views (ListView, TreeView, MultiColumn variants).
+- `BaseListViewExtensions` — applies to ListView and MultiColumnListView.
+- `BaseTreeViewExtensions` — applies to TreeView and MultiColumnTreeView.
+- `ListViewExtensions` / `TreeViewExtensions` — `MakeItem`/`BindItem`/`UnbindItem`/`DestroyItem` factories per view.
+- `MultiColumnListViewExtensions` / `MultiColumnTreeViewExtensions` — multi-column-specific helpers.
+
+```csharp
+listView
+ .SetItemsSource(items)
+ .SetMakeItem(() => new Label())
+ .SetBindItem((el, i) => ((Label)el).SetText(items[i]))
+ .SetSelectionType(SelectionType.Single)
+ .AddSelectionChanged(selected => Debug.Log(selected));
+```
+
+#### Source, layout and behavior — `BaseVerticalCollectionView`
+
+| Method | Description | Notes |
+|--------|-------------|-------|
+| `SetItemsSource(IList)` | Underlying data source | |
+| `SetReorderable(bool)` | Enables drag-to-reorder | |
+| `SetSelectedIndex(int)` | Selects a specific index | |
+| `SetSelectionType(SelectionType)` | None / Single / Multiple | |
+| `SetFixedItemHeight(float)` | Fixed item height (for `FixedHeight` virtualization) | |
+| `SetVirtualizationMethod(CollectionVirtualizationMethod)` | `FixedHeight` or `DynamicHeight` | |
+| `SetHorizontalScrollingEnabled(bool)` | Enables horizontal scrolling | |
+| `SetShowAlternatingRowBackgrounds(AlternatingRowBackground)` | Zebra striping mode | |
+| `SetMakeFooter(Func)` · `AddMakeFooter` · `RemoveMakeFooter` | Footer factory | Unity 6+ |
+| `SetMakeHeader(Func)` · `AddMakeHeader` · `RemoveMakeHeader` | Header factory | Unity 6+ |
+| `SetMakeNoneElement(Func)` · `AddMakeNoneElement` · `RemoveMakeNoneElement` | Empty-state factory | Unity 6+ |
+
+#### Events — `BaseVerticalCollectionView`
+
+| Method | Description |
+|--------|-------------|
+| `AddItemsChosen(Action>)` / `RemoveItemsChosen` | Items confirmed (e.g. double-click / Enter) |
+| `AddSelectionChanged(Action>)` / `RemoveSelectionChanged` | Selection changed (objects) |
+| `AddSelectedIndicesChanged(Action>)` / `RemoveSelectedIndicesChanged` | Selection changed (indices) |
+| `AddItemIndexChanged(Action)` / `RemoveItemIndexChanged` | Item moved (drag-reorder) |
+| `AddItemsSourceChanged(Action)` / `RemoveItemsSourceChanged` | `itemsSource` reference changed |
+| `AddCanStartDrag(Func)` / `RemoveCanStartDrag` | Custom drag-start gating |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Drag-and-drop preparation |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Drag-and-drop visual mode |
+| `AddHandleDrop(Func)` / `RemoveHandleDrop` | Drop handling |
+
+#### `BaseListView`-specific
+
+| Method | Description |
+|--------|-------------|
+| `SetAllowAdd(bool)` · `SetAllowRemove(bool)` | Toggles built-in add/remove buttons |
+| `SetHeaderTitle(string)` | Title shown when foldout header is on |
+| `SetShowFoldoutHeader(bool)` | Wraps the list in a `Foldout` |
+| `SetShowAddRemoveFooter(bool)` | Toggles the add/remove footer |
+| `SetShowBoundCollectionSize(bool)` | Shows the collection-size field |
+| `SetReorderMode(ListViewReorderMode)` | `Simple` or `Animated` |
+| `SetBindingSourceSelectionMode(BindingSourceSelectionMode)` | Auto-assign / manual |
+| `SetOnAdd(Action)` · `AddOnAdd` · `RemoveOnAdd` | Custom add-button callback |
+| `SetOnRemove(Action)` · `AddOnRemove` · `RemoveOnRemove` | Custom remove-button callback |
+| `SetOverridingAddButtonBehavior(Action)` · `AddOverridingAddButtonBehavior` · `RemoveOverridingAddButtonBehavior` | Replace default add-button click |
+| `AddItemsAdded(Action>)` / `RemoveItemsAdded` | Items added by index |
+| `AddItemsRemoved(Action>)` / `RemoveItemsRemoved` | Items removed by index |
+
+#### `BaseTreeView`-specific
+
+| Method | Description |
+|--------|-------------|
+| `SetAutoExpand(bool)` | Auto-expand new nodes |
+| `AddItemExpandedChanged(Action)` / `RemoveItemExpandedChanged` | Subscription to expansion changes |
+
+#### `ListView` / `TreeView` item factories
+
+These methods are duplicated across `ListViewExtensions` and `TreeViewExtensions` (each operating on its own view type).
+
+| Method | Description |
+|--------|-------------|
+| `SetMakeItem(Func)` · `AddMakeItem` · `RemoveMakeItem` | Item factory |
+| `SetBindItem(Action)` · `AddBindItem` · `RemoveBindItem` | Item binding |
+| `SetUnbindItem(Action)` · `AddUnbindItem` · `RemoveUnbindItem` | Item unbinding |
+| `SetDestroyItem(Action)` · `AddDestroyItem` · `RemoveDestroyItem` | Item teardown |
+| `SetItemTemplate(VisualTreeAsset)` | UXML template used to build items |
+
+#### `MultiColumnListView` / `MultiColumnTreeView`
+
+| Method | Description |
+|--------|-------------|
+| `SetSortingMode(ColumnSortingMode)` | Built-in sorting mode for the column header |
+
+## Editor commands (editor-only)
+
+```csharp
+using Aspid.FastTools.UIElements.Editors;
+
+image.AddOpenScriptCommand(target);
+// Double-clicking the element opens the script for 'target' in the IDE
+```
+
+| Method | Target | Description |
+|--------|--------|-------------|
+| `AddOpenScriptCommand(Object)` | `VisualElement` | Registers a double-click handler that opens the source script for the given `MonoBehaviour` / `ScriptableObject` in the IDE. |
+| `BindTo(SerializedObject)` | `VisualElement` | Calls `BindingExtensions.Bind` on the element. |
+| `BindTo(SerializedObject, string propertyPath)` | `IBindable` | Sets `bindingPath` and binds to the given `SerializedObject`. |
+| `BindPropertyTo(SerializedProperty)` | `IBindable` | Calls `BindingExtensions.BindProperty` with the supplied property. |
+| `Initialize(Enum defaultValue, bool includeObsoleteValues = false)` | `EnumField` / `EnumFlagsField` | Initializes the field to the supplied default enum value. |
+| `AddValueChanged(EventCallback)` / `RemoveValueChanged(...)` | `PropertyField` | Subscribes / unsubscribes to property change notifications. |
+
+## USS custom-style helpers (`ICustomStyle`)
+
+```csharp
+using Aspid.FastTools.UIElements;
+
+private static readonly CustomStyleProperty ThemeProperty = new("--aspid-fasttools-prop-theme");
+
+void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
+{
+ if (evt.customStyle.TryGetByEnum(ThemeProperty, out ThemeStyle.Type theme))
+ ApplyTheme(theme);
+}
+```
+
+| Method | Description |
+|--------|-------------|
+| `ICustomStyle.TryGetByEnum(CustomStyleProperty, out T)` | Resolves a string-typed USS custom property and parses it case-insensitively as the enum `T`. Used by every `*Style` struct that exposes a USS-driven enum (`ThemeStyle`, `StatusStyle`, `AspidLabelSizeStyle`, etc.). |
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions.md.meta
new file mode 100644
index 0000000..9a0b54f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a732922edf5904ae49ce09e54bf91bf6
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions_RU.md
new file mode 100644
index 0000000..92e5d2a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions_RU.md
@@ -0,0 +1,600 @@
+# Расширения VisualElement — полный справочник
+
+Fluent-методы расширения для построения UIToolkit-деревьев в коде. Все методы возвращают `T` (сам элемент) для цепочки вызовов.
+
+```csharp
+using Aspid.FastTools.UIElements; // runtime-расширения
+using Aspid.FastTools.UIElements.Editors; // editor-only расширения (например, AddOpenScriptCommand)
+```
+
+## Основные операции с элементами
+
+```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 |
+| `InsertChild(int, VisualElement)` | Вставляет дочерний элемент по указанному индексу |
+| `InsertChildren(int, params VisualElement[])` | Вставляет несколько дочерних элементов начиная с индекса |
+| `InsertChildren(int, IEnumerable)` | Вставляет из последовательности |
+| `InsertChildren(int, List)` | Вставляет из списка |
+| `InsertChildren(int, Span)` | Вставляет из span |
+| `InsertChildren(int, 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?)` | |
+| `SetWidth(StyleLength)` | `width` |
+| `SetMinWidth(StyleLength)` | `minWidth` |
+| `SetMaxWidth(StyleLength)` | `maxWidth` |
+| `SetHeight(StyleLength)` | `height` |
+| `SetMinHeight(StyleLength)` | `minHeight` |
+| `SetMaxHeight(StyleLength)` | `maxHeight` |
+
+### Отступы
+
+Все методы отступов имеют перегрузку с единым значением, перегрузку по сторонам (`top`, `right`, `bottom`, `left`), сеттеры по одной стороне и сеттеры по парам осей X/Y.
+
+| Метод | Свойства стиля |
+|-------|----------------|
+| `SetMargin(…)` / `SetPadding(…)` / `SetDistance(…)` | `Top/Right/Bottom/Left` (общее значение или per-side) |
+| `SetMarginX/Y` · `SetPaddingX/Y` · `SetDistanceX/Y` | Устанавливает горизонтальную (X = `Left`+`Right`) или вертикальную (Y = `Top`+`Bottom`) пару |
+| `SetMarginTop/Right/Bottom/Left` | Margin одной стороны |
+| `SetPaddingTop/Right/Bottom/Left` | Padding одной стороны |
+| `SetDistanceTop/Right/Bottom/Left` *(через `SetTop` / `SetRight` / `SetBottom` / `SetLeft`)* | Смещение одной стороны для абсолютного позиционирования (свойства `top` / `right` / `bottom` / `left`) |
+
+> `SetDistance` — обёртка для четырёх свойств `top`/`right`/`bottom`/`left`, используемых при абсолютном позиционировании. `SetTop`, `SetRight`, `SetBottom`, `SetLeft` — это прямые алиасы для одного свойства.
+
+### Шрифт
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `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` |
+| `SetColor(string)` | `color`, разобранный из HTML-строки (`"#RRGGBB"` или именованный цвет) |
+| `SetOpacity(StyleFloat)` | `opacity` |
+
+### Рамка
+
+| Метод | Описание |
+|-------|----------|
+| `SetBorderColor(StyleColor)` | Все стороны |
+| `SetBorderColor(top?, right?, bottom?, left?)` | По стороне |
+| `SetBorderColorX(StyleColor)` · `SetBorderColorY(StyleColor)` | Горизонтальная (left + right) или вертикальная (top + bottom) пара |
+| `SetBorderColorTop/Right/Bottom/Left(StyleColor)` | Одна сторона |
+| `SetBorderRadius(StyleLength)` | Все углы |
+| `SetBorderRadius(topLeft?, topRight?, bottomLeft?, bottomRight?)` | По углу |
+| `SetBorderRadiusTop(StyleLength)` · `SetBorderRadiusBottom(StyleLength)` | Пара верхних или нижних углов |
+| `SetBorderRadiusTopLeft/TopRight/BottomLeft/BottomRight(StyleLength)` | Один угол |
+| `SetBorderWidth(StyleFloat)` | Все стороны |
+| `SetBorderWidth(top?, right?, bottom?, left?)` | По стороне |
+| `SetBorderWidthX(StyleFloat)` · `SetBorderWidthY(StyleFloat)` | Горизонтальная или вертикальная пара |
+| `SetBorderWidthTop/Right/Bottom/Left(StyleFloat)` | Одна сторона |
+
+### Фон
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetBackgroundColor(StyleColor)` | `backgroundColor` |
+| `SetBackgroundColor(string)` | `backgroundColor`, разобранный из HTML-строки (`"#RRGGBB"` или именованный цвет) |
+| `SetBackgroundImage(StyleBackground)` | `backgroundImage` |
+| `SetBackgroundImageFromResource(string)` | Загружает `Texture2D` через `Resources.Load` и присваивает его в `backgroundImage` |
+| `SetBackgroundSize(StyleBackgroundSize)` | `backgroundSize` |
+| `SetBackgroundRepeat(StyleBackgroundRepeat)` | `backgroundRepeat` |
+| `SetBackgroundPosition(StyleBackgroundPosition)` | X и Y одновременно |
+| `SetBackgroundPosition(x?, y?)` | Независимо |
+| `SetBackgroundPositionX(StyleBackgroundPosition)` | `backgroundPositionX` |
+| `SetBackgroundPositionY(StyleBackgroundPosition)` | `backgroundPositionY` |
+| `SetUnityBackgroundImageTintColor(StyleColor)` | `unityBackgroundImageTintColor` |
+
+### Трансформации
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetScale(StyleScale)` | `scale` |
+| `SetRotate(StyleRotate)` | `rotate` |
+| `SetTranslate(StyleTranslate)` | `translate` |
+| `SetTransformOrigin(StyleTransformOrigin)` | `transformOrigin` |
+
+### Aspect, Filter и Material
+
+Доступно начиная с Unity 6000.3+.
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetAspectRation(StyleRatio)` | `aspectRatio` *(имя метода сохраняет опечатку из исходника)* |
+| `SetFilter(StyleList)` | `filter` |
+| `SetUnityMaterial(StyleMaterialDefinition)` | `unityMaterial` |
+
+### Анимации переходов
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `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?, right?, bottom?, left?)` | По стороне |
+| `SetUnitySliceX(StyleInt)` · `SetUnitySliceY(StyleInt)` | Горизонтальная (left + right) или вертикальная (top + bottom) пара |
+| `SetUnitySliceTop/Right/Bottom/Left(StyleInt)` | Одна сторона |
+| `SetUnitySliceScale(StyleFloat)` | `unitySliceScale` |
+| `SetUnitySliceType(StyleEnum)` | Unity 6+ |
+
+### Курсор
+
+| Метод | Свойство стиля |
+|-------|----------------|
+| `SetCursor(StyleCursor)` | `cursor` |
+
+## Расширения для специализированных элементов
+
+### TextElement
+
+```csharp
+label
+ .SetText("Hello World")
+ .SetEnableRichText(true)
+ .SetParseEscapeSequences(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Устанавливает отображаемый текст |
+| `SetEnableRichText(bool)` | Включает разбор тегов rich-text |
+| `SetEmojiFallbackSupport(bool)` | Включает emoji-fallback при рендеринге |
+| `SetParseEscapeSequences(bool)` | Обрабатывать ли escape-последовательности (например, `\n`) |
+| `SetDisplayTooltipWhenElided(bool)` | Показывать обрезанный текст в подсказке при наведении |
+
+### ITextEdition (TextField, IntegerField, …)
+
+```csharp
+textField
+ .SetPlaceholder("Поиск…")
+ .SetMaxLength(64)
+ .SetIsDelayed(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetMaxLength(int)` | Максимальное число символов |
+| `SetMaskChar(char)` | Символ для маскировки пароля |
+| `SetIsDelayed(bool)` | Откладывает изменение значения до потери фокуса / Enter |
+| `SetIsReadOnly(bool)` | Запрещает редактирование |
+| `SetIsPassword(bool)` | Включает password-режим (использует mask char) |
+| `SetPlaceholder(string)` | Текст-плейсхолдер для пустого поля |
+| `SetAutoCorrection(bool)` | Включает автокоррекцию (mobile) |
+| `SetHideMobileInput(bool)` | Скрывает мобильный soft input |
+| `SetHideSoftKeyboard(bool)` | Скрывает экранную клавиатуру |
+| `SetHidePlaceholderOnFocus(bool)` | Убирает плейсхолдер при фокусе |
+| `SetKeyboardType(TouchScreenKeyboardType)` | Тип touch-screen клавиатуры |
+
+### ITextSelection
+
+```csharp
+textField
+ .SetIsSelectable(true)
+ .SetSelectAllOnFocus(true)
+ .AddOnCursorIndexChange(() => Debug.Log(textField.cursorIndex));
+```
+
+| Метод | Описание |
+|-------|----------|
+| `AddOnCursorIndexChange(Action)` / `RemoveOnCursorIndexChange(Action)` | Подписка на изменение позиции курсора |
+| `AddOnSelectIndexChange(Action)` / `RemoveOnSelectIndexChange(Action)` | Подписка на изменение якоря выделения |
+| `SetCursorIndex(int)` | Текущая позиция курсора |
+| `SetSelectIndex(int)` | Текущий якорь выделения |
+| `SetIsSelectable(bool)` | Можно ли выделять текст |
+| `SetSelectAllOnFocus(bool)` | Выделять весь текст при фокусе |
+| `SetSelectAllOnMouseUp(bool)` | Выделять весь текст при отпускании мыши |
+| `SetDoubleClickSelectsWord(bool)` | Двойной клик выделяет слово |
+| `SetTripleClickSelectsLine(bool)` | Тройной клик выделяет строку |
+
+### BaseField\
+
+```csharp
+field.SetLabel("My Field");
+field.SetValue(42);
+```
+
+### BaseBoolField (Toggle)
+
+```csharp
+toggle
+ .SetLabel("Включено")
+ .SetText("Показать расширенные настройки")
+ .SetToggleOnLabelClick(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Устанавливает текст рядом с чекбоксом |
+| `SetLabel(string)` | Устанавливает label поля |
+| `SetToggleOnLabelClick(bool)` | Переключать ли значение по клику на label |
+
+### INotifyValueChanged\
+
+```csharp
+field.SetValue(42, notify: false); // устанавливает значение без генерации ChangeEvent
+field.AddValueChanged(evt => Debug.Log(evt.newValue));
+field.RemoveValueChanged(myCallback);
+```
+
+Типизированные перегрузки доступны для `int`, `uint`, `nint`, `nuint`, `long`, `ulong`, `short`, `ushort`, `byte`, `sbyte`, `float`, `double`, `decimal`, `char`, `string`, `bool`, `Color`, `Vector2/3/4`, `Vector2Int/3Int`, `Rect/RectInt`, `Bounds/BoundsInt`, `Hash128`, `GUID`, `Quaternion`, `Matrix4x4`, `Gradient`, `AnimationCurve`, `Delegate`, `Enum`, `Object`, `object`, плюс обобщённый fallback `SetValue`.
+
+> При установленном пакете `com.unity.mathematics` автоматически выставляется define `ASPID_FASTTOOLS_UNITY_MATHEMATICS_INTEGRATION` и добавляются перегрузки `SetValue` / `AddValueChanged` / `RemoveValueChanged` для `int2/3/4` (и `intMxN`), `float2/3/4` (и `floatMxN`), `bool2/3/4` (и `boolMxN`), а также `quaternion`.
+
+### 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
+ .SetText("Что-то пошло не так")
+ .SetMessageType(HelpBoxMessageType.Warning);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Текст сообщения help-box |
+| `SetMessageType(HelpBoxMessageType)` | Иконка / уровень (`None` / `Info` / `Warning` / `Error`) |
+
+### Foldout
+
+```csharp
+foldout
+ .SetText("Section Title")
+ .SetToggleOnLabelClick(true)
+ .SetValue(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetText(string)` | Заголовок foldout |
+| `SetToggleOnLabelClick(bool)` | Переключать ли раскрытие по клику на заголовок |
+
+### Image
+
+```csharp
+image
+ .SetImage(myTexture)
+ .SetTintColor(Color.white)
+ .SetScaleMode(ScaleMode.ScaleToFit);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetImage(Texture)` | Устанавливает `Image.image` |
+| `SetImageFromResource(string)` | Загружает текстуру через `Resources.Load` |
+| `SetSprite(Sprite)` | Устанавливает `Image.sprite` |
+| `SetSpriteFromResource(string)` | Загружает sprite через `Resources.Load` |
+| `SetVectorImage(VectorImage)` | Устанавливает `Image.vectorImage` |
+| `SetVectorImageFromResource(string)` | Загружает vector image через `Resources.Load` |
+| `SetUv(Rect)` | Устанавливает UV-rect |
+| `SetSourceRect(Rect)` | Устанавливает source rect |
+| `SetTintColor(Color)` | Цветовой tint изображения |
+| `SetScaleMode(ScaleMode)` | Режим масштабирования |
+
+### IMGUIContainer
+
+```csharp
+container
+ .SetOnGUIHandler(() => GUILayout.Label("IMGUI"))
+ .SetCullingEnabled(true);
+```
+
+| Метод | Описание |
+|-------|----------|
+| `SetOnGUIHandler(Action)` | Заменяет коллбэк `onGUIHandler` |
+| `AddOnGUIHandler(Action)` | Подписка на `onGUIHandler` |
+| `RemoveOnGUIHandler(Action)` | Отписка от `onGUIHandler` |
+| `SetCullingEnabled(bool)` | Пропускает `onGUIHandler`, когда элемент за пределами экрана |
+| `SetContextType(ContextType)` | Устанавливает тип контекста IMGUI |
+
+### Collection-views (ListView, TreeView, MultiColumn-варианты)
+
+Общие методы распределены по нескольким специализированным расширениям:
+
+- `BaseVerticalCollectionViewExtensions` — применяется ко **всем** collection-views (ListView, TreeView, MultiColumn-варианты).
+- `BaseListViewExtensions` — применяется к ListView и MultiColumnListView.
+- `BaseTreeViewExtensions` — применяется к TreeView и MultiColumnTreeView.
+- `ListViewExtensions` / `TreeViewExtensions` — фабрики `MakeItem`/`BindItem`/`UnbindItem`/`DestroyItem` для своего вью.
+- `MultiColumnListViewExtensions` / `MultiColumnTreeViewExtensions` — хелперы для multi-column-вариантов.
+
+```csharp
+listView
+ .SetItemsSource(items)
+ .SetMakeItem(() => new Label())
+ .SetBindItem((el, i) => ((Label)el).SetText(items[i]))
+ .SetSelectionType(SelectionType.Single)
+ .AddSelectionChanged(selected => Debug.Log(selected));
+```
+
+#### Источник, layout и поведение — `BaseVerticalCollectionView`
+
+| Метод | Описание | Примечания |
+|-------|----------|------------|
+| `SetItemsSource(IList)` | Источник данных | |
+| `SetReorderable(bool)` | Включает drag-reorder | |
+| `SetSelectedIndex(int)` | Выбирает элемент по индексу | |
+| `SetSelectionType(SelectionType)` | None / Single / Multiple | |
+| `SetFixedItemHeight(float)` | Фиксированная высота элемента (для виртуализации `FixedHeight`) | |
+| `SetVirtualizationMethod(CollectionVirtualizationMethod)` | `FixedHeight` или `DynamicHeight` | |
+| `SetHorizontalScrollingEnabled(bool)` | Включает горизонтальную прокрутку | |
+| `SetShowAlternatingRowBackgrounds(AlternatingRowBackground)` | Режим зебра-полос | |
+| `SetMakeFooter(Func)` · `AddMakeFooter` · `RemoveMakeFooter` | Фабрика подвала | Unity 6+ |
+| `SetMakeHeader(Func)` · `AddMakeHeader` · `RemoveMakeHeader` | Фабрика заголовка | Unity 6+ |
+| `SetMakeNoneElement(Func)` · `AddMakeNoneElement` · `RemoveMakeNoneElement` | Фабрика empty-state | Unity 6+ |
+
+#### События — `BaseVerticalCollectionView`
+
+| Метод | Описание |
+|-------|----------|
+| `AddItemsChosen(Action>)` / `RemoveItemsChosen` | Подтверждение элементов (двойной клик / Enter) |
+| `AddSelectionChanged(Action>)` / `RemoveSelectionChanged` | Изменение выделения (объекты) |
+| `AddSelectedIndicesChanged(Action>)` / `RemoveSelectedIndicesChanged` | Изменение выделения (индексы) |
+| `AddItemIndexChanged(Action)` / `RemoveItemIndexChanged` | Перемещение элемента (drag-reorder) |
+| `AddItemsSourceChanged(Action)` / `RemoveItemsSourceChanged` | Смена ссылки `itemsSource` |
+| `AddCanStartDrag(Func)` / `RemoveCanStartDrag` | Кастомный gating старта drag |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Подготовка drag-and-drop |
+| `AddSetupDragAndDrop(Func)` / `RemoveSetupDragAndDrop` | Визуальный режим drag-and-drop |
+| `AddHandleDrop(Func)` / `RemoveHandleDrop` | Обработка drop |
+
+#### Только для `BaseListView`
+
+| Метод | Описание |
+|-------|----------|
+| `SetAllowAdd(bool)` · `SetAllowRemove(bool)` | Включают встроенные кнопки add/remove |
+| `SetHeaderTitle(string)` | Заголовок при включённом foldout-header |
+| `SetShowFoldoutHeader(bool)` | Оборачивает список в `Foldout` |
+| `SetShowAddRemoveFooter(bool)` | Показывает footer с add/remove |
+| `SetShowBoundCollectionSize(bool)` | Поле размера коллекции |
+| `SetReorderMode(ListViewReorderMode)` | `Simple` или `Animated` |
+| `SetBindingSourceSelectionMode(BindingSourceSelectionMode)` | Auto-assign / manual |
+| `SetOnAdd(Action)` · `AddOnAdd` · `RemoveOnAdd` | Кастомный коллбэк add-кнопки |
+| `SetOnRemove(Action)` · `AddOnRemove` · `RemoveOnRemove` | Кастомный коллбэк remove-кнопки |
+| `SetOverridingAddButtonBehavior(Action)` · `AddOverridingAddButtonBehavior` · `RemoveOverridingAddButtonBehavior` | Подменяет дефолтное поведение add |
+| `AddItemsAdded(Action>)` / `RemoveItemsAdded` | Добавление элементов по индексам |
+| `AddItemsRemoved(Action>)` / `RemoveItemsRemoved` | Удаление элементов по индексам |
+
+#### Только для `BaseTreeView`
+
+| Метод | Описание |
+|-------|----------|
+| `SetAutoExpand(bool)` | Авто-разворачивание новых узлов |
+| `AddItemExpandedChanged(Action)` / `RemoveItemExpandedChanged` | Подписка на изменение раскрытия |
+
+#### Item-фабрики `ListView` / `TreeView`
+
+Эти методы дублируются в `ListViewExtensions` и `TreeViewExtensions` (каждое работает со своим типом view).
+
+| Метод | Описание |
+|-------|----------|
+| `SetMakeItem(Func)` · `AddMakeItem` · `RemoveMakeItem` | Фабрика элементов |
+| `SetBindItem(Action)` · `AddBindItem` · `RemoveBindItem` | Привязка элемента |
+| `SetUnbindItem(Action)` · `AddUnbindItem` · `RemoveUnbindItem` | Отвязка элемента |
+| `SetDestroyItem(Action)` · `AddDestroyItem` · `RemoveDestroyItem` | Уничтожение элемента |
+| `SetItemTemplate(VisualTreeAsset)` | UXML-шаблон, по которому строятся элементы |
+
+#### `MultiColumnListView` / `MultiColumnTreeView`
+
+| Метод | Описание |
+|-------|----------|
+| `SetSortingMode(ColumnSortingMode)` | Встроенный режим сортировки заголовка колонки |
+
+## Команды редактора (только для редактора)
+
+```csharp
+using Aspid.FastTools.UIElements.Editors;
+
+image.AddOpenScriptCommand(target);
+// Двойной клик на элемент открывает скрипт 'target' в IDE
+```
+
+| Метод | Цель | Описание |
+|-------|------|----------|
+| `AddOpenScriptCommand(Object)` | `VisualElement` | Регистрирует обработчик двойного клика, открывающий исходный скрипт `MonoBehaviour` / `ScriptableObject` в IDE. |
+| `BindTo(SerializedObject)` | `VisualElement` | Вызывает `BindingExtensions.Bind` на элементе. |
+| `BindTo(SerializedObject, string propertyPath)` | `IBindable` | Устанавливает `bindingPath` и привязывается к указанному `SerializedObject`. |
+| `BindPropertyTo(SerializedProperty)` | `IBindable` | Вызывает `BindingExtensions.BindProperty` для переданного property. |
+| `Initialize(Enum defaultValue, bool includeObsoleteValues = false)` | `EnumField` / `EnumFlagsField` | Инициализирует поле указанным значением enum по умолчанию. |
+| `AddValueChanged(EventCallback)` / `RemoveValueChanged(...)` | `PropertyField` | Подписка / отписка от уведомлений об изменении свойства. |
+
+## USS custom-style helpers (`ICustomStyle`)
+
+```csharp
+using Aspid.FastTools.UIElements;
+
+private static readonly CustomStyleProperty ThemeProperty = new("--aspid-fasttools-prop-theme");
+
+void OnCustomStyleResolved(CustomStyleResolvedEvent evt)
+{
+ if (evt.customStyle.TryGetByEnum(ThemeProperty, out ThemeStyle.Type theme))
+ ApplyTheme(theme);
+}
+```
+
+| Метод | Описание |
+|-------|----------|
+| `ICustomStyle.TryGetByEnum(CustomStyleProperty, out T)` | Резолвит USS custom-property со строковым значением и парсит её регистронезависимо как enum `T`. Используется во всех `*Style`-структурах с USS-driven enum (`ThemeStyle`, `StatusStyle`, `AspidLabelSizeStyle` и т. д.). |
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions_RU.md.meta
new file mode 100644
index 0000000..33dbcc0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Documentation/VisualElementExtensions_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a181f7da19b70437eb557c0cda2dad2a
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues.meta
similarity index 77%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues.meta
index a72d9f7..2580f46 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c6490710e847c4157a38f045d5205648
+guid: 7c561c66bd9a74b5b910a51cbf1bcca3
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs.meta
similarity index 67%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs.meta
index f833786..d27664c 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs.meta
@@ -1,5 +1,6 @@
fileFormatVersion: 2
-guid: 55b0a716a25414d609e45c84c5e34851
+guid: e9d7c8b6a5f4e3d2c1b0a9f8e7d6c5b4
+folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab
new file mode 100644
index 0000000..3b8c25c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab
@@ -0,0 +1,112 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: DamageDealer
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 3bd9644f273549748da7b083e4de1b37, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.EnumValues::Aspid.FastTools.Samples.EnumValues.DamageDealer
+ _damageMultipliers:
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _defaultValue: 1
+ _values:
+ - _key: Physical
+ _value: 1
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Fire
+ _value: 1.5
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Ice
+ _value: 0.8
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Poison
+ _value: 0.6
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _damageColors:
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _defaultValue: {r: 1, g: 1, b: 1, a: 1}
+ _values:
+ - _key: Physical
+ _value: {r: 0.85, g: 0.85, b: 0.85, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Fire
+ _value: {r: 1, g: 0.5, b: 0.1, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Ice
+ _value: {r: 0.4, g: 0.8, b: 1, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Poison
+ _value: {r: 0.5, g: 0.9, b: 0.3, a: 1}
+ _enumType: Aspid.FastTools.Samples.EnumValues.DamageType, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _speedMultipliersByStatus:
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _defaultValue: 1
+ _values:
+ - _key: Burning, Slowed
+ _value: 0.4
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Burning
+ _value: 1
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Frozen
+ _value: 0.2
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - _key: Slowed
+ _value: 0.5
+ _enumType: Aspid.FastTools.Samples.EnumValues.StatusEffect, Aspid.FastTools.Samples.EnumValues,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _currentDamageType: 1
+ _activeEffects: 5
+ _baseDamage: 10
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab.meta
similarity index 74%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab.meta
index cfdabfd..78d6e5a 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/Types/Prefabs/TypeSelectorTest.prefab.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Prefabs/EnumValues.prefab.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: d6fe21a6dc34048eea8586d6afdbd9f2
+guid: b6af73ff1ec54f22b2b637c93cf4287b
PrefabImporter:
externalObjects: {}
userData:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README.md
new file mode 100644
index 0000000..f080c2e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README.md
@@ -0,0 +1,21 @@
+# EnumValues Sample
+
+A tiny combat damage system that maps enum members to typed values through `EnumValues`. `DamageDealer` picks a `DamageType` and `StatusEffect` in the Inspector, then on `Space` applies damage — pulling the damage multiplier, log color, and speed modifier from three `EnumValues` fields.
+
+Look at:
+- `Scripts/DamageDealer.cs:9` — `EnumValues` mapping `DamageType` to damage multiplier.
+- `Scripts/DamageDealer.cs:10` — `EnumValues` mapping `DamageType` to debug log color.
+- `Scripts/DamageDealer.cs:14` — `EnumValues` keyed on `[Flags]` enum `StatusEffect`; composite entries like `Burning | Slowed` must come before single-flag entries (see the inline comment).
+- `Scripts/DamageDealer.cs:30` — `GetValue` call on the Flags-keyed field.
+- `Scripts/StatusEffect.cs` — `[Flags]` enum used by the third mapping.
+
+## How to run
+
+Open `Scenes/EnumValues.unity` and enter Play Mode. The scene hosts a `DamageDealer` wired up from `Prefabs/EnumValues.prefab`, which is pre-seeded with:
+
+- `_damageMultipliers`: `Physical = 1.0`, `Fire = 1.5`, `Ice = 0.8`, `Poison = 0.6`.
+- `_damageColors`: grey / orange / cyan / acid-green per `DamageType`.
+- `_speedMultipliersByStatus`: `Burning | Slowed = 0.4` **first**, then `Burning = 1.0`, `Frozen = 0.2`, `Slowed = 0.5` — composite-first ordering is what makes the combined flag resolve to `0.4` instead of falling through to the first single-flag match.
+- `_currentDamageType = Fire`, `_activeEffects = Burning | Slowed`, `_baseDamage = 10`.
+
+Press `Space` and the Console prints `Fire hit: 15 dmg (speed mod: 0.40)` in orange. Change the enums in the Inspector (or toggle flags in `_activeEffects`) to see different lookups.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README.md.meta
new file mode 100644
index 0000000..7aa3b86
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 5fb2e3aa6c2a846b4b53ffdd69fccf9b
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README_RU.md
new file mode 100644
index 0000000..3d47f2d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README_RU.md
@@ -0,0 +1,21 @@
+# Пример EnumValues
+
+Маленькая система боевого урона, которая сопоставляет члены enum типизированным значениям через `EnumValues`. `DamageDealer` выбирает `DamageType` и `StatusEffect` в Inspector, а по нажатию `Space` наносит урон — извлекая множитель урона, цвет лога и модификатор скорости из трёх полей `EnumValues`.
+
+Смотрите:
+- `Scripts/DamageDealer.cs:9` — `EnumValues`, сопоставляющий `DamageType` множителю урона.
+- `Scripts/DamageDealer.cs:10` — `EnumValues`, сопоставляющий `DamageType` цвету отладочного лога.
+- `Scripts/DamageDealer.cs:14` — `EnumValues` по `[Flags]` enum `StatusEffect`; композитные записи вроде `Burning | Slowed` должны идти до одиночных флагов (см. комментарий рядом).
+- `Scripts/DamageDealer.cs:30` — вызов `GetValue` на Flags-поле.
+- `Scripts/StatusEffect.cs` — `[Flags]` enum, используемый в третьем сопоставлении.
+
+## Как запустить
+
+Откройте `Scenes/EnumValues.unity` и войдите в Play Mode. В сцене есть `DamageDealer`, подключённый из `Prefabs/EnumValues.prefab`, который предзаполнен:
+
+- `_damageMultipliers`: `Physical = 1.0`, `Fire = 1.5`, `Ice = 0.8`, `Poison = 0.6`.
+- `_damageColors`: серый / оранжевый / голубой / ядовито-зелёный по `DamageType`.
+- `_speedMultipliersByStatus`: `Burning | Slowed = 0.4` **первой**, затем `Burning = 1.0`, `Frozen = 0.2`, `Slowed = 0.5` — порядок с композитом впереди именно и гарантирует, что комбинация флагов разрешается в `0.4`, а не проваливается на первое совпадение одиночного флага.
+- `_currentDamageType = Fire`, `_activeEffects = Burning | Slowed`, `_baseDamage = 10`.
+
+Нажмите `Space` — в Console появится оранжевое `Fire hit: 15 dmg (speed mod: 0.40)`. Меняйте значения enum в Inspector (или переключайте флаги в `_activeEffects`), чтобы увидеть другие варианты поиска.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README_RU.md.meta
new file mode 100644
index 0000000..cb485b4
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 7a3b1c4d5e6f7081929304a5b6c7d8e9
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes.meta
similarity index 77%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes.meta
index 78aeed3..c445a59 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: c5527f6f042e7408ebabfc82d150433b
+guid: d68d6fc528e76449db0a639caa762203
folderAsset: yes
DefaultImporter:
externalObjects: {}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity
new file mode 100644
index 0000000..8eeccc3
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity
@@ -0,0 +1,449 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 10
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 0
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 13
+ m_BakeOnSceneLoad: 0
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 1
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 12
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_ExtractAmbientOcclusion: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 1
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 512
+ m_PVRBounces: 2
+ m_PVREnvironmentSampleCount: 256
+ m_PVREnvironmentReferencePointCount: 2048
+ m_PVRFilteringMode: 1
+ m_PVRDenoiserTypeDirect: 1
+ m_PVRDenoiserTypeIndirect: 1
+ m_PVRDenoiserTypeAO: 1
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVREnvironmentMIS: 1
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 1
+ m_PVRFilteringGaussRadiusAO: 1
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ExportTrainingData: 0
+ m_TrainingDataDestination: TrainingData
+ m_LightProbeSampleCountMultiplier: 4
+ m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
+ m_LightingSettings: {fileID: 0}
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 3
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ buildHeightMesh: 0
+ maxJobWorkers: 0
+ preserveTilesOutsideBounds: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &100001
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 100003}
+ - component: {fileID: 100002}
+ - component: {fileID: 100004}
+ m_Layer: 0
+ m_Name: Directional Light
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!108 &100002
+Light:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ m_Enabled: 1
+ serializedVersion: 13
+ m_Type: 1
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
+ m_Intensity: 1
+ m_Range: 10
+ m_SpotAngle: 30
+ m_InnerSpotAngle: 21.80208
+ m_CookieSize2D: {x: 10, y: 10}
+ m_Shadows:
+ m_Type: 2
+ m_Resolution: -1
+ m_CustomResolution: -1
+ m_Strength: 1
+ m_Bias: 0.05
+ m_NormalBias: 0.4
+ m_NearPlane: 0.2
+ m_CullingMatrixOverride:
+ e00: 1
+ e01: 0
+ e02: 0
+ e03: 0
+ e10: 0
+ e11: 1
+ e12: 0
+ e13: 0
+ e20: 0
+ e21: 0
+ e22: 1
+ e23: 0
+ e30: 0
+ e31: 0
+ e32: 0
+ e33: 1
+ m_UseCullingMatrixOverride: 0
+ m_Cookie: {fileID: 0}
+ m_DrawHalo: 0
+ m_Flare: {fileID: 0}
+ m_RenderMode: 0
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingLayerMask: 1
+ m_Lightmapping: 4
+ m_LightShadowCasterMode: 0
+ m_AreaSize: {x: 1, y: 1}
+ m_BounceIntensity: 1
+ m_ColorTemperature: 6570
+ m_UseColorTemperature: 0
+ m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_UseBoundingSphereOverride: 0
+ m_UseViewFrustumForShadowCasterCull: 1
+ m_ForceVisible: 0
+ m_ShapeRadius: 0
+ m_ShadowAngle: 0
+ m_LightUnit: 1
+ m_LuxAtDistance: 1
+ m_EnableSpotReflector: 1
+--- !u!4 &100003
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
+ m_LocalPosition: {x: 0, y: 3, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
+--- !u!114 &100004
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
+ m_UsePipelineSettings: 1
+ m_AdditionalLightsShadowResolutionTier: 2
+ m_CustomShadowLayers: 0
+ m_LightCookieSize: {x: 1, y: 1}
+ m_LightCookieOffset: {x: 0, y: 0}
+ m_SoftShadowQuality: 0
+ m_RenderingLayersMask:
+ serializedVersion: 0
+ m_Bits: 1
+ m_ShadowRenderingLayersMask:
+ serializedVersion: 0
+ m_Bits: 1
+ m_Version: 4
+ m_LightLayerMask: 1
+ m_ShadowLayerMask: 1
+ m_RenderingLayers: 1
+ m_ShadowRenderingLayers: 1
+--- !u!1 &200001
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 200004}
+ - component: {fileID: 200002}
+ - component: {fileID: 200003}
+ - component: {fileID: 200005}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &200002
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+--- !u!20 &200003
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_GateFitMode: 2
+ m_FOVAxisMode: 0
+ m_Iso: 200
+ m_ShutterSpeed: 0.005
+ m_Aperture: 16
+ m_FocusDistance: 10
+ m_FocalLength: 50
+ m_BladeCount: 5
+ m_Curvature: {x: 2, y: 11}
+ m_BarrelClipping: 0.25
+ m_Anamorphism: 0
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 0
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &200004
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 1, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &200005
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
+ m_RenderShadows: 1
+ m_RequiresDepthTextureOption: 2
+ m_RequiresOpaqueTextureOption: 2
+ m_CameraType: 0
+ m_Cameras: []
+ m_RendererIndex: -1
+ m_VolumeLayerMask:
+ serializedVersion: 2
+ m_Bits: 1
+ m_VolumeTrigger: {fileID: 0}
+ m_VolumeFrameworkUpdateModeOption: 2
+ m_RenderPostProcessing: 0
+ m_Antialiasing: 0
+ m_AntialiasingQuality: 2
+ m_StopNaN: 0
+ m_Dithering: 0
+ m_ClearDepth: 1
+ m_AllowXRRendering: 1
+ m_AllowHDROutput: 1
+ m_UseScreenCoordOverride: 0
+ m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
+ m_RequiresDepthTexture: 0
+ m_RequiresColorTexture: 0
+ m_TaaSettings:
+ m_Quality: 3
+ m_FrameInfluence: 0.1
+ m_JitterScale: 1
+ m_MipBias: 0
+ m_VarianceClampScale: 0.9
+ m_ContrastAdaptiveSharpening: 0
+ m_Version: 2
+--- !u!1001 &1501333895
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 0}
+ m_Modifications:
+ - target: {fileID: 1234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_Name
+ value: EnumValues
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: b6af73ff1ec54f22b2b637c93cf4287b, type: 3}
+--- !u!1660057539 &9223372036854775807
+SceneRoots:
+ m_ObjectHideFlags: 0
+ m_Roots:
+ - {fileID: 200004}
+ - {fileID: 100003}
+ - {fileID: 1501333895}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity.meta
new file mode 100644
index 0000000..8d581a2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scenes/EnumValues.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: dbdae9a1d49d4a6ca82c5a3ab5bc1d24
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts.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/Samples~/EnumValues/Scripts.meta
index c1b162f..46f29a9 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts.meta
@@ -1,5 +1,6 @@
fileFormatVersion: 2
-guid: 7f88981b89c7a48f196d36c976c31f20
+guid: d3bd9c0a75f8b4c77986e2aeb360dc13
+folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef
similarity index 76%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef
index 5199cfd..9e824ff 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef
@@ -1,8 +1,8 @@
{
- "name": "Aspid.UnityFastTools.ProfilerMarkers",
+ "name": "Aspid.FastTools.Samples.EnumValues",
"rootNamespace": "",
"references": [
- "GUID:7c010b89992542508a6b6189977e64d4"
+ "Aspid.FastTools.Unity"
],
"includePlatforms": [],
"excludePlatforms": [],
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef.meta
new file mode 100644
index 0000000..46d73b1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/Aspid.FastTools.Samples.EnumValues.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: fe2fa73e3f3d499f8e95516349d3768a
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs
new file mode 100644
index 0000000..957d980
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs
@@ -0,0 +1,37 @@
+using UnityEngine;
+using Aspid.FastTools.Enums;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.EnumValues
+{
+ public sealed class DamageDealer : MonoBehaviour
+ {
+ [SerializeField] private EnumValues _damageMultipliers;
+ [SerializeField] private EnumValues _damageColors;
+
+ // Flag combinations (e.g. Burning | Slowed) match via HasFlag and first-hit wins, so list
+ // composite entries BEFORE their constituent flags — otherwise the single-flag entry matches first.
+ [SerializeField] private EnumValues _speedMultipliersByStatus;
+
+ [SerializeField] private DamageType _currentDamageType = DamageType.Physical;
+ [SerializeField] private StatusEffect _activeEffects = StatusEffect.None;
+ [SerializeField] private float _baseDamage = 10f;
+
+ private void Update()
+ {
+ if (!Input.GetKeyDown(KeyCode.Space)) return;
+ DealDamage();
+ }
+
+ private void DealDamage()
+ {
+ var multiplier = _damageMultipliers.GetValue(_currentDamageType);
+ var color = _damageColors.GetValue(_currentDamageType);
+ var speedMod = _speedMultipliersByStatus.GetValue(_activeEffects);
+ var finalDamage = _baseDamage * multiplier;
+ var colorHex = ColorUtility.ToHtmlStringRGB(color);
+
+ Debug.Log($"{_currentDamageType} hit: {finalDamage} dmg (speed mod: {speedMod:F2})");
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs.meta
new file mode 100644
index 0000000..2f16782
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageDealer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3bd9644f273549748da7b083e4de1b37
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs
new file mode 100644
index 0000000..3a42c2f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs
@@ -0,0 +1,11 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.EnumValues
+{
+ public enum DamageType
+ {
+ Physical,
+ Fire,
+ Ice,
+ Poison,
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs.meta
new file mode 100644
index 0000000..7b7289d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/DamageType.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8617fbe193994a1d9ffd71e02ac3c5b5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs
new file mode 100644
index 0000000..8922e13
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs
@@ -0,0 +1,17 @@
+using System;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.EnumValues
+{
+ [Flags]
+ public enum StatusEffect
+ {
+ None = 0,
+ Burning = 1,
+ Frozen = 2,
+ Slowed = 4,
+ Stunned = 8,
+ // Combinations such as Burning | Slowed are matched via HasFlag semantics in EnumValues
+ // and can be registered as their own entry with a dedicated value.
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs.meta
new file mode 100644
index 0000000..52cb56f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/EnumValues/Scripts/StatusEffect.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9f66e403cd7044cc96c00ad003ccc68a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids.meta
new file mode 100644
index 0000000..d3271a0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5ba755d592364471fa27430d0b3b2299
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data.meta
new file mode 100644
index 0000000..81bb9e9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: aabb1122334455667788990011223344
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset
new file mode 100644
index 0000000..f117b9f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset
@@ -0,0 +1,23 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 70e390d14588e72409a6a95aca1461a7, type: 3}
+ m_Name: IdRegistry_EnemyId
+ m_EditorClassIdentifier: Aspid.FastTools.Unity::Aspid.FastTools.Ids.IdRegistry
+ _targetStructType: Aspid.FastTools.Samples.Ids.EnemyId, Aspid.FastTools.Samples.Ids,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _nextId: 5
+ _ids: 01000000020000000300000004000000
+ _names:
+ - fly_enemy_dragon
+ - walk_enemy_goblin
+ - walk_enemy_orc
+ - walk_enemy_skeleton
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset.meta
new file mode 100644
index 0000000..ae394d4
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/IdRegistry_EnemyId.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3b547b6a17aa54f5f8c80dd0aa23d0e2
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset
new file mode 100644
index 0000000..ba1dde9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: fly_enemy_dragon
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: fly_enemy_dragon
+ _id: 1
+ _displayName: Dragon
+ _maxHealth: 500
+ _moveSpeed: 4
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset.meta
new file mode 100644
index 0000000..5fa2fe1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/fly_enemy_dragon.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: edca3936962f4004af0272178ad75bfd
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset
new file mode 100644
index 0000000..c258690
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: walk_enemy_goblin
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: walk_enemy_goblin
+ _id: 2
+ _displayName: Goblin
+ _maxHealth: 80
+ _moveSpeed: 3.5
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset.meta
new file mode 100644
index 0000000..1a10750
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_goblin.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d92adbaedb324bff9fcbe616fbf03416
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset
new file mode 100644
index 0000000..91665de
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: walk_enemy_orc
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: walk_enemy_orc
+ _id: 3
+ _displayName: Orc
+ _maxHealth: 150
+ _moveSpeed: 2
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset.meta
new file mode 100644
index 0000000..5c039b9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_orc.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: fc61e5b486184ad4b3ed8c54016f6d5f
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset
new file mode 100644
index 0000000..cbf097d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset
@@ -0,0 +1,20 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 953e77fd8a034ac6974fb9fa4c49aae4, type: 3}
+ m_Name: walk_enemy_skeleton
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemyDefinition
+ _id:
+ __stringId: walk_enemy_skeleton
+ _id: 4
+ _displayName: Skeleton
+ _maxHealth: 60
+ _moveSpeed: 3
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset.meta
new file mode 100644
index 0000000..0de4d09
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Data/walk_enemy_skeleton.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 56ba102dc1764873b64e5f7803207cc6
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs.meta
new file mode 100644
index 0000000..8b71e2a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bbcc1122334455667788990011223344
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab
new file mode 100644
index 0000000..7963311
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab
@@ -0,0 +1,54 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: Ids
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: ce0efc593ee24a5d97d8511d669c9de7, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Ids::Aspid.FastTools.Samples.Ids.EnemySpawner
+ _catalog:
+ - {fileID: 11400000, guid: edca3936962f4004af0272178ad75bfd, type: 2}
+ - {fileID: 11400000, guid: d92adbaedb324bff9fcbe616fbf03416, type: 2}
+ - {fileID: 11400000, guid: fc61e5b486184ad4b3ed8c54016f6d5f, type: 2}
+ - {fileID: 11400000, guid: 56ba102dc1764873b64e5f7803207cc6, type: 2}
+ _spawnTarget:
+ __stringId: walk_enemy_orc
+ _id: 3
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab.meta
new file mode 100644
index 0000000..059c269
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Prefabs/Ids.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: bbe54125698a41cb8a8dab897d567ff8
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README.md
new file mode 100644
index 0000000..84c3064
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README.md
@@ -0,0 +1,30 @@
+# Ids Sample
+
+Demonstrates the `IId` / `IdRegistry` / `[UniqueId]` trio: fields show a human-readable string in the Inspector while serializing as a stable integer, and the Inspector catches collisions at edit-time.
+
+## How it works
+
+- `IId` — a marker interface declaring the `int Id { get; }` property.
+- `IdRegistry` — a `ScriptableObject` that binds a struct type to a list of `(Id, Name)` entries and keeps the name ↔ int map available at runtime. The property drawer renders a dropdown sourced from this registry.
+- `[UniqueId]` — validates at edit-time that no two `ScriptableObject` assets share the same resolved integer ID.
+
+## Scenario
+
+An enemy catalog. Each `EnemyDefinition` asset holds a unique `EnemyId` plus display data (`_displayName`, `_maxHealth`, `_moveSpeed`). An `EnemySpawner` picks a target `EnemyId` via dropdown and looks the matching asset up in its catalog on `Start()`.
+
+Look at:
+
+- `Scripts/EnemyId.cs` — `partial struct : IId`. `IdStructGenerator` emits `__stringId`, `_id`, and the `Id` property.
+- `Scripts/EnemyDefinition.cs:10` — `[UniqueId]` on a serialized `EnemyId` field prevents duplicate IDs across assets.
+- `Data/IdRegistry_EnemyId.asset` — the registry binding names (`fly_enemy_dragon`, `walk_enemy_goblin`, `walk_enemy_orc`, `walk_enemy_skeleton`) to stable ints.
+- `Scripts/EnemySpawner.cs:9` — dropdown-selected `EnemyId` resolved to `int` at runtime via `.Id`.
+
+## How to run
+
+Open `Scenes/Ids.unity` — it contains an `EnemySpawner` GameObject (also available as `Prefabs/Ids.prefab`). Wire it up once:
+
+1. Drag the four `Data/*_enemy_*.asset` files into the spawner's `Catalog` array.
+2. Pick a target enemy from the `Spawn Target` dropdown — the picker is sourced from `IdRegistry_EnemyId`.
+3. Enter Play Mode — the Console logs the resolved `EnemyDefinition` (display name, HP, move speed). Switch the dropdown to see different lookups.
+
+To create more entries, open `Data/IdRegistry_EnemyId.asset` to add registry rows, then `Assets > Create > Aspid > FastTools > Samples > Enemy Definition` for the asset side.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README.md.meta
new file mode 100644
index 0000000..55d313e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 79a0f34c9d7ac4d45a00d0f1a9909de4
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README_RU.md
new file mode 100644
index 0000000..4cacef1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README_RU.md
@@ -0,0 +1,30 @@
+# Пример Ids
+
+Демонстрирует связку `IId` / `IdRegistry` / `[UniqueId]`: поля показывают человекочитаемую строку в Inspector, а сериализуются как стабильное целое число, и Inspector ловит коллизии прямо при редактировании.
+
+## Как это работает
+
+- `IId` — маркерный интерфейс, объявляющий свойство `int Id { get; }`.
+- `IdRegistry` — `ScriptableObject`, связывающий тип-структуру со списком записей `(Id, Name)` и сохраняющий отображение имя ↔ int доступным во рантайме. Property drawer отрисовывает выпадающий список, источником которого является этот реестр.
+- `[UniqueId]` — валидирует во время редактирования, что ни два `ScriptableObject`-актива не имеют одинакового результирующего целочисленного ID.
+
+## Сценарий
+
+Каталог врагов. Каждый актив `EnemyDefinition` хранит уникальный `EnemyId` плюс данные для отображения (`_displayName`, `_maxHealth`, `_moveSpeed`). `EnemySpawner` выбирает целевой `EnemyId` через выпадающий список и ищет соответствующий актив в своём каталоге в `Start()`.
+
+Смотрите:
+
+- `Scripts/EnemyId.cs` — `partial struct : IId`. `IdStructGenerator` генерирует `__stringId`, `_id` и свойство `Id`.
+- `Scripts/EnemyDefinition.cs:10` — `[UniqueId]` на сериализованном поле `EnemyId` предотвращает дублирование ID между активами.
+- `Data/IdRegistry_EnemyId.asset` — реестр, связывающий имена (`fly_enemy_dragon`, `walk_enemy_goblin`, `walk_enemy_orc`, `walk_enemy_skeleton`) со стабильными целочисленными значениями.
+- `Scripts/EnemySpawner.cs:9` — выбранный из списка `EnemyId`, преобразуется в `int` во время выполнения через `.Id`.
+
+## Как запустить
+
+Откройте `Scenes/Ids.unity` — в сцене лежит GameObject `EnemySpawner` (также доступен как `Prefabs/Ids.prefab`). Подключите его один раз:
+
+1. Перетащите четыре актива `Data/*_enemy_*.asset` в массив `Catalog` спавнера.
+2. Выберите цель в выпадающем списке `Spawn Target` — список берётся из `IdRegistry_EnemyId`.
+3. Войдите в Play Mode — в Console появится лог найденного `EnemyDefinition` (display name, HP, move speed). Меняйте значение в списке, чтобы увидеть другие варианты поиска.
+
+Чтобы добавить новые записи, откройте `Data/IdRegistry_EnemyId.asset` для строк реестра и `Assets > Create > Aspid > FastTools > Samples > Enemy Definition` — для соответствующих ассетов.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README_RU.md.meta
new file mode 100644
index 0000000..bdd6ddd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: ad6e4f7081924b3c5d6e7f80910a1b2c
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes.meta
new file mode 100644
index 0000000..9ba291b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ccdd1122334455667788990011223344
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity
similarity index 75%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity
index 42337b6..4731c20 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scenes/Visual Elements.unity
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity
@@ -119,7 +119,7 @@ NavMeshSettings:
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
---- !u!1 &1956111
+--- !u!1 &100001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -127,9 +127,9 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 1956113}
- - component: {fileID: 1956112}
- - component: {fileID: 1956114}
+ - component: {fileID: 100003}
+ - component: {fileID: 100002}
+ - component: {fileID: 100004}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
@@ -137,22 +137,22 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!108 &1956112
+--- !u!108 &100002
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
- serializedVersion: 11
+ serializedVersion: 13
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
- m_CookieSize: 10
+ m_CookieSize2D: {x: 10, y: 10}
m_Shadows:
m_Type: 2
m_Resolution: -1
@@ -197,18 +197,18 @@ Light:
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
- m_ShadowRadius: 0
+ m_ShapeRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
---- !u!4 &1956113
+--- !u!4 &100003
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
@@ -217,13 +217,13 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
---- !u!114 &1956114
+--- !u!114 &100004
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
@@ -246,7 +246,7 @@ MonoBehaviour:
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
---- !u!1 &628555577
+--- !u!1 &200001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -254,10 +254,10 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 628555580}
- - component: {fileID: 628555579}
- - component: {fileID: 628555578}
- - component: {fileID: 628555581}
+ - component: {fileID: 200004}
+ - component: {fileID: 200002}
+ - component: {fileID: 200003}
+ - component: {fileID: 200005}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
@@ -265,21 +265,21 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!81 &628555578
+--- !u!81 &200002
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
---- !u!20 &628555579
+--- !u!20 &200003
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
@@ -324,13 +324,13 @@ Camera:
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
---- !u!4 &628555580
+--- !u!4 &200004
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
@@ -339,13 +339,13 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &628555581
+--- !u!114 &200005
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
@@ -383,54 +383,67 @@ MonoBehaviour:
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
m_Version: 2
---- !u!1 &642957226
-GameObject:
+--- !u!1001 &1455822910
+PrefabInstance:
m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- serializedVersion: 6
- m_Component:
- - component: {fileID: 642957227}
- - component: {fileID: 642957228}
- m_Layer: 0
- m_Name: Visual Element Inspector
- m_TagString: Untagged
- m_Icon: {fileID: 0}
- m_NavMeshLayer: 0
- m_StaticEditorFlags: 0
- m_IsActive: 1
---- !u!4 &642957227
-Transform:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
serializedVersion: 2
- m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: -0, y: 0, z: -0}
- m_LocalScale: {x: 1, y: 1, z: 1}
- m_ConstrainProportionsScale: 0
- m_Children: []
- m_Father: {fileID: 0}
- m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &642957228
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: dcfbfa43e527425e8881c33eec85bec3, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp-firstpass::Aspid.UnityFastTools.Samples.VisualElements.VisualElementInspector
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 0}
+ m_Modifications:
+ - target: {fileID: 1234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_Name
+ value: Ids
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: bbe54125698a41cb8a8dab897d567ff8, type: 3}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- - {fileID: 628555580}
- - {fileID: 1956113}
- - {fileID: 642957227}
+ - {fileID: 200004}
+ - {fileID: 100003}
+ - {fileID: 1455822910}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity.meta
new file mode 100644
index 0000000..bad758d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scenes/Ids.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a978c0327ba8450696b7a546a1c26959
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts.meta
new file mode 100644
index 0000000..3926b38
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4b48604dab5254cd296df004a49949ff
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/Aspid.UnityFastTools.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef
similarity index 74%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/Aspid.UnityFastTools.asmdef
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef
index f0f15e4..113ba78 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Runtime/Aspid.UnityFastTools.asmdef
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef
@@ -1,7 +1,9 @@
{
- "name": "Aspid.UnityFastTools",
+ "name": "Aspid.FastTools.Samples.Ids",
"rootNamespace": "",
- "references": [],
+ "references": [
+ "Aspid.FastTools.Unity"
+ ],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef.meta
new file mode 100644
index 0000000..a62fec6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/Aspid.FastTools.Samples.Ids.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 34984631d7b44f53aa977da146924369
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs
new file mode 100644
index 0000000..7910e72
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs
@@ -0,0 +1,22 @@
+using UnityEngine;
+using Aspid.FastTools.Ids;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Ids
+{
+ [CreateAssetMenu(fileName = "New Enemy Definition", menuName = "Aspid/FastTools/Samples/Enemy Definition")]
+ public class EnemyDefinition : ScriptableObject
+ {
+ [UniqueId]
+ [SerializeField] private EnemyId _id;
+
+ [SerializeField] private string _displayName;
+ [SerializeField] private int _maxHealth;
+ [SerializeField] private float _moveSpeed;
+
+ public EnemyId Id => _id;
+ public string DisplayName => _displayName;
+ public int MaxHealth => _maxHealth;
+ public float MoveSpeed => _moveSpeed;
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs.meta
new file mode 100644
index 0000000..43009ab
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyDefinition.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 953e77fd8a034ac6974fb9fa4c49aae4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs
new file mode 100644
index 0000000..632905f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs
@@ -0,0 +1,11 @@
+using System;
+using Aspid.FastTools.Ids;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Ids
+{
+ // Declaring `partial struct : IId` triggers IdStructGenerator, which emits __stringId, _id,
+ // and the Id property — so consumers only ever write the one-liner below.
+ [Serializable]
+ public partial struct EnemyId : IId { }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs.meta
new file mode 100644
index 0000000..9b79001
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemyId.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1d2651027ff84fa5a6f13b1bc0ab8978
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs
new file mode 100644
index 0000000..52e3c67
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs
@@ -0,0 +1,24 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Ids
+{
+ public class EnemySpawner : MonoBehaviour
+ {
+ [SerializeField] private EnemyDefinition[] _catalog;
+ [SerializeField] private EnemyId _spawnTarget;
+
+ private void Start()
+ {
+ foreach (var enemy in _catalog)
+ {
+ if (enemy.Id.Id != _spawnTarget.Id) continue;
+
+ Debug.Log($"Spawning {enemy.DisplayName} — HP: {enemy.MaxHealth}, Speed: {enemy.MoveSpeed}");
+ return;
+ }
+
+ Debug.LogWarning($"No EnemyDefinition found for id {_spawnTarget.Id}");
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs.meta
new file mode 100644
index 0000000..4729ced
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Ids/Scripts/EnemySpawner.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: ce0efc593ee24a5d97d8511d669c9de7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
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/Prefabs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs.meta
new file mode 100644
index 0000000..9f9d905
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 67e55e4b150b9426293f6e52bf919fdd
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab
new file mode 100644
index 0000000..d4c819d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab
@@ -0,0 +1,46 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: FrameProfiler
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 81773b495ca84416af8f5b4210f650f6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.ProfilerMarkers::Aspid.FastTools.Samples.ProfilerMarkers.FrameProfiler
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab.meta
new file mode 100644
index 0000000..3370b57
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Prefabs/ProfilerMarkers.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4d438180f96141d9b383946bbf647c1c
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README.md
new file mode 100644
index 0000000..098fb27
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README.md
@@ -0,0 +1,61 @@
+# ProfilerMarkers Sample
+
+Demonstrates the three supported usage forms of `this.Marker()`. In every
+form the Aspid.FastTools source generator replaces each call site with a
+unique `ProfilerMarker` keyed by `(type, method, line)`. `.WithName(...)`
+is optional — when omitted, the generator auto-names the marker after the
+enclosing type and method.
+
+## Supported forms
+
+1. **Block `using`-statement with an explicit name.** Marker scope is the
+ block; the full display name is `"{TypeName}.{WithName} ({line})"`, so
+ pass the short suffix only.
+ ```csharp
+ using (this.Marker().WithName("Physics")) // Profiler: "FrameProfiler.Physics ()"
+ SimulatePhysics();
+ ```
+2. **`using`-declaration without `WithName`.** Marker scope is the rest of
+ the enclosing block; the generator auto-names the marker after the
+ enclosing method.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateInput ()"
+ ```
+3. **Combined form.** A method-wide `using`-declaration paired with a
+ nested `using`-statement — useful when you want one outer marker for the
+ whole method and a narrower marker around a hot sub-step. Both get
+ auto-named after the method; their different line numbers produce
+ distinct Profiler entries.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateAudio ()"
+ using (this.Marker()) // Profiler: "FrameProfiler.SimulateAudio ()"
+ {
+ // Some code.
+ }
+ ```
+
+## How to run
+
+1. Open `Scenes/ProfilerMarkers.unity` — it already contains a `FrameProfiler`
+ GameObject. The `Prefabs/ProfilerMarkers.prefab` variant can be dropped
+ into your own scenes.
+2. Open `Window → Analysis → Profiler`.
+3. Enter Play Mode and inspect the CPU track for the named markers.
+
+## Where to look
+
+- `Scripts/FrameProfiler.cs:19,22,25` — three top-level markers with explicit
+ names in `Update` (`FrameProfiler.Physics`, `FrameProfiler.AI`,
+ `FrameProfiler.Render`).
+- `Scripts/FrameProfiler.cs:44` — nested `FrameProfiler.AI.Agent` marker
+ emitted once per loop iteration in `SimulateAI`, appearing under the `AI`
+ scope.
+- `Scripts/FrameProfiler.cs:68` — `using`-declaration form without `WithName`
+ in `SimulateInput`; the generator names the marker after the method.
+- `Scripts/FrameProfiler.cs:84,87` — combined form in `SimulateAudio`: an
+ outer method-wide `using`-declaration plus a nested `using`-statement
+ around `MixAudio()`.
+
+Every `using` scope (statement or declaration) starts and ends the
+generated marker automatically, so the Profiler shows precise self-time
+for every phase.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README.md.meta
new file mode 100644
index 0000000..95a29bc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: b168656911e1546e5b8b5036803240be
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md
new file mode 100644
index 0000000..554a954
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md
@@ -0,0 +1,61 @@
+# Пример ProfilerMarkers
+
+Демонстрирует три поддерживаемые формы вызова `this.Marker()`. В каждой
+форме source-генератор Aspid.FastTools заменяет место вызова уникальным
+`ProfilerMarker`, привязанным к `(type, method, line)`. `.WithName(...)`
+не обязателен — если его не указать, генератор автоматически назовёт
+маркер по имени содержащего типа и метода.
+
+## Поддерживаемые формы
+
+1. **Блок `using`-statement с явным именем.** Область маркера — блок;
+ полное отображаемое имя имеет вид `"{TypeName}.{WithName} ({line})"`,
+ поэтому в `WithName` передавайте только короткий суффикс.
+ ```csharp
+ using (this.Marker().WithName("Physics")) // Profiler: "FrameProfiler.Physics ()"
+ SimulatePhysics();
+ ```
+2. **`using`-declaration без `WithName`.** Область маркера — остаток
+ содержащего блока; генератор именует маркер по имени содержащего
+ метода.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateInput ()"
+ ```
+3. **Комбинированная форма.** Метод-широкий `using`-declaration в паре с
+ вложенным `using`-statement — удобно, когда нужен один внешний маркер
+ на весь метод и более узкий — на «горячий» подэтап. Оба маркера
+ получают автоимя по методу; разные номера строк дают разные записи в
+ Profiler.
+ ```csharp
+ using var _ = this.Marker(); // Profiler: "FrameProfiler.SimulateAudio ()"
+ using (this.Marker()) // Profiler: "FrameProfiler.SimulateAudio ()"
+ {
+ // Некоторый код.
+ }
+ ```
+
+## Как запустить
+
+1. Откройте `Scenes/ProfilerMarkers.unity` — в сцене уже есть GameObject с
+ `FrameProfiler`. Вариант `Prefabs/ProfilerMarkers.prefab` можно добавить
+ в собственные сцены.
+2. Откройте `Window → Analysis → Profiler`.
+3. Войдите в Play Mode и осмотрите CPU-трек на наличие именованных маркеров.
+
+## Где смотреть
+
+- `Scripts/FrameProfiler.cs:19,22,25` — три маркера верхнего уровня с явными
+ именами в `Update` (`FrameProfiler.Physics`, `FrameProfiler.AI`,
+ `FrameProfiler.Render`).
+- `Scripts/FrameProfiler.cs:44` — вложенный маркер `FrameProfiler.AI.Agent`
+ в `SimulateAI`, генерируемый раз за итерацию цикла, отображается под
+ областью `AI`.
+- `Scripts/FrameProfiler.cs:68` — форма `using`-declaration без `WithName`
+ в `SimulateInput`; генератор именует маркер по имени метода.
+- `Scripts/FrameProfiler.cs:84,87` — комбинированная форма в
+ `SimulateAudio`: внешний метод-широкий `using`-declaration плюс
+ вложенный `using`-statement вокруг `MixAudio()`.
+
+Каждый блок `using` (statement или declaration) автоматически запускает и
+завершает сгенерированный маркер, поэтому Profiler показывает точное
+self-time для каждой фазы.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md.meta
new file mode 100644
index 0000000..9118597
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 9c5d3e6f70819203b4c5d6e7f8091a2b
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
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/ProfilerMarkers.unity
similarity index 89%
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/ProfilerMarkers.unity
index 404b967..750900c 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scenes/Profiler Markers.unity
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity
@@ -119,7 +119,7 @@ NavMeshSettings:
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
---- !u!1 &1956111
+--- !u!1 &100001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -127,9 +127,9 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 1956113}
- - component: {fileID: 1956112}
- - component: {fileID: 1956114}
+ - component: {fileID: 100003}
+ - component: {fileID: 100002}
+ - component: {fileID: 100004}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
@@ -137,22 +137,22 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!108 &1956112
+--- !u!108 &100002
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
- serializedVersion: 11
+ serializedVersion: 13
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_InnerSpotAngle: 21.80208
- m_CookieSize: 10
+ m_CookieSize2D: {x: 10, y: 10}
m_Shadows:
m_Type: 2
m_Resolution: -1
@@ -197,18 +197,18 @@ Light:
m_UseBoundingSphereOverride: 0
m_UseViewFrustumForShadowCasterCull: 1
m_ForceVisible: 0
- m_ShadowRadius: 0
+ m_ShapeRadius: 0
m_ShadowAngle: 0
m_LightUnit: 1
m_LuxAtDistance: 1
m_EnableSpotReflector: 1
---- !u!4 &1956113
+--- !u!4 &100003
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
serializedVersion: 2
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
@@ -217,17 +217,17 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
---- !u!114 &1956114
+--- !u!114 &100004
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 1956111}
+ m_GameObject: {fileID: 100001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
- m_Name:
+ m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
m_UsePipelineSettings: 1
m_AdditionalLightsShadowResolutionTier: 2
@@ -246,7 +246,7 @@ MonoBehaviour:
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
---- !u!1 &628555577
+--- !u!1 &200001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -254,10 +254,10 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 628555580}
- - component: {fileID: 628555579}
- - component: {fileID: 628555578}
- - component: {fileID: 628555581}
+ - component: {fileID: 200004}
+ - component: {fileID: 200002}
+ - component: {fileID: 200003}
+ - component: {fileID: 200005}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
@@ -265,21 +265,21 @@ GameObject:
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!81 &628555578
+--- !u!81 &200002
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
---- !u!20 &628555579
+--- !u!20 &200003
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
@@ -324,13 +324,13 @@ Camera:
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
---- !u!4 &628555580
+--- !u!4 &200004
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
@@ -339,17 +339,17 @@ Transform:
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &628555581
+--- !u!114 &200005
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 628555577}
+ m_GameObject: {fileID: 200001}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
- m_Name:
+ m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
m_RenderShadows: 1
m_RequiresDepthTextureOption: 2
@@ -383,7 +383,7 @@ MonoBehaviour:
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
m_Version: 2
---- !u!1 &642957226
+--- !u!1 &300001
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
@@ -391,46 +391,46 @@ GameObject:
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- - component: {fileID: 642957227}
- - component: {fileID: 642957228}
+ - component: {fileID: 300002}
+ - component: {fileID: 300003}
m_Layer: 0
- m_Name: Marker Test
+ m_Name: FrameProfiler
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
---- !u!4 &642957227
+--- !u!4 &300002
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
+ m_GameObject: {fileID: 300001}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
- m_LocalPosition: {x: -0, y: 0, z: -0}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
---- !u!114 &642957228
+--- !u!114 &300003
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 642957226}
+ m_GameObject: {fileID: 300001}
m_Enabled: 1
m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 199696d16b85e4112a63975aa9dcfd55, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp-firstpass::Aspid.UnityFastTools.Samples.ProfilerMarkers.MarkerTest
+ m_Script: {fileID: 11500000, guid: 81773b495ca84416af8f5b4210f650f6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.ProfilerMarkers::Aspid.FastTools.Samples.ProfilerMarkers.FrameProfiler
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- - {fileID: 628555580}
- - {fileID: 1956113}
- - {fileID: 642957227}
+ - {fileID: 200004}
+ - {fileID: 100003}
+ - {fileID: 300002}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity.meta
new file mode 100644
index 0000000..7adfa9b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scenes/ProfilerMarkers.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 345b39adb7834b58b853bfeee78228bb
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
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.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef
new file mode 100644
index 0000000..092138d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "Aspid.FastTools.Samples.ProfilerMarkers",
+ "rootNamespace": "",
+ "references": [
+ "Aspid.FastTools.Unity"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef.meta
new file mode 100644
index 0000000..99365ca
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/Aspid.FastTools.Samples.ProfilerMarkers.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 240a32c5c53f4b2dafe608de70ef1eb2
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs
new file mode 100644
index 0000000..02b328b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs
@@ -0,0 +1,107 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.ProfilerMarkers
+{
+ public sealed class FrameProfiler : MonoBehaviour
+ {
+ [SerializeField] [Min(1)] private int _physicsLoad = 5000;
+ [SerializeField] [Min(1)] private int _aiAgents = 20;
+ [SerializeField] [Min(1)] private int _aiStepsPerAgent = 500;
+ [SerializeField] [Min(1)] private int _renderLoad = 3000;
+ [SerializeField] [Min(1)] private int _inputLoad = 1500;
+ [SerializeField] [Min(1)] private int _audioLoad = 2000;
+
+ private void Update()
+ {
+ // Every call site of this.Marker() becomes a unique ProfilerMarker.
+ // Display name: "{TypeName}.{WithName-or-method} ({line})".
+ using (this.Marker().WithName("Physics")) // Profiler: FrameProfiler.Physics (19)
+ SimulatePhysics();
+
+ using (this.Marker().WithName("AI")) // Profiler: FrameProfiler.AI (22)
+ SimulateAI();
+
+ using (this.Marker().WithName("Render")) // Profiler: FrameProfiler.Render (25)
+ SimulateRender();
+
+ SimulateInput();
+ SimulateAudio();
+ }
+
+ private void SimulatePhysics()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _physicsLoad; i++)
+ sum += Mathf.Sqrt(i);
+ _ = sum;
+ }
+
+ private void SimulateAI()
+ {
+ for (var agent = 0; agent < _aiAgents; agent++)
+ {
+ using (this.Marker().WithName("AI.Agent")) // Profiler: FrameProfiler.AI.Agent (44)
+ StepAgent();
+ }
+ }
+
+ private void StepAgent()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _aiStepsPerAgent; i++)
+ sum += Mathf.Sin(i);
+ _ = sum;
+ }
+
+ private void SimulateRender()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _renderLoad; i++)
+ sum += Mathf.Cos(i);
+ _ = sum;
+ }
+
+ // using-declaration without .WithName(): auto-named after the method.
+ private void SimulateInput()
+ {
+ using var _ = this.Marker(); // Profiler: FrameProfiler.SimulateInput (68)
+ DoInputWork();
+ }
+
+ private void DoInputWork()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _inputLoad; i++)
+ sum += Mathf.Tan(i);
+ _ = sum;
+ }
+
+ // Combined form: outer method-wide using-declaration + nested using-statement.
+ // Both auto-named after the method; distinct because their line numbers differ.
+ private void SimulateAudio()
+ {
+ using var _ = this.Marker(); // Profiler: FrameProfiler.SimulateAudio (84)
+ PrepareAudio();
+
+ using (this.Marker()) // Profiler: FrameProfiler.SimulateAudio (87)
+ MixAudio();
+ }
+
+ private void PrepareAudio()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _audioLoad; i++)
+ sum += Mathf.Sqrt(i);
+ _ = sum;
+ }
+
+ private void MixAudio()
+ {
+ var sum = 0f;
+ for (var i = 0; i < _audioLoad; i++)
+ sum += Mathf.Cos(i);
+ _ = sum;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs.meta
new file mode 100644
index 0000000..209bacc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/ProfilerMarkers/Scripts/FrameProfiler.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 81773b495ca84416af8f5b4210f650f6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types.meta
new file mode 100644
index 0000000..ccc47fc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5549194b371a41d08e429360a65e08ff
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs.meta
new file mode 100644
index 0000000..84e9ac9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d7c22acfa3b44fdeba05b6ac683bbc1b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab
new file mode 100644
index 0000000..fc0897f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab
@@ -0,0 +1,56 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &1234567890123456789
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 2234567890123456789}
+ - component: {fileID: 3234567890123456789}
+ m_Layer: 0
+ m_Name: AbilitySelector
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &2234567890123456789
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &3234567890123456789
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 1234567890123456789}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 5c99d520a5f74297b09811a16d0e65fd, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Types::Aspid.FastTools.Samples.Types.AbilitySelector
+ _abilityType:
+ _assemblyQualifiedName: Aspid.FastTools.Samples.Types.Dash, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ _modifierTypes:
+ - Aspid.FastTools.Samples.Types.CooldownReductionModifier, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - Aspid.FastTools.Samples.Types.DoubleDamageModifier, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
+ - Aspid.FastTools.Samples.Types.RangeBoostModifier, Aspid.FastTools.Samples.Types,
+ Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab.meta
new file mode 100644
index 0000000..7a58b3f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/AbilitySelector.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: c922b4c1592c4065b541f02f0acc425b
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab
new file mode 100644
index 0000000..c5d0184
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab
@@ -0,0 +1,47 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!1 &9120107065992179554
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 3305627272239008027}
+ - component: {fileID: 9066011880317238500}
+ m_Layer: 0
+ m_Name: Enemy
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!4 &3305627272239008027
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 9120107065992179554}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 0, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &9066011880317238500
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 9120107065992179554}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 0667ffc8ad8b4dbba538545a568350f3, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.Types::Aspid.FastTools.Samples.Types.FastEnemy
+ _health: 100
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab.meta
new file mode 100644
index 0000000..c782e27
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Prefabs/Enemy.prefab.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f2ebce908ab18403bbddb4e5101b460a
+PrefabImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README.md
new file mode 100644
index 0000000..038dca6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README.md
@@ -0,0 +1,21 @@
+# Types Sample
+
+A tiny ability system that demonstrates polymorphic type selection in the Unity Inspector using `SerializableType`, `TypeSelectorAttribute`, and `ComponentTypeSelector`. The player picks an `Ability` subclass and a list of `AbilityModifier` subclasses; enemies use `ComponentTypeSelector` so the concrete enemy script can be hot-swapped from the Inspector.
+
+Look at:
+
+- `Scripts/Abilities/AbilitySelector.cs:20` — `SerializableType` field, constrained picker for a single subtype.
+- `Scripts/Abilities/AbilitySelector.cs:25` — `[TypeSelector(typeof(AbilityModifier))]` on a `string[]` field.
+- `Scripts/Enemies/EnemyBase.cs:18` — `ComponentTypeSelector` declaration that swaps the attached script in place.
+
+Both Type drawers ship a UIToolkit and an IMGUI rendering path. Parallel `IMGUI*` variants force the IMGUI path so you can compare them side by side or migrate IMGUI-only projects:
+
+- `Scripts/Abilities/IMGUIAbilitySelector.cs` + `Scripts/Editor/IMGUIAbilitySelectorEditor.cs` — same `SerializableType` / `[TypeSelector]` fields rendered via `OnInspectorGUI`.
+- `Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs` (+ `IMGUIFastEnemy`, `IMGUITankEnemy`) + `Scripts/Editor/IMGUIEnemyBaseEditor.cs` — IMGUI counterpart of the `ComponentTypeSelector` swap flow.
+
+## How to run
+
+Open `Scenes/Types.unity` — it contains two prefab instances:
+
+- **AbilitySelector** (`Prefabs/AbilitySelector.prefab`) — an `AbilitySelector` with `Dash` pre-picked and all three modifiers filled in. Enter Play Mode to see the Console log the activated ability and each applied modifier.
+- **Enemy** (`Prefabs/Enemy.prefab`) — a `FastEnemy` wired up through `ComponentTypeSelector`. Select it in the Hierarchy and use the type dropdown at the top of the Inspector to swap between `FastEnemy` and `TankEnemy` in place; the `Health` field persists across the swap.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README.md.meta
new file mode 100644
index 0000000..851455a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: c7e265c2e7c3641f4bfa4ade9da6a940
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README_RU.md
new file mode 100644
index 0000000..7ebc335
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README_RU.md
@@ -0,0 +1,21 @@
+# Пример Types
+
+Маленькая система способностей, демонстрирующая полиморфный выбор типа в Unity Inspector с помощью `SerializableType`, `TypeSelectorAttribute` и `ComponentTypeSelector`. Игрок выбирает наследника `Ability` и список наследников `AbilityModifier`; для врагов используется `ComponentTypeSelector`, чтобы конкретный скрипт врага можно было заменить «на лету» из Inspector.
+
+Смотрите:
+
+- `Scripts/Abilities/AbilitySelector.cs:20` — поле `SerializableType`, ограниченный выбор одного подтипа.
+- `Scripts/Abilities/AbilitySelector.cs:25` — `[TypeSelector(typeof(AbilityModifier))]` на поле `string[]`.
+- `Scripts/Enemies/EnemyBase.cs:18` — объявление `ComponentTypeSelector`, заменяющее прикреплённый скрипт по месту.
+
+Оба Type-drawer’а поддерживают и UIToolkit, и IMGUI. Параллельные `IMGUI*`-варианты принудительно используют IMGUI-путь — удобно для сравнения или миграции IMGUI-проектов:
+
+- `Scripts/Abilities/IMGUIAbilitySelector.cs` + `Scripts/Editor/IMGUIAbilitySelectorEditor.cs` — те же поля `SerializableType` / `[TypeSelector]`, отрисованные через `OnInspectorGUI`.
+- `Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs` (+ `IMGUIFastEnemy`, `IMGUITankEnemy`) + `Scripts/Editor/IMGUIEnemyBaseEditor.cs` — IMGUI-эквивалент потока подмены через `ComponentTypeSelector`.
+
+## Как запустить
+
+Откройте `Scenes/Types.unity` — в сцене два prefab-инстанса:
+
+- **AbilitySelector** (`Prefabs/AbilitySelector.prefab`) — `AbilitySelector` с предвыбранной способностью `Dash` и тремя заполненными модификаторами. Войдите в Play Mode, чтобы увидеть в Console лог активированной способности и каждого применённого модификатора.
+- **Enemy** (`Prefabs/Enemy.prefab`) — `FastEnemy`, подключённый через `ComponentTypeSelector`. Выделите его в Hierarchy и используйте выпадающий список выбора типа в верхней части Inspector, чтобы переключиться между `FastEnemy` и `TankEnemy` по месту; значение `Health` сохраняется при замене.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README_RU.md.meta
new file mode 100644
index 0000000..2fee21c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8b4c2d5e6f7081923a4b5c6d7e8f9012
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes.meta
new file mode 100644
index 0000000..34e5f9f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes/Types.unity b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes/Types.unity
new file mode 100644
index 0000000..dccc62e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes/Types.unity
@@ -0,0 +1,507 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!29 &1
+OcclusionCullingSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_OcclusionBakeSettings:
+ smallestOccluder: 5
+ smallestHole: 0.25
+ backfaceThreshold: 100
+ m_SceneGUID: 00000000000000000000000000000000
+ m_OcclusionCullingData: {fileID: 0}
+--- !u!104 &2
+RenderSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 10
+ m_Fog: 0
+ m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
+ m_FogMode: 3
+ m_FogDensity: 0.01
+ m_LinearFogStart: 0
+ m_LinearFogEnd: 300
+ m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
+ m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
+ m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
+ m_AmbientIntensity: 1
+ m_AmbientMode: 0
+ m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
+ m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
+ m_HaloStrength: 0.5
+ m_FlareStrength: 1
+ m_FlareFadeSpeed: 3
+ m_HaloTexture: {fileID: 0}
+ m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
+ m_DefaultReflectionMode: 0
+ m_DefaultReflectionResolution: 128
+ m_ReflectionBounces: 1
+ m_ReflectionIntensity: 1
+ m_CustomReflection: {fileID: 0}
+ m_Sun: {fileID: 0}
+ m_UseRadianceAmbientProbe: 0
+--- !u!157 &3
+LightmapSettings:
+ m_ObjectHideFlags: 0
+ serializedVersion: 13
+ m_BakeOnSceneLoad: 0
+ m_GISettings:
+ serializedVersion: 2
+ m_BounceScale: 1
+ m_IndirectOutputScale: 1
+ m_AlbedoBoost: 1
+ m_EnvironmentLightingMode: 0
+ m_EnableBakedLightmaps: 1
+ m_EnableRealtimeLightmaps: 0
+ m_LightmapEditorSettings:
+ serializedVersion: 12
+ m_Resolution: 2
+ m_BakeResolution: 40
+ m_AtlasSize: 1024
+ m_AO: 0
+ m_AOMaxDistance: 1
+ m_CompAOExponent: 1
+ m_CompAOExponentDirect: 0
+ m_ExtractAmbientOcclusion: 0
+ m_Padding: 2
+ m_LightmapParameters: {fileID: 0}
+ m_LightmapsBakeMode: 1
+ m_TextureCompression: 1
+ m_ReflectionCompression: 2
+ m_MixedBakeMode: 2
+ m_BakeBackend: 1
+ m_PVRSampling: 1
+ m_PVRDirectSampleCount: 32
+ m_PVRSampleCount: 512
+ m_PVRBounces: 2
+ m_PVREnvironmentSampleCount: 256
+ m_PVREnvironmentReferencePointCount: 2048
+ m_PVRFilteringMode: 1
+ m_PVRDenoiserTypeDirect: 1
+ m_PVRDenoiserTypeIndirect: 1
+ m_PVRDenoiserTypeAO: 1
+ m_PVRFilterTypeDirect: 0
+ m_PVRFilterTypeIndirect: 0
+ m_PVRFilterTypeAO: 0
+ m_PVREnvironmentMIS: 1
+ m_PVRCulling: 1
+ m_PVRFilteringGaussRadiusDirect: 1
+ m_PVRFilteringGaussRadiusIndirect: 1
+ m_PVRFilteringGaussRadiusAO: 1
+ m_PVRFilteringAtrousPositionSigmaDirect: 0.5
+ m_PVRFilteringAtrousPositionSigmaIndirect: 2
+ m_PVRFilteringAtrousPositionSigmaAO: 1
+ m_ExportTrainingData: 0
+ m_TrainingDataDestination: TrainingData
+ m_LightProbeSampleCountMultiplier: 4
+ m_LightingDataAsset: {fileID: 20201, guid: 0000000000000000f000000000000000, type: 0}
+ m_LightingSettings: {fileID: 0}
+--- !u!196 &4
+NavMeshSettings:
+ serializedVersion: 2
+ m_ObjectHideFlags: 0
+ m_BuildSettings:
+ serializedVersion: 3
+ agentTypeID: 0
+ agentRadius: 0.5
+ agentHeight: 2
+ agentSlope: 45
+ agentClimb: 0.4
+ ledgeDropHeight: 0
+ maxJumpAcrossDistance: 0
+ minRegionArea: 2
+ manualCellSize: 0
+ cellSize: 0.16666667
+ manualTileSize: 0
+ tileSize: 256
+ buildHeightMesh: 0
+ maxJobWorkers: 0
+ preserveTilesOutsideBounds: 0
+ debug:
+ m_Flags: 0
+ m_NavMeshData: {fileID: 0}
+--- !u!1 &100001
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 100003}
+ - component: {fileID: 100002}
+ - component: {fileID: 100004}
+ m_Layer: 0
+ m_Name: Directional Light
+ m_TagString: Untagged
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!108 &100002
+Light:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ m_Enabled: 1
+ serializedVersion: 13
+ m_Type: 1
+ m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
+ m_Intensity: 1
+ m_Range: 10
+ m_SpotAngle: 30
+ m_InnerSpotAngle: 21.80208
+ m_CookieSize2D: {x: 10, y: 10}
+ m_Shadows:
+ m_Type: 2
+ m_Resolution: -1
+ m_CustomResolution: -1
+ m_Strength: 1
+ m_Bias: 0.05
+ m_NormalBias: 0.4
+ m_NearPlane: 0.2
+ m_CullingMatrixOverride:
+ e00: 1
+ e01: 0
+ e02: 0
+ e03: 0
+ e10: 0
+ e11: 1
+ e12: 0
+ e13: 0
+ e20: 0
+ e21: 0
+ e22: 1
+ e23: 0
+ e30: 0
+ e31: 0
+ e32: 0
+ e33: 1
+ m_UseCullingMatrixOverride: 0
+ m_Cookie: {fileID: 0}
+ m_DrawHalo: 0
+ m_Flare: {fileID: 0}
+ m_RenderMode: 0
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingLayerMask: 1
+ m_Lightmapping: 4
+ m_LightShadowCasterMode: 0
+ m_AreaSize: {x: 1, y: 1}
+ m_BounceIntensity: 1
+ m_ColorTemperature: 6570
+ m_UseColorTemperature: 0
+ m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_UseBoundingSphereOverride: 0
+ m_UseViewFrustumForShadowCasterCull: 1
+ m_ForceVisible: 0
+ m_ShapeRadius: 0
+ m_ShadowAngle: 0
+ m_LightUnit: 1
+ m_LuxAtDistance: 1
+ m_EnableSpotReflector: 1
+--- !u!4 &100003
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
+ m_LocalPosition: {x: 0, y: 3, z: 0}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
+--- !u!114 &100004
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 100001}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: 474bcb49853aa07438625e644c072ee6, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalLightData
+ m_UsePipelineSettings: 1
+ m_AdditionalLightsShadowResolutionTier: 2
+ m_CustomShadowLayers: 0
+ m_LightCookieSize: {x: 1, y: 1}
+ m_LightCookieOffset: {x: 0, y: 0}
+ m_SoftShadowQuality: 0
+ m_RenderingLayersMask:
+ serializedVersion: 0
+ m_Bits: 1
+ m_ShadowRenderingLayersMask:
+ serializedVersion: 0
+ m_Bits: 1
+ m_Version: 4
+ m_LightLayerMask: 1
+ m_ShadowLayerMask: 1
+ m_RenderingLayers: 1
+ m_ShadowRenderingLayers: 1
+--- !u!1 &200001
+GameObject:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ serializedVersion: 6
+ m_Component:
+ - component: {fileID: 200004}
+ - component: {fileID: 200002}
+ - component: {fileID: 200003}
+ - component: {fileID: 200005}
+ m_Layer: 0
+ m_Name: Main Camera
+ m_TagString: MainCamera
+ m_Icon: {fileID: 0}
+ m_NavMeshLayer: 0
+ m_StaticEditorFlags: 0
+ m_IsActive: 1
+--- !u!81 &200002
+AudioListener:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+--- !u!20 &200003
+Camera:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+ serializedVersion: 2
+ m_ClearFlags: 1
+ m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
+ m_projectionMatrixMode: 1
+ m_GateFitMode: 2
+ m_FOVAxisMode: 0
+ m_Iso: 200
+ m_ShutterSpeed: 0.005
+ m_Aperture: 16
+ m_FocusDistance: 10
+ m_FocalLength: 50
+ m_BladeCount: 5
+ m_Curvature: {x: 2, y: 11}
+ m_BarrelClipping: 0.25
+ m_Anamorphism: 0
+ m_SensorSize: {x: 36, y: 24}
+ m_LensShift: {x: 0, y: 0}
+ m_NormalizedViewPortRect:
+ serializedVersion: 2
+ x: 0
+ y: 0
+ width: 1
+ height: 1
+ near clip plane: 0.3
+ far clip plane: 1000
+ field of view: 60
+ orthographic: 0
+ orthographic size: 5
+ m_Depth: -1
+ m_CullingMask:
+ serializedVersion: 2
+ m_Bits: 4294967295
+ m_RenderingPath: -1
+ m_TargetTexture: {fileID: 0}
+ m_TargetDisplay: 0
+ m_TargetEye: 3
+ m_HDR: 1
+ m_AllowMSAA: 1
+ m_AllowDynamicResolution: 0
+ m_ForceIntoRT: 0
+ m_OcclusionCulling: 1
+ m_StereoConvergence: 10
+ m_StereoSeparation: 0.022
+--- !u!4 &200004
+Transform:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ serializedVersion: 2
+ m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
+ m_LocalPosition: {x: 0, y: 1, z: -10}
+ m_LocalScale: {x: 1, y: 1, z: 1}
+ m_ConstrainProportionsScale: 0
+ m_Children: []
+ m_Father: {fileID: 0}
+ m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
+--- !u!114 &200005
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 200001}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
+ m_Name:
+ m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
+ m_RenderShadows: 1
+ m_RequiresDepthTextureOption: 2
+ m_RequiresOpaqueTextureOption: 2
+ m_CameraType: 0
+ m_Cameras: []
+ m_RendererIndex: -1
+ m_VolumeLayerMask:
+ serializedVersion: 2
+ m_Bits: 1
+ m_VolumeTrigger: {fileID: 0}
+ m_VolumeFrameworkUpdateModeOption: 2
+ m_RenderPostProcessing: 0
+ m_Antialiasing: 0
+ m_AntialiasingQuality: 2
+ m_StopNaN: 0
+ m_Dithering: 0
+ m_ClearDepth: 1
+ m_AllowXRRendering: 1
+ m_AllowHDROutput: 1
+ m_UseScreenCoordOverride: 0
+ m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
+ m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
+ m_RequiresDepthTexture: 0
+ m_RequiresColorTexture: 0
+ m_TaaSettings:
+ m_Quality: 3
+ m_FrameInfluence: 0.1
+ m_JitterScale: 1
+ m_MipBias: 0
+ m_VarianceClampScale: 0.9
+ m_ContrastAdaptiveSharpening: 0
+ m_Version: 2
+--- !u!1001 &970635237
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 0}
+ m_Modifications:
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 3
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 3305627272239008027, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 9120107065992179554, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+ propertyPath: m_Name
+ value: Enemy
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: f2ebce908ab18403bbddb4e5101b460a, type: 3}
+--- !u!1001 &1794465240
+PrefabInstance:
+ m_ObjectHideFlags: 0
+ serializedVersion: 2
+ m_Modification:
+ serializedVersion: 3
+ m_TransformParent: {fileID: 0}
+ m_Modifications:
+ - target: {fileID: 1234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_Name
+ value: AbilitySelector
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalPosition.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalPosition.y
+ value: 3
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalPosition.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalRotation.w
+ value: 1
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalRotation.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalRotation.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalRotation.z
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.x
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.y
+ value: 0
+ objectReference: {fileID: 0}
+ - target: {fileID: 2234567890123456789, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+ propertyPath: m_LocalEulerAnglesHint.z
+ value: 0
+ objectReference: {fileID: 0}
+ m_RemovedComponents: []
+ m_RemovedGameObjects: []
+ m_AddedGameObjects: []
+ m_AddedComponents: []
+ m_SourcePrefab: {fileID: 100100000, guid: c922b4c1592c4065b541f02f0acc425b, type: 3}
+--- !u!1660057539 &9223372036854775807
+SceneRoots:
+ m_ObjectHideFlags: 0
+ m_Roots:
+ - {fileID: 200004}
+ - {fileID: 100003}
+ - {fileID: 1794465240}
+ - {fileID: 970635237}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes/Types.unity.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes/Types.unity.meta
new file mode 100644
index 0000000..b944dbc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scenes/Types.unity.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: a551a863bae542399c669640b76d544b
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts.meta
new file mode 100644
index 0000000..6a7f9fb
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: bb5f0e994a694cc68a6ad846fb3c94e1
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities.meta
new file mode 100644
index 0000000..444e390
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5f15b5a12db04b76a37194e14b45df5d
+timeCreated: 1776546281
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Ability.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Ability.cs
new file mode 100644
index 0000000..f2de744
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Ability.cs
@@ -0,0 +1,10 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public abstract class Ability : MonoBehaviour
+ {
+ public abstract void Activate();
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Ability.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Ability.cs.meta
new file mode 100644
index 0000000..260241d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Ability.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3f81a50759684a12a0e30c2af6f84e7c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/AbilitySelector.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/AbilitySelector.cs
new file mode 100644
index 0000000..0b8bbd0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/AbilitySelector.cs
@@ -0,0 +1,55 @@
+using System;
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ // Demonstrates two complementary ways to pick a System.Type from the Inspector:
+ //
+ // 1. SerializableType — strongly typed wrapper, constrained via generic parameter.
+ // Resolution is cached; implicit conversion to Type? is free.
+ // 2. [TypeSelector] + string — annotate a raw string field to get the same picker window.
+ // Useful when you want multiple base constraints
+ // or want to opt out of the wrapper allocation.
+ //
+ // Both forms store the assembly-qualified name and resolve lazily at first access.
+ public sealed class AbilitySelector : MonoBehaviour
+ {
+ // Picker is constrained to Ability subtypes by the generic argument.
+ [SerializeField] private SerializableType _abilityType;
+
+ // Array field + attribute: each element is its own picker constrained to AbilityModifier.
+ // Allow defaults to TypeAllow.None — abstract bases and interfaces are hidden;
+ // set Allow = TypeAllow.Abstract / Interface / All to opt in.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private string[] _modifierTypes;
+
+ private void Start()
+ {
+ // .Type performs the lazy GetType() lookup and caches the result.
+ // Returns null if the stored assembly-qualified name no longer resolves
+ // (e.g., the type was renamed).
+ var abilityType = _abilityType.Type;
+ if (abilityType is null) return;
+
+ // Implicit conversion is guaranteed safe: the picker only offers Ability subtypes.
+ var ability = (Ability)gameObject.AddComponent(abilityType);
+ ability.Activate();
+
+ // Raw-string form: resolve manually via Type.GetType.
+ // Skip silently on unresolved names to keep the sample self-contained —
+ // production code should log or surface the missing type.
+ foreach (var qualifiedName in _modifierTypes)
+ {
+ var modifierType = Type.GetType(qualifiedName);
+ if (modifierType is null) continue;
+
+ // Modifiers are plain C# classes (not components) — Activator.CreateInstance
+ // is appropriate here; AddComponent would fail for non-UnityEngine.Object types.
+ var modifier = (AbilityModifier)Activator.CreateInstance(modifierType);
+ modifier.Apply();
+ }
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/AbilitySelector.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/AbilitySelector.cs.meta
new file mode 100644
index 0000000..80bc5b0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/AbilitySelector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5c99d520a5f74297b09811a16d0e65fd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Dash.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Dash.cs
new file mode 100644
index 0000000..1002567
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Dash.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class Dash : Ability
+ {
+ public override void Activate() =>
+ Debug.Log("Dash performed!");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Dash.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Dash.cs.meta
new file mode 100644
index 0000000..d5b874d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Dash.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fd1b54437b9541c1b53f6820cf40e7a2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Fireball.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Fireball.cs
new file mode 100644
index 0000000..627fca2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Fireball.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class Fireball : Ability
+ {
+ public override void Activate() =>
+ Debug.Log("Fireball launched!");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Fireball.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Fireball.cs.meta
new file mode 100644
index 0000000..db60c1d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Fireball.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f86571f0dfa54afa89d3e98721bc6c1b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Heal.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Heal.cs
new file mode 100644
index 0000000..66ca581
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Heal.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class Heal : Ability
+ {
+ public override void Activate() =>
+ Debug.Log("Heal cast!");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Heal.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Heal.cs.meta
new file mode 100644
index 0000000..d3a8bed
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/Heal.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: df96ad82595246f284ec3aad4a445e6e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/IMGUIAbilitySelector.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/IMGUIAbilitySelector.cs
new file mode 100644
index 0000000..ea85607
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/IMGUIAbilitySelector.cs
@@ -0,0 +1,28 @@
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ // Demonstrates the IMGUI rendering path for Aspid.FastTools Type drawers.
+ //
+ // The same SerializableType and [TypeSelector] fields used by the UIToolkit-based
+ // AbilitySelector sample are reused here, but the companion editor
+ // (IMGUIAbilitySelectorEditor) overrides OnInspectorGUI without CreateInspectorGUI,
+ // forcing Unity to render the inspector — and every nested Type picker — through
+ // the IMGUI code path (TypeIMGUIPropertyDrawer).
+ //
+ // Useful when migrating projects that still rely on IMGUI editors, or when verifying
+ // that both rendering paths stay visually and behaviourally aligned.
+ public sealed class IMGUIAbilitySelector : MonoBehaviour
+ {
+ // Picker is constrained to Ability subtypes by the generic argument.
+ [SerializeField] private SerializableType _primaryAbility;
+
+ // Array field + attribute: each element is its own picker constrained to AbilityModifier.
+ // Allow defaults to TypeAllow.None — abstract bases and interfaces are hidden;
+ // set Allow = TypeAllow.Abstract / Interface / All to opt in.
+ [TypeSelector(typeof(AbilityModifier))]
+ [SerializeField] private string[] _modifierTypes;
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/IMGUIAbilitySelector.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/IMGUIAbilitySelector.cs.meta
new file mode 100644
index 0000000..a2cff07
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Abilities/IMGUIAbilitySelector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a3f17c8b29d04e7eaa2d9a1c75b8e404
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Aspid.FastTools.Samples.Types.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Aspid.FastTools.Samples.Types.asmdef
new file mode 100644
index 0000000..b537bd2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Aspid.FastTools.Samples.Types.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "Aspid.FastTools.Samples.Types",
+ "rootNamespace": "",
+ "references": [
+ "Aspid.FastTools.Unity"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Aspid.FastTools.Samples.Types.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Aspid.FastTools.Samples.Types.asmdef.meta
new file mode 100644
index 0000000..a57965b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Aspid.FastTools.Samples.Types.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 56ba1765a6eb4975ada12506d8c4afda
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor.meta
new file mode 100644
index 0000000..89a7efd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 1d6a8b3f2c45418792e07c8541b9d2e3
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/Aspid.FastTools.Samples.Types.Editor.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/Aspid.FastTools.Samples.Types.Editor.asmdef
new file mode 100644
index 0000000..a6f07de
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/Aspid.FastTools.Samples.Types.Editor.asmdef
@@ -0,0 +1,19 @@
+{
+ "name": "Aspid.FastTools.Samples.Types.Editor",
+ "references": [
+ "GUID:56ba1765a6eb4975ada12506d8c4afda",
+ "GUID:7c010b89992542508a6b6189977e64d4",
+ "GUID:94dcbbdbbd3ca48b891ee4fc8455c434"
+ ],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/Aspid.FastTools.Samples.Types.Editor.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/Aspid.FastTools.Samples.Types.Editor.asmdef.meta
new file mode 100644
index 0000000..20ba97a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/Aspid.FastTools.Samples.Types.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f7b30a1e2c5d4ee0b34c1f2a7e9d4068
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIAbilitySelectorEditor.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIAbilitySelectorEditor.cs
new file mode 100644
index 0000000..2fad35d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIAbilitySelectorEditor.cs
@@ -0,0 +1,23 @@
+using UnityEditor;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types.Editors
+{
+ // Forces IMGUI rendering for the IMGUIAbilitySelector inspector.
+ //
+ // Unity decides between IMGUI and UIToolkit at the Editor level: when
+ // CreateInspectorGUI is NOT overridden but OnInspectorGUI is, the entire inspector —
+ // including every nested PropertyDrawer — falls back to the IMGUI path. That routes
+ // SerializableType and [TypeSelector] fields through TypeIMGUIPropertyDrawer.OnGUI
+ // instead of CreatePropertyGUI, demonstrating the IMGUI rendering of the picker.
+ [CustomEditor(typeof(IMGUIAbilitySelector))]
+ internal sealed class IMGUIAbilitySelectorEditor : Editor
+ {
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+ DrawPropertiesExcluding(serializedObject, "m_Script");
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIAbilitySelectorEditor.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIAbilitySelectorEditor.cs.meta
new file mode 100644
index 0000000..08ada24
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIAbilitySelectorEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8e2c44a7b91f4dc9a06d45f7c81b09a2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIEnemyBaseEditor.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIEnemyBaseEditor.cs
new file mode 100644
index 0000000..1a51790
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIEnemyBaseEditor.cs
@@ -0,0 +1,22 @@
+using UnityEditor;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types.Editors
+{
+ // Forces IMGUI rendering for IMGUIEnemyBase and every concrete subtype.
+ //
+ // editorForChildClasses: true makes this editor apply to IMGUIFastEnemy / IMGUITankEnemy
+ // too, so swapping the component's m_Script via ComponentTypeSelector keeps the inspector
+ // in IMGUI mode after Unity rebuilds the editor for the new subtype. Without that flag,
+ // the post-swap subtype would fall back to the default UIToolkit inspector.
+ [CustomEditor(typeof(IMGUIEnemyBase), editorForChildClasses: true)]
+ internal sealed class IMGUIEnemyBaseEditor : Editor
+ {
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+ DrawPropertiesExcluding(serializedObject, "m_Script");
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIEnemyBaseEditor.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIEnemyBaseEditor.cs.meta
new file mode 100644
index 0000000..9a35112
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Editor/IMGUIEnemyBaseEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9b7e1d2a5c4f48a3a05e7c41b9d28a36
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies.meta
new file mode 100644
index 0000000..b27daeb
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 7b2295ff4cc94122ad458718856ad4a0
+timeCreated: 1776546314
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/EnemyBase.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/EnemyBase.cs
new file mode 100644
index 0000000..fd90625
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/EnemyBase.cs
@@ -0,0 +1,26 @@
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ // Demonstrates ComponentTypeSelector: a serialized marker that adds an Inspector
+ // dropdown letting you swap this component's script to any subtype of EnemyBase
+ // (FastEnemy, TankEnemy, ...) without removing and re-adding the component.
+ //
+ // The picker auto-discovers subtypes via the field's declaring class — no
+ // configuration needed. Selection rewrites m_Script on the SerializedObject,
+ // so all fields persist where the new subtype declares a matching name.
+ public abstract class EnemyBase : MonoBehaviour
+ {
+ // The struct itself is empty; it only carries a PropertyDrawer.
+ // Place one field per root class — typically at the top of the Inspector.
+ [SerializeField] private ComponentTypeSelector _enemyType;
+
+ [SerializeField] [Min(0)] private float _health = 100f;
+
+ protected float Health => _health;
+
+ public abstract void Attack();
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/EnemyBase.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/EnemyBase.cs.meta
new file mode 100644
index 0000000..5877749
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/EnemyBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 171cf8180e114d27bfbbd9e73261652f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/FastEnemy.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/FastEnemy.cs
new file mode 100644
index 0000000..1080395
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/FastEnemy.cs
@@ -0,0 +1,13 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class FastEnemy : EnemyBase
+ {
+ [SerializeField] [Min(0)] private float _speed = 25f;
+
+ public override void Attack() =>
+ Debug.Log("Fast enemy strikes!");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/FastEnemy.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/FastEnemy.cs.meta
new file mode 100644
index 0000000..bc20a6f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/FastEnemy.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0667ffc8ad8b4dbba538545a568350f3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI.meta
new file mode 100644
index 0000000..d8c9d0e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2b5e9a17b3ed13840b378e2bcd037e43
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs
new file mode 100644
index 0000000..b11c559
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs
@@ -0,0 +1,27 @@
+using UnityEngine;
+using Aspid.FastTools.Types;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ // Demonstrates the IMGUI rendering path for ComponentTypeSelector.
+ //
+ // The mechanic mirrors the EnemyBase / Fast/Tank Enemy hierarchy: a single
+ // serialized ComponentTypeSelector field surfaces a subtype dropdown in the Inspector,
+ // and selecting a subtype rewrites m_Script in place — fields with matching names persist.
+ //
+ // The companion IMGUIEnemyBaseEditor (with editorForChildClasses: true) overrides
+ // OnInspectorGUI without CreateInspectorGUI, so every subclass renders through IMGUI
+ // and the picker goes through ComponentTypeSelectorPropertyDrawer.OnGUI instead of
+ // CreatePropertyGUI.
+ public abstract class IMGUIEnemyBase : MonoBehaviour
+ {
+ [SerializeField] private ComponentTypeSelector _enemyType;
+
+ [SerializeField] [Min(0)] private float _health = 100f;
+
+ protected float Health => _health;
+
+ public abstract void Attack();
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs.meta
new file mode 100644
index 0000000..d77aeda
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIEnemyBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5a3b9c7e1d2f48a6b95c3e1d27a8b604
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIFastEnemy.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIFastEnemy.cs
new file mode 100644
index 0000000..f0bd5fd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIFastEnemy.cs
@@ -0,0 +1,13 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class IMGUIFastEnemy : IMGUIEnemyBase
+ {
+ [SerializeField] [Min(0)] private float _speed = 25f;
+
+ public override void Attack() =>
+ Debug.Log("Fast IMGUI enemy strikes!");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIFastEnemy.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIFastEnemy.cs.meta
new file mode 100644
index 0000000..6fc9c2e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUIFastEnemy.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2c8e91a4b73f4d62a85e9f3d7c41b80a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUITankEnemy.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUITankEnemy.cs
new file mode 100644
index 0000000..d8ea793
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUITankEnemy.cs
@@ -0,0 +1,13 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class IMGUITankEnemy : IMGUIEnemyBase
+ {
+ [SerializeField] [Min(0)] private float _armor = 50f;
+
+ public override void Attack() =>
+ Debug.Log("IMGUI tank attacks!");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUITankEnemy.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUITankEnemy.cs.meta
new file mode 100644
index 0000000..a118ddb
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/IMGUI/IMGUITankEnemy.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4c5b3e2d1a987456b8e063c41d27a8b6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/TankEnemy.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/TankEnemy.cs
new file mode 100644
index 0000000..55d9369
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/TankEnemy.cs
@@ -0,0 +1,13 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class TankEnemy : EnemyBase
+ {
+ [SerializeField] [Min(0)] private float _armor = 50f;
+
+ public override void Attack() =>
+ Debug.Log("Tank attacks!");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/TankEnemy.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/TankEnemy.cs.meta
new file mode 100644
index 0000000..c43eeee
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Enemies/TankEnemy.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1fbf86c93e134055adf1f2a443bd502c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers.meta
new file mode 100644
index 0000000..76fee5e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: d74d3a5c5ac44c18858299d4cc480df1
+timeCreated: 1776546333
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/AbilityModifier.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/AbilityModifier.cs
new file mode 100644
index 0000000..8355285
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/AbilityModifier.cs
@@ -0,0 +1,8 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public abstract class AbilityModifier
+ {
+ public abstract void Apply();
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/AbilityModifier.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/AbilityModifier.cs.meta
new file mode 100644
index 0000000..bab24a3
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/AbilityModifier.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 96e6f60018cc4efab26e8543e24da49b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/CooldownReductionModifier.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/CooldownReductionModifier.cs
new file mode 100644
index 0000000..71e1cdc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/CooldownReductionModifier.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class CooldownReductionModifier : AbilityModifier
+ {
+ public override void Apply() =>
+ Debug.Log($"{nameof(CooldownReductionModifier)} applied.");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/CooldownReductionModifier.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/CooldownReductionModifier.cs.meta
new file mode 100644
index 0000000..2da45e2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/CooldownReductionModifier.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 4f3b09571b8549df90b7841e94f07410
+timeCreated: 1776546350
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/DoubleDamageModifier.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/DoubleDamageModifier.cs
new file mode 100644
index 0000000..b57e900
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/DoubleDamageModifier.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class DoubleDamageModifier : AbilityModifier
+ {
+ public override void Apply() =>
+ Debug.Log($"{nameof(DoubleDamageModifier)} applied.");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/DoubleDamageModifier.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/DoubleDamageModifier.cs.meta
new file mode 100644
index 0000000..20f076e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/DoubleDamageModifier.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 44b4b7f2062c4b138266c17847aa2b02
+timeCreated: 1776546354
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/RangeBoostModifier.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/RangeBoostModifier.cs
new file mode 100644
index 0000000..f83907e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/RangeBoostModifier.cs
@@ -0,0 +1,11 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.Types
+{
+ public sealed class RangeBoostModifier : AbilityModifier
+ {
+ public override void Apply() =>
+ Debug.Log($"{nameof(RangeBoostModifier)} applied.");
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/RangeBoostModifier.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/RangeBoostModifier.cs.meta
new file mode 100644
index 0000000..93b4b78
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/Types/Scripts/Modifiers/RangeBoostModifier.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a6d9ae55ba3148508c5c58f03ac9b54e
+timeCreated: 1776546345
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements.meta
new file mode 100644
index 0000000..efd874d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: dd811fb9dcc9c49898350cf0a1f5657e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README.md
new file mode 100644
index 0000000..989c282
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README.md
@@ -0,0 +1,16 @@
+# VisualElements Sample
+
+A custom UIToolkit Inspector for an `AbilityConfig` ScriptableObject that showcases the fluent `VisualElement` extension API — building a card-style inspector entirely in code, with a status badge that reacts to Mana Cost edits.
+
+Look at:
+
+- `Scripts/Editor/AbilityConfigEditor.cs:23` — `CreateInspectorGUI` composes the card from plain `VisualElement` / `Label` / `HelpBox` chained with the Aspid fluent extensions (`.SetPadding`, `.SetBorderRadius`, `.SetBorderColor`, `.SetFlexDirection`, `.AddChild`).
+- `Scripts/Editor/AbilityConfigEditor.cs:38` — `target.GetScriptName()` (from `Aspid.FastTools.Editors`) is used as the header title.
+- `Scripts/Editor/AbilityConfigEditor.cs:65` — `PropertyField(...).AddValueChanged(_ => UpdateState())` re-runs the badge / help-box logic on every Mana Cost edit.
+- `Scripts/Editor/AbilityConfigEditor.cs:88` — `UpdateState()` flips the badge text/color and toggles `helpBox.SetDisplay(...)` whenever `ManaCost is 0`.
+
+To try it:
+
+1. Select `ScriptableObjects/fireball_1.asset` (paid) or `ScriptableObjects/fireball_free.asset` in the Project window — the custom inspector appears in the Inspector panel.
+2. Edit the fields. Set `Mana Cost` to `0` to see the status badge switch to "FREE" and the warning help box appear inline.
+3. Or create your own via `Assets > Create > Aspid > FastTools > Samples > Ability Config`.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README.md.meta
new file mode 100644
index 0000000..5efd556
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 4e6850127fdae442c85dc56d37cd070f
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README_RU.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README_RU.md
new file mode 100644
index 0000000..13a947c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README_RU.md
@@ -0,0 +1,16 @@
+# Пример VisualElements
+
+Кастомный UIToolkit Inspector для `ScriptableObject` `AbilityConfig`, демонстрирующий fluent-API расширений `VisualElement` — карточный inspector, собранный целиком в коде, со статус-бейджем, реагирующим на правки Mana Cost.
+
+Смотрите:
+
+- `Scripts/Editor/AbilityConfigEditor.cs:23` — `CreateInspectorGUI` собирает карточку из обычных `VisualElement` / `Label` / `HelpBox` с цепочкой Aspid fluent-расширений (`.SetPadding`, `.SetBorderRadius`, `.SetBorderColor`, `.SetFlexDirection`, `.AddChild`).
+- `Scripts/Editor/AbilityConfigEditor.cs:38` — `target.GetScriptName()` (из `Aspid.FastTools.Editors`) используется как заголовок шапки.
+- `Scripts/Editor/AbilityConfigEditor.cs:65` — `PropertyField(...).AddValueChanged(_ => UpdateState())` перезапускает логику бейджа и help-box при каждой правке Mana Cost.
+- `Scripts/Editor/AbilityConfigEditor.cs:88` — `UpdateState()` переключает текст и цвет бейджа и `helpBox.SetDisplay(...)`, когда `ManaCost is 0`.
+
+Чтобы попробовать:
+
+1. Выберите `ScriptableObjects/fireball_1.asset` (платная) или `ScriptableObjects/fireball_free.asset` в окне Project — кастомный inspector появится в панели Inspector.
+2. Отредактируйте поля. Установите `Mana Cost` в `0`, чтобы увидеть, как статус-бейдж переключится на "FREE", и появится встроенный help box с предупреждением.
+3. Или создайте свой через `Assets > Create > Aspid > FastTools > Samples > Ability Config`.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README_RU.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README_RU.md.meta
new file mode 100644
index 0000000..6807ac1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/README_RU.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: be7f508192a3b4c5d6e7f80910a1b2c3
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects.meta
new file mode 100644
index 0000000..e8422d2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 3c3b500b54c6c4141b78263db88a607d
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_1.asset b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_1.asset
new file mode 100644
index 0000000..6c086f8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_1.asset
@@ -0,0 +1,18 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6, type: 3}
+ m_Name: fireball_1
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.VisualElements::Aspid.FastTools.Samples.VisualElements.AbilityConfig
+ _abilityName: Fireball
+ _description: Hurls a ball of fire that explodes on impact.
+ _manaCost: 100
+ _cooldown: 3.5
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_1.asset.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_1.asset.meta
new file mode 100644
index 0000000..7ffe7e5
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_1.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 47df49f0b76bc4bd38c447548a7dfbee
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_free.asset b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_free.asset
new file mode 100644
index 0000000..1d6e18f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_free.asset
@@ -0,0 +1,18 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6, type: 3}
+ m_Name: fireball_free
+ m_EditorClassIdentifier: Aspid.FastTools.Samples.VisualElements::Aspid.FastTools.Samples.VisualElements.AbilityConfig
+ _abilityName: Fireball - Free
+ _description: Hurls a ball of fire that explodes on impact.
+ _manaCost: 0
+ _cooldown: 50
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_free.asset.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_free.asset.meta
new file mode 100644
index 0000000..16cf7d9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/ScriptableObjects/fireball_free.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts.meta
new file mode 100644
index 0000000..3eb3994
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 48284416efb4f482fb664e2ff9dc4f22
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/AbilityConfig.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/AbilityConfig.cs
new file mode 100644
index 0000000..50d5bfe
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/AbilityConfig.cs
@@ -0,0 +1,23 @@
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.VisualElements
+{
+ [CreateAssetMenu(fileName = "New Ability Config", menuName = "Aspid/FastTools/Samples/Ability Config")]
+ public sealed class AbilityConfig : ScriptableObject
+ {
+ [SerializeField] private string _abilityName = "New Ability";
+ [SerializeField] [TextArea] private string _description;
+
+ [SerializeField] [Min(0)] private int _manaCost = 10;
+ [SerializeField] [Min(0f)] private float _cooldown = 1f;
+
+ public int ManaCost => _manaCost;
+
+ public float Cooldown => _cooldown;
+
+ public string AbilityName => _abilityName;
+
+ public string Description => _description;
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/AbilityConfig.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/AbilityConfig.cs.meta
new file mode 100644
index 0000000..bef311e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/AbilityConfig.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Aspid.FastTools.Samples.VisualElements.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Aspid.FastTools.Samples.VisualElements.asmdef
new file mode 100644
index 0000000..0f91491
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Aspid.FastTools.Samples.VisualElements.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "Aspid.FastTools.Samples.VisualElements",
+ "rootNamespace": "",
+ "references": [
+ "Aspid.FastTools.Unity"
+ ],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Aspid.FastTools.Samples.VisualElements.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Aspid.FastTools.Samples.VisualElements.asmdef.meta
new file mode 100644
index 0000000..14109c0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Aspid.FastTools.Samples.VisualElements.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 6a3c7b14e2f94d71b15d0e8f2f4a1c9d
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor.meta
new file mode 100644
index 0000000..f75b2e1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e59e68d2d7da84e24b1c6be44953f34b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/AbilityConfigEditor.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/AbilityConfigEditor.cs
new file mode 100644
index 0000000..e844e62
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/AbilityConfigEditor.cs
@@ -0,0 +1,103 @@
+using UnityEditor;
+using UnityEngine;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+using Aspid.FastTools.Editors;
+using Aspid.FastTools.UIElements;
+using Aspid.FastTools.UIElements.Editors;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Samples.VisualElements.Editors
+{
+ [CustomEditor(typeof(AbilityConfig))]
+ internal sealed class AbilityConfigEditor : Editor
+ {
+ private static readonly Color _cardBackground = new(0.16f, 0.17f, 0.19f);
+ private static readonly Color _cardBorder = new(0.26f, 0.28f, 0.31f);
+ private static readonly Color _dividerColor = new(0.22f, 0.24f, 0.27f);
+ private static readonly Color _titleColor = new(0.93f, 0.93f, 0.94f);
+ private static readonly Color _subtleColor = new(0.58f, 0.61f, 0.66f);
+ private static readonly Color _accent = new(0.42f, 0.69f, 1.00f);
+ private static readonly Color _warning = new(1.00f, 0.76f, 0.30f);
+
+ public override VisualElement CreateInspectorGUI()
+ {
+ var config = (AbilityConfig)target;
+
+ var statusBadge = new Label()
+ .SetFontSize(10)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold)
+ .SetUnityTextAlign(TextAnchor.MiddleCenter)
+ .SetLetterSpacing(1)
+ .SetPadding(top: 3, bottom: 3, left: 10, right: 10)
+ .SetBorderRadius(10)
+ .SetBorderWidth(1);
+
+ var titles = new VisualElement()
+ .SetFlexGrow(1)
+ .AddChild(new Label(target.GetScriptName())
+ .SetColor(_titleColor)
+ .SetFontSize(15)
+ .SetUnityFontStyleAndWeight(FontStyle.Bold))
+ .AddChild(new Label("ABILITY CONFIGURATION")
+ .SetColor(_subtleColor)
+ .SetFontSize(10)
+ .SetLetterSpacing(2)
+ .SetMarginTop(3));
+
+ var header = new VisualElement()
+ .SetFlexDirection(FlexDirection.Row)
+ .SetAlignItems(Align.Center)
+ .SetPadding(top: 12, bottom: 12, left: 14, right: 14)
+ .AddChild(titles)
+ .AddChild(statusBadge);
+
+ var divider = new VisualElement()
+ .SetHeight(1)
+ .SetBackgroundColor(_dividerColor);
+
+ var helpBox = new HelpBox()
+ .SetText("This ability costs no mana — is that intentional?")
+ .SetMessageType(HelpBoxMessageType.Warning)
+ .SetMarginTop(8)
+ .SetBorderRadius(6);
+
+ var manaCostField = new PropertyField(serializedObject.FindProperty("_manaCost"))
+ .AddValueChanged(_ => UpdateState());
+
+ var body = new VisualElement()
+ .SetPadding(top: 12, bottom: 12, left: 14, right: 14)
+ .AddChild(new PropertyField(serializedObject.FindProperty("_abilityName")))
+ .AddChild(new PropertyField(serializedObject.FindProperty("_description")))
+ .AddChild(new PropertyField(serializedObject.FindProperty("_cooldown")))
+ .AddChild(manaCostField)
+ .AddChild(helpBox);
+
+ var card = new VisualElement()
+ .SetBackgroundColor(_cardBackground)
+ .SetBorderColor(_cardBorder)
+ .SetBorderWidth(1)
+ .SetBorderRadius(10)
+ .AddChild(header)
+ .AddChild(divider)
+ .AddChild(body);
+
+ UpdateState();
+ return card;
+
+ void UpdateState()
+ {
+ var manaCost = config.ManaCost;
+ var isFree = manaCost is 0;
+ var color = isFree ? _warning : _accent;
+
+ statusBadge
+ .SetText(isFree ? "FREE" : $"{manaCost} MP")
+ .SetColor(color)
+ .SetBorderColor(color);
+
+ helpBox.SetDisplay(isFree ? DisplayStyle.Flex : DisplayStyle.None);
+ }
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/AbilityConfigEditor.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/AbilityConfigEditor.cs.meta
new file mode 100644
index 0000000..26a6038
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/AbilityConfigEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Editor/Aspid.UnityFastTools.VisualElements.Editor.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/Aspid.FastTools.Samples.VisualElements.Editor.asmdef
similarity index 72%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Editor/Aspid.UnityFastTools.VisualElements.Editor.asmdef
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/Aspid.FastTools.Samples.VisualElements.Editor.asmdef
index ad6ee98..7c4ce85 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/VisualElements/Scripts/Editor/Aspid.UnityFastTools.VisualElements.Editor.asmdef
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/Aspid.FastTools.Samples.VisualElements.Editor.asmdef
@@ -1,10 +1,10 @@
{
- "name": "Aspid.UnityFastTools.VisualElements.Editor",
- "rootNamespace": "",
+ "name": "Aspid.FastTools.Samples.VisualElements.Editor",
"references": [
+ "GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:7c010b89992542508a6b6189977e64d4",
"GUID:94dcbbdbbd3ca48b891ee4fc8455c434",
- "GUID:0acbb74fa21d2442393a327f6c8d5639"
+ "GUID:6a3c7b14e2f94d71b15d0e8f2f4a1c9d"
],
"includePlatforms": [
"Editor"
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/Aspid.FastTools.Samples.VisualElements.Editor.asmdef.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/Aspid.FastTools.Samples.VisualElements.Editor.asmdef.meta
new file mode 100644
index 0000000..1d07f6c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Samples~/VisualElements/Scripts/Editor/Aspid.FastTools.Samples.VisualElements.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 8b2f4c9e7a1d4862a97b5f3e1c2d8e7a
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
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/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.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/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef.meta
index 379034d..ea49f73 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Samples/ProfilerMarkers/Scripts/Aspid.UnityFastTools.ProfilerMarkers.asmdef.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Aspid.FastTools.asmdef.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: 72fb96818833940038502bdd77f5dff9
+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..30773f3
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Source/Extensions/TypeExtensions.cs
@@ -0,0 +1,45 @@
+#nullable enable
+using System;
+using System.Reflection;
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Reflection
+{
+ public static class TypeExtensions
+ {
+ ///
+ /// Returns the members of and its base classes in declaration order (base → derived),
+ /// matching the Unity inspector's traversal order.
+ ///
+ /// The type to inspect.
+ /// The binding flags used to filter members. is forced on internally to avoid duplicate members from base classes.
+ /// Optional ancestor type at which to stop walking the chain. When null, walks all the way to the root type.
+ /// A flat list of instances ordered from the topmost base class down to .
+ 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..efd43cd 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: 2d380e9402a9f4c5b8eb637f9f40c400, 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.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources.meta
new file mode 100644
index 0000000..915c6e0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4c03e99f7975a44b6ae45192a73b498e
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons.meta
new file mode 100644
index 0000000..8e8cf88
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 13b76b63bb7a462f8956f3eee824d2e4
+timeCreated: 1777461222
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_blue_1020x1008.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_blue_1020x1008.png
new file mode 100644
index 0000000..444a013
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_blue_1020x1008.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/Unity/Editor/Resources/Icons/aspid_icon_medium_blue_1020x1008.png.meta
similarity index 98%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Resources/Editor/Aspid.UnityFastTools Icon.png.meta
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_blue_1020x1008.png.meta
index 91a7f78..97b77e5 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Resources/Editor/Aspid.UnityFastTools Icon.png.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_blue_1020x1008.png.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: daa63609570f146cfa1b178f6d6c8bda
+guid: 16cb73b44172a4ea9bf2d058c90a9614
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_green_1020x1008.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_green_1020x1008.png
new file mode 100644
index 0000000..5875c9b
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_green_1020x1008.png differ
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_green_1020x1008.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_green_1020x1008.png.meta
new file mode 100644
index 0000000..f447b5a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_green_1020x1008.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 2d380e9402a9f4c5b8eb637f9f40c400
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ 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: 1
+ wrapV: 1
+ wrapW: 0
+ nPOTScale: 0
+ 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: 1
+ spriteTessellationDetail: -1
+ textureType: 2
+ 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.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_red_1020x1008.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_red_1020x1008.png
new file mode 100644
index 0000000..161865f
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_red_1020x1008.png differ
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_red_1020x1008.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_red_1020x1008.png.meta
new file mode 100644
index 0000000..2103427
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_red_1020x1008.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: ec5938e9935444547813083d1e9c6392
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ 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: 1
+ wrapV: 1
+ wrapW: 0
+ nPOTScale: 0
+ 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: 1
+ spriteTessellationDetail: -1
+ textureType: 2
+ 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.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_yellow_1020x1008.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_yellow_1020x1008.png
new file mode 100644
index 0000000..1114ab5
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_yellow_1020x1008.png differ
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_yellow_1020x1008.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_yellow_1020x1008.png.meta
new file mode 100644
index 0000000..730f0d1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/aspid_icon_medium_yellow_1020x1008.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 45e1e8af6ec4a47f192980d99bf0dbb9
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ 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: 1
+ wrapV: 1
+ wrapW: 0
+ nPOTScale: 0
+ 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: 1
+ spriteTessellationDetail: -1
+ textureType: 2
+ 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.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/open_button_icon.png b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/open_button_icon.png
new file mode 100644
index 0000000..249989d
Binary files /dev/null and b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/open_button_icon.png differ
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/open_button_icon.png.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/open_button_icon.png.meta
new file mode 100644
index 0000000..8bdde85
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/Icons/open_button_icon.png.meta
@@ -0,0 +1,143 @@
+fileFormatVersion: 2
+guid: 3666b4404e4ca46f3b92a61a42a557ba
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 0
+ 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: 1
+ wrapV: 1
+ wrapW: 0
+ nPOTScale: 0
+ 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: 1
+ spriteTessellationDetail: -1
+ textureType: 2
+ 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.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI.meta
new file mode 100644
index 0000000..b97b6de
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a96d1d061b50406a82df4b7fda53a110
+timeCreated: 1777206331
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Aspid-FastTools-Default-Dark.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Aspid-FastTools-Default-Dark.uss
new file mode 100644
index 0000000..2c59e3c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Aspid-FastTools-Default-Dark.uss
@@ -0,0 +1,386 @@
+:root {
+ /* ---------------------------------- ICONS ---------------------------------- */
+ --aspid-icons-status-success: resource("Icons/aspid_icon_medium_green_1020x1008");
+ --aspid-icons-status-warning: resource("Icons/aspid_icon_medium_yellow_1020x1008");
+ --aspid-icons-status-error: resource("Icons/aspid_icon_medium_red_1020x1008");
+ --aspid-icons-status-info: resource("Icons/aspid_icon_medium_blue_1020x1008");
+
+ /* ------------------------------- BACKGROUNDS ------------------------------- */
+ --aspid-colors-bg-darkness: rgb(26, 26, 26); /* #1A1A1A */
+ --aspid-colors-bg-dark: rgb(36, 36, 36); /* #242424 */
+ --aspid-colors-bg-light: rgb(46, 46, 46); /* #2E2E2E */
+ --aspid-colors-bg-lightness: rgb(56, 56, 56); /* #383838 */
+
+ /* ---------------------- TEXT: GENERAL (High Contrast) ---------------------- */
+ --aspid-colors-text-lightness: rgb(220, 220, 220); /* #DCDCDC */
+ --aspid-colors-text-light: rgb(190, 190, 190); /* #BEBEBE */
+ --aspid-colors-text-dark: rgb(150, 150, 150); /* #969696 */
+ --aspid-colors-text-darkness: rgb(110, 110, 110); /* #6E6E6E */
+
+ /* -------------------- SHADES: GENERAL (Lines & Borders) -------------------- */
+ --aspid-colors-shade-darkness: rgb(45, 45, 45); /* #2D2D2D */
+ --aspid-colors-shade-dark: rgb(60, 60, 60); /* #3C3C3C */
+ --aspid-colors-shade-light: rgb(80, 80, 80); /* #505050 */
+ --aspid-colors-shade-lightness: rgb(100, 100, 100); /* #646464 */
+
+ /* ---------------- STATUS: BACKGROUNDS (Deep Gemstone Bases) ---------------- */
+ --aspid-colors-status-success-darkness: rgb(8, 40, 20); /* #082814 */
+ --aspid-colors-status-success-dark: rgb(12, 65, 30); /* #0C411E */
+ --aspid-colors-status-success-light: rgb(20, 90, 45); /* #145A2D */
+ --aspid-colors-status-success-lightness: rgb(30, 120, 60); /* #1E783C */
+
+ --aspid-colors-status-warning-darkness: rgb(45, 30, 5); /* #2D1E05 */
+ --aspid-colors-status-warning-dark: rgb(85, 55, 10); /* #55370A */
+ --aspid-colors-status-warning-light: rgb(125, 85, 20); /* #7D5514 */
+ --aspid-colors-status-warning-lightness: rgb(165, 115, 30); /* #A5731E */
+
+ --aspid-colors-status-error-darkness: rgb(50, 10, 10); /* #320A0A */
+ --aspid-colors-status-error-dark: rgb(85, 20, 20); /* #551414 */
+ --aspid-colors-status-error-light: rgb(125, 35, 35); /* #7D2323 */
+ --aspid-colors-status-error-lightness: rgb(165, 50, 50); /* #A53232 */
+
+ --aspid-colors-status-info-darkness: rgb(8, 25, 50); /* #081932 */
+ --aspid-colors-status-info-dark: rgb(15, 45, 85); /* #0F2D55 */
+ --aspid-colors-status-info-light: rgb(25, 75, 130); /* #194B82 */
+ --aspid-colors-status-info-lightness: rgb(35, 105, 180); /* #2369B4 */
+
+ /* --------------- STATUS ACCENTS: TEXT (Gemstone Brilliance) ---------------- */
+ --aspid-colors-status-success-text-darkness: rgb(55, 110, 65); /* #376E41 */
+ --aspid-colors-status-success-text-dark: rgb(85, 175, 100); /* #55AF64 */
+ --aspid-colors-status-success-text-light: rgb(120, 235, 145); /* #78EB91 */
+ --aspid-colors-status-success-text-lightness: rgb(180, 255, 200); /* #B4FFC8 */
+
+ --aspid-colors-status-warning-text-darkness: rgb(120, 85, 35); /* #785523 */
+ --aspid-colors-status-warning-text-dark: rgb(185, 135, 60); /* #B9873C */
+ --aspid-colors-status-warning-text-light: rgb(245, 185, 85); /* #F5B955 */
+ --aspid-colors-status-warning-text-lightness: rgb(255, 235, 175); /* #FFEBAF */
+
+ --aspid-colors-status-error-text-darkness: rgb(125, 45, 45); /* #7D2D2D */
+ --aspid-colors-status-error-text-dark: rgb(185, 65, 65); /* #B94141 */
+ --aspid-colors-status-error-text-light: rgb(235, 95, 95); /* #EB5F5F */
+ --aspid-colors-status-error-text-lightness: rgb(255, 175, 175); /* #FFAFAF */
+
+ --aspid-colors-status-info-text-darkness: rgb(40, 80, 125); /* #28507D */
+ --aspid-colors-status-info-text-dark: rgb(65, 130, 185); /* #4182B9 */
+ --aspid-colors-status-info-text-light: rgb(90, 185, 240); /* #5AB9F0 */
+ --aspid-colors-status-info-text-lightness: rgb(190, 240, 255); /* #BEF0FF */
+
+ /* ---------------- STATUS ACCENTS: SHADES (Muted Gemstones) ----------------- */
+ --aspid-colors-status-success-shade-darkness: rgb(25, 55, 30); /* #19371E */
+ --aspid-colors-status-success-shade-dark: rgb(40, 85, 50); /* #285532 */
+ --aspid-colors-status-success-shade-light: rgb(65, 130, 80); /* #418250 */
+ --aspid-colors-status-success-shade-lightness: rgb(95, 190, 115); /* #5FBE73 */
+
+ --aspid-colors-status-warning-shade-darkness: rgb(55, 40, 20); /* #372814 */
+ --aspid-colors-status-warning-shade-dark: rgb(90, 65, 30); /* #5A411E */
+ --aspid-colors-status-warning-shade-light: rgb(135, 100, 50); /* #876432 */
+ --aspid-colors-status-warning-shade-lightness: rgb(185, 140, 75); /* #B98C4B */
+
+ --aspid-colors-status-error-shade-darkness: rgb(55, 25, 25); /* #371919 */
+ --aspid-colors-status-error-shade-dark: rgb(90, 45, 45); /* #5A2D2D */
+ --aspid-colors-status-error-shade-light: rgb(135, 65, 65); /* #874141 */
+ --aspid-colors-status-error-shade-lightness: rgb(190, 95, 95); /* #BE5F5F */
+
+ --aspid-colors-status-info-shade-darkness: rgb(20, 35, 55); /* #142337 */
+ --aspid-colors-status-info-shade-dark: rgb(35, 65, 95); /* #23415F */
+ --aspid-colors-status-info-shade-light: rgb(55, 100, 145); /* #376491 */
+ --aspid-colors-status-info-shade-lightness: rgb(85, 145, 200); /* #5591C8 */
+}
+
+.aspid-fasttools-inspector-container {
+ margin-left: -10px;
+}
+
+.aspid-fasttools-inspector-container .unity-property-field__inspector-property .unity-base-field__inspector-field {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.aspid-fasttools-inspector-container .unity-property-field__inspector-property Toggle.unity-base-field__inspector-field {
+ margin-left: -1px;
+}
+
+/* -------------------------------------------------- BACKGROUNDS --------------------------------------------------- */
+.aspid-fasttools-background { }
+
+.aspid-fasttools-background--rounded {
+ padding: 10px;
+ border-radius: 10px;
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--darkness {
+ background-color: var(--aspid-colors-bg-darkness);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--dark {
+ background-color: var(--aspid-colors-bg-dark);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--light {
+ background-color: var(--aspid-colors-bg-light);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--lightness {
+ background-color: var(--aspid-colors-bg-lightness);
+}
+
+/* -------------------------------- SUCCESS: BACKGROUNDS -------------------------------- */
+.aspid-fasttools-background.aspid-fasttools-theme--darkness.aspid-fasttools-status--success {
+ background-color: var(--aspid-colors-status-success-darkness);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--dark.aspid-fasttools-status--success {
+ background-color: var(--aspid-colors-status-success-dark);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--light.aspid-fasttools-status--success {
+ background-color: var(--aspid-colors-status-success-light);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--lightness.aspid-fasttools-status--success {
+ background-color: var(--aspid-colors-status-success-lightness);
+}
+
+/* -------------------------------- WARNING: BACKGROUNDS -------------------------------- */
+.aspid-fasttools-background.aspid-fasttools-theme--darkness.aspid-fasttools-status--warning {
+ background-color: var(--aspid-colors-status-warning-darkness);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--dark.aspid-fasttools-status--warning {
+ background-color: var(--aspid-colors-status-warning-dark);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--light.aspid-fasttools-status--warning {
+ background-color: var(--aspid-colors-status-warning-light);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--lightness.aspid-fasttools-status--warning {
+ background-color: var(--aspid-colors-status-warning-lightness);
+}
+
+/* --------------------------------- ERROR: BACKGROUNDS --------------------------------- */
+.aspid-fasttools-background.aspid-fasttools-theme--darkness.aspid-fasttools-status--error {
+ background-color: var(--aspid-colors-status-error-darkness);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--dark.aspid-fasttools-status--error {
+ background-color: var(--aspid-colors-status-error-dark);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--light.aspid-fasttools-status--error {
+ background-color: var(--aspid-colors-status-error-light);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--lightness.aspid-fasttools-status--error {
+ background-color: var(--aspid-colors-status-error-lightness);
+}
+
+/* --------------------------------- INFO: BACKGROUNDS ---------------------------------- */
+.aspid-fasttools-background.aspid-fasttools-theme--darkness.aspid-fasttools-status--info {
+ background-color: var(--aspid-colors-status-info-darkness);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--dark.aspid-fasttools-status--info {
+ background-color: var(--aspid-colors-status-info-dark);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--light.aspid-fasttools-status--info {
+ background-color: var(--aspid-colors-status-info-light);
+}
+
+.aspid-fasttools-background.aspid-fasttools-theme--lightness.aspid-fasttools-status--info {
+ background-color: var(--aspid-colors-status-info-lightness);
+}
+/*--------------------------------------------------------------------------------------------------------------------*/
+
+/* ---------------------------------------------------- LABELS ------------------------------------------------------ */
+Label.aspid-fasttools-theme--darkness {
+ color: var(--aspid-colors-text-darkness);
+}
+
+Label.aspid-fasttools-theme--dark {
+ color: var(--aspid-colors-text-dark);
+}
+
+Label.aspid-fasttools-theme--light {
+ color: var(--aspid-colors-text-light);
+}
+
+Label.aspid-fasttools-theme--lightness {
+ color: var(--aspid-colors-text-lightness);
+}
+
+/* ------------------------ SUCCESS: LABELS ------------------------*/
+Label.aspid-fasttools-theme--darkness.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-darkness);
+}
+
+Label.aspid-fasttools-theme--dark.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-dark);
+}
+
+Label.aspid-fasttools-theme--light.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-light);
+}
+
+Label.aspid-fasttools-theme--lightness.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-lightness);
+}
+
+/* ------------------------ WARNING: LABELS ------------------------*/
+Label.aspid-fasttools-theme--darkness.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-darkness);
+}
+
+Label.aspid-fasttools-theme--dark.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-dark);
+}
+
+Label.aspid-fasttools-theme--light.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-light);
+}
+
+Label.aspid-fasttools-theme--lightness.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-lightness);
+}
+
+/* ------------------------- ERROR: LABELS -------------------------*/
+Label.aspid-fasttools-theme--darkness.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-darkness);
+}
+
+Label.aspid-fasttools-theme--dark.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-dark);
+}
+
+Label.aspid-fasttools-theme--light.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-light);
+}
+
+Label.aspid-fasttools-theme--lightness.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-lightness);
+}
+
+/* ------------------------- INFO: LABELS --------------------------*/
+Label.aspid-fasttools-theme--darkness.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-darkness);
+}
+
+Label.aspid-fasttools-theme--dark.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-dark);
+}
+
+Label.aspid-fasttools-theme--light.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-light);
+}
+
+Label.aspid-fasttools-theme--lightness.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-lightness);
+}
+/*--------------------------------------------------------------------------------------------------------------------*/
+
+/* --------------------------------------------------- BUTTONS ------------------------------------------------------ */
+Button.aspid-fasttools-theme--darkness {
+ color: var(--aspid-colors-text-darkness);
+ background-color: var(--aspid-colors-bg-darkness);
+}
+
+Button.aspid-fasttools-theme--dark {
+ color: var(--aspid-colors-text-dark);
+ background-color: var(--aspid-colors-bg-dark);
+}
+
+Button.aspid-fasttools-theme--light {
+ color: var(--aspid-colors-text-light);
+ background-color: var(--aspid-colors-bg-lightness);
+}
+
+Button.aspid-fasttools-theme--lightness {
+ color: var(--aspid-colors-text-lightness);
+ background-color: var(--aspid-colors-bg-lightness);
+}
+
+/* ----------------------- SUCCESS: BUTTONS ------------------------*/
+Button.aspid-fasttools-theme--darkness.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-darkness);
+ background-color: var(--aspid-colors-status-success-darkness);
+}
+
+Button.aspid-fasttools-theme--dark.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-dark);
+ background-color: var(--aspid-colors-status-success-dark);
+}
+
+Button.aspid-fasttools-theme--light.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-light);
+ background-color: var(--aspid-colors-status-success-light);
+}
+
+Button.aspid-fasttools-theme--lightness.aspid-fasttools-status--success {
+ color: var(--aspid-colors-status-success-text-lightness);
+ background-color: var(--aspid-colors-status-success-lightness);
+}
+
+/* ----------------------- WARNING: BUTTONS ------------------------*/
+Button.aspid-fasttools-theme--darkness.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-darkness);
+ background-color: var(--aspid-colors-status-warning-darkness);
+}
+
+Button.aspid-fasttools-theme--dark.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-dark);
+ background-color: var(--aspid-colors-status-warning-dark);
+}
+
+Button.aspid-fasttools-theme--light.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-light);
+ background-color: var(--aspid-colors-status-warning-light);
+}
+
+Button.aspid-fasttools-theme--lightness.aspid-fasttools-status--warning {
+ color: var(--aspid-colors-status-warning-text-lightness);
+ background-color: var(--aspid-colors-status-warning-lightness);
+}
+
+/* ------------------------ ERROR: BUTTONS -------------------------*/
+Button.aspid-fasttools-theme--darkness.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-darkness);
+ background-color: var(--aspid-colors-status-error-darkness);
+}
+
+Button.aspid-fasttools-theme--dark.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-dark);
+ background-color: var(--aspid-colors-status-error-dark);
+}
+
+Button.aspid-fasttools-theme--light.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-light);
+ background-color: var(--aspid-colors-status-error-light);
+}
+
+Button.aspid-fasttools-theme--lightness.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-lightness);
+ background-color: var(--aspid-colors-status-error-lightness);
+}
+
+/* ------------------------ INFO: BUTTONS --------------------------*/
+Button.aspid-fasttools-theme--darkness.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-darkness);
+ background-color: var(--aspid-colors-status-info-darkness);
+}
+
+Button.aspid-fasttools-theme--dark.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-dark);
+ background-color: var(--aspid-colors-status-info-dark);
+}
+
+Button.aspid-fasttools-theme--light.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-light);
+ background-color: var(--aspid-colors-status-info-light);
+}
+
+Button.aspid-fasttools-theme--lightness.aspid-fasttools-status--info {
+ color: var(--aspid-colors-status-info-text-lightness);
+ background-color: var(--aspid-colors-status-info-lightness);
+}
+/*--------------------------------------------------------------------------------------------------------------------*/
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Aspid-FastTools-Default-Dark.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Aspid-FastTools-Default-Dark.uss.meta
new file mode 100644
index 0000000..7f52e45
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Aspid-FastTools-Default-Dark.uss.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: a58072b37b804991bbb887d0fd65e2f8
+timeCreated: 1775572843
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components.meta
new file mode 100644
index 0000000..cc95912
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 96c5e18e9327448f9fb6ab17cd214d0c
+timeCreated: 1777206343
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedDotsBackground.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedDotsBackground.uss
new file mode 100644
index 0000000..8717fe6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedDotsBackground.uss
@@ -0,0 +1,15 @@
+:root {
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ position: absolute;
+
+ --aspid-fasttools-colors-dot_blob-color_1: var(--aspid-colors-status-success-text-dark);
+ --aspid-fasttools-colors-dot_blob-color_2: var(--aspid-colors-status-warning-text-dark);
+ --aspid-fasttools-colors-dot_blob-color_3: var(--aspid-colors-status-error-text-dark);
+
+ --aspid-fasttools-metrics-dot_radius: 1.55;
+ --aspid-fasttools-metrics-dot_spacing: 18;
+ --aspid-fasttools-metrics-dot_scale_reference: 420;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedDotsBackground.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedDotsBackground.uss.meta
new file mode 100644
index 0000000..f38c2f6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedDotsBackground.uss.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: b49b2ab57fd74e35bd5d3cbfdec0df23
+timeCreated: 1777155817
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedLogo.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedLogo.uss
new file mode 100644
index 0000000..6e8ed5e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedLogo.uss
@@ -0,0 +1,19 @@
+:root {
+ --aspid-fasttools-prop-animated_logo-pulse_speed: 5;
+ --aspid-fasttools-prop-animated_logo-pulse_hover_amplitude: 0.04;
+}
+
+.aspid-fasttools-animated-logo__layer {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ opacity: 0;
+ transition-duration: 1.4s;
+ transition-timing-function: ease-in-out;
+}
+
+.aspid-fasttools-animated-logo__layer--visible {
+ opacity: 1;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedLogo.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedLogo.uss.meta
new file mode 100644
index 0000000..5f97430
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedLogo.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: db3a67c313db1432ead5570e0907cdbe
+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/UI/Components/Aspid-FastTools-AspidAnimatedTitle.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedTitle.uss
new file mode 100644
index 0000000..231ebba
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedTitle.uss
@@ -0,0 +1,23 @@
+:root {
+ flex-direction: row;
+ flex-wrap: wrap;
+
+ --aspid-fasttools-prop-animated_title-color_stride: 0.12;
+ --aspid-fasttools-prop-animated_title-color_speed: 0.4;
+ --aspid-fasttools-prop-animated_title-wave_stride: 0.55;
+ --aspid-fasttools-prop-animated_title-wave_speed: 1.6;
+ --aspid-fasttools-prop-animated_title-wave_amplitude: 3;
+
+ --aspid-fasttools-colors-animated_title-color_1: var(--aspid-colors-status-success-text-dark);
+ --aspid-fasttools-colors-animated_title-color_2: var(--aspid-colors-status-warning-text-dark);
+ --aspid-fasttools-colors-animated_title-color_3: var(--aspid-colors-status-error-text-dark);
+}
+
+.aspid-fasttools-animated-title__word {
+ margin-right: 14px;
+ flex-direction: row;
+}
+
+.aspid-fasttools-animated-title__word > Label {
+ padding: 0;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedTitle.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedTitle.uss.meta
new file mode 100644
index 0000000..b8d3ac9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidAnimatedTitle.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 904e5346298574aad9c72c548d6081cc
+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/UI/Components/Aspid-FastTools-AspidBox.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidBox.uss
new file mode 100644
index 0000000..2acfd21
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidBox.uss
@@ -0,0 +1,3 @@
+:root {
+ flex-grow: 1;
+}
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidBox.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidBox.uss.meta
new file mode 100644
index 0000000..a2dc0b2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidBox.uss.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 6ccaac2029f54f82bbad6b8a29d72276
+timeCreated: 1777385488
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidDividingLine.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidDividingLine.uss
new file mode 100644
index 0000000..d28b0ac
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidDividingLine.uss
@@ -0,0 +1,127 @@
+:root {
+ flex-grow: 1;
+ display: none;
+ flex-shrink: 0;
+}
+
+.aspid-fasttools-dividing-line--thin.aspid-fasttools-dividing-line--vertical {
+ width: 1px;
+ min-width: 1px;
+ max-width: 1px;
+ display: flex;
+}
+
+.aspid-fasttools-dividing-line--medium.aspid-fasttools-dividing-line--vertical {
+ width: 2px;
+ min-width: 2px;
+ max-width: 2px;
+ display: flex;
+}
+
+.aspid-fasttools-dividing-line--bold.aspid-fasttools-dividing-line--vertical {
+ width: 3px;
+ min-width: 3px;
+ max-width: 3px;
+ display: flex;
+}
+
+.aspid-fasttools-dividing-line--thin.aspid-fasttools-dividing-line--horizontal {
+ height: 1px;
+ min-height: 1px;
+ max-height: 1px;
+ display: flex;
+}
+
+.aspid-fasttools-dividing-line--medium.aspid-fasttools-dividing-line--horizontal {
+ height: 2px;
+ min-height: 2px;
+ max-height: 2px;
+ display: flex;
+}
+
+.aspid-fasttools-dividing-line--bold.aspid-fasttools-dividing-line--horizontal {
+ height: 3px;
+ min-height: 3px;
+ max-height: 3px;
+ display: flex;
+}
+
+.aspid-fasttools-theme--darkness {
+ background-color: var(--aspid-colors-shade-darkness);
+}
+
+.aspid-fasttools-theme--dark {
+ background-color: var(--aspid-colors-shade-dark);
+}
+
+.aspid-fasttools-theme--light {
+ background-color: var(--aspid-colors-shade-light);
+}
+
+.aspid-fasttools-theme--lightness {
+ background-color: var(--aspid-colors-shade-lightness);
+}
+
+.aspid-fasttools-status--success.aspid-fasttools-theme--darkness {
+ background-color: var(--aspid-colors-status-success-shade-darkness);
+}
+
+.aspid-fasttools-status--success.aspid-fasttools-theme--dark {
+ background-color: var(--aspid-colors-status-success-shade-dark);
+}
+
+.aspid-fasttools-status--success.aspid-fasttools-theme--light {
+ background-color: var(--aspid-colors-status-success-shade-light);
+}
+
+.aspid-fasttools-status--success.aspid-fasttools-theme--lightness {
+ background-color: var(--aspid-colors-status-success-shade-lightness);
+}
+
+.aspid-fasttools-status--warning.aspid-fasttools-theme--darkness {
+ background-color: var(--aspid-colors-status-warning-shade-darkness);
+}
+
+.aspid-fasttools-status--warning.aspid-fasttools-theme--dark {
+ background-color: var(--aspid-colors-status-warning-shade-dark);
+}
+
+.aspid-fasttools-status--warning.aspid-fasttools-theme--light {
+ background-color: var(--aspid-colors-status-warning-shade-light);
+}
+
+.aspid-fasttools-status--warning.aspid-fasttools-theme--lightness {
+ background-color: var(--aspid-colors-status-warning-shade-lightness);
+}
+
+.aspid-fasttools-status--error.aspid-fasttools-theme--darkness {
+ background-color: var(--aspid-colors-status-error-shade-darkness);
+}
+
+.aspid-fasttools-status--error.aspid-fasttools-theme--dark {
+ background-color: var(--aspid-colors-status-error-shade-dark);
+}
+
+.aspid-fasttools-status--error.aspid-fasttools-theme--light {
+ background-color: var(--aspid-colors-status-error-shade-light);
+}
+
+.aspid-fasttools-status--error.aspid-fasttools-theme--lightness {
+ background-color: var(--aspid-colors-status-error-shade-lightness);
+}
+
+.aspid-fasttools-status--info.aspid-fasttools-theme--darkness {
+ background-color: var(--aspid-colors-status-info-shade-darkness);
+}
+
+.aspid-fasttools-status--info.aspid-fasttools-theme--dark {
+ background-color: var(--aspid-colors-status-info-shade-dark);
+}
+
+.aspid-fasttools-status--info.aspid-fasttools-theme--light {
+ background-color: var(--aspid-colors-status-info-shade-light);
+}
+
+.aspid-fasttools-status--info.aspid-fasttools-theme--lightness {
+ background-color: var(--aspid-colors-status-info-shade-lightness);
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidDividingLine.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidDividingLine.uss.meta
new file mode 100644
index 0000000..d821d66
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidDividingLine.uss.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9d78d5a4719442e6a8a4dcecfb88191e
+timeCreated: 1777229664
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidGradientButton.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidGradientButton.uss
new file mode 100644
index 0000000..00d6e7b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidGradientButton.uss
@@ -0,0 +1,21 @@
+:root {
+ color: var(--aspid-colors-text-light);
+ height: 30px;
+ margin: 0 0 5px 0;
+ padding: 0 10px;
+ overflow: hidden;
+ font-size: 14px;
+ border-width: 0;
+ border-radius: 10px;
+ flex-direction: row;
+ -unity-font-style: bold;
+ -unity-text-align: middle-left;
+
+ --aspid-fasttools-colors-gradient-button-bg: var(--aspid-colors-bg-darkness);
+ --aspid-fasttools-colors-gradient-button-accent: var(--aspid-colors-status-success-text-dark);
+}
+
+.aspid-fasttools-gradient-button__trailing-label {
+ margin-left: 10px;
+ -unity-text-align: middle-right;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidGradientButton.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidGradientButton.uss.meta
new file mode 100644
index 0000000..8a2ea1e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidGradientButton.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: fad030e63d05f4f058e0c736e8f3e22a
+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/UI/Components/Aspid-FastTools-AspidHelpBox.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHelpBox.uss
new file mode 100644
index 0000000..19ada11
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHelpBox.uss
@@ -0,0 +1,84 @@
+:root {
+ padding: 8px 12px;
+ border-width: 1px;
+ border-radius: 8px;
+ flex-direction: row;
+ align-items: center;
+ background-color: var(--aspid-colors-bg-dark);
+ border-color: var(--aspid-colors-shade-light);
+}
+
+:root.aspid-fasttools-status--success {
+ background-color: var(--aspid-colors-status-success-light);
+ border-color: var(--aspid-colors-status-success-shade-lightness);
+}
+
+:root.aspid-fasttools-status--warning {
+ background-color: var(--aspid-colors-status-warning-light);
+ border-color: var(--aspid-colors-status-warning-shade-lightness);
+}
+
+:root.aspid-fasttools-status--error {
+ background-color: var(--aspid-colors-status-error-light);
+ border-color: var(--aspid-colors-status-error-shade-lightness);
+}
+
+:root.aspid-fasttools-status--info {
+ background-color: var(--aspid-colors-status-info-light);
+ border-color: var(--aspid-colors-status-info-shade-lightness);
+}
+
+/* --- ICON --- */
+.aspid-fasttools-help-box__icon {
+ min-width: 34px;
+ max-width: 34px;
+ min-height: 32px;
+ max-height: 32px;
+ margin-right: 9px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-image: var(--aspid-fasttools-icons-help-box);
+}
+
+.aspid-fasttools-help-box__icon--hidden {
+ display: none;
+}
+
+:root.aspid-fasttools-help-box--info {
+ --aspid-fasttools-icons-help-box: resource("d_console.infpicon@2x");
+}
+
+:root.aspid-fasttools-help-box--warning {
+ --aspid-fasttools-icons-help-box: resource("d_console.warnicon@2x");
+}
+
+:root.aspid-fasttools-help-box--error {
+ --aspid-fasttools-icons-help-box: resource("d_console.erroricon@2x");
+}
+
+/* --- TEXT --- */
+.aspid-fasttools-help-box__text-container {
+ flex-grow: 1;
+}
+
+:root AspidLabel > Label {
+ cursor: text;
+ overflow: hidden;
+}
+
+:root.aspid-fasttools-status--success AspidLabel > Label {
+ color: var(--aspid-colors-status-success-text-lightness);
+}
+
+:root.aspid-fasttools-status--warning AspidLabel > Label {
+ color: var(--aspid-colors-status-warning-text-lightness);
+}
+
+:root.aspid-fasttools-status--error AspidLabel > Label {
+ color: var(--aspid-colors-status-error-text-lightness);
+}
+
+:root.aspid-fasttools-status--info AspidLabel > Label {
+ color: var(--aspid-colors-status-info-text-lightness);
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHelpBox.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHelpBox.uss.meta
new file mode 100644
index 0000000..01883be
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHelpBox.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: ceaba8591b4d348ba998c9d124cf708c
+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/UI/Components/Aspid-FastTools-AspidHoverGradientOverlay.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHoverGradientOverlay.uss
new file mode 100644
index 0000000..e795fe7
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHoverGradientOverlay.uss
@@ -0,0 +1,13 @@
+:root {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ --aspid-fasttools-colors-hover_overlay: rgba(0, 0, 0, 0);
+
+ --aspid-fasttools-metrics-hover_overlay_steps: 75;
+ --aspid-fasttools-metrics-hover_overlay_lerp_rate: 0.12;
+ --aspid-fasttools-metrics-hover_overlay_alpha_scale: 0.35;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHoverGradientOverlay.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHoverGradientOverlay.uss.meta
new file mode 100644
index 0000000..f4726fb
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidHoverGradientOverlay.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 8eca34af204014596bd08d24b161e5b7
+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/UI/Components/Aspid-FastTools-AspidInspectorHeader.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidInspectorHeader.uss
new file mode 100644
index 0000000..3321a83
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidInspectorHeader.uss
@@ -0,0 +1,71 @@
+:root {
+ --aspid-fasttools-prop-status: success;
+ --aspid-fasttools-colors-gradient: var(--aspid-colors-status-success-lightness);
+ --aspid-fasttools-icons-inspector-header: var(--aspid-icons-status-success);
+}
+
+:root.aspid-fasttools-status--success {
+ --aspid-fasttools-colors-gradient: var(--aspid-colors-status-success-lightness);
+ --aspid-fasttools-icons-inspector-header: var(--aspid-icons-status-success);
+}
+
+:root.aspid-fasttools-status--warning {
+ --aspid-fasttools-colors-gradient: var(--aspid-colors-status-warning-lightness);
+ --aspid-fasttools-icons-inspector-header: var(--aspid-icons-status-warning);
+}
+
+:root.aspid-fasttools-status--error {
+ --aspid-fasttools-colors-gradient: var(--aspid-colors-status-error-lightness);
+ --aspid-fasttools-icons-inspector-header: var(--aspid-icons-status-error);
+}
+
+:root.aspid-fasttools-status--info {
+ --aspid-fasttools-colors-gradient: var(--aspid-colors-status-info-lightness);
+ --aspid-fasttools-icons-inspector-header: var(--aspid-icons-status-info);
+}
+
+.aspid-fasttools-inspector-header__container {
+ padding: 5px 10px;
+ overflow: hidden;
+ flex-direction: row;
+ transition-duration: 0.25s;
+ transition-timing-function: ease-in-out;
+}
+
+.aspid-fasttools-inspector-header__text > Label,
+.aspid-fasttools-inspector-header__subtext > Label {
+ transition-property: color;
+ transition-duration: 0.25s;
+ transition-timing-function: ease-in-out;
+}
+
+.aspid-fasttools-inspector-header__icon {
+ width: 40px;
+ height: 40px;
+ margin: 0 10px 0 0;
+ display: flex;
+ flex-shrink: 0;
+ background-image: var(--aspid-fasttools-icons-inspector-header);
+}
+
+.aspid-fasttools-inspector-header__text-container {
+ overflow: hidden;
+ flex-grow: 1;
+ align-self: center;
+ flex-shrink: 1;
+}
+
+.aspid-fasttools-inspector-header__text {
+ --aspid-fasttools-prop-theme: light;
+ --aspid-fasttools-metrics-label_size: H4;
+}
+
+.aspid-fasttools-inspector-header__subtext {
+ --aspid-fasttools-prop-theme: dark;
+ --aspid-fasttools-metrics-label_size: H6;
+}
+
+.aspid-fasttools-inspector-header__text AspidDividingLine,
+.aspid-fasttools-inspector-header__subtext AspidDividingLine {
+ --aspid-fasttools-metrics-line_size: none;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidInspectorHeader.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidInspectorHeader.uss.meta
new file mode 100644
index 0000000..679d304
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidInspectorHeader.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 2575ae7e2cae8493c9ed576520f7ca15
+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/UI/Components/Aspid-FastTools-AspidLabel.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidLabel.uss
new file mode 100644
index 0000000..7bc96c0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidLabel.uss
@@ -0,0 +1,124 @@
+:root {
+ flex-direction: column;
+}
+
+Label {
+ padding: 0;
+ flex-grow: 1;
+ white-space: normal
+}
+
+AspidDividingLine {
+ margin: 5px 0 0 0;
+}
+
+/* --- LABEL SIZES --- */
+.aspid-fasttools-label-size--h1 {
+ font-size: 36px;
+}
+
+.aspid-fasttools-label-size--h2 {
+ font-size: 24px;
+}
+
+.aspid-fasttools-label-size--h3 {
+ font-size: 18px;
+}
+
+.aspid-fasttools-label-size--h4 {
+ font-size: 16px;
+}
+
+.aspid-fasttools-label-size--h5 {
+ font-size: 14px;
+}
+
+.aspid-fasttools-label-size--h6 {
+ font-size: 13px;
+}
+
+.aspid-fasttools-label-size--h7 {
+ font-size: 12px;
+}
+
+/* General Text */
+.aspid-fasttools-theme--darkness Label {
+ color: var(--aspid-colors-text-darkness);
+}
+
+.aspid-fasttools-theme--dark Label {
+ color: var(--aspid-colors-text-dark);
+}
+
+.aspid-fasttools-theme--light Label {
+ color: var(--aspid-colors-text-light);
+}
+
+.aspid-fasttools-theme--lightness Label {
+ color: var(--aspid-colors-text-lightness);
+}
+
+/* Status Text */
+AspidLabel.aspid-fasttools-status--success.aspid-fasttools-theme--darkness > Label {
+ color: var(--aspid-colors-status-success-text-darkness);
+}
+
+AspidLabel.aspid-fasttools-status--success.aspid-fasttools-theme--dark > Label {
+ color: var(--aspid-colors-status-success-text-dark);
+}
+
+AspidLabel.aspid-fasttools-status--success.aspid-fasttools-theme--light > Label {
+ color: var(--aspid-colors-status-success-text-light);
+}
+
+AspidLabel.aspid-fasttools-status--success.aspid-fasttools-theme--lightness > Label {
+ color: var(--aspid-colors-status-success-text-lightness);
+}
+
+AspidLabel.aspid-fasttools-status--warning.aspid-fasttools-theme--darkness > Label {
+ color: var(--aspid-colors-status-warning-text-darkness);
+}
+
+AspidLabel.aspid-fasttools-status--warning.aspid-fasttools-theme--dark > Label {
+ color: var(--aspid-colors-status-warning-text-dark);
+}
+
+AspidLabel.aspid-fasttools-status--warning.aspid-fasttools-theme--light > Label {
+ color: var(--aspid-colors-status-warning-text-light);
+}
+
+AspidLabel.aspid-fasttools-status--warning.aspid-fasttools-theme--lightness > Label {
+ color: var(--aspid-colors-status-warning-text-lightness);
+}
+
+AspidLabel.aspid-fasttools-status--error.aspid-fasttools-theme--darkness > Label {
+ color: var(--aspid-colors-status-error-text-darkness);
+}
+
+AspidLabel.aspid-fasttools-status--error.aspid-fasttools-theme--dark > Label {
+ color: var(--aspid-colors-status-error-text-dark);
+}
+
+AspidLabel.aspid-fasttools-status--error.aspid-fasttools-theme--light > Label {
+ color: var(--aspid-colors-status-error-text-light);
+}
+
+AspidLabel.aspid-fasttools-status--error.aspid-fasttools-theme--lightness > Label {
+ color: var(--aspid-colors-status-error-text-lightness);
+}
+
+AspidLabel.aspid-fasttools-status--info.aspid-fasttools-theme--darkness > Label {
+ color: var(--aspid-colors-status-info-text-darkness);
+}
+
+AspidLabel.aspid-fasttools-status--info.aspid-fasttools-theme--dark > Label {
+ color: var(--aspid-colors-status-info-text-dark);
+}
+
+AspidLabel.aspid-fasttools-status--info.aspid-fasttools-theme--light > Label {
+ color: var(--aspid-colors-status-info-text-light);
+}
+
+AspidLabel.aspid-fasttools-status--info.aspid-fasttools-theme--lightness > Label {
+ color: var(--aspid-colors-status-info-text-lightness);
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidLabel.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidLabel.uss.meta
new file mode 100644
index 0000000..37cc058
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Components/Aspid-FastTools-AspidLabel.uss.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 039c741d44a74e0c8f8a1a09759f0c4b
+timeCreated: 1777301557
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums.meta
new file mode 100644
index 0000000..16f7b71
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: e11d6db414db47b4aa2a2e3d03262075
+timeCreated: 1778148053
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums/Aspid-FastTools-EnumValues.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums/Aspid-FastTools-EnumValues.uss
new file mode 100644
index 0000000..6b17c21
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums/Aspid-FastTools-EnumValues.uss
@@ -0,0 +1,28 @@
+:root {
+ margin: 1px -2px 1px 3px;
+}
+
+.unity-property-field__inspector-property .unity-base-field__inspector-field {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+.unity-property-field__inspector-property Toggle.unity-base-field__inspector-field {
+ margin-left: -1px;
+}
+
+.aspid-fasttools-enum-values__header {
+ padding: 5px 5px 2px 5px;
+ border-width: 1px;
+ border-radius: 5px 5px 0 0;
+ border-color: var(--aspid-colors-bg-dark);
+ background-color: var(--aspid-colors-bg-light);
+}
+
+.aspid-fasttools-enum-values__container {
+ padding: 2px 5px 5px 5px;
+ border-width: 0 1px 1px 1px;
+ border-radius: 0 0 5px 5px;
+ border-color: var(--aspid-colors-bg-dark);
+ background-color: var(--aspid-colors-bg-lightness);
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums/Aspid-FastTools-EnumValues.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums/Aspid-FastTools-EnumValues.uss.meta
new file mode 100644
index 0000000..c39aa7b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Enums/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/UI/Ids.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids.meta
new file mode 100644
index 0000000..6478ace
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 44078cd255964115a4bd50eed7cbe89f
+timeCreated: 1777878288
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Field.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Field.uss
new file mode 100644
index 0000000..4051b4c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Field.uss
@@ -0,0 +1,14 @@
+Button {
+ padding: 0;
+ min-width: 18px;
+ max-width: 18px;
+ min-height: 18px;
+ max-height: 18px;
+ margin: 0 0 0 1px;
+}
+
+Button > VisualElement {
+ width: 100%;
+ height: 100%;
+ background-image: resource("d_ScriptableObject Icon");
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Field.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Field.uss.meta
new file mode 100644
index 0000000..1caa955
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Field.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 63776338b4e545a9a57c9421ec4ff590
+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/UI/Ids/Aspid-FastTools-Id-Registry.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Registry.uss
new file mode 100644
index 0000000..49affbc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Registry.uss
@@ -0,0 +1,233 @@
+AspidInspectorHeader {
+ margin-top: 2px;
+}
+
+ToolbarSearchField {
+ width: auto;
+ padding-right: 4px;
+ margin: 5px -1px 0 -1px;
+}
+
+.aspid-fasttools-id-registry__toolbar {
+ margin: 5px -1px;
+ padding-bottom: 5px;
+ flex-direction: row;
+ border-width: 0;
+ border-bottom-width: 1px;
+ border-bottom-color: var(--aspid-colors-bg-lightness);
+}
+
+IdRegistryToolbarVisualElement > VisualElement {
+ flex-direction: row;
+ align-items: center;
+ flex-grow: 1;
+ flex-basis: 0;
+}
+
+IdRegistryToolbarVisualElement > VisualElement > Label {
+ flex-shrink: 0;
+ margin: 0 6px 0 0;
+ padding: 0;
+ font-size: 11px;
+ color: var(--aspid-colors-text-dark);
+ -unity-text-align: middle-left;
+}
+
+IdRegistryToolbarVisualElement > VisualElement > EnumField {
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin: 0;
+}
+
+.aspid-fasttools-id-registry__group-foldout {
+ margin-top: 5px;
+}
+
+.aspid-fasttools-id-registry__group-foldout > Toggle {
+ margin-left: 0;
+}
+
+.aspid-fasttools-id-registry__list .unity-scroller--vertical {
+ margin-left: 5px;
+}
+
+IdRegistryEntryVisualElement {
+ margin-bottom: 0;
+ flex-direction: column;
+}
+
+IdRegistryEntryVisualElement > VisualElement {
+ padding: 5px;
+ margin-bottom: 3px;
+ min-height: 22px;
+ flex-direction: row;
+ align-items: center;
+ border-radius: 10px;
+ background-color: var(--aspid-colors-shade-dark);
+}
+
+IdRegistryEntryVisualElement > VisualElement > Label {
+ margin: 0;
+ flex-shrink: 0;
+ font-size: 11px;
+ min-width: 50px;
+ padding: 2px 8px;
+ border-width: 1px;
+ border-radius: 10px;
+ border-color: var(--aspid-colors-bg-darkness);
+ -unity-text-align: middle-center;
+
+ color: var(--aspid-colors-text-light);
+ background-color: var(--aspid-colors-bg-light);
+}
+
+IdRegistryEntryVisualElement > VisualElement > Label.aspid-fasttools-status--error {
+ color: var(--aspid-colors-status-error-text-light);
+ background-color: var(--aspid-colors-status-error-dark);
+}
+
+IdRegistryEntryVisualElement TextField {
+ flex-grow: 1;
+ flex-shrink: 1;
+ margin: 0 0 0 5px;
+ border-radius: 6px;
+}
+
+IdRegistryEntryVisualElement Button {
+ margin: 0 0 0 5px;
+ padding: 0 0 2px 0;
+ flex-shrink: 0;
+ min-width: 20px;
+ max-width: 20px;
+ min-height: 18px;
+ max-height: 18px;
+ font-size: 14px;
+ border-radius: 3px;
+ color: var(--aspid-colors-text-lightness);
+ border-color: var(--aspid-colors-bg-darkness);
+ background-color: var(--aspid-colors-bg-light);
+}
+
+IdRegistryEntryVisualElement .aspid-fasttools-id-registry__delete:hover {
+ color: var(--aspid-colors-status-error-text-light);
+ background-color: var(--aspid-colors-status-error-dark);
+ border-color: var(--aspid-colors-status-error-text-light);
+}
+
+IdRegistryEntryVisualElement .aspid-fasttools-id-registry__confirm:enabled:hover {
+ color: var(--aspid-colors-status-success-text-light);
+ border-color: var(--aspid-colors-status-success-text-light);
+ background-color: var(--aspid-colors-status-success-dark);
+}
+
+IdRegistryEntryVisualElement > Label {
+ color: var(--aspid-colors-status-error-text-light);
+ font-size: 11px;
+ margin: 0 0 3px 0;
+}
+
+IdRegistryNextIdRowVisualElement {
+ border-width: 0;
+ padding-top: 5px;
+ margin: 5px 0 0 0;
+ border-top-width: 1px;
+ border-top-color: var(--aspid-colors-bg-lightness);
+}
+
+IdRegistryNextIdRowVisualElement > VisualElement {
+ padding: 5px;
+ margin: 5px 0 0 0;
+ align-items: center;
+ flex-direction: row;
+ border-radius: 10px;
+ background-color: var(--aspid-colors-shade-dark);
+}
+
+IdRegistryNextIdRowVisualElement PropertyField {
+ flex-grow: 1;
+ margin: 0 2px 0 3px;
+}
+
+IdRegistryNextIdRowVisualElement Image {
+ width: 16px;
+ height: 16px;
+ display: none;
+ margin-left: 5px;
+ --unity-image: resource("console.warnicon.sml");
+}
+
+IdRegistryNextIdRowVisualElement Image:enabled {
+ display: flex;
+}
+
+.aspid-fasttools-id-registry__add {
+ padding: 5px;
+ margin: 5px 0 0 0;
+ border-radius: 10px;
+ background-color: var(--aspid-colors-shade-dark);
+}
+
+.aspid-fasttools-id-registry__add > VisualElement {
+ border-width: 0;
+ flex-direction: row;
+}
+
+.aspid-fasttools-id-registry__add TextField {
+ margin: 0;
+ flex-grow: 1;
+ flex-shrink: 1;
+ border-radius: 6px;
+}
+
+.aspid-fasttools-id-registry__add Button {
+ margin: 0 0 0 5px;
+ padding: 0 0 2px 0;
+ color: var(--aspid-colors-text-lightness);
+ flex-shrink: 0;
+ min-width: 20px;
+ max-width: 20px;
+ min-height: 18px;
+ max-height: 18px;
+ font-size: 14px;
+ border-radius: 3px;
+ border-color: var(--aspid-colors-bg-darkness);
+ background-color: var(--aspid-colors-bg-light);
+}
+
+.aspid-fasttools-id-registry__add Button:enabled:hover {
+ color: var(--aspid-colors-status-success-text-light);
+ border-color: var(--aspid-colors-status-success-text-light);
+ background-color: var(--aspid-colors-status-success-dark);
+}
+
+.aspid-fasttools-id-registry__add > Label {
+ color: var(--aspid-colors-status-error-text-light);
+ font-size: 11px;
+ margin: 3px 0 0 0;
+}
+
+IdRegistryWarningVisualElement {
+ display: none;
+ flex-direction: row;
+ align-items: center;
+ padding: 5px 8px;
+ margin-bottom: 4px;
+ border-radius: 6px;
+ background-color: var(--aspid-colors-status-error-dark);
+}
+
+IdRegistryWarningVisualElement.aspid-fasttools-id-registry__warning--visible {
+ display: flex;
+}
+
+IdRegistryWarningVisualElement > Label {
+ flex-grow: 1;
+ font-size: 11px;
+ color: var(--aspid-colors-status-error-text-lightness);
+}
+
+IdRegistryWarningVisualElement > Button {
+ flex-shrink: 0;
+ margin-left: 8px;
+ padding: 2px 8px;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Registry.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Registry.uss.meta
new file mode 100644
index 0000000..39b138c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Registry.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 3fba5ca171f36413d8d2a664d3e00fec
+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/UI/Ids/Aspid-FastTools-Id-Selector.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Selector.uss
new file mode 100644
index 0000000..80ae55e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Selector.uss
@@ -0,0 +1,40 @@
+:root {
+ padding: 5px;
+}
+
+ToolbarSearchField {
+ width: auto;
+ flex-shrink: 1;
+ margin: 5px 0 10;
+}
+
+.aspid-fasttools-id-selector__error {
+ margin-top: 2px;
+ font-size: 11px;
+ white-space: normal;
+}
+
+.aspid-fasttools-id-selector__item
+{
+ height: 22px;
+ padding-right: 6px;
+ flex-direction: row;
+ align-items: center;
+}
+
+.aspid-fasttools-id-selector__item-name
+{
+ flex-grow: 1;
+ flex-shrink: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -unity-text-align: middle-left;
+}
+
+.aspid-fasttools-id-selector__item-id
+{
+ flex-shrink: 0;
+ font-size: 11px;
+ margin-left: 8px;
+ -unity-text-align: middle-right;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Selector.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Selector.uss.meta
new file mode 100644
index 0000000..f6c3f0c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Ids/Aspid-FastTools-Id-Selector.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 3496bde5cb21d4adaaeb383fbf4c9aef
+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/UI/Types.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types.meta
new file mode 100644
index 0000000..7d43d0c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: bbfde89107d740eaa6e2a61e8bb93779
+timeCreated: 1777985852
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-SerializableType.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-SerializableType.uss
new file mode 100644
index 0000000..8b52281
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-SerializableType.uss
@@ -0,0 +1,18 @@
+Button {
+ padding: 0;
+ min-width: 18px;
+ max-width: 18px;
+ min-height: 18px;
+ max-height: 18px;
+ margin: 0 0 0 1px;
+}
+
+Button > VisualElement {
+ width: 100%;
+ height: 100%;
+ background-image: resource("d_Folder Icon");
+}
+
+Button > VisualElement:enabled:hover {
+ background-image: resource("d_FolderOpened Icon");
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-SerializableType.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-SerializableType.uss.meta
new file mode 100644
index 0000000..90e5964
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/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/UI/Types/Aspid-FastTools-TypeSelectorWindow.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-TypeSelectorWindow.uss
new file mode 100644
index 0000000..6a81272
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-TypeSelectorWindow.uss
@@ -0,0 +1,65 @@
+:root {
+ padding: 5px;
+ flex-grow: 1;
+ flex-direction: column;
+}
+
+.aspid-fasttools-type-selector-header {
+ padding-left: 10;
+ height: 20px;
+ min-height: 20px;
+ align-items: center;
+ flex-direction: row;
+ margin: -5px -5px 5px;
+ background-color: var(--aspid-colors-bg-dark);
+}
+
+/* -------------------------------------------------- Back Button --------------------------------------------------- */
+.aspid-fasttools-type-selector-header > Button {
+ margin: 0;
+ width: 20px;
+ height: 16px;
+ border-width: 0;
+ margin-right: 4px;
+ background-color: rgba(0, 0, 0, 0);
+ color: var(--aspid-colors-text-lightness);
+}
+
+.aspid-fasttools-type-selector-header > Button:enabled:hover,
+.aspid-fasttools-type-selector-header > Button:enabled:focus {
+ border-width: 1px;
+ color: var(--aspid-colors-status-info-text-lightness);
+ border-color: var(--aspid-colors-status-info-shade-lightness);
+}
+/* ------------------------------------------------------------------------------------------------------------------ */
+
+/* ----------------------------------------------------- Title ------------------------------------------------------ */
+.aspid-fasttools-type-selector-header > Label {
+ flex-grow: 1;
+ -unity-font-style: bold;
+}
+/* ------------------------------------------------------------------------------------------------------------------ */
+
+ToolbarSearchField {
+ width: auto;
+ margin-bottom: 5px;
+}
+
+/* ------------------------------------------------------ Item ------------------------------------------------------ */
+.unity-collection-view__item {
+ height: 20px;
+ align-items: center;
+ padding-left: 5px;
+ padding-right: 5px;
+ flex-direction: row;
+}
+
+.aspid-fasttools-type-selector-item-title {
+ color: var(--aspid-colors-text-lightness);
+ flex-grow: 1;
+}
+
+.aspid-fasttools-type-selector-item-arrow {
+ color: var(--aspid-colors-text-light);
+}
+/* ------------------------------------------------------------------------------------------------------------------ */
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-TypeSelectorWindow.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/Aspid-FastTools-TypeSelectorWindow.uss.meta
new file mode 100644
index 0000000..59cd700
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Types/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/Resources/UI/Windows.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows.meta
new file mode 100644
index 0000000..8081311
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 969007ba29344c1787e6d3298756778c
+timeCreated: 1777206348
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome.meta
new file mode 100644
index 0000000..ba4e3ea
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: cf278e6fe44f24f2eb9364608c2e6b79
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uss b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uss
new file mode 100644
index 0000000..80af57b
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uss
@@ -0,0 +1,103 @@
+:root {
+ --aspid-fasttools-colors-bg: rgb(0, 0, 0);
+}
+
+AspidAnimatedDotsBackground {
+ background-color: var(--aspid-fasttools-colors-bg);
+}
+
+.aspid-fasttools-welcome__scroll {
+ flex-grow: 1;
+}
+
+.aspid-fasttools-welcome__content {
+ padding: 10px;
+ flex-grow: 1;
+ align-items: stretch;
+}
+
+.aspid-fasttools-welcome__hero {
+ padding: 10px;
+ align-items: center;
+ flex-direction: row;
+}
+
+.aspid-fasttools-welcome__logo {
+ width: 100px;
+ height: 100px;
+ flex-shrink: 1;
+ margin-right: 15px;
+}
+
+.aspid-fasttools-welcome__hero-text {
+ flex-grow: 1;
+}
+
+AspidAnimatedTitle {
+ margin-bottom: 5px;
+ font-size: 36px;
+ -unity-font-style: bold;
+}
+
+.aspid-fasttools-welcome__description {
+ white-space: normal;
+ border-radius: 10px;
+ background-color: var(--aspid-fasttools-colors-bg);
+}
+
+.aspid-fasttools-welcome__card {
+ padding: 10px;
+ border-radius: 10px;
+}
+
+.aspid-fasttools-welcome__footer-link:hover {
+ color: var(--aspid-colors-status-success-text-light);
+}
+
+.aspid-fasttools-welcome__card-header {
+ margin-bottom: 10px;
+ --aspid-fasttools-metrics-label_size: h2;
+}
+
+AspidDividingLine {
+ opacity: 0.5;
+}
+
+.aspid-fasttools-welcome__footer {
+}
+
+.aspid-fasttools-welcome__footer-link {
+ color: var(--aspid-colors-status-success-text-dark);
+ font-size: 14px;
+ -unity-font-style: bold;
+}
+
+.aspid-fasttools-welcome__footer-version {
+ color: var(--aspid-colors-text-darkness);
+ font-size: 14px;
+}
+
+.aspid-fasttools-welcome__footer-version:hover {
+ color: var(--aspid-colors-text-light);
+}
+
+.aspid-fasttools-welcome__toast {
+ color: var(--aspid-colors-status-info-text-lightness);
+ opacity: 0;
+ padding: 10px;
+ position: absolute;
+ max-width: 80%;
+ font-size: 14px;
+ white-space: normal;
+ border-width: 1px;
+ border-radius: 10px;
+ background-color: var(--aspid-colors-status-info-dark);
+ -unity-font-style: bold;
+ -unity-text-align: middle-center;
+ transition-duration: 0.3s;
+ transition-timing-function: ease-out;
+}
+
+.aspid-fasttools-welcome__toast--visible {
+ opacity: 0.75;
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uss.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uss.meta
new file mode 100644
index 0000000..d0e149f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uss.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 74929072ccf9442f6a07a76964c94928
+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/UI/Windows/Welcome/Aspid-FastTools-Welcome.uxml b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uxml
new file mode 100644
index 0000000..914923e
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uxml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uxml.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uxml.meta
new file mode 100644
index 0000000..f58743c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Resources/UI/Windows/Welcome/Aspid-FastTools-Welcome.uxml.meta
@@ -0,0 +1,10 @@
+fileFormatVersion: 2
+guid: 3e140245ef0fc4102b4d7889261216f3
+ScriptedImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 2
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+ script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 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/Aspid.UnityFastTools.Editor.asmdef b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Aspid.FastTools.Unity.Editor.asmdef
similarity index 73%
rename from Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Aspid.UnityFastTools.Editor.asmdef
rename to Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Aspid.FastTools.Unity.Editor.asmdef
index c948561..14674f4 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Aspid.UnityFastTools.Editor.asmdef
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Aspid.FastTools.Unity.Editor.asmdef
@@ -1,8 +1,8 @@
{
- "name": "Aspid.UnityFastTools.Editor",
- "rootNamespace": "",
+ "name": "Aspid.FastTools.Unity.Editor",
"references": [
- "GUID:7c010b89992542508a6b6189977e64d4"
+ "Aspid.FastTools",
+ "Aspid.FastTools.Unity"
],
"includePlatforms": [
"Editor"
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/Scripts/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/Scripts/Aspid.FastTools.Unity.Editor.asmdef.meta
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/AssemblyInfo.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/AssemblyInfo.cs
new file mode 100644
index 0000000..583f3da
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo(assemblyName: "Aspid.MVVM.Unity.Editor")]
+[assembly: InternalsVisibleTo(assemblyName: "Aspid.MVVM.StarterKit.Unity.Editor")]
+[assembly: InternalsVisibleTo(assemblyName: "Aspid.FastTools.Unity.Editor.Tests")]
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/AssemblyInfo.cs.meta
similarity index 64%
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/AssemblyInfo.cs.meta
index 38dc2e4..3649642 100644
--- a/Aspid.UnityFastTools/Assets/Plugins/Aspid/UnityFastTools/Source/Editor/Types/SerializableTypeUtility.cs.meta
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/AssemblyInfo.cs.meta
@@ -1,11 +1,11 @@
fileFormatVersion: 2
-guid: 02cb721291c44a22b0d55e5aee9e3974
+guid: f872c5784972f4119b4e6362fc7bb415
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
- icon: {fileID: 2800000, guid: daa63609570f146cfa1b178f6d6c8bda, type: 3}
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
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.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs
new file mode 100644
index 0000000..9a201d4
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuePropertyDrawer.cs
@@ -0,0 +1,103 @@
+using System;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+using Aspid.FastTools.Editors;
+using Aspid.FastTools.UIElements;
+using Aspid.FastTools.UIElements.Editors;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Enums.Editors
+{
+ ///
+ /// Property drawer for . Picks an EnumField/EnumFlagsField
+ /// for the row's key based on the enum type configured on the parent
+ /// , and falls back to a raw string field when the type
+ /// can't be resolved.
+ ///
+ [CustomPropertyDrawer(typeof(EnumValue<>))]
+ internal sealed class EnumValuePropertyDrawer : PropertyDrawer
+ {
+ public override VisualElement CreatePropertyGUI(SerializedProperty property)
+ {
+ var container = new VisualElement();
+
+ var keyProperty = property.FindPropertyRelative("_key");
+ var enumTypeProperty = property.FindPropertyRelative("_enumType");
+
+ 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);
+
+ enumTypeField.AddValueChanged(_ => UpdateValue());
+
+ keyEnumField.AddValueChanged(e =>
+ {
+ keyProperty.SetStringAndApply(e.newValue.ToString());
+ });
+
+ keyEnumFlagField.AddValueChanged(e =>
+ {
+ keyProperty.SetStringAndApply(e.newValue.ToString());
+ });
+
+ container
+ .AddChild(enumTypeField)
+ .AddChild(keyField)
+ .AddChild(keyEnumField)
+ .AddChild(keyEnumFlagField)
+ .AddChild(new PropertyField(property.FindPropertyRelative("_value"), label: string.Empty));
+
+ // Sync visibility with the currently serialized enum type — without this the
+ // EnumField/EnumFlagsField stay hidden until the user edits the type.
+ UpdateValue();
+
+ return container;
+
+ void UpdateValue()
+ {
+ var enumType = Type.GetType(enumTypeProperty.stringValue, throwOnError: false);
+
+ keyField.SetDisplay(DisplayStyle.None);
+ keyEnumField.SetDisplay(DisplayStyle.None);
+ keyEnumFlagField.SetDisplay(DisplayStyle.None);
+
+ if (enumType is null)
+ {
+ keyField.SetDisplay(DisplayStyle.Flex);
+ return;
+ }
+
+ if (!Enum.TryParse(enumType, keyProperty.stringValue, out var parsed))
+ {
+ var values = Enum.GetValues(enumType);
+ if (values.Length is 0)
+ {
+ keyField.SetDisplay(DisplayStyle.Flex);
+ return;
+ }
+ parsed = values.GetValue(0);
+ }
+
+ var enumValue = (Enum)parsed;
+ keyProperty.SetStringAndApply(enumValue.ToString());
+
+ if (enumType.IsDefined(typeof(FlagsAttribute), false))
+ {
+ keyEnumFlagField
+ .SetValue(null)
+ .Initialize(enumValue)
+ .SetDisplay(DisplayStyle.Flex);
+ }
+ else
+ {
+ keyEnumField
+ .Initialize(enumValue)
+ .SetDisplay(DisplayStyle.Flex);
+ }
+ }
+ }
+ }
+}
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..addbc49 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: 2d380e9402a9f4c5b8eb637f9f40c400, 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..f74ccf4
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawer.cs
@@ -0,0 +1,73 @@
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+using Aspid.FastTools.Editors;
+using Aspid.FastTools.UIElements;
+using Aspid.FastTools.UIElements.Editors;
+using Aspid.FastTools.UIElements.Manipulators;
+using Aspid.FastTools.UIElements.Editors.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Enums.Editors
+{
+ ///
+ /// Property drawer for . Renders a header with the enum-type
+ /// picker, the entries list, and the default-value field, and exposes a context-menu action
+ /// that fills in any missing enum members from the configured type.
+ ///
+ [CustomPropertyDrawer(typeof(EnumValues<>))]
+ internal sealed class EnumValuesPropertyDrawer : PropertyDrawer
+ {
+ private const string StylesheetPath = "UI/Enums/Aspid-FastTools-EnumValues";
+ private const string HeaderClass = "aspid-fasttools-enum-values__header";
+ private const string ContainerClass = "aspid-fasttools-enum-values__container";
+
+ public override VisualElement CreatePropertyGUI(SerializedProperty property)
+ {
+ var serializedObject = property.serializedObject;
+ var valuesPath = property.FindPropertyRelative("_values").propertyPath;
+ var enumTypePath = property.FindPropertyRelative("_enumType").propertyPath;
+ var defaultValuePath = property.FindPropertyRelative("_defaultValue").propertyPath;
+
+ // Push the parent enum type into every existing entry up-front so already-serialized
+ // arrays don't render with a stale per-element _enumType until the user re-edits.
+ UpdateValues();
+
+ return new VisualElement()
+ .SetName($"enum-values-{property.displayName}")
+ .AddStyleSheetsFromResource(StylesheetPath)
+ .AddStyleSheetsFromResource(AspidStyles.DefaultStyleSheet)
+ .AddManipulatorSelf(EnumValuesPropertyDrawerHelper.CreatePopulateMenuManipulator(
+ values: serializedObject.FindProperty(valuesPath),
+ enumType: serializedObject.FindProperty(enumTypePath),
+ defaultValue: serializedObject.FindProperty(defaultValuePath))
+ )
+ .AddChild(new VisualElement()
+ .AddClass(HeaderClass)
+ .AddChild(new Label(property.displayName))
+ .AddChild(new PropertyField(serializedObject.FindProperty(enumTypePath), label: string.Empty)
+ .AddValueChanged(_ => UpdateValues())
+ )
+ )
+ .AddChild(new VisualElement()
+ .AddClass(ContainerClass)
+ .AddChild(new PropertyField(serializedObject.FindProperty(valuesPath))
+ .AddValueChanged(_ => UpdateValues())
+ )
+ .AddChild(new PropertyField(serializedObject.FindProperty(defaultValuePath)))
+ );
+
+ void UpdateValues()
+ {
+ var values = serializedObject.FindProperty(valuesPath);
+ var enumTypeValue = serializedObject.FindProperty(enumTypePath).stringValue;
+
+ for (var i = 0; i < values.arraySize; i++)
+ {
+ var element = values.GetArrayElementAtIndex(i);
+ element.FindPropertyRelative("_enumType").SetStringAndApply(enumTypeValue);
+ }
+ }
+ }
+ }
+}
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..11e9077 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: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
userData:
assetBundleName:
assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawerHelper.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawerHelper.cs
new file mode 100644
index 0000000..e3576d9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawerHelper.cs
@@ -0,0 +1,102 @@
+#nullable enable
+using System;
+using UnityEditor;
+using System.Linq;
+using UnityEngine.UIElements;
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Enums.Editors
+{
+ ///
+ /// Helpers shared by the property drawer.
+ ///
+ internal static class EnumValuesPropertyDrawerHelper
+ {
+ private const string PopulateMenuItem = "Populate Missing Enum Members";
+
+ ///
+ /// Builds a context-menu manipulator that adds a "Populate Missing Enum Members"
+ /// action. The action appends entries for every enum member of the configured type
+ /// that is not yet present in , using
+ /// as the seed for the new entries.
+ /// The action is disabled when nothing is missing.
+ ///
+ ///
+ /// For [Flags] enums also returns named composite
+ /// values (e.g. All = A | B), so they will be added as separate rows.
+ ///
+ public static ContextualMenuManipulator CreatePopulateMenuManipulator(
+ SerializedProperty values,
+ SerializedProperty enumType,
+ SerializedProperty defaultValue)
+ {
+ var serializedObject = values.serializedObject;
+ var valuesPath = values.propertyPath;
+ var enumTypePath = enumType.propertyPath;
+ var defaultValuePath = defaultValue.propertyPath;
+
+ return new ContextualMenuManipulator(evt =>
+ {
+ var valuesProp = serializedObject.FindProperty(valuesPath);
+ var enumTypeProp = serializedObject.FindProperty(enumTypePath);
+ var defaultValueProp = serializedObject.FindProperty(defaultValuePath);
+
+ var status = HasMissingMembers(valuesProp, enumTypeProp)
+ ? DropdownMenuAction.Status.Normal
+ : DropdownMenuAction.Status.Disabled;
+
+ evt.menu.AppendAction(
+ PopulateMenuItem,
+ _ => PopulateMissing(valuesProp, enumTypeProp, defaultValueProp),
+ status);
+ });
+ }
+
+ private static void PopulateMissing(
+ SerializedProperty values,
+ SerializedProperty enumType,
+ SerializedProperty defaultValue)
+ {
+ var type = Type.GetType(enumType.stringValue, throwOnError: false);
+ if (type is null || !type.IsEnum) return;
+
+ var existing = CollectExistingKeys(values);
+ var added = false;
+
+ foreach (var name in Enum.GetNames(type))
+ {
+ if (!existing.Add(name)) continue;
+
+ values.arraySize++;
+ var element = values.GetArrayElementAtIndex(values.arraySize - 1);
+ element.FindPropertyRelative("_key").stringValue = name;
+ element.FindPropertyRelative("_enumType").stringValue = enumType.stringValue;
+ element.FindPropertyRelative("_value").boxedValue = defaultValue.boxedValue;
+ added = true;
+ }
+
+ if (added) values.serializedObject.ApplyModifiedProperties();
+ }
+
+ private static bool HasMissingMembers(SerializedProperty values, SerializedProperty enumType)
+ {
+ var type = Type.GetType(enumType.stringValue, throwOnError: false);
+ if (type is null || !type.IsEnum) return false;
+
+ var existing = CollectExistingKeys(values);
+ return Enum.GetNames(type).Any(name => !existing.Contains(name));
+ }
+
+ private static HashSet CollectExistingKeys(SerializedProperty values)
+ {
+ var set = new HashSet(values.arraySize);
+ for (var i = 0; i < values.arraySize; i++)
+ {
+ var element = values.GetArrayElementAtIndex(i);
+ set.Add(element.FindPropertyRelative("_key").stringValue);
+ }
+ return set;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawerHelper.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawerHelper.cs.meta
new file mode 100644
index 0000000..251586d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Enums/EnumValuesPropertyDrawerHelper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 64065f09d8034b8481239e18d9c7e998
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, 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..70ce84c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Extensions/EditorExtensions.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Linq;
+using UnityEditor;
+using UnityEngine;
+using Object = UnityEngine.Object;
+
+// ReSharper disable CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ ///
+ /// Editor-side extension methods for and its subclass
+ /// 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 (or any of its base types) is decorated with ,
+ /// the name is taken from , which honours the menu name;
+ /// 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
+ /// or has been destroyed.
+ ///
+ public static string GetScriptName(this Object obj)
+ {
+ if (!obj) return string.Empty;
+
+ var targetType = obj.GetType();
+ return Attribute.IsDefined(targetType, typeof(AddComponentMenu), inherit: true)
+ ? ObjectNames.GetInspectorTitle(obj)
+ : ObjectNames.NicifyVariableName(targetType.Name);
+ }
+
+ ///
+ /// Returns the display name of a component with a 1-based numeric suffix appended when multiple
+ /// components of the exact same type exist on the same . The index reflects
+ /// the order returned by .
+ /// For example, the second AudioSource on the object is 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
+ /// or has been destroyed.
+ ///
+ public static string GetScriptNameWithIndex(this Component targetComponent)
+ {
+ if (!targetComponent) return null;
+
+ var type = targetComponent.GetType();
+ var components = targetComponent.GetComponents(type)
+ .Where(component => component.GetType() == type)
+ .ToArray();
+
+ if (components.Length <= 1) return targetComponent.GetScriptName();
+
+ for (var i = 0; i < components.Length; i++)
+ {
+ if (components[i] == targetComponent)
+ return $"{targetComponent.GetScriptName()} ({i + 1})";
+ }
+
+ return targetComponent.GetScriptName();
+ }
+ }
+}
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..6b97799 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: 2d380e9402a9f4c5b8eb637f9f40c400, 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..a2bb78a
--- /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();
+ }
+}
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..010a134 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: 2d380e9402a9f4c5b8eb637f9f40c400, 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..5dd362a
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/IMGUI/ScrollViewScope.cs
@@ -0,0 +1,121 @@
+using UnityEditor;
+using UnityEngine;
+
+// ReSharper disable CheckNamespace
+namespace Aspid.FastTools.Editors
+{
+ ///
+ /// Disposable ref struct wrapper around /
+ /// that updates the caller's scroll position in place
+ /// and exposes it via . Use in a using statement to automatically
+ /// close the scroll view.
+ ///
+ 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, updating 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();
+ }
+}
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..2b6dc0d 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: 2d380e9402a9f4c5b8eb637f9f40c400, 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..c532bba
--- /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();
+ }
+}
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..a50edc5 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: 2d380e9402a9f4c5b8eb637f9f40c400, 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/CLAUDE.md b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/CLAUDE.md
new file mode 100644
index 0000000..98f9d5f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/CLAUDE.md
@@ -0,0 +1,113 @@
+# Id Registries — Editor
+
+Editor side of the Id system: PropertyDrawer for `IId`-implementing structs, custom Inspector for the registry, and the resolver that binds them.
+Runtime contracts live in `Unity/Runtime/Ids/`; struct-side boilerplate is generated by `IdStructGenerator`.
+
+## Layout
+
+Mirrors the sibling `Types/` feature — `Drawers/` for IMGUI + UIToolkit drawers, `Selectors/` for the picker window, `VisualElements/` for the bound field, `Registries/` for the `IdRegistry` inspector, `Resolvers/` for the AQN → registry index.
+
+```
+Ids/
+├── Constants.cs ← USS class names + field names (single source of truth)
+├── UniqueIdIndex.cs ← project-wide [UniqueId] collision index
+├── Drawers/
+│ ├── IdStructPropertyDrawer.cs ← [CustomPropertyDrawer(typeof(IId), useForChildren:true)]
+│ ├── IdStructIMGUIPropertyDrawer.cs ← static IMGUI drawer body (mirrors TypeIMGUIPropertyDrawer)
+│ ├── IdStructUIToolkitPropertyDrawer.cs ← static UIToolkit drawer body (mirrors TypeUIToolkitPropertyDrawer)
+│ ├── IdStructDrawerContext.cs ← shared drawer DTO (label + field/declaring types + child SerializedProperties)
+│ └── IdStructDrawerHelper.cs ← caption building, int↔string sync, selection apply
+├── Selectors/
+│ └── IdSelectorWindow.cs ← dropdown picker shown by the drawer
+├── VisualElements/
+│ ├── IdField.cs ← UIToolkit field; mirrors TypeField
+│ └── InspectorIdField.cs ← Inspector-styled variant; mirrors InspectorTypeField
+├── Resolvers/
+│ ├── IdRegistryResolver.cs ← AQN → registry asset, with one-per-type enforcement
+│ └── IdRegistryResolverCacheInvalidator.cs ← AssetPostprocessor invalidating the resolver cache
+└── Registries/
+ ├── RegistryEditorCore.cs ← orchestrator: SerializedObject, view-model, mutation cycle, event wiring
+ ├── IdRegistryEditor.cs ← [CustomEditor(typeof(IdRegistry))]
+ ├── IdRegistryEntryData.cs ← row DTO
+ ├── IdRegistryValidator.cs ← IsValidName + Summarize for the Clean-up flow
+ ├── CleanUpSummary.cs ← Clean-up summary DTO
+ ├── AddRowValidation.cs / NextIdWarning.cs ← DTOs returned by core's validation funnels
+ ├── RegistrySortMode.cs / RegistryGroupMode.cs ← enums shared between core and toolbar element
+ └── VisualElements/
+ ├── IdRegistryEntryVisualElement.cs ← single row VisualElement
+ ├── IdRegistryToolbarVisualElement.cs ← Sort/Group enum-dropdowns, fires SortChanged/GroupChanged
+ ├── IdRegistryAddRowVisualElement.cs ← input + button + error label, fires AddRequested
+ ├── IdRegistryNextIdRowVisualElement.cs ← PropertyField(_nextId) + warning icon, fires ValueChanged
+ ├── IdRegistryWarningVisualElement.cs ← Clean-up warning row, fires ReviewRequested
+ └── IdRegistryListVisualElement.cs ← flat ListView + grouped foldouts, re-emits row events
+```
+
+`IdRegistryEditor` is a 5-line shell that hands its `SerializedObject` to `RegistryEditorCore`. **`IdRegistry` mutations live only in `RegistryEditorCore`** (via the `Record` → `Add`/`SetName`/`RemoveAt` → `Commit` cycle, see Storage below). UI is split into `IdRegistry*VisualElement` components that are dumb shells: they own DOM and emit events, never touch the asset's `SerializedProperty`s. Wiring (event subscriptions and `Bind` calls) happens once inside `RegistryEditorCore.Build()`. Adding new inspector behavior means: a new component if it's a UI surface, plus the wiring + handler in core. The one allowed exception is `IdRegistryNextIdRowVisualElement`, which holds a `PropertyField(_nextId)` because Unity's `PropertyField` already records its own Undo and writes through `SerializedObject` — core listens for the change to invalidate the runtime cache.
+
+## Storage
+
+`RegistryEditorCore` reads and writes the registry's parallel `_ids` / `_names` arrays directly through `SerializedProperty`. The previous accessor seam was removed once a single `IdRegistry` storage layout remained — there is no abstraction layer between the editor and the asset. Mutation helpers (`Add`, `RemoveAt`) keep both arrays in sync — every length change touches `_idsProp` and `_namesProp` before `ApplyModifiedProperties`.
+
+### Mutation cycle — always three steps
+
+```csharp
+Record("Operation Name"); // 1. Undo.RegisterCompleteObjectUndo on the registry asset
+// SerializedProperty edits on _ids / _names (Add / SetName / RemoveAt)
+Commit(); // 2. ApplyModifiedProperties + InvalidateCache + SetDirty
+```
+
+`Record` and `Commit` are `private` helpers on `RegistryEditorCore` — every mutation in core funnels through them. Skipping `Record` breaks Undo. Skipping `Commit` leaves the runtime cache stale (the registry caches `HashSet`/`Dictionary` and only invalidates via `InvalidateCache()` from `Commit` or `OnValidate`). Direct `serializedObject.ApplyModifiedProperties()` is wrong — it bypasses cache invalidation.
+
+## Resolver invariants
+
+`IdRegistryResolver.Find(declaringType)` is the only sanctioned way to locate a registry asset for an `IId` struct type:
+
+- Indexes by `Type.AssemblyQualifiedName` against the registry's `_targetStructType` field.
+- Lazy single-rebuild on first `Find` after a reset; `IdRegistryResolverCacheInvalidator` updates the index in place via `OnAssetImported` for `.asset` imports and triggers a full reset on delete/move (next `Find` rescans).
+- Logs an error and returns the first match when **two** `IdRegistry` assets bind to one struct AQN (one-registry-per-type rule, enforced at lookup, not at creation).
+
+**Do not bypass this** with `AssetDatabase.FindAssets("t:IdRegistry")` — duplicate detection won't run, and the cache won't help. Use `Find` / `Create` / `GetOrCreate`. If a new code path mutates `_targetStructType`, ensure the asset is reimported (or call `ClearCache()`); silent edits via `SerializedObject` in non-asset contexts will leave the cache stale.
+
+`IdStructPropertyDrawer` resolves by `fieldInfo.FieldType` (the struct type itself), **not** `DeclaringType`. The `[UniqueId]` collision check goes through `UniqueIdIndex.IsUnique(declaringType, stringId, currentAssetGuid)` — a project-wide index keyed by the asset type that declares the field. The index lazily scans every asset type that has at least one `[UniqueId]` field (via `TypeCache.GetFieldsWithAttribute()`), and `IdRegistryResolverCacheInvalidator` patches it incrementally on `.asset` imports.
+
+## Field name and USS class registry — `Constants.cs`
+
+Cross-component USS classes and serialized field names go through `Constants`; per-component classes (e.g. those used only inside `IdSelectorWindow` or `IdField`) stay as `private const` strings on their owning class — same convention as `Types/` (`TypeField`, `TypeSelectorWindow`).
+
+- `Constants.IntIdFieldName` / `StringIdFieldName` — fields generated by `IdStructGenerator` on the struct side; consumed by both the drawer and `UniqueIdIndex`.
+- `Constants.Registry.*` — classes for `RegistryEditorCore` (shared with its `IdRegistry*VisualElement` components) + threshold constants (`ScrollThreshold`, `MaxVisibleRows`, `RowHeight`).
+- `Constants.NoneOption` — display string for the "no id selected" option, shared between caption building and the selector window.
+
+All class names follow BEM (`__` between block and element) per the root `CLAUDE.md`. Promote a local `private const` to `Constants.cs` as soon as a second class needs to read it — never hard-code the same USS class string in two places.
+
+## Validation and Clean-up
+
+`IdRegistryValidator.IsValidName(input, isTaken, out error)` is the **only** name check. Rules: not whitespace; matches `^[A-Za-z_][A-Za-z0-9_\-]*$`; ≤ 255 chars; not flagged by `isTaken` (pass `null` to skip the duplicate check). The Add-row, rename flow, and Clean-up summary all funnel through it — keep it that way so error messages stay consistent.
+
+For per-keystroke validation reuse `RegistryEditorCore.IsTakenExcluding(int)` (excludes the row being edited so renaming back to the same name doesn't trigger duplicate errors) or its no-arg overload `IsTakenExcluding()` (no exclusion, used by the Add row). Both return a `Func` backed by a name-occurrences cache built once per `RebuildEntries`, so renames don't re-scan the registry on every keystroke. Components like `IdRegistryAddRowVisualElement` receive a validation delegate (`Func`) from core via `Bind` rather than calling these helpers directly — this keeps validation funneled through `IdRegistryValidator.IsValidName` and combines name + Next-Id checks in one place.
+
+`Summarize` + `EnumerateInvalidIndices` drive the warning row and Clean-up dialog. Definition of "invalid": empty name OR duplicate name. Adding a new invalidity criterion requires updating both methods together, plus `CleanUpSummary.ToShortLabel` and the `ShowCleanUpDialog` message.
+
+## UI state persistence
+
+Sort mode, group mode, and per-group foldout state persist in `SessionState`, keyed by **asset GUID** (falling back to `InstanceID` for unsaved registries). State is per-session, not per-project. If a new toggle needs persistence, follow the same `Aspid.FastTools.Ids.Registry:{guid}:{key}` namespace. **Only `RegistryEditorCore` reads/writes `SessionState`** — components receive getter/setter delegates (e.g. `IdRegistryListVisualElement.Bind` accepts foldout `Func` / `Action`) so the key format stays in one place.
+
+## Common tasks
+
+### Add a new mutation to RegistryEditorCore
+
+1. In `RegistryEditorCore`, wrap the call in `Record` → mutate `_ids` / `_names` via `SerializedProperty` (always pair length changes) → `Commit`.
+2. Trigger `RebuildEntries()` if the change affects what's shown. `TrackSerializedObjectValue` already runs `RebuildEntries` on external edits, so direct mutations inside core don't need a manual call.
+
+### Add a new UI surface to the inspector
+
+1. Build a new `IdRegistry*VisualElement : VisualElement`. Constructor wires DOM + USS classes (only via `Constants.Registry.*`) and registers internal callbacks. Mutations of `IdRegistry` state are **not allowed** here — emit an event for core to handle.
+2. Expose either a `Bind(...)` method (for state pushed in on every `RebuildEntries`) or a one-shot bind in ctor for stable inputs. Public events use `Action<...>` and are subscribed by core in `Build()`.
+3. In `RegistryEditorCore.Build()`: instantiate the component, subscribe to its events with handlers that funnel through `Record`/`Commit`, add it to the IDs `container` in the right order, then call `Bind`. If the component reflects view-model state, call its `Bind` from `RebuildEntries()` too.
+4. New persisted UI toggles get `SessionState` keys inside core (alongside `SortKey`/`GroupKey`/`GroupExpandedKey`); pass getter/setter delegates to the component if it needs to read/write the state.
+
+### Add a new IdStruct in the codebase
+
+1. Declare the `partial struct` implementing `IId` (with `[UniqueId]` if it must be globally unique among assets of its declaring type). The generator emits `_id`, `Id`, and editor-only `__stringId`.
+2. In Unity: `Assets → Create → Aspid → Id Registry → Id Registry`.
+3. Set `_targetStructType` via the registry inspector's `Type` field — `IdRegistryResolver.Find` indexes by this AQN.
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/CLAUDE.md.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/CLAUDE.md.meta
new file mode 100644
index 0000000..bfe8a96
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/CLAUDE.md.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: cc091cf4b79884d14947f3c3c419e6ce
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Constants.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Constants.cs
new file mode 100644
index 0000000..a7d6bfe
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Constants.cs
@@ -0,0 +1,25 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal static class Constants
+ {
+ public const string NoneOption = "";
+ public const string IntIdFieldName = "_id";
+ public const string StringIdFieldName = "__stringId";
+
+ public static class Registry
+ {
+ public const string StyleSheetPath = "UI/Ids/Aspid-FastTools-Id-Registry";
+
+ public const string Add = "aspid-fasttools-id-registry__add";
+ public const string WarningVisible = "aspid-fasttools-id-registry__warning--visible";
+ public const string Toolbar = "aspid-fasttools-id-registry__toolbar";
+ public const string GroupFoldout = "aspid-fasttools-id-registry__group-foldout";
+ public const string List = "aspid-fasttools-id-registry__list";
+
+ public const int ScrollThreshold = 10;
+ public const int MaxVisibleRows = 10;
+ public const float RowHeight = 32.5f;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Constants.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Constants.cs.meta
new file mode 100644
index 0000000..840cc65
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Constants.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b2e9d839e485475c9acfc4bfe96dbe0c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers.meta
new file mode 100644
index 0000000..ef5c1a2
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: ceb5d0a5cfa144189d83b3cc0e9138ff
+timeCreated: 1776370591
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerContext.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerContext.cs
new file mode 100644
index 0000000..cf266f3
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerContext.cs
@@ -0,0 +1,58 @@
+using System;
+using UnityEditor;
+using System.Reflection;
+using Aspid.FastTools.Editors;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal sealed class IdStructDrawerContext
+ {
+ public readonly string Label;
+ public readonly Type FieldType;
+ public readonly Type DeclaringType;
+ public readonly SerializedProperty Property;
+ public readonly SerializedProperty IntProperty;
+ public readonly SerializedProperty StringProperty;
+
+ public SerializedObject SerializedObject => Property.serializedObject;
+
+ public IdStructDrawerContext(
+ string label,
+ FieldInfo fieldInfo,
+ SerializedProperty property)
+ {
+ Label = label;
+ FieldType = fieldInfo.FieldType;
+ DeclaringType = fieldInfo.DeclaringType;
+
+ Property = property.Persistent();
+ IntProperty = Property.FindPropertyRelative(Constants.IntIdFieldName);
+ StringProperty = Property.FindPropertyRelative(Constants.StringIdFieldName);
+ }
+
+ public void OpenRegistryAsset()
+ {
+ var registry = FindRegistry();
+ if (registry is null) return;
+
+ EditorGUIUtility.PingObject(registry);
+ Selection.activeObject = registry;
+ }
+
+ public IdRegistry GetOrCreate() =>
+ IdRegistryResolver.GetOrCreate(FieldType);
+
+ public IdRegistry FindRegistry() =>
+ IdRegistryResolver.Find(FieldType);
+
+ public IdRegistry Create() =>
+ IdRegistryResolver.Create(FieldType);
+
+ public string GetCurrentAssetGuid()
+ {
+ var path = AssetDatabase.GetAssetPath(SerializedObject.targetObject);
+ return string.IsNullOrEmpty(path) ? string.Empty : AssetDatabase.AssetPathToGUID(path);
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerContext.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerContext.cs.meta
new file mode 100644
index 0000000..7f7bea0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerContext.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0e7aafb7251e4dba83668b6a67adfce3
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerHelper.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerHelper.cs
new file mode 100644
index 0000000..04f074d
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerHelper.cs
@@ -0,0 +1,69 @@
+using Aspid.FastTools.Editors;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal static class IdStructDrawerHelper
+ {
+ public static string BuildCaption(IdStructDrawerContext ctx, out bool isMissing) =>
+ BuildCaption(ctx.FindRegistry(), ctx.IntProperty.intValue, ctx.StringProperty.stringValue, out isMissing);
+
+ public static string BuildCaption(IdRegistry registry, int id, string fallbackName, out bool isMissing)
+ {
+ var nameId = fallbackName ?? string.Empty;
+
+ var hasName = registry is not null
+ && id > 0
+ && registry.TryGetName(id, out nameId);
+
+ nameId ??= string.Empty;
+ var hasNotNameId = string.IsNullOrEmpty(nameId);
+ isMissing = registry is not null && id > 0 && !hasName;
+
+ return isMissing
+ ? hasNotNameId ? $"" : $""
+ : hasNotNameId ? Constants.NoneOption : nameId;
+ }
+
+ public static void SyncStringFromInt(IdStructDrawerContext ctx)
+ {
+ var registry = ctx.FindRegistry();
+
+ var currentId = ctx.IntProperty.intValue;
+ if (currentId <= 0 || registry is null) return;
+
+ if (!registry.TryGetName(currentId, out var registryName)) return;
+ if (registryName == ctx.StringProperty.stringValue) return;
+
+ ctx.StringProperty.SetStringAndApply(registryName);
+ ctx.Property.ApplyModifiedProperties();
+ }
+
+ public static void ApplySelection(
+ string selected,
+ IdStructDrawerContext ctx)
+ {
+ var id = 0;
+ var nameId = selected ?? string.Empty;
+
+ if (!string.IsNullOrEmpty(nameId))
+ {
+ var registry = ctx.FindRegistry();
+ if (registry is not null && registry.TryGetId(nameId, out var foundId))
+ id = foundId;
+ }
+
+ SetFields(id, nameId, ctx);
+ }
+
+ private static void SetFields(
+ int id,
+ string nameId,
+ IdStructDrawerContext ctx)
+ {
+ ctx.IntProperty.SetIntAndApply(id);
+ ctx.StringProperty.SetStringAndApply(nameId);
+ ctx.Property.ApplyModifiedPropertiesWithoutUndo();
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerHelper.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerHelper.cs.meta
new file mode 100644
index 0000000..d80e173
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructDrawerHelper.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 16e87b3f191b479aa0f7b2c380b30544
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructIMGUIPropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructIMGUIPropertyDrawer.cs
new file mode 100644
index 0000000..c1b5ce5
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructIMGUIPropertyDrawer.cs
@@ -0,0 +1,102 @@
+using UnityEditor;
+using UnityEngine;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal static class IdStructIMGUIPropertyDrawer
+ {
+ private const float ButtonGap = 2f;
+ private const float OpenButtonWidth = 22f;
+ private const string OpenButtonIconName = "d_ScriptableObject Icon";
+
+ private static GUIStyle _missingDropdownStyle;
+ private static readonly Color _missingTextColor = new(r: 1f, g: 0.4f, b: 0.4f);
+
+ public static float GetIMGUIHeight(IdStructDrawerContext ctx, bool isUnique, ref string lastStringId)
+ {
+ var height = EditorGUIUtility.singleLineHeight;
+ if (!isUnique) return height;
+
+ var stringId = ctx.StringProperty.stringValue;
+
+ if (stringId != lastStringId)
+ {
+ lastStringId = stringId;
+ UniqueIdIndex.RefreshAsset(ctx.SerializedObject.targetObject);
+ }
+
+ if (IsUniqueFor(ctx)) return height;
+ return height + EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
+ }
+
+ public static void Draw(Rect position, IdStructDrawerContext ctx, bool isUnique)
+ {
+ IdStructDrawerHelper.SyncStringFromInt(ctx);
+
+ if (!string.IsNullOrWhiteSpace(ctx.Label))
+ {
+ EditorGUI.LabelField(position, ctx.Label);
+ position.x += EditorGUIUtility.labelWidth;
+ position.width -= EditorGUIUtility.labelWidth;
+ }
+
+ var (dropRect, openRect) = SplitRect(position);
+
+ var caption = IdStructDrawerHelper.BuildCaption(ctx, out var isMissing);
+ var dropdownStyle = isMissing ? GetMissingDropdownStyle() : EditorStyles.miniPullDown;
+
+ if (EditorGUI.DropdownButton(dropRect, new GUIContent(caption), FocusType.Passive, dropdownStyle))
+ {
+ var screen = GUIUtility.GUIToScreenPoint(new Vector2(dropRect.x, dropRect.y));
+ var screenRect = new Rect(screen.x, screen.y, dropRect.width, dropRect.height);
+ IdSelectorWindow.Show(screenRect, ctx.FieldType, ctx.StringProperty.stringValue, selected => IdStructDrawerHelper.ApplySelection(selected, ctx));
+ }
+
+ using (new EditorGUI.DisabledScope(ctx.FindRegistry() is null))
+ {
+ if (GUI.Button(openRect, EditorGUIUtility.IconContent(OpenButtonIconName)))
+ ctx.OpenRegistryAsset();
+ }
+
+ if (!isUnique) return;
+ if (IsUniqueFor(ctx)) return;
+
+ var warningY = position.y + position.height + EditorGUIUtility.standardVerticalSpacing;
+ var warningRect = new Rect(position.x, warningY, position.width, EditorGUIUtility.singleLineHeight);
+
+ EditorGUI.HelpBox(warningRect, "ID is not unique among assets of this type", MessageType.Warning);
+ }
+
+ private static bool IsUniqueFor(IdStructDrawerContext ctx) =>
+ UniqueIdIndex.IsUnique(ctx.DeclaringType, ctx.StringProperty.stringValue, ctx.GetCurrentAssetGuid());
+
+ private static (Rect dropRect, Rect openRect) SplitRect(Rect position)
+ {
+ var height = EditorGUIUtility.singleLineHeight;
+ var dropRect = new Rect(position.x, position.y, position.width - OpenButtonWidth - ButtonGap, height);
+ var openRect = new Rect(dropRect.xMax + ButtonGap, position.y, OpenButtonWidth, height);
+
+ return (dropRect, openRect);
+ }
+
+ private static GUIStyle GetMissingDropdownStyle()
+ {
+ if (_missingDropdownStyle is not null)
+ return _missingDropdownStyle;
+
+ var state = new GUIStyleState
+ {
+ textColor = _missingTextColor
+ };
+
+ return _missingDropdownStyle = new GUIStyle(EditorStyles.miniPullDown)
+ {
+ normal = state,
+ hover = state,
+ focused = state,
+ active = state,
+ };
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructIMGUIPropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructIMGUIPropertyDrawer.cs.meta
new file mode 100644
index 0000000..28e152f
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructIMGUIPropertyDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 31aa14fabd960064489a422808195610
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructPropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructPropertyDrawer.cs
new file mode 100644
index 0000000..cefea83
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructPropertyDrawer.cs
@@ -0,0 +1,36 @@
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ [CustomPropertyDrawer(typeof(IId), useForChildren: true)]
+ internal sealed class IdStructPropertyDrawer : PropertyDrawer
+ {
+ private bool? _isUnique;
+ private string _lastStringId;
+
+ private bool IsUnique => _isUnique ??=
+ fieldInfo.GetCustomAttributes(typeof(UniqueIdAttribute), false).Length > 0;
+
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ var ctx = new IdStructDrawerContext(label.text, fieldInfo, property);
+ return IdStructIMGUIPropertyDrawer.GetIMGUIHeight(ctx, IsUnique, ref _lastStringId);
+ }
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ var height = GetPropertyHeight(property, label);
+ var drawRect = new Rect(position.x, position.y, position.width, height);
+
+ var ctx = new IdStructDrawerContext(label.text, fieldInfo, property);
+ IdStructIMGUIPropertyDrawer.Draw(drawRect, ctx, IsUnique);
+ }
+
+ public override VisualElement CreatePropertyGUI(SerializedProperty property) => IdStructUIToolkitPropertyDrawer.Draw(
+ ctx: new IdStructDrawerContext(preferredLabel, fieldInfo, property),
+ isUnique: IsUnique);
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructPropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructPropertyDrawer.cs.meta
new file mode 100644
index 0000000..dd0b582
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructPropertyDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4a01d15a4e71aac45aa79852f3bf79a6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructUIToolkitPropertyDrawer.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructUIToolkitPropertyDrawer.cs
new file mode 100644
index 0000000..5873be1
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructUIToolkitPropertyDrawer.cs
@@ -0,0 +1,66 @@
+using System;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+using Aspid.FastTools.UIElements;
+using Aspid.FastTools.UIElements.Editors.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal static class IdStructUIToolkitPropertyDrawer
+ {
+ public static VisualElement Draw(IdStructDrawerContext ctx, bool isUnique)
+ {
+ var idField = new InspectorIdField(ctx.Label, ctx.Property)
+ {
+ IdType = ctx.FieldType,
+ };
+
+ var root = new VisualElement()
+ .AddStyleSheetsFromResource(AspidStyles.DefaultStyleSheet)
+ .AddChild(new PropertyField(ctx.Property).SetDisplay(DisplayStyle.None))
+ .AddChild(idField);
+
+ Action refresh = () =>
+ {
+ IdStructDrawerHelper.SyncStringFromInt(ctx);
+ idField.RefreshFromBoundProperty();
+ };
+
+ root.RegisterCallback(_ => IdRegistryResolver.RegistryChanged += refresh);
+ root.RegisterCallback(_ => IdRegistryResolver.RegistryChanged -= refresh);
+ idField.schedule.Execute(() => refresh()).StartingIn(0);
+
+ if (isUnique)
+ root.AddChild(BuildWarningLabel(ctx));
+
+ return root;
+ }
+
+ private static Label BuildWarningLabel(IdStructDrawerContext ctx)
+ {
+ var warningLabel = new Label(text: "⚠ ID is not unique among assets of this type")
+ .AddClass(ThemeStyle.LightClass)
+ .AddClass(StatusStyle.WarningClass);
+
+ warningLabel.TrackPropertyValue(ctx.StringProperty, prop =>
+ {
+ UniqueIdIndex.RefreshAsset(prop.serializedObject.targetObject);
+ Refresh();
+ });
+
+ Action onIndexChanged = Refresh;
+ warningLabel.RegisterCallback(_ => UniqueIdIndex.IndexChanged += onIndexChanged);
+ warningLabel.RegisterCallback(_ => UniqueIdIndex.IndexChanged -= onIndexChanged);
+ warningLabel.schedule.Execute(Refresh).StartingIn(0);
+
+ return warningLabel;
+
+ void Refresh()
+ {
+ var unique = UniqueIdIndex.IsUnique(ctx.DeclaringType, ctx.StringProperty.stringValue, ctx.GetCurrentAssetGuid());
+ warningLabel.SetDisplay(unique ? DisplayStyle.None : DisplayStyle.Flex);
+ }
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructUIToolkitPropertyDrawer.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructUIToolkitPropertyDrawer.cs.meta
new file mode 100644
index 0000000..83b8f15
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Drawers/IdStructUIToolkitPropertyDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4e0880c0eede41aeaca70a913736cfb7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries.meta
new file mode 100644
index 0000000..14cc3d0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1977d33176f44dea89f9cd4bd3486f92
+timeCreated: 1776370591
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/AddRowValidation.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/AddRowValidation.cs
new file mode 100644
index 0000000..6549ae0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/AddRowValidation.cs
@@ -0,0 +1,21 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal readonly struct AddRowValidation
+ {
+ public readonly bool IsValid;
+ public readonly string Error;
+
+ private AddRowValidation(bool isValid, string error)
+ {
+ IsValid = isValid;
+ Error = error;
+ }
+
+ public static AddRowValidation Valid() =>
+ new(isValid: true, error: null);
+
+ public static AddRowValidation Invalid(string error) =>
+ new(isValid: false, error: error);
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/AddRowValidation.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/AddRowValidation.cs.meta
new file mode 100644
index 0000000..87b85bf
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/AddRowValidation.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 549cee3d86b664e08904ff691a960b31
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/CleanUpSummary.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/CleanUpSummary.cs
new file mode 100644
index 0000000..68709e6
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/CleanUpSummary.cs
@@ -0,0 +1,29 @@
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal readonly struct CleanUpSummary
+ {
+ public readonly int EmptyCount;
+ public readonly int DuplicateCount;
+
+ public CleanUpSummary(int emptyCount, int duplicateCount)
+ {
+ EmptyCount = emptyCount;
+ DuplicateCount = duplicateCount;
+ }
+
+ public int Total =>
+ EmptyCount + DuplicateCount;
+
+ public string ToShortLabel()
+ {
+ var parts = new List();
+ if (DuplicateCount > 0) parts.Add($"{DuplicateCount} duplicates");
+ if (EmptyCount > 0) parts.Add($"{EmptyCount} empty name" + (EmptyCount is 1 ? string.Empty : "s"));
+
+ return $"⚠ {Total} invalid enter{(Total == 1 ? "y" : "ies")} ({string.Join(", ", parts)})";
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/CleanUpSummary.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/CleanUpSummary.cs.meta
new file mode 100644
index 0000000..febe6fc
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/CleanUpSummary.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a79891dd43c44f50b3a839e80de81aa8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEditor.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEditor.cs
new file mode 100644
index 0000000..1bd0d26
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEditor.cs
@@ -0,0 +1,13 @@
+using UnityEditor;
+using UnityEngine.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ [CustomEditor(typeof(IdRegistry))]
+ internal sealed class IdRegistryEditor : Editor
+ {
+ public override VisualElement CreateInspectorGUI() =>
+ new RegistryEditorCore((IdRegistry)target).Build();
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEditor.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEditor.cs.meta
new file mode 100644
index 0000000..6defbd7
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f38578ffc72b42189bc5cbe4ef4fd9a1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEntryData.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEntryData.cs
new file mode 100644
index 0000000..96064ef
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEntryData.cs
@@ -0,0 +1,19 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal readonly struct IdRegistryEntryData
+ {
+ public readonly int Id;
+ public readonly string Name;
+ public readonly bool IsDuplicate;
+ public readonly int OriginalIndex;
+
+ public IdRegistryEntryData(int originalIndex, string name, int id, bool isDuplicate)
+ {
+ Id = id;
+ Name = name;
+ IsDuplicate = isDuplicate;
+ OriginalIndex = originalIndex;
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEntryData.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEntryData.cs.meta
new file mode 100644
index 0000000..8c26cf0
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryEntryData.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 05bd04a183c242cbbc6737270fa6ff36
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryValidator.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryValidator.cs
new file mode 100644
index 0000000..a6c7042
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryValidator.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal static class IdRegistryValidator
+ {
+ private const int MaxNameLength = 255;
+
+ private static readonly Regex _identifierPattern =
+ new(@"^[A-Za-z_][A-Za-z0-9_\-]*$", RegexOptions.Compiled);
+
+ ///
+ /// Validates a candidate id name. Rules, in order: not whitespace,
+ /// starts with a letter/underscore and contains only letters, digits,
+ /// underscore or hyphen, length ≤ 255, not flagged by .
+ /// Callers should pass a delegate over a cached lookup instead of
+ /// materializing a HashSet on every keystroke.
+ ///
+ public static bool IsValidName(string input, Func isTaken, out string error)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ {
+ error = "Name cannot be empty.";
+ return false;
+ }
+
+ if (!_identifierPattern.IsMatch(input))
+ {
+ error = "Name must start with a letter or underscore and contain only letters, digits, underscore or hyphen.";
+ return false;
+ }
+
+ if (input.Length > MaxNameLength)
+ {
+ error = $"Name is too long (max {MaxNameLength} chars).";
+ return false;
+ }
+
+ if (isTaken != null && isTaken(input))
+ {
+ error = $"'{input}' already exists.";
+ return false;
+ }
+
+ error = null;
+ return true;
+ }
+
+ public static CleanUpSummary Summarize(int count, Func getName)
+ {
+ if (getName is null) throw new ArgumentNullException(nameof(getName));
+
+ var empty = 0;
+ var duplicates = 0;
+ var seen = new HashSet();
+ for (var i = 0; i < count; i++)
+ {
+ var name = getName(i);
+ if (string.IsNullOrEmpty(name)) empty++;
+ else if (!seen.Add(name)) duplicates++;
+ }
+
+ return new CleanUpSummary(empty, duplicates);
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryValidator.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryValidator.cs.meta
new file mode 100644
index 0000000..83226e8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/IdRegistryValidator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3f952ffe31bd4358820c2af0ac7511de
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/NextIdWarning.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/NextIdWarning.cs
new file mode 100644
index 0000000..cd27603
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/NextIdWarning.cs
@@ -0,0 +1,21 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal readonly struct NextIdWarning
+ {
+ public readonly bool Show;
+ public readonly string Tooltip;
+
+ public NextIdWarning(bool show, string tooltip)
+ {
+ Show = show;
+ Tooltip = tooltip;
+ }
+
+ public static NextIdWarning Hidden(string tooltip = null) =>
+ new(show: false, tooltip: tooltip ?? string.Empty);
+
+ public static NextIdWarning Visible(string tooltip) =>
+ new(show: true, tooltip: tooltip);
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/NextIdWarning.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/NextIdWarning.cs.meta
new file mode 100644
index 0000000..a1dd444
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/NextIdWarning.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 15ef3dcdbcf3747038b6b0f4c70a237e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryEditorCore.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryEditorCore.cs
new file mode 100644
index 0000000..12866a9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryEditorCore.cs
@@ -0,0 +1,490 @@
+using System;
+using UnityEditor;
+using UnityEditor.UIElements;
+using UnityEngine.UIElements;
+using Aspid.FastTools.Editors;
+using Aspid.FastTools.UIElements;
+using System.Collections.Generic;
+using Aspid.FastTools.UIElements.Editors.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ ///
+ /// Builds the inspector UI for . Acts as the sole orchestrator:
+ /// owns the , the view-model, and the mutation cycle
+ /// ( → SerializedProperty edits → ).
+ /// UI is split into dedicated IdRegistry*VisualElement components that emit
+ /// events; this class wires them and performs all mutations.
+ ///
+ internal sealed class RegistryEditorCore
+ {
+ private readonly IdRegistry _registry;
+ private readonly SerializedObject _serializedObject;
+ private readonly SerializedProperty _idsProp;
+ private readonly SerializedProperty _namesProp;
+ private readonly SerializedProperty _nextIdProp;
+ private readonly SerializedProperty _targetStructTypeProp;
+
+ private readonly List _viewModel = new();
+
+ private IdRegistryListVisualElement _list;
+ private IdRegistryToolbarVisualElement _toolbar;
+ private IdRegistryAddRowVisualElement _addRow;
+ private IdRegistryNextIdRowVisualElement _nextIdRow;
+ private IdRegistryWarningVisualElement _warning;
+
+ private string _assetGuid = string.Empty;
+ private string _searchQuery = string.Empty;
+ private RegistryGroupMode _groupMode = RegistryGroupMode.None;
+ private RegistrySortMode _sortMode = RegistrySortMode.RegistryOrder;
+
+ // Invalidated to null inside RebuildEntries; rebuilt lazily by EnsureNameOccurrencesCache.
+ private Dictionary _nameOccurrencesCache;
+
+ public RegistryEditorCore(IdRegistry registry)
+ {
+ _registry = registry;
+ _serializedObject = new SerializedObject(registry);
+ _idsProp = _serializedObject.FindProperty("_ids");
+ _namesProp = _serializedObject.FindProperty("_names");
+ _nextIdProp = _serializedObject.FindProperty("_nextId");
+ _targetStructTypeProp = _serializedObject.FindProperty("_targetStructType");
+ }
+
+ public VisualElement Build()
+ {
+ var assetPath = AssetDatabase.GetAssetPath(_registry);
+ _assetGuid = string.IsNullOrEmpty(assetPath)
+ ? _registry.GetInstanceID().ToString()
+ : AssetDatabase.AssetPathToGUID(assetPath);
+
+ _sortMode = (RegistrySortMode)SessionState.GetInt(SortKey, (int)RegistrySortMode.RegistryOrder);
+ _groupMode = (RegistryGroupMode)SessionState.GetInt(GroupKey, (int)RegistryGroupMode.None);
+
+ var root = new VisualElement()
+ .AddStyleSheetsFromResource(AspidStyles.DefaultStyleSheet)
+ .AddStyleSheetsFromResource(Constants.Registry.StyleSheetPath)
+ .AddClass("aspid-fasttools-inspector-container");
+
+ root.Add(new AspidInspectorHeader(_registry.name + " (Beta)", _registry)
+ {
+ Subtext = _registry.GetType().Name,
+ });
+
+ var typeContainer = new AspidBox()
+ .SetMarginTop(5);
+
+ typeContainer.AddChild(new AspidLabel("Type").SetMarginBottom(5));
+ typeContainer.AddChild(new PropertyField(_targetStructTypeProp, label: string.Empty));
+
+ var container = new AspidBox()
+ .SetMarginTop(5);
+
+ container.AddChild(BuildSectionTitle("IDs"));
+
+ _warning = new IdRegistryWarningVisualElement();
+ _warning.ReviewRequested += ShowCleanUpDialog;
+ container.AddChild(_warning);
+
+ var searchField = new ToolbarSearchField();
+ searchField.RegisterValueChangedCallback(e =>
+ {
+ _searchQuery = e.newValue ?? string.Empty;
+ RebuildEntries();
+ });
+ container.AddChild(searchField);
+
+ _toolbar = new IdRegistryToolbarVisualElement(_sortMode, _groupMode);
+ _toolbar.SortChanged += OnSortChanged;
+ _toolbar.GroupChanged += OnGroupChanged;
+ container.AddChild(_toolbar);
+
+ _list = new IdRegistryListVisualElement();
+ _list.RowNameFocusIn += OnRowNameFocusIn;
+ _list.RowNameChanging += OnRowNameChanging;
+ _list.RowNameCommitRequested += OnRowNameCommitRequested;
+ _list.RowDeleteRequested += OnRowDeleteRequested;
+ container.AddChild(_list);
+
+ _nextIdRow = new IdRegistryNextIdRowVisualElement(_nextIdProp, ResolveNextIdWarning);
+ _nextIdRow.ValueChanged += OnNextIdValueChanged;
+ container.AddChild(_nextIdRow);
+
+ _addRow = new IdRegistryAddRowVisualElement(ValidateAddRow);
+ _addRow.AddRequested += OnAddRowAddRequested;
+ container.AddChild(_addRow);
+
+ container.TrackSerializedObjectValue(_serializedObject, _ => RebuildEntries());
+ RebuildEntries();
+
+ return root
+ .AddChild(typeContainer)
+ .AddChild(container);
+ }
+
+ private static VisualElement BuildSectionTitle(string text) =>
+ new AspidLabel(text, new AspidLabelPreset()
+ .SetTheme(ThemeStyle.Type.Light)
+ .SetLabelSize(AspidLabelSizeStyle.Type.H5)
+ .SetLineSize(AspidDividingLineSizeStyle.Type.Medium));
+
+ // -------- view-model --------
+
+ private void RebuildEntries()
+ {
+ _viewModel.Clear();
+ _nameOccurrencesCache = null;
+ var count = Count;
+
+ var duplicates = new HashSet();
+ var seen = new HashSet();
+ for (var i = 0; i < count; i++)
+ {
+ var name = GetName(i);
+ if (!string.IsNullOrEmpty(name) && !seen.Add(name))
+ duplicates.Add(name);
+ }
+
+ var query = _searchQuery?.Trim() ?? string.Empty;
+ for (var i = 0; i < count; i++)
+ {
+ var name = GetName(i);
+ var id = GetId(i);
+ if (!MatchesQuery(name, id, query)) continue;
+
+ _viewModel.Add(new IdRegistryEntryData(i, name, id, duplicates.Contains(name)));
+ }
+
+ ApplySort(_viewModel);
+
+ _list?.Bind(_viewModel, _groupMode, GetFoldoutExpanded, SetFoldoutExpanded);
+ _warning?.Bind(IdRegistryValidator.Summarize(Count, GetName));
+ _addRow?.Revalidate();
+ }
+
+ private void ApplySort(List list)
+ {
+ switch (_sortMode)
+ {
+ case RegistrySortMode.NameAscending:
+ list.Sort((a, b) => StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name));
+ break;
+ case RegistrySortMode.NameDescending:
+ list.Sort((a, b) => StringComparer.OrdinalIgnoreCase.Compare(b.Name, a.Name));
+ break;
+ case RegistrySortMode.IdAscending:
+ list.Sort((a, b) => a.Id.CompareTo(b.Id));
+ break;
+ case RegistrySortMode.IdDescending:
+ list.Sort((a, b) => b.Id.CompareTo(a.Id));
+ break;
+ case RegistrySortMode.RegistryOrder:
+ default:
+ break;
+ }
+ }
+
+ private static bool MatchesQuery(string name, int id, string query)
+ {
+ if (string.IsNullOrEmpty(query)) return true;
+ if (!string.IsNullOrEmpty(name) && name.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0) return true;
+ return id.ToString().IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0;
+ }
+
+ private Func IsTakenExcluding()
+ {
+ var occurrences = EnsureNameOccurrencesCache();
+ return name => occurrences.ContainsKey(name);
+ }
+
+ private Func IsTakenExcluding(int exceptIndex)
+ {
+ var occurrences = EnsureNameOccurrencesCache();
+
+ string exceptName = null;
+ if (exceptIndex >= 0 && exceptIndex < Count)
+ {
+ var n = GetName(exceptIndex);
+ if (!string.IsNullOrEmpty(n)) exceptName = n;
+ }
+
+ return name =>
+ {
+ if (!occurrences.TryGetValue(name, out var count)) return false;
+ if (exceptName != null && exceptName == name) return count > 1;
+ return true;
+ };
+ }
+
+ private Dictionary EnsureNameOccurrencesCache()
+ {
+ if (_nameOccurrencesCache != null)
+ return _nameOccurrencesCache;
+
+ var count = Count;
+ var cache = new Dictionary();
+
+ for (var i = 0; i < count; i++)
+ {
+ var name = GetName(i);
+ if (string.IsNullOrEmpty(name)) continue;
+
+ cache.TryGetValue(name, out var c);
+ cache[name] = c + 1;
+ }
+
+ return _nameOccurrencesCache = cache;
+ }
+
+ // -------- component handlers --------
+
+ private void OnSortChanged(RegistrySortMode mode)
+ {
+ _sortMode = mode;
+ SessionState.SetInt(SortKey, (int)mode);
+ RebuildEntries();
+ }
+
+ private void OnGroupChanged(RegistryGroupMode mode)
+ {
+ _groupMode = mode;
+ SessionState.SetInt(GroupKey, (int)mode);
+ RebuildEntries();
+ }
+
+ private void OnNextIdValueChanged(int _)
+ {
+ _registry.InvalidateCache();
+ _addRow?.Revalidate();
+ }
+
+ private void OnAddRowAddRequested(string name)
+ {
+ Record($"Add ID '{name}'");
+ Add(name);
+ Commit();
+
+ _addRow.Reset();
+ RebuildEntries();
+
+ var newIndex = _viewModel.FindIndex(v => v.Name == name);
+ if (newIndex >= 0) _list.ScrollToItem(newIndex);
+ }
+
+ private void OnRowNameFocusIn(IdRegistryEntryVisualElement row, IdRegistryEntryData data)
+ {
+ if (data.IsDuplicate)
+ row.SetError("Name already exists.");
+ }
+
+ private void OnRowNameChanging(IdRegistryEntryVisualElement row, IdRegistryEntryData data, string newValue)
+ {
+ var trimmed = newValue?.Trim() ?? string.Empty;
+
+ if (trimmed == data.Name)
+ {
+ row.SetEditMode(false);
+ row.ClearError();
+ return;
+ }
+
+ if (IdRegistryValidator.IsValidName(trimmed, IsTakenExcluding(data.OriginalIndex), out var error))
+ {
+ row.SetEditMode(true, canConfirm: true);
+ row.ClearError();
+ }
+ else
+ {
+ row.SetEditMode(true, canConfirm: false);
+ row.SetError(error);
+ }
+ }
+
+ private void OnRowNameCommitRequested(IdRegistryEntryVisualElement row, IdRegistryEntryData data, string rawValue)
+ {
+ var trimmed = rawValue?.Trim() ?? string.Empty;
+ if (trimmed == data.Name || string.IsNullOrEmpty(trimmed)) return;
+
+ if (!IdRegistryValidator.IsValidName(trimmed, IsTakenExcluding(data.OriginalIndex), out _)) return;
+
+ Record($"Rename ID '{data.Name}' → '{trimmed}'");
+ SetName(data.OriginalIndex, trimmed);
+ Commit();
+ row.SetEditMode(false);
+ row.ClearError();
+ }
+
+ private void OnRowDeleteRequested(IdRegistryEntryVisualElement row, IdRegistryEntryData data)
+ {
+ var name = data.Name;
+ if (!EditorUtility.DisplayDialog(
+ "Delete ID",
+ $"Delete '{name}'?\n\nAssets referencing this ID will display until reassigned.",
+ "Delete",
+ "Cancel"))
+ return;
+
+ Record($"Delete ID '{name}'");
+ RemoveAt(data.OriginalIndex);
+ Commit();
+ }
+
+ private AddRowValidation ValidateAddRow(string trimmed)
+ {
+ if (!IdRegistryValidator.IsValidName(trimmed, IsTakenExcluding(), out var err))
+ return AddRowValidation.Invalid(err ?? string.Empty);
+
+ var nextId = _nextIdProp.intValue;
+ if (nextId < 1)
+ return AddRowValidation.Invalid("Next ID must be ≥ 1.");
+
+ return NextIdCollides(nextId)
+ ? AddRowValidation.Invalid($"Next ID {nextId} is already used — change it before adding.")
+ : AddRowValidation.Valid();
+
+ }
+
+ private NextIdWarning ResolveNextIdWarning(int value)
+ {
+ var maxAssigned = MaxAssignedId;
+ var show = value <= maxAssigned && value >= 1;
+
+ if (show)
+ return NextIdWarning.Visible(
+ $"Reusing ID {value} may silently remap references: assets that previously pointed to this ID will appear bound to the next name you create. Proceed only if you know these IDs are unused.");
+
+ return value < 1
+ ? NextIdWarning.Hidden("Next ID must be ≥ 1.")
+ : NextIdWarning.Hidden();
+ }
+
+ private bool NextIdCollides(int nextId)
+ {
+ if (nextId < 1) return false;
+ var count = Count;
+ for (var i = 0; i < count; i++)
+ if (GetId(i) == nextId) return true;
+ return false;
+ }
+
+ // -------- clean-up flow --------
+
+ private void ShowCleanUpDialog()
+ {
+ var summary = IdRegistryValidator.Summarize(Count, GetName);
+ if (summary.Total == 0) return;
+
+ var message = $"This will remove {summary.Total} invalid entr{(summary.Total == 1 ? "y" : "ies")}:\n"
+ + (summary.DuplicateCount > 0 ? $" • {summary.DuplicateCount} duplicate name(s)\n" : string.Empty)
+ + (summary.EmptyCount > 0 ? $" • {summary.EmptyCount} empty name(s)\n" : string.Empty)
+ + "\nProceed?";
+
+ if (!EditorUtility.DisplayDialog("Clean up invalid entries", message, "Clean up", "Cancel"))
+ return;
+
+ Record("Clean Up Invalid IDs");
+
+ // Single source of truth for invalidity criteria — see EnumerateInvalidIndices.
+ var toRemove = new List(EnumerateInvalidIndices());
+ for (var i = toRemove.Count - 1; i >= 0; i--)
+ RemoveAt(toRemove[i]);
+
+ Commit();
+ }
+
+ private IEnumerable EnumerateInvalidIndices()
+ {
+ var count = Count;
+ var seen = new HashSet();
+
+ for (var i = 0; i < count; i++)
+ {
+ var name = GetName(i);
+
+ if (string.IsNullOrEmpty(name))
+ {
+ yield return i;
+ continue;
+ }
+
+ if (!seen.Add(name))
+ yield return i;
+ }
+ }
+
+ // -------- SessionState keys --------
+
+ private string SortKey => $"Aspid.FastTools.Ids.Registry:{_assetGuid}:Sort";
+ private string GroupKey => $"Aspid.FastTools.Ids.Registry:{_assetGuid}:Group";
+ private string GroupExpandedKey(string group) =>
+ $"Aspid.FastTools.Ids.Registry:{_assetGuid}:Group:{group}:Expanded";
+
+ private bool GetFoldoutExpanded(string group) =>
+ SessionState.GetBool(GroupExpandedKey(group), defaultValue: true);
+
+ private void SetFoldoutExpanded(string group, bool expanded) =>
+ SessionState.SetBool(GroupExpandedKey(group), expanded);
+
+ // -------- storage / mutation cycle --------
+ // Every mutation goes Record → SerializedProperty edits → Commit.
+ // Skipping Record breaks Undo. Skipping Commit leaves the runtime cache stale.
+
+ private int Count => _idsProp.arraySize;
+
+ private int MaxAssignedId
+ {
+ get
+ {
+ var max = 0;
+ var count = Count;
+ for (var i = 0; i < count; i++)
+ {
+ var id = GetId(i);
+ if (id > max) max = id;
+ }
+ return max;
+ }
+ }
+
+ private int GetId(int index) =>
+ _idsProp.GetArrayElementAtIndex(index).intValue;
+
+ private string GetName(int index) =>
+ _namesProp.GetArrayElementAtIndex(index).stringValue;
+
+ private void Add(string name)
+ {
+ var id = _nextIdProp.intValue;
+ _nextIdProp.intValue = id + 1;
+
+ var newIndex = _idsProp.arraySize;
+
+ _idsProp.SetArraySize(newIndex + 1);
+ _namesProp.SetArraySize(newIndex + 1);
+
+ _idsProp.GetArrayElementAtIndex(newIndex).intValue = id;
+ _namesProp.GetArrayElementAtIndex(newIndex).stringValue = name;
+ }
+
+ private void SetName(int index, string name) =>
+ _namesProp.GetArrayElementAtIndex(index).stringValue = name;
+
+ private void RemoveAt(int index)
+ {
+ _idsProp.DeleteArrayElementAtIndex(index);
+ _namesProp.DeleteArrayElementAtIndex(index);
+ }
+
+ private void Record(string operationName) =>
+ Undo.RegisterCompleteObjectUndo(_registry, operationName);
+
+ private void Commit()
+ {
+ _serializedObject.ApplyModifiedProperties();
+ _registry.InvalidateCache();
+
+ EditorUtility.SetDirty(_registry);
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryEditorCore.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryEditorCore.cs.meta
new file mode 100644
index 0000000..8576350
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryEditorCore.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 90fe428956d834c5f901a430eb3acb07
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryGroupMode.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryGroupMode.cs
new file mode 100644
index 0000000..c7f8933
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryGroupMode.cs
@@ -0,0 +1,9 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal enum RegistryGroupMode
+ {
+ None,
+ ByPrefix,
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryGroupMode.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryGroupMode.cs.meta
new file mode 100644
index 0000000..117d2d8
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistryGroupMode.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 55e9ea7b515184e569fe229781ed5a6e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistrySortMode.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistrySortMode.cs
new file mode 100644
index 0000000..0aecae9
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistrySortMode.cs
@@ -0,0 +1,12 @@
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal enum RegistrySortMode
+ {
+ RegistryOrder,
+ NameAscending,
+ NameDescending,
+ IdAscending,
+ IdDescending,
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistrySortMode.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistrySortMode.cs.meta
new file mode 100644
index 0000000..931e997
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/RegistrySortMode.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: dde444e0c74ae45dcaba9fe29aa5197e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements.meta
new file mode 100644
index 0000000..a0880bd
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 035c99b87ee149b68270bac109b10fcc
+timeCreated: 1777912655
\ No newline at end of file
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryAddRowVisualElement.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryAddRowVisualElement.cs
new file mode 100644
index 0000000..0e9d37c
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryAddRowVisualElement.cs
@@ -0,0 +1,82 @@
+using System;
+using UnityEngine.UIElements;
+using Aspid.FastTools.UIElements;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal sealed class IdRegistryAddRowVisualElement : VisualElement
+ {
+ private readonly Button _button;
+ private readonly TextField _input;
+ private readonly Label _errorLabel;
+ private readonly Func _validate;
+
+ public event Action AddRequested;
+
+ public IdRegistryAddRowVisualElement(Func validate)
+ {
+ this.AddClass(Constants.Registry.Add);
+
+ _input = new TextField()
+ .AddValueChanged(_ => Revalidate());
+
+ _button = new Button()
+ .SetText("+")
+ .SetEnabledSelf(false)
+ .AddClicked(OnAddClicked);
+
+ _errorLabel = new Label()
+ .SetDisplay(DisplayStyle.None);
+
+ this.AddChild(new VisualElement()
+ .AddChild(_input)
+ .AddChild(_button))
+ .AddChild(_errorLabel);
+
+ _validate = validate;
+ Revalidate();
+ }
+
+ public void Revalidate()
+ {
+ if (_validate is null) return;
+
+ var trimmed = _input.value?.Trim() ?? string.Empty;
+ if (string.IsNullOrEmpty(trimmed))
+ {
+ _button.SetEnabled(false);
+ _errorLabel.SetDisplay(DisplayStyle.None);
+
+ return;
+ }
+
+ var result = _validate(trimmed);
+ if (!result.IsValid)
+ {
+ _button.SetEnabled(false);
+ _errorLabel.SetText(result.Error ?? string.Empty)
+ .SetDisplay(DisplayStyle.Flex);
+
+ return;
+ }
+
+ _errorLabel.SetDisplay(DisplayStyle.None);
+ _button.SetEnabled(true);
+ }
+
+ public void Reset()
+ {
+ _input.SetValueWithoutNotify(string.Empty);
+ Revalidate();
+ }
+
+ private void OnAddClicked()
+ {
+ var trimmed = _input.value?.Trim();
+ if (string.IsNullOrEmpty(trimmed)) return;
+
+ AddRequested?.Invoke(trimmed);
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryAddRowVisualElement.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryAddRowVisualElement.cs.meta
new file mode 100644
index 0000000..81e8201
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryAddRowVisualElement.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bad806ac8858049e09f40ee7e2ac5890
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryEntryVisualElement.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryEntryVisualElement.cs
new file mode 100644
index 0000000..9e372ac
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryEntryVisualElement.cs
@@ -0,0 +1,110 @@
+using System;
+using UnityEngine.UIElements;
+using Aspid.FastTools.UIElements;
+using Aspid.FastTools.UIElements.Editors.Internal;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal sealed class IdRegistryEntryVisualElement : VisualElement
+ {
+ private const string DeleteButtonClass = "aspid-fasttools-id-registry__delete";
+ private const string ConfirmButtonClass = "aspid-fasttools-id-registry__confirm";
+
+ private readonly TextField _nameField;
+
+ private readonly Label _idBadge;
+ private readonly Label _errorLabel;
+
+ private readonly Button _deleteButton;
+ private readonly Button _confirmButton;
+
+ public IdRegistryEntryData Data { get; private set; }
+
+ public event Action NameFocusIn;
+
+ public event Action NameChanging;
+
+ public event Action NameCommitRequested;
+
+ public event Action DeleteRequested;
+
+ public IdRegistryEntryVisualElement()
+ {
+ _idBadge = new Label().SetIsSelectable(true);
+ _nameField = new TextField();
+
+ _deleteButton = new Button()
+ .SetText("×")
+ .AddClass(DeleteButtonClass)
+ .AddClicked(() => DeleteRequested?.Invoke(this, Data));
+
+ _confirmButton = new Button()
+ .SetText("✓")
+ .AddClass(ConfirmButtonClass)
+ .SetDisplay(DisplayStyle.None)
+ .AddClicked(() => NameCommitRequested?.Invoke(this, Data, _nameField.value));
+
+ _errorLabel = new Label()
+ .SetDisplay(DisplayStyle.None);
+
+ this.AddChild(new VisualElement()
+ .AddChild(_idBadge)
+ .AddChild(_nameField)
+ .AddChild(_deleteButton)
+ .AddChild(_confirmButton))
+ .AddChild(_errorLabel);
+
+ _nameField.RegisterCallback(_ => NameFocusIn?.Invoke(this, Data));
+ _nameField.RegisterValueChangedCallback(e => NameChanging?.Invoke(this, Data, e.newValue));
+ _nameField.RegisterCallback(OnNameFieldFocusOut);
+ }
+
+ private void OnNameFieldFocusOut(FocusOutEvent evt)
+ {
+ if (evt.relatedTarget is VisualElement target && IsCommitTarget(target)) return;
+ if (_nameField.value == Data.Name) return;
+
+ _nameField.SetValueWithoutNotify(Data.Name);
+ SetEditMode(false);
+
+ if (Data.IsDuplicate) SetError("Name already exists.");
+ else ClearError();
+ }
+
+ private bool IsCommitTarget(VisualElement target) =>
+ target == _confirmButton || _confirmButton.Contains(target);
+
+ public void SetEditMode(bool editing, bool canConfirm = true)
+ {
+ _deleteButton.SetDisplay(editing ? DisplayStyle.None : DisplayStyle.Flex);
+ _confirmButton.SetDisplay(editing ? DisplayStyle.Flex : DisplayStyle.None);
+ _confirmButton.SetEnabled(canConfirm);
+ }
+
+ public void Bind(in IdRegistryEntryData data)
+ {
+ Data = data;
+
+ _nameField.SetValueWithoutNotify(data.Name);
+ _idBadge.text = data.Id.ToString();
+ SetEditMode(false);
+
+ if (data.IsDuplicate) SetError("Name already exists.");
+ else ClearError();
+ }
+
+ public void SetError(string message)
+ {
+ _errorLabel.text = message;
+ _errorLabel.SetDisplay(DisplayStyle.Flex);
+ _idBadge.EnableInClassList(StatusStyle.ErrorClass, true);
+ }
+
+ public void ClearError()
+ {
+ _errorLabel.SetDisplay(DisplayStyle.None);
+ _idBadge.EnableInClassList(StatusStyle.ErrorClass, false);
+ }
+ }
+}
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryEntryVisualElement.cs.meta b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryEntryVisualElement.cs.meta
new file mode 100644
index 0000000..7e81849
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryEntryVisualElement.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c3f4392976594db282d366f478327b41
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 2d380e9402a9f4c5b8eb637f9f40c400, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryListVisualElement.cs b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryListVisualElement.cs
new file mode 100644
index 0000000..42d46aa
--- /dev/null
+++ b/Aspid.FastTools/Assets/Plugins/Aspid/FastTools/Unity/Editor/Scripts/Ids/Registries/VisualElements/IdRegistryListVisualElement.cs
@@ -0,0 +1,166 @@
+using System;
+using UnityEngine.UIElements;
+using Aspid.FastTools.UIElements;
+using System.Collections.Generic;
+
+// ReSharper disable once CheckNamespace
+namespace Aspid.FastTools.Ids.Editors
+{
+ internal sealed class IdRegistryListVisualElement : VisualElement
+ {
+ private const string UngroupedKey = "";
+
+ private ListView _flatListView;
+ private List _currentItems;
+ private RegistryGroupMode _currentGroupMode = RegistryGroupMode.None;
+
+ private Func _getFoldoutExpanded;
+ private Action _setFoldoutExpanded;
+
+ public event Action