mirror of
https://github.com/AnnulusGames/Alchemy.git
synced 2025-01-22 08:18:51 -05:00
Initial commit
This commit is contained in:
commit
a720750e46
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Auto detect text files and perform LF normalization
|
||||||
|
* text=auto
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# DS_Store
|
||||||
|
*.DS_Store
|
403
Alchemy.SourceGenerator/.gitignore
vendored
Normal file
403
Alchemy.SourceGenerator/.gitignore
vendored
Normal file
@ -0,0 +1,403 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
.idea
|
||||||
|
|
||||||
|
## DS_Store
|
||||||
|
*.DS_Store
|
14
Alchemy.SourceGenerator/Alchemy.SourceGenerator.csproj
Normal file
14
Alchemy.SourceGenerator/Alchemy.SourceGenerator.csproj
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
<LangVersion>9.0</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.9.0" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="3.9.0" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
25
Alchemy.SourceGenerator/Alchemy.SourceGenerator.sln
Normal file
25
Alchemy.SourceGenerator/Alchemy.SourceGenerator.sln
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 16
|
||||||
|
VisualStudioVersion = 25.0.1706.1
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Alchemy.SourceGenerator", "Alchemy.SourceGenerator.csproj", "{073160C2-08C9-4905-A4D0-7897AADB4E6C}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{073160C2-08C9-4905-A4D0-7897AADB4E6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{073160C2-08C9-4905-A4D0-7897AADB4E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{073160C2-08C9-4905-A4D0-7897AADB4E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{073160C2-08C9-4905-A4D0-7897AADB4E6C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {D02A2C17-448E-4F43-BF08-7E77D0E2208B}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
190
Alchemy.SourceGenerator/AlchemySerializeGenerator.cs
Normal file
190
Alchemy.SourceGenerator/AlchemySerializeGenerator.cs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
|
namespace Alchemy.SourceGenerator
|
||||||
|
{
|
||||||
|
[Generator]
|
||||||
|
public sealed class AlchemySerializeGenerator : ISourceGenerator
|
||||||
|
{
|
||||||
|
public void Initialize(GeneratorInitializationContext context)
|
||||||
|
{
|
||||||
|
context.RegisterForSyntaxNotifications(SyntaxReceiver.Create);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Execute(GeneratorExecutionContext context)
|
||||||
|
{
|
||||||
|
if (context.SyntaxReceiver is not SyntaxReceiver receiver || receiver.TargetTypes.Count == 0) return;
|
||||||
|
|
||||||
|
var compilation = context.Compilation;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var typeSyntax in receiver.TargetTypes)
|
||||||
|
{
|
||||||
|
var fieldSymbols = new List<IFieldSymbol>();
|
||||||
|
var fields = typeSyntax.Members
|
||||||
|
.Where(x => x is FieldDeclarationSyntax)
|
||||||
|
.Select(x => (FieldDeclarationSyntax)x);
|
||||||
|
foreach (var field in fields)
|
||||||
|
{
|
||||||
|
var model = context.Compilation.GetSemanticModel(field.SyntaxTree);
|
||||||
|
foreach (var variable in field.Declaration.Variables)
|
||||||
|
{
|
||||||
|
var fieldSymbol = model.GetDeclaredSymbol(variable) as IFieldSymbol;
|
||||||
|
var attribute = fieldSymbol.GetAttributes()
|
||||||
|
.FirstOrDefault(x =>
|
||||||
|
x.AttributeClass.Name is "AlchemySerializeField"
|
||||||
|
or "AlchemySerializeFieldAttribute"
|
||||||
|
or "Alchemy.Serialization.AlchemySerializeField"
|
||||||
|
or "Alchemy.Serialization.AlchemySerializeFieldAttribute");
|
||||||
|
if (attribute != null)
|
||||||
|
{
|
||||||
|
fieldSymbols.Add(fieldSymbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var typeSymbol = context.Compilation.GetSemanticModel(typeSyntax.SyntaxTree)
|
||||||
|
.GetDeclaredSymbol(typeSyntax);
|
||||||
|
|
||||||
|
var sourceText = ProcessClass((INamedTypeSymbol)typeSymbol, fieldSymbols);
|
||||||
|
var fullType = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||||
|
.Replace("global::", "")
|
||||||
|
.Replace("<", "_")
|
||||||
|
.Replace(">", "_");
|
||||||
|
|
||||||
|
context.AddSource(fullType + ".AlchemySerializeGenerator.g.cs", sourceText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var diagnosticDescriptor = new DiagnosticDescriptor("AlchemySerializeGeneratorError", "AlchemySerializeGeneratorError", $"Generation failed with:\n {ex}", "AlchemySerializeGeneratorError", DiagnosticSeverity.Error, true);
|
||||||
|
context.ReportDiagnostic(Diagnostic.Create(diagnosticDescriptor, Location.None, DiagnosticSeverity.Error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static string ProcessClass(INamedTypeSymbol typeSymbol, List<IFieldSymbol> fieldSymbols)
|
||||||
|
{
|
||||||
|
var onAfterDeserializeCodeBuilder = new StringBuilder();
|
||||||
|
var onBeforeSerializeCodeBuilder = new StringBuilder();
|
||||||
|
var serializationDataCodeBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
var hasShowSerializationData = typeSymbol.GetAttributes().Any(x => x.AttributeClass.Name
|
||||||
|
is "ShowAlchemySerializationData"
|
||||||
|
or "ShowAlchemySerializationDataAttribute"
|
||||||
|
or "Alchemy.Serialization.ShowAlchemySerializationData"
|
||||||
|
or "Alchemy.Serialization.ShowAlchemySerializationDataAttribute");
|
||||||
|
|
||||||
|
var serializationDataAttibutesCode = hasShowSerializationData ? "[global::Alchemy.Inspector.ReadOnly, global::UnityEngine.TextArea(3, 999), global::UnityEngine.SerializeField]" : "[global::UnityEngine.HideInInspector, global::UnityEngine.SerializeField]";
|
||||||
|
|
||||||
|
// target class namespace
|
||||||
|
var ns = typeSymbol.ContainingNamespace.IsGlobalNamespace ? string.Empty : $"namespace {typeSymbol.ContainingNamespace} {{";
|
||||||
|
|
||||||
|
foreach (var field in fieldSymbols)
|
||||||
|
{
|
||||||
|
var serializeCode =
|
||||||
|
@$"try
|
||||||
|
{{
|
||||||
|
alchemySerializationData.{field.Name}.data = global::Alchemy.Serialization.Internal.SerializationHelper.ToJson(this.{field.Name} , alchemySerializationData.UnityObjectReferences);
|
||||||
|
alchemySerializationData.{field.Name}.isCreated = true;
|
||||||
|
}}
|
||||||
|
catch (global::System.Exception ex)
|
||||||
|
{{
|
||||||
|
global::UnityEngine.Debug.LogException(ex);
|
||||||
|
}}";
|
||||||
|
|
||||||
|
var deserializeCode =
|
||||||
|
@$"try
|
||||||
|
{{
|
||||||
|
if (alchemySerializationData.{field.Name}.isCreated)
|
||||||
|
{{
|
||||||
|
this.{field.Name} = global::Alchemy.Serialization.Internal.SerializationHelper.FromJson<{field.Type.ToDisplayString()}>(alchemySerializationData.{field.Name}.data, alchemySerializationData.UnityObjectReferences);
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
catch (global::System.Exception ex)
|
||||||
|
{{
|
||||||
|
global::UnityEngine.Debug.LogException(ex);
|
||||||
|
}}";
|
||||||
|
|
||||||
|
onBeforeSerializeCodeBuilder.AppendLine(serializeCode);
|
||||||
|
onAfterDeserializeCodeBuilder.AppendLine(deserializeCode);
|
||||||
|
|
||||||
|
serializationDataCodeBuilder.Append("public Item ").Append(field.Name).Append(" = new();");
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
@$"
|
||||||
|
// <auto-generated/>
|
||||||
|
{ns}
|
||||||
|
|
||||||
|
partial class {typeSymbol.Name} : global::UnityEngine.ISerializationCallbackReceiver
|
||||||
|
{{
|
||||||
|
void global::UnityEngine.ISerializationCallbackReceiver.OnAfterDeserialize()
|
||||||
|
{{
|
||||||
|
{onAfterDeserializeCodeBuilder}
|
||||||
|
if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnAfterDeserialize();
|
||||||
|
}}
|
||||||
|
|
||||||
|
void global::UnityEngine.ISerializationCallbackReceiver.OnBeforeSerialize()
|
||||||
|
{{
|
||||||
|
if (this is global::Alchemy.Serialization.IAlchemySerializationCallbackReceiver receiver) receiver.OnBeforeSerialize();
|
||||||
|
alchemySerializationData.UnityObjectReferences.Clear();
|
||||||
|
{onBeforeSerializeCodeBuilder}
|
||||||
|
}}
|
||||||
|
|
||||||
|
[global::System.Serializable]
|
||||||
|
sealed class AlchemySerializationData
|
||||||
|
{{
|
||||||
|
{serializationDataCodeBuilder}
|
||||||
|
|
||||||
|
[global::UnityEngine.SerializeField] private global::System.Collections.Generic.List<UnityEngine.Object> unityObjectReferences = new();
|
||||||
|
public global::System.Collections.Generic.IList<UnityEngine.Object> UnityObjectReferences => unityObjectReferences;
|
||||||
|
|
||||||
|
[global::System.Serializable]
|
||||||
|
public sealed class Item
|
||||||
|
{{
|
||||||
|
[global::UnityEngine.HideInInspector] public bool isCreated = false;
|
||||||
|
[global::UnityEngine.TextArea(1, 999)] public string data;
|
||||||
|
}}
|
||||||
|
}}
|
||||||
|
|
||||||
|
{serializationDataAttibutesCode} private AlchemySerializationData alchemySerializationData = new();
|
||||||
|
}}
|
||||||
|
|
||||||
|
{(string.IsNullOrEmpty(ns) ? "" : "}")}
|
||||||
|
";
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class SyntaxReceiver : ISyntaxReceiver
|
||||||
|
{
|
||||||
|
internal static ISyntaxReceiver Create()
|
||||||
|
{
|
||||||
|
return new SyntaxReceiver();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TypeDeclarationSyntax> TargetTypes { get; } = new();
|
||||||
|
|
||||||
|
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||||
|
{
|
||||||
|
if (syntaxNode is TypeDeclarationSyntax typeDeclarationSyntax)
|
||||||
|
{
|
||||||
|
var hasAttribute = typeDeclarationSyntax.AttributeLists
|
||||||
|
.SelectMany(x => x.Attributes)
|
||||||
|
.Any(x => x.Name.ToString()
|
||||||
|
is "AlchemySerialize"
|
||||||
|
or "AlchemySerializeAttribute"
|
||||||
|
or "Alchemy.Serialization.AlchemySerialize"
|
||||||
|
or "Alchemy.Serialization.AlchemySerializeAttribute");
|
||||||
|
if (hasAttribute)
|
||||||
|
{
|
||||||
|
TargetTypes.Add(typeDeclarationSyntax);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
Alchemy.SourceGenerator/DiagnosticDescriptors.cs
Normal file
4
Alchemy.SourceGenerator/DiagnosticDescriptors.cs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
namespace Alchemy.SourceGenerator
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
4
Alchemy/.editorconfig
Normal file
4
Alchemy/.editorconfig
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[*.cs]
|
||||||
|
|
||||||
|
# IDE0027: アクセサーに式本体を使用する
|
||||||
|
csharp_style_expression_bodied_accessors = false
|
78
Alchemy/.gitignore
vendored
Normal file
78
Alchemy/.gitignore
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# This .gitignore file should be placed at the root of your Unity project directory
|
||||||
|
#
|
||||||
|
# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore
|
||||||
|
#
|
||||||
|
/[Ll]ibrary/
|
||||||
|
/[Tt]emp/
|
||||||
|
/[Oo]bj/
|
||||||
|
/[Bb]uild/
|
||||||
|
/[Bb]uilds/
|
||||||
|
/[Ll]ogs/
|
||||||
|
/[Uu]ser[Ss]ettings/
|
||||||
|
|
||||||
|
# MemoryCaptures can get excessive in size.
|
||||||
|
# They also could contain extremely sensitive data
|
||||||
|
/[Mm]emoryCaptures/
|
||||||
|
|
||||||
|
# Recordings can get excessive in size
|
||||||
|
/[Rr]ecordings/
|
||||||
|
|
||||||
|
# Uncomment this line if you wish to ignore the asset store tools plugin
|
||||||
|
# /[Aa]ssets/AssetStoreTools*
|
||||||
|
|
||||||
|
# Autogenerated Jetbrains Rider plugin
|
||||||
|
/[Aa]ssets/Plugins/Editor/JetBrains*
|
||||||
|
|
||||||
|
# Visual Studio cache directory
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
# Gradle cache directory
|
||||||
|
.gradle/
|
||||||
|
|
||||||
|
# Autogenerated VS/MD/Consulo solution and project files
|
||||||
|
ExportedObj/
|
||||||
|
.consulo/
|
||||||
|
*.csproj
|
||||||
|
*.unityproj
|
||||||
|
*.sln
|
||||||
|
*.suo
|
||||||
|
*.tmp
|
||||||
|
*.user
|
||||||
|
*.userprefs
|
||||||
|
*.pidb
|
||||||
|
*.booproj
|
||||||
|
*.svd
|
||||||
|
*.pdb
|
||||||
|
*.mdb
|
||||||
|
*.opendb
|
||||||
|
*.VC.db
|
||||||
|
|
||||||
|
# Unity3D generated meta files
|
||||||
|
*.pidb.meta
|
||||||
|
*.pdb.meta
|
||||||
|
*.mdb.meta
|
||||||
|
|
||||||
|
# Unity3D generated file on crash reports
|
||||||
|
sysinfo.txt
|
||||||
|
|
||||||
|
# Builds
|
||||||
|
*.apk
|
||||||
|
*.aab
|
||||||
|
*.unitypackage
|
||||||
|
*.app
|
||||||
|
|
||||||
|
# Crashlytics generated file
|
||||||
|
crashlytics-build.properties
|
||||||
|
|
||||||
|
# Packed Addressables
|
||||||
|
/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin*
|
||||||
|
|
||||||
|
# Temporary auto-generated Android Assets
|
||||||
|
/[Aa]ssets/[Ss]treamingAssets/aa.meta
|
||||||
|
/[Aa]ssets/[Ss]treamingAssets/aa/*
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
## DS_Store
|
||||||
|
*.DS_Store
|
8
Alchemy/Assets/Alchemy.meta
Normal file
8
Alchemy/Assets/Alchemy.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4def02a2f2f60434e82ef3fd321ee6be
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Editor.meta
Normal file
8
Alchemy/Assets/Alchemy/Editor.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 285cbd8c7df70446c901672504e736c2
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
25
Alchemy/Assets/Alchemy/Editor/Alchemy.Editor.asmdef
Normal file
25
Alchemy/Assets/Alchemy/Editor/Alchemy.Editor.asmdef
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "Alchemy.Editor",
|
||||||
|
"rootNamespace": "Alchemy.Editor",
|
||||||
|
"references": [
|
||||||
|
"GUID:88be65f96b86746888c927a5c8ff3534",
|
||||||
|
"GUID:2765e68924a08a94ea0ea66b31c0168f"
|
||||||
|
],
|
||||||
|
"includePlatforms": [
|
||||||
|
"Editor"
|
||||||
|
],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [
|
||||||
|
{
|
||||||
|
"name": "com.unity.serialization",
|
||||||
|
"expression": "",
|
||||||
|
"define": "ALCHEMY_SUPPORT_SERIALIZATION"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
7
Alchemy/Assets/Alchemy/Editor/Alchemy.Editor.asmdef.meta
Normal file
7
Alchemy/Assets/Alchemy/Editor/Alchemy.Editor.asmdef.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5fd453cd0d182422093c4a764fd5eadb
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
71
Alchemy/Assets/Alchemy/Editor/AlchemyEditor.cs
Normal file
71
Alchemy/Assets/Alchemy/Editor/AlchemyEditor.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
using Alchemy.Serialization;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Alchemy.Editor
|
||||||
|
{
|
||||||
|
using Editor = UnityEditor.Editor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Editor base class for Inspector drawing in Alchemy
|
||||||
|
/// </summary>
|
||||||
|
public abstract class AlchemyEditor : Editor
|
||||||
|
{
|
||||||
|
const string ScriptFieldName = "m_Script";
|
||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
const string AlchemySerializationWarning = "In the current version, fields with the [AlchemySerializedField] attribute do not support editing multiple objects.";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public override VisualElement CreateInspectorGUI()
|
||||||
|
{
|
||||||
|
var root = new VisualElement();
|
||||||
|
var targetType = target.GetType();
|
||||||
|
|
||||||
|
if (targetType.HasCustomAttribute<DisableAlchemyEditorAttribute>())
|
||||||
|
{
|
||||||
|
// Create default inspector
|
||||||
|
InspectorElement.FillDefaultInspector(root, serializedObject, this);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
if (targetType.HasCustomAttribute<AlchemySerializeAttribute>() && targets.Length > 1)
|
||||||
|
{
|
||||||
|
root.Add(new HelpBox(AlchemySerializationWarning, HelpBoxMessageType.Error));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Add script field
|
||||||
|
if (targetType.GetCustomAttribute<HideScriptFieldAttribute>() == null)
|
||||||
|
{
|
||||||
|
var scriptField = new PropertyField(serializedObject.FindProperty(ScriptFieldName));
|
||||||
|
scriptField.SetEnabled(false);
|
||||||
|
root.Add(scriptField);
|
||||||
|
root.Add(new VisualElement()
|
||||||
|
{
|
||||||
|
style = { height = EditorGUIUtility.standardVerticalSpacing * 0.5f }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add elements
|
||||||
|
InspectorHelper.BuildElements(serializedObject, root, target, name => serializedObject.FindProperty(name), 0);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomEditor(typeof(MonoBehaviour), editorForChildClasses: true, isFallback = true)]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
|
internal sealed class MonoBehaviourEditor : AlchemyEditor { }
|
||||||
|
|
||||||
|
[CustomEditor(typeof(ScriptableObject), editorForChildClasses: true, isFallback = true)]
|
||||||
|
[CanEditMultipleObjects]
|
||||||
|
internal sealed class ScriptableObjectEditor : AlchemyEditor { }
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/AlchemyEditor.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/AlchemyEditor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 09b0ba97e97f24db4a0dce5a1e942810
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
222
Alchemy/Assets/Alchemy/Editor/BuiltinPropertyGroupDrawers.cs
Normal file
222
Alchemy/Assets/Alchemy/Editor/BuiltinPropertyGroupDrawers.cs
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
using Alchemy.Editor.Elements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.GroupDrawers
|
||||||
|
{
|
||||||
|
[CustomPropertyGroupDrawer(typeof(GroupAttribute))]
|
||||||
|
public sealed class GroupDrawer : PropertyGroupDrawer
|
||||||
|
{
|
||||||
|
public override VisualElement CreateRootElement(string label)
|
||||||
|
{
|
||||||
|
return new Box()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = Length.Percent(100f),
|
||||||
|
marginTop = 3f,
|
||||||
|
paddingBottom = 2f,
|
||||||
|
paddingRight = 1f,
|
||||||
|
paddingLeft = 1f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyGroupDrawer(typeof(BoxGroupAttribute))]
|
||||||
|
public sealed class BoxGroupDrawer : PropertyGroupDrawer
|
||||||
|
{
|
||||||
|
public override VisualElement CreateRootElement(string label)
|
||||||
|
{
|
||||||
|
var helpBox = new HelpBox()
|
||||||
|
{
|
||||||
|
text = label,
|
||||||
|
style = {
|
||||||
|
flexDirection = FlexDirection.Column,
|
||||||
|
width = Length.Percent(100f),
|
||||||
|
marginTop = 3f,
|
||||||
|
paddingBottom = 3f,
|
||||||
|
paddingRight = 3f,
|
||||||
|
paddingLeft = 3f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var labelElement = helpBox.Q<Label>();
|
||||||
|
labelElement.style.top = 2f;
|
||||||
|
labelElement.style.left = 2f;
|
||||||
|
labelElement.style.fontSize = 12f;
|
||||||
|
labelElement.style.minHeight = EditorGUIUtility.singleLineHeight;
|
||||||
|
labelElement.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
labelElement.style.alignSelf = Align.Stretch;
|
||||||
|
|
||||||
|
return helpBox;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyGroupDrawer(typeof(TabGroupAttribute))]
|
||||||
|
public sealed class TabGroupDrawer : PropertyGroupDrawer
|
||||||
|
{
|
||||||
|
VisualElement rootElement;
|
||||||
|
readonly Dictionary<string, VisualElement> tabElements = new();
|
||||||
|
|
||||||
|
string[] keyArrayCache = new string[0];
|
||||||
|
int tabIndex;
|
||||||
|
int prevTabIndex;
|
||||||
|
|
||||||
|
sealed class TabItem
|
||||||
|
{
|
||||||
|
public string name;
|
||||||
|
public VisualElement element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override VisualElement CreateRootElement(string label)
|
||||||
|
{
|
||||||
|
var configKey = UniqueId + "_TabGroup";
|
||||||
|
int.TryParse(EditorUserSettings.GetConfigValue(configKey), out tabIndex);
|
||||||
|
prevTabIndex = tabIndex;
|
||||||
|
|
||||||
|
rootElement = new HelpBox()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
flexDirection = FlexDirection.Column,
|
||||||
|
width = Length.Percent(100f),
|
||||||
|
marginTop = 3f,
|
||||||
|
paddingBottom = 3f,
|
||||||
|
paddingRight = 3f,
|
||||||
|
paddingLeft = 3f,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rootElement.Remove(rootElement.Q<Label>());
|
||||||
|
|
||||||
|
var tabGUIElement = new IMGUIContainer(() =>
|
||||||
|
{
|
||||||
|
var rect = EditorGUILayout.GetControlRect();
|
||||||
|
rect.xMin -= 3.7f;
|
||||||
|
rect.xMax += 3.7f;
|
||||||
|
rect.yMin -= 3.7f;
|
||||||
|
rect.yMax -= 1f;
|
||||||
|
tabIndex = GUI.Toolbar(rect, tabIndex, keyArrayCache);
|
||||||
|
if (tabIndex != prevTabIndex)
|
||||||
|
{
|
||||||
|
EditorUserSettings.SetConfigValue(configKey, tabIndex.ToString());
|
||||||
|
prevTabIndex = tabIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var kv in tabElements)
|
||||||
|
{
|
||||||
|
kv.Value.style.display = keyArrayCache[tabIndex] == kv.Key ? DisplayStyle.Flex : DisplayStyle.None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = Length.Percent(100f),
|
||||||
|
marginLeft = 0f,
|
||||||
|
marginRight = 0f,
|
||||||
|
marginTop = 0f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rootElement.Add(tabGUIElement);
|
||||||
|
|
||||||
|
return rootElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override VisualElement GetGroupElement(Attribute attribute)
|
||||||
|
{
|
||||||
|
var tabGroupAttribute = (TabGroupAttribute)attribute;
|
||||||
|
|
||||||
|
var tabName = tabGroupAttribute.TabName;
|
||||||
|
if (!tabElements.TryGetValue(tabName, out var element))
|
||||||
|
{
|
||||||
|
element = new VisualElement()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = Length.Percent(100f)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
rootElement.Add(element);
|
||||||
|
tabElements.Add(tabName, element);
|
||||||
|
|
||||||
|
keyArrayCache = tabElements.Keys.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyGroupDrawer(typeof(FoldoutGroupAttribute))]
|
||||||
|
public sealed class FoldoutGroupDrawer : PropertyGroupDrawer
|
||||||
|
{
|
||||||
|
public override VisualElement CreateRootElement(string label)
|
||||||
|
{
|
||||||
|
var configKey = UniqueId + "_FoldoutGroup";
|
||||||
|
bool.TryParse(EditorUserSettings.GetConfigValue(configKey), out var result);
|
||||||
|
|
||||||
|
var foldout = new Foldout()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = Length.Percent(100f)
|
||||||
|
},
|
||||||
|
text = label,
|
||||||
|
value = result
|
||||||
|
};
|
||||||
|
|
||||||
|
foldout.RegisterValueChangedCallback(x =>
|
||||||
|
{
|
||||||
|
EditorUserSettings.SetConfigValue(configKey, x.newValue.ToString());
|
||||||
|
});
|
||||||
|
|
||||||
|
return foldout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyGroupDrawer(typeof(HorizontalGroupAttribute))]
|
||||||
|
public sealed class HorizontalGroupDrawer : PropertyGroupDrawer
|
||||||
|
{
|
||||||
|
public override VisualElement CreateRootElement(string label)
|
||||||
|
{
|
||||||
|
var root = new VisualElement()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = Length.Percent(100f),
|
||||||
|
flexDirection = FlexDirection.Row
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void AdjustLabel(PropertyField element, VisualElement inspector, int childCount)
|
||||||
|
{
|
||||||
|
if (element.childCount == 0) return;
|
||||||
|
if (element.Q<Foldout>() != null) return;
|
||||||
|
|
||||||
|
var field = element[0];
|
||||||
|
field.RemoveFromClassList("unity-base-field__aligned");
|
||||||
|
|
||||||
|
var labelElement = field.Q<Label>();
|
||||||
|
labelElement.style.minWidth = 0f;
|
||||||
|
labelElement.style.width = GUIHelper.CalculateLabelWidth(element, inspector) * 0.8f / childCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.schedule.Execute(() =>
|
||||||
|
{
|
||||||
|
if (root.childCount <= 1) return;
|
||||||
|
|
||||||
|
var inspector = root.GetFirstAncestorOfType<InspectorElement>();
|
||||||
|
foreach (var field in root.Query<PropertyField>().Build())
|
||||||
|
{
|
||||||
|
AdjustLabel(field, inspector, root.childCount);
|
||||||
|
}
|
||||||
|
foreach (var field in root.Query<GenericField>().Children<PropertyField>().Build())
|
||||||
|
{
|
||||||
|
AdjustLabel(field, inspector, root.childCount);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fa0ce00655a224efd9ed552633ccd715
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
306
Alchemy/Assets/Alchemy/Editor/BuiltinPropertyProcessors.cs
Normal file
306
Alchemy/Assets/Alchemy/Editor/BuiltinPropertyProcessors.cs
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
using Alchemy.Editor.Elements;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Processors
|
||||||
|
{
|
||||||
|
[CustomPropertyProcessor(typeof(ReadOnlyAttribute))]
|
||||||
|
public sealed class ReadOnlyProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
Element.SetEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(IndentAttribute))]
|
||||||
|
public sealed class IndentProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
const float IndentPadding = 15f;
|
||||||
|
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
Element.RegisterCallback<GeometryChangedEvent>(x => AddPadding());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddPadding()
|
||||||
|
{
|
||||||
|
var label = Element.Q<Label>();
|
||||||
|
if (label == null) return;
|
||||||
|
label.style.paddingLeft = ((IndentAttribute)Attribute).indent * IndentPadding;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(HideInPlayModeAttribute))]
|
||||||
|
public sealed class HideInPlayModeProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
Element.style.display = Application.isPlaying ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(HideInEditModeAttribute))]
|
||||||
|
public sealed class HideInEditModeProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
Element.style.display = !Application.isPlaying ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(DisableInPlayModeAttribute))]
|
||||||
|
public sealed class DisableInPlayModeProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
if (Application.isPlaying) Element.SetEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(DisableInEditModeAttribute))]
|
||||||
|
public sealed class DisableInEditModeProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
if (!Application.isPlaying) Element.SetEnabled(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(HideLabelAttribute))]
|
||||||
|
public sealed class HideLabelProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
if (Element is AlchemyPropertyField field)
|
||||||
|
{
|
||||||
|
field.Label = string.Empty;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var labelElement = Element.Q<Label>();
|
||||||
|
if (labelElement == null) return;
|
||||||
|
labelElement.text = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(LabelTextAttribute))]
|
||||||
|
public sealed class LabelTextProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
var labelTextAttribute = (LabelTextAttribute)Attribute;
|
||||||
|
|
||||||
|
switch (Element)
|
||||||
|
{
|
||||||
|
case AlchemyPropertyField alchemyPropertyField:
|
||||||
|
alchemyPropertyField.Label = labelTextAttribute.Text;
|
||||||
|
break;
|
||||||
|
case Button button:
|
||||||
|
button.text = labelTextAttribute.Text;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
var labelElement = Element.Q<Label>();
|
||||||
|
if (labelElement == null) return;
|
||||||
|
labelElement.text = labelElement.text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(HideIfAttribute))]
|
||||||
|
public sealed class HideIfProcessor : TrackSerializedObjectPropertyProcessor
|
||||||
|
{
|
||||||
|
protected override void OnInspectorChanged()
|
||||||
|
{
|
||||||
|
var condition = ReflectionHelper.GetValueBool(Target, ((HideIfAttribute)Attribute).Condition);
|
||||||
|
Element.style.display = condition ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(ShowIfAttribute))]
|
||||||
|
public sealed class ShowIfProcessor : TrackSerializedObjectPropertyProcessor
|
||||||
|
{
|
||||||
|
protected override void OnInspectorChanged()
|
||||||
|
{
|
||||||
|
var condition = ReflectionHelper.GetValueBool(Target, ((ShowIfAttribute)Attribute).Condition);
|
||||||
|
Element.style.display = !condition ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(DisableIfAttribute))]
|
||||||
|
public sealed class DisableIfProcessor : TrackSerializedObjectPropertyProcessor
|
||||||
|
{
|
||||||
|
protected override void OnInspectorChanged()
|
||||||
|
{
|
||||||
|
var condition = ReflectionHelper.GetValueBool(Target, ((DisableIfAttribute)Attribute).Condition);
|
||||||
|
Element.SetEnabled(!condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(EnableIfAttribute))]
|
||||||
|
public sealed class EnableIfProcessor : TrackSerializedObjectPropertyProcessor
|
||||||
|
{
|
||||||
|
protected override void OnInspectorChanged()
|
||||||
|
{
|
||||||
|
var condition = ReflectionHelper.GetValueBool(Target, ((EnableIfAttribute)Attribute).Condition);
|
||||||
|
Element.SetEnabled(condition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(RequiredAttribute))]
|
||||||
|
public sealed class RequiredProcessor : TrackSerializedObjectPropertyProcessor
|
||||||
|
{
|
||||||
|
HelpBox helpBox;
|
||||||
|
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
if (SerializedProperty.propertyType != SerializedPropertyType.ObjectReference) return;
|
||||||
|
|
||||||
|
var message = ((RequiredAttribute)Attribute).Message ?? ObjectNames.NicifyVariableName(SerializedProperty.displayName) + " is required.";
|
||||||
|
helpBox = new HelpBox(message, HelpBoxMessageType.Error);
|
||||||
|
|
||||||
|
var parent = Element.parent;
|
||||||
|
parent.Insert(parent.IndexOf(Element), helpBox);
|
||||||
|
|
||||||
|
base.Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInspectorChanged()
|
||||||
|
{
|
||||||
|
helpBox.style.display = SerializedProperty.objectReferenceValue != null ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(ValidateInputAttribute))]
|
||||||
|
public sealed class ValidateInputProcessor : TrackSerializedObjectPropertyProcessor
|
||||||
|
{
|
||||||
|
HelpBox helpBox;
|
||||||
|
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
var message = ((ValidateInputAttribute)Attribute).Message ?? ObjectNames.NicifyVariableName(SerializedProperty.displayName) + " is not valid.";
|
||||||
|
helpBox = new HelpBox(message, HelpBoxMessageType.Error);
|
||||||
|
|
||||||
|
var parent = Element.parent;
|
||||||
|
parent.Insert(parent.IndexOf(Element), helpBox);
|
||||||
|
|
||||||
|
base.Execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInspectorChanged()
|
||||||
|
{
|
||||||
|
var result = ReflectionHelper.Invoke(Target, ((ValidateInputAttribute)Attribute).Condition, SerializedProperty.GetValue<object>());
|
||||||
|
helpBox.style.display = result is bool flag && flag ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(HelpBoxAttribute))]
|
||||||
|
public sealed class HelpBoxProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
HelpBox helpBox;
|
||||||
|
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
var att = (HelpBoxAttribute)Attribute;
|
||||||
|
helpBox = new HelpBox(att.Message, att.MessageType);
|
||||||
|
|
||||||
|
var parent = Element.parent;
|
||||||
|
parent.Insert(parent.IndexOf(Element), helpBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(HorizontalLineAttribute))]
|
||||||
|
public sealed class HorizontalLineProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
var att = (HorizontalLineAttribute)Attribute;
|
||||||
|
var parent = Element.parent;
|
||||||
|
var line = GUIHelper.CreateLine(att.Color, EditorGUIUtility.standardVerticalSpacing * 4f);
|
||||||
|
parent.Insert(parent.IndexOf(Element), line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(TitleAttribute))]
|
||||||
|
public sealed class TitleProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
var att = (TitleAttribute)Attribute;
|
||||||
|
var parent = Element.parent;
|
||||||
|
|
||||||
|
var title = new Label(att.TitleText)
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold,
|
||||||
|
paddingLeft = 3f,
|
||||||
|
marginTop = 4f,
|
||||||
|
marginBottom = -2f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
parent.Insert(parent.IndexOf(Element), title);
|
||||||
|
|
||||||
|
if (att.SubitleText != null)
|
||||||
|
{
|
||||||
|
var subtitle = new Label(att.SubitleText)
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
fontSize = 10f,
|
||||||
|
paddingLeft = 4.5f,
|
||||||
|
marginTop = 1.5f,
|
||||||
|
color = GUIHelper.SubtitleColor,
|
||||||
|
unityTextAlign = TextAnchor.MiddleLeft
|
||||||
|
}
|
||||||
|
};
|
||||||
|
parent.Insert(parent.IndexOf(Element), subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
var line = GUIHelper.CreateLine(GUIHelper.LineColor, EditorGUIUtility.standardVerticalSpacing * 3f);
|
||||||
|
parent.Insert(parent.IndexOf(Element), line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CustomPropertyProcessor(typeof(BlockquoteAttribute))]
|
||||||
|
public sealed class BlockquoteProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public BlockquoteProcessor()
|
||||||
|
{
|
||||||
|
textStyle = EditorStyles.label;
|
||||||
|
textStyle.wordWrap = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly GUIStyle textStyle;
|
||||||
|
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
var att = (BlockquoteAttribute)Attribute;
|
||||||
|
var blockquote = new IMGUIContainer(() =>
|
||||||
|
{
|
||||||
|
var width = EditorGUIUtility.currentViewWidth;
|
||||||
|
var labelContent = new GUIContent(att.Text);
|
||||||
|
var labelHeight = textStyle.CalcHeight(labelContent, width - 3f);
|
||||||
|
var position = EditorGUILayout.GetControlRect(false, labelHeight + EditorGUIUtility.standardVerticalSpacing * 2f);
|
||||||
|
|
||||||
|
var blockRect = position;
|
||||||
|
var backgroundColor = GUIHelper.TextColor;
|
||||||
|
backgroundColor.a = 0.06f;
|
||||||
|
EditorGUI.DrawRect(blockRect, backgroundColor);
|
||||||
|
blockRect.x = position.xMin;
|
||||||
|
blockRect.width = 3;
|
||||||
|
EditorGUI.DrawRect(blockRect, GUIHelper.TextColor);
|
||||||
|
|
||||||
|
var labelPosition = position;
|
||||||
|
labelPosition.xMin += 7f;
|
||||||
|
EditorGUI.LabelField(labelPosition, labelContent, textStyle);
|
||||||
|
});
|
||||||
|
|
||||||
|
var parent = Element.parent;
|
||||||
|
parent.Insert(parent.IndexOf(Element), blockquote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 82c33c1d3a21f4e9a9e57fa46d4900aa
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor
|
||||||
|
{
|
||||||
|
public sealed class CustomPropertyGroupDrawerAttribute : Attribute
|
||||||
|
{
|
||||||
|
public CustomPropertyGroupDrawerAttribute(Type targetAttributeType)
|
||||||
|
{
|
||||||
|
this.targetAttributeType = targetAttributeType;
|
||||||
|
}
|
||||||
|
public readonly Type targetAttributeType;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f0c6d37c5a83b4eef8f5359c67329e56
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,16 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor
|
||||||
|
{
|
||||||
|
public sealed class CustomPropertyProcessorAttribute : Attribute
|
||||||
|
{
|
||||||
|
public CustomPropertyProcessorAttribute(Type targetAttributeType, int order = 0)
|
||||||
|
{
|
||||||
|
this.targetAttributeType = targetAttributeType;
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Type targetAttributeType;
|
||||||
|
public readonly int order;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e5369cccdbb3a47c0ac7e5fcff993276
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Editor/Elements.meta
Normal file
8
Alchemy/Assets/Alchemy/Editor/Elements.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4dc283a9b881942e6bcea33ae81ca853
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,94 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visual Element that draws properties based on Alchemy attributes
|
||||||
|
/// </summary>
|
||||||
|
public sealed class AlchemyPropertyField : BindableElement
|
||||||
|
{
|
||||||
|
public AlchemyPropertyField(SerializedProperty property, int depth)
|
||||||
|
{
|
||||||
|
if (depth > 20) return;
|
||||||
|
var labelText = ObjectNames.NicifyVariableName(property.displayName);
|
||||||
|
|
||||||
|
switch (property.propertyType)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
element = new PropertyField(property);
|
||||||
|
break;
|
||||||
|
case SerializedPropertyType.ObjectReference:
|
||||||
|
if (property.GetAttribute<InlineEditorAttribute>() != null)
|
||||||
|
{
|
||||||
|
element = new InlineEditorObjectField(property, depth);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element = GUIHelper.CreateObjectField(property);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SerializedPropertyType.Generic:
|
||||||
|
if (property.isArray)
|
||||||
|
{
|
||||||
|
element = new PropertyListView(property, depth);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var targetType = property.GetPropertyType();
|
||||||
|
var foldout = new Foldout() { text = labelText };
|
||||||
|
|
||||||
|
var clickable = InternalAPIHelper.GetClickable(foldout.Q<Toggle>());
|
||||||
|
InternalAPIHelper.SetAcceptClicksIfDisabled(clickable, true);
|
||||||
|
InspectorHelper.BuildElements(property.serializedObject, foldout, property.GetValue<object>(), name => property.FindPropertyRelative(name), depth + 1);
|
||||||
|
foldout.BindProperty(property);
|
||||||
|
element = foldout;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SerializedPropertyType.ManagedReference:
|
||||||
|
element = new SerializeReferenceField(property, depth);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly VisualElement element;
|
||||||
|
|
||||||
|
public string Label
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return element switch
|
||||||
|
{
|
||||||
|
Foldout foldout => foldout.text,
|
||||||
|
PropertyField propertyField => propertyField.label,
|
||||||
|
SerializeReferenceField serializeReferenceField => serializeReferenceField.foldout.text,
|
||||||
|
InlineEditorObjectField inlineEditorObjectField => inlineEditorObjectField.Label,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
switch (element)
|
||||||
|
{
|
||||||
|
case Foldout foldout:
|
||||||
|
foldout.text = value;
|
||||||
|
break;
|
||||||
|
case PropertyField propertyField:
|
||||||
|
propertyField.label = value;
|
||||||
|
break;
|
||||||
|
case SerializeReferenceField serializeReferenceField:
|
||||||
|
serializeReferenceField.foldout.text = value;
|
||||||
|
break;
|
||||||
|
case InlineEditorObjectField inlineEditorObjectField:
|
||||||
|
inlineEditorObjectField.Label = value;
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 90a3b3c6a5e434218b95ca18fd032a79
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
68
Alchemy/Assets/Alchemy/Editor/Elements/ClassField.cs
Normal file
68
Alchemy/Assets/Alchemy/Editor/Elements/ClassField.cs
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
public sealed class ClassField : VisualElement
|
||||||
|
{
|
||||||
|
public ClassField(Type type, string label, int depth) : this(TypeHelper.CreateDefaultInstance(type), type, label, depth) { }
|
||||||
|
public ClassField(object obj, Type type, string label, int depth)
|
||||||
|
{
|
||||||
|
if (depth > InspectorHelper.MaxDepth) return;
|
||||||
|
|
||||||
|
var foldout = new Foldout
|
||||||
|
{
|
||||||
|
text = label,
|
||||||
|
value = false
|
||||||
|
};
|
||||||
|
|
||||||
|
var toggle = foldout.Q<Toggle>();
|
||||||
|
var clickable = InternalAPIHelper.GetClickable(toggle);
|
||||||
|
InternalAPIHelper.SetAcceptClicksIfDisabled(clickable, true);
|
||||||
|
|
||||||
|
// Build node
|
||||||
|
var rootNode = InspectorHelper.BuildInspectorNode(type);
|
||||||
|
|
||||||
|
// Add elements
|
||||||
|
foreach (var node in rootNode.DescendantsAndSelf())
|
||||||
|
{
|
||||||
|
// Get or create group element
|
||||||
|
if (node.Parent == null)
|
||||||
|
{
|
||||||
|
node.VisualElement = foldout;
|
||||||
|
}
|
||||||
|
else if (node.Drawer == null)
|
||||||
|
{
|
||||||
|
node.VisualElement = node.Parent.VisualElement;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.VisualElement = node.Drawer.CreateRootElement(node.Name);
|
||||||
|
node.Parent.VisualElement.Add(node.VisualElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add member elements
|
||||||
|
foreach (var member in node.Members.OrderByAttributeThenByMemberType())
|
||||||
|
{
|
||||||
|
var element = new ReflectionField(obj, member, depth + 1);
|
||||||
|
element.style.width = Length.Percent(100f);
|
||||||
|
element.OnValueChanged += x => OnValueChanged?.Invoke(obj);
|
||||||
|
|
||||||
|
var e = node.Drawer?.GetGroupElement(member.GetCustomAttribute<PropertyGroupAttribute>());
|
||||||
|
if (e == null) node.VisualElement.Add(element);
|
||||||
|
else e.Add(element);
|
||||||
|
PropertyProcessor.ExecuteProcessors(null, null, obj, member, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(foldout);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<object> OnValueChanged;
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Elements/ClassField.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Elements/ClassField.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6b645f1746ed04e3dbb48f5e2568f401
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
165
Alchemy/Assets/Alchemy/Editor/Elements/DictionaryField.cs
Normal file
165
Alchemy/Assets/Alchemy/Editor/Elements/DictionaryField.cs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
public sealed class DictionaryField : HashMapFieldBase
|
||||||
|
{
|
||||||
|
public DictionaryField(object collection, string label, int depth) : base(collection, label, depth)
|
||||||
|
{
|
||||||
|
if (collection != null)
|
||||||
|
{
|
||||||
|
keyType = collection.GetType().GenericTypeArguments[0];
|
||||||
|
valueType = collection.GetType().GenericTypeArguments[1];
|
||||||
|
kvType = typeof(KeyValuePair<,>).MakeGenericType(keyType, valueType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string CollectionTypeName => TypeName;
|
||||||
|
|
||||||
|
const string TypeName = "Dictionary";
|
||||||
|
const string KeyName = "Key";
|
||||||
|
const string ValueName = "Value";
|
||||||
|
|
||||||
|
readonly Type keyType;
|
||||||
|
readonly Type valueType;
|
||||||
|
readonly Type kvType;
|
||||||
|
|
||||||
|
public override bool CheckElement(object element)
|
||||||
|
{
|
||||||
|
var keyObj = ReflectionHelper.GetPropertyValue(element, element.GetType(), KeyName);
|
||||||
|
if (keyObj is string str && string.IsNullOrEmpty(str)) return true;
|
||||||
|
if (keyObj == null) return true;
|
||||||
|
return (bool)ReflectionHelper.Invoke(Collection, "ContainsKey", keyObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object CreateElement()
|
||||||
|
{
|
||||||
|
var keyObj = TypeHelper.CreateDefaultInstance(keyType);
|
||||||
|
var valueObj = TypeHelper.CreateDefaultInstance(valueType);
|
||||||
|
return Activator.CreateInstance(kvType, keyObj, valueObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddElement(object element)
|
||||||
|
{
|
||||||
|
var keyObj = ReflectionHelper.GetPropertyValue(element, kvType, KeyName);
|
||||||
|
var valueObj = ReflectionHelper.GetPropertyValue(element, kvType, ValueName);
|
||||||
|
ReflectionHelper.Invoke(Collection, "Add", keyObj, valueObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool RemoveElement(object element)
|
||||||
|
{
|
||||||
|
return (bool)ReflectionHelper.Invoke(Collection, "Remove", ReflectionHelper.GetPropertyValue(element, kvType, KeyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ClearElements()
|
||||||
|
{
|
||||||
|
ReflectionHelper.Invoke(Collection, "Clear");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override HashMapItemBase CreateItem(object collection, object elementObj, string label, int depth)
|
||||||
|
{
|
||||||
|
return new Item(collection, elementObj, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Item : HashMapItemBase
|
||||||
|
{
|
||||||
|
public Item(object collection, object keyValuePair, int depth)
|
||||||
|
{
|
||||||
|
var box = new Box()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
marginBottom = 3.5f,
|
||||||
|
marginRight = -2f,
|
||||||
|
flexDirection = FlexDirection.Row
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
kvType = keyValuePair.GetType();
|
||||||
|
var keyType = kvType.GenericTypeArguments[0];
|
||||||
|
var valueType = kvType.GenericTypeArguments[1];
|
||||||
|
|
||||||
|
key = ReflectionHelper.GetPropertyValue(keyValuePair, kvType, KeyName);
|
||||||
|
value = ReflectionHelper.GetPropertyValue(keyValuePair, kvType, ValueName);
|
||||||
|
|
||||||
|
this.collection = collection;
|
||||||
|
this.keyValuePair = keyValuePair;
|
||||||
|
|
||||||
|
var keyValueElement = new VisualElement()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
flexDirection = FlexDirection.Column,
|
||||||
|
flexGrow = 1f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
box.Add(keyValueElement);
|
||||||
|
|
||||||
|
keyField = new GenericField(key, keyType, KeyName, depth)
|
||||||
|
{
|
||||||
|
style = { flexGrow = 1f }
|
||||||
|
};
|
||||||
|
keyField.OnValueChanged += SetKey;
|
||||||
|
keyValueElement.Add(keyField);
|
||||||
|
|
||||||
|
valueField = new GenericField(value, valueType, ValueName, depth)
|
||||||
|
{
|
||||||
|
style = { flexGrow = 1f }
|
||||||
|
};
|
||||||
|
valueField.OnValueChanged += SetValue;
|
||||||
|
keyValueElement.Add(valueField);
|
||||||
|
|
||||||
|
var closeButton = new Button(() => OnClose?.Invoke())
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = EditorGUIUtility.singleLineHeight,
|
||||||
|
height = EditorGUIUtility.singleLineHeight,
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold,
|
||||||
|
fontSize = 10f
|
||||||
|
},
|
||||||
|
text = "X",
|
||||||
|
};
|
||||||
|
box.Add(closeButton);
|
||||||
|
Add(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly GenericField keyField;
|
||||||
|
readonly GenericField valueField;
|
||||||
|
|
||||||
|
readonly object collection;
|
||||||
|
public override object Value => keyValuePair;
|
||||||
|
|
||||||
|
object key;
|
||||||
|
object value;
|
||||||
|
object keyValuePair;
|
||||||
|
readonly Type kvType;
|
||||||
|
|
||||||
|
public override void Lock()
|
||||||
|
{
|
||||||
|
keyField.SetEnabled(false);
|
||||||
|
valueField.OnValueChanged -= SetValue;
|
||||||
|
valueField.OnValueChanged += x =>
|
||||||
|
{
|
||||||
|
ReflectionHelper.GetProperty(collection.GetType(), "Item").SetValue(collection, x, new object[] { key });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetKey(object obj)
|
||||||
|
{
|
||||||
|
key = obj;
|
||||||
|
keyValuePair = Activator.CreateInstance(kvType, key, value);
|
||||||
|
OnValueChanged?.Invoke(keyValuePair);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetValue(object obj)
|
||||||
|
{
|
||||||
|
value = obj;
|
||||||
|
keyValuePair = Activator.CreateInstance(kvType, key, value);
|
||||||
|
OnValueChanged?.Invoke(keyValuePair);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ef13233afbf8a44f68f8641b03e2303f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
230
Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs
Normal file
230
Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visual Element that creates a suitable input Field from object type
|
||||||
|
/// </summary>
|
||||||
|
public sealed class GenericField : VisualElement
|
||||||
|
{
|
||||||
|
public GenericField(object obj, Type type, string label, int depth, bool isDelayed = false)
|
||||||
|
{
|
||||||
|
Build(obj, type, label, depth, isDelayed);
|
||||||
|
GUIHelper.ScheduleAdjustLabelWidth(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Build(object obj, Type type, string label, int depth, bool isDelayed)
|
||||||
|
{
|
||||||
|
if (depth > InspectorHelper.MaxDepth) return;
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
if (obj == null && !typeof(UnityEngine.Object).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
var nullLabelElement = new VisualElement()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = Length.Percent(100f),
|
||||||
|
height = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing,
|
||||||
|
paddingLeft = 3f,
|
||||||
|
flexDirection = FlexDirection.Row
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
nullLabelElement.Add(new Label(label + " (Null)")
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
flexGrow = 1f,
|
||||||
|
unityTextAlign = TextAnchor.MiddleLeft
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: support polymorphism
|
||||||
|
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||||
|
{
|
||||||
|
nullLabelElement.Add(new Button(() =>
|
||||||
|
{
|
||||||
|
var instance = Activator.CreateInstance(type, Activator.CreateInstance(type.GenericTypeArguments[0]));
|
||||||
|
Build(instance, type, label, depth, isDelayed);
|
||||||
|
OnValueChanged?.Invoke(instance);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
text = "Create..."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (TypeHelper.HasDefaultConstructor(type))
|
||||||
|
{
|
||||||
|
nullLabelElement.Add(new Button(() =>
|
||||||
|
{
|
||||||
|
var instance = TypeHelper.CreateDefaultInstance(type);
|
||||||
|
Build(instance, type, label, depth, isDelayed);
|
||||||
|
OnValueChanged?.Invoke(instance);
|
||||||
|
})
|
||||||
|
{
|
||||||
|
text = "Create..."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(nullLabelElement);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isDelayed = isDelayed;
|
||||||
|
|
||||||
|
if (type == typeof(bool))
|
||||||
|
{
|
||||||
|
AddField(new Toggle(label), (bool)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(int))
|
||||||
|
{
|
||||||
|
AddField(new IntegerField(label), (int)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(uint))
|
||||||
|
{
|
||||||
|
AddField(new UnsignedIntegerField(label), (uint)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(long))
|
||||||
|
{
|
||||||
|
AddField(new LongField(label), (long)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(ulong))
|
||||||
|
{
|
||||||
|
AddField(new UnsignedLongField(label), (ulong)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(float))
|
||||||
|
{
|
||||||
|
AddField(new FloatField(label), (float)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(double) || type == typeof(decimal))
|
||||||
|
{
|
||||||
|
AddField(new DoubleField(label), (double)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(string))
|
||||||
|
{
|
||||||
|
AddField(new TextField(label), (string)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(char))
|
||||||
|
{
|
||||||
|
var charField = new TextField(label, 1, false, false, default) { value = obj.ToString() };
|
||||||
|
charField.RegisterValueChangedCallback(x => OnValueChanged?.Invoke(x.newValue[0]));
|
||||||
|
Add(charField);
|
||||||
|
}
|
||||||
|
else if (type.IsEnum)
|
||||||
|
{
|
||||||
|
var value = (Enum)obj;
|
||||||
|
if (value.GetType().HasCustomAttribute<FlagsAttribute>()) AddField(new EnumFlagsField(label, value), value);
|
||||||
|
else AddField(new EnumField(label, value), value);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Vector2))
|
||||||
|
{
|
||||||
|
AddField(new Vector2Field(label), (Vector2)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Vector2Int))
|
||||||
|
{
|
||||||
|
AddField(new Vector2IntField(label), (Vector2Int)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Vector3))
|
||||||
|
{
|
||||||
|
AddField(new Vector3Field(label), (Vector3)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Vector3Int))
|
||||||
|
{
|
||||||
|
AddField(new Vector3IntField(label), (Vector3Int)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Vector4))
|
||||||
|
{
|
||||||
|
AddField(new Vector4Field(label), (Vector4)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Rect))
|
||||||
|
{
|
||||||
|
AddField(new RectField(label), (Rect)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(RectInt))
|
||||||
|
{
|
||||||
|
AddField(new RectIntField(label), (RectInt)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Bounds))
|
||||||
|
{
|
||||||
|
AddField(new BoundsField(label), (Bounds)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(BoundsInt))
|
||||||
|
{
|
||||||
|
AddField(new BoundsIntField(label), (BoundsInt)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Color))
|
||||||
|
{
|
||||||
|
AddField(new ColorField(label), (Color)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Gradient))
|
||||||
|
{
|
||||||
|
AddField(new GradientField(label), (Gradient)obj);
|
||||||
|
}
|
||||||
|
else if (type == typeof(Hash128))
|
||||||
|
{
|
||||||
|
AddField(new Hash128Field(label), (Hash128)obj);
|
||||||
|
}
|
||||||
|
else if (typeof(UnityEngine.Object).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
AddField(new ObjectField(label) { objectType = type }, (UnityEngine.Object)obj);
|
||||||
|
}
|
||||||
|
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(HashSet<>))
|
||||||
|
{
|
||||||
|
var field = new HashSetField(obj, label, depth + 1);
|
||||||
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
||||||
|
|
||||||
|
Add(field);
|
||||||
|
}
|
||||||
|
else if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||||
|
{
|
||||||
|
var field = new DictionaryField(obj, label, depth + 1);
|
||||||
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
||||||
|
Add(field);
|
||||||
|
}
|
||||||
|
else if (typeof(IList).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
var field = new ListField((IList)obj, label, depth + 1);
|
||||||
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
||||||
|
Add(field);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var field = new ClassField(obj, type, label, depth + 1);
|
||||||
|
field.OnValueChanged += x => OnValueChanged?.Invoke(x);
|
||||||
|
Add(field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<object> OnValueChanged;
|
||||||
|
bool isDelayed;
|
||||||
|
bool changed;
|
||||||
|
|
||||||
|
void AddField<T>(BaseField<T> control, T value)
|
||||||
|
{
|
||||||
|
control.value = value;
|
||||||
|
if (isDelayed && control is not ObjectField) // ignore ObjectField
|
||||||
|
{
|
||||||
|
control.RegisterValueChangedCallback(x => changed = true);
|
||||||
|
control.RegisterCallback<FocusOutEvent>(x =>
|
||||||
|
{
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
OnValueChanged?.Invoke(control.value);
|
||||||
|
changed = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
control.RegisterValueChangedCallback(x => OnValueChanged?.Invoke(x.newValue));
|
||||||
|
}
|
||||||
|
Add(control);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Elements/GenericField.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5d48be02c25cf4bc0be72b50cae0a0c7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
171
Alchemy/Assets/Alchemy/Editor/Elements/HashMapFieldBase.cs
Normal file
171
Alchemy/Assets/Alchemy/Editor/Elements/HashMapFieldBase.cs
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
public abstract class HashMapFieldBase : VisualElement
|
||||||
|
{
|
||||||
|
public HashMapFieldBase(object collection, string label, int depth)
|
||||||
|
{
|
||||||
|
this.depth = depth;
|
||||||
|
this.collection = collection;
|
||||||
|
|
||||||
|
var foldout = new Foldout()
|
||||||
|
{
|
||||||
|
text = label
|
||||||
|
};
|
||||||
|
Add(foldout);
|
||||||
|
foldout.Q<Label>().style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
|
||||||
|
contents = new();
|
||||||
|
foldout.Add(contents);
|
||||||
|
|
||||||
|
inputForm = new();
|
||||||
|
foldout.Add(inputForm);
|
||||||
|
|
||||||
|
addButton = new Button(() =>
|
||||||
|
{
|
||||||
|
if (isInputting) EndInput();
|
||||||
|
else StartInput();
|
||||||
|
})
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
alignSelf = Align.FlexEnd,
|
||||||
|
minWidth = 60f
|
||||||
|
},
|
||||||
|
text = "+ Add"
|
||||||
|
};
|
||||||
|
foldout.Add(addButton);
|
||||||
|
|
||||||
|
foldout.Add(new VisualElement() { style = { minHeight = 4f } });
|
||||||
|
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly VisualElement inputForm;
|
||||||
|
readonly VisualElement contents;
|
||||||
|
readonly Button addButton;
|
||||||
|
|
||||||
|
public object Collection => collection;
|
||||||
|
readonly object collection;
|
||||||
|
readonly int depth;
|
||||||
|
|
||||||
|
bool isInputting;
|
||||||
|
|
||||||
|
public event Action<object> OnValueChanged;
|
||||||
|
|
||||||
|
public void RegisterOnValueChangedCallback(Action<object> callback)
|
||||||
|
{
|
||||||
|
OnValueChanged += callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StartInput()
|
||||||
|
{
|
||||||
|
void ValidateValue(object x)
|
||||||
|
{
|
||||||
|
var contains = CheckElement(x);
|
||||||
|
addButton.SetEnabled(!contains);
|
||||||
|
addButton.text = contains ? "(Invalid key)" : "Done";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isInputting) return;
|
||||||
|
isInputting = true;
|
||||||
|
|
||||||
|
var initValue = CreateElement();
|
||||||
|
var form = CreateItem(collection, initValue, "New Value", depth);
|
||||||
|
inputForm.Clear();
|
||||||
|
inputForm.Add(form);
|
||||||
|
form.OnValueChanged += ValidateValue;
|
||||||
|
form.OnClose += () =>
|
||||||
|
{
|
||||||
|
CancelInput();
|
||||||
|
};
|
||||||
|
|
||||||
|
ValidateValue(initValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EndInput()
|
||||||
|
{
|
||||||
|
if (!isInputting) return;
|
||||||
|
isInputting = false;
|
||||||
|
|
||||||
|
addButton.text = "+ Add";
|
||||||
|
addButton.SetEnabled(true);
|
||||||
|
AddElement(inputForm.Q<HashMapItemBase>().Value);
|
||||||
|
OnValueChanged?.Invoke(collection);
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CancelInput()
|
||||||
|
{
|
||||||
|
isInputting = false;
|
||||||
|
|
||||||
|
addButton.text = "+ Add";
|
||||||
|
addButton.SetEnabled(true);
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuild GUI contents
|
||||||
|
public void Rebuild()
|
||||||
|
{
|
||||||
|
contents.Clear();
|
||||||
|
inputForm.Clear();
|
||||||
|
|
||||||
|
if (collection == null) return;
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
foreach (var item in (IEnumerable)collection)
|
||||||
|
{
|
||||||
|
var element = CreateItem(collection, item, "Element " + i, depth);
|
||||||
|
element.OnClose += () =>
|
||||||
|
{
|
||||||
|
if (isInputting) return;
|
||||||
|
var remove = RemoveElement(item);
|
||||||
|
if (remove)
|
||||||
|
{
|
||||||
|
OnValueChanged?.Invoke(collection);
|
||||||
|
Rebuild();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
element.Lock();
|
||||||
|
|
||||||
|
contents.Add(element);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == 0)
|
||||||
|
{
|
||||||
|
var box = new Box();
|
||||||
|
box.style.minHeight = EditorGUIUtility.singleLineHeight * 1.2f;
|
||||||
|
box.style.paddingLeft = 6f;
|
||||||
|
|
||||||
|
var label = new Label(CollectionTypeName + " is empty.");
|
||||||
|
label.style.height = Length.Percent(100f);
|
||||||
|
label.style.unityTextAlign = TextAnchor.MiddleLeft;
|
||||||
|
box.Add(label);
|
||||||
|
|
||||||
|
inputForm.Add(box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract HashMapItemBase CreateItem(object collection, object elementObj, string label, int depth);
|
||||||
|
public abstract bool CheckElement(object element);
|
||||||
|
public abstract object CreateElement();
|
||||||
|
public abstract void AddElement(object element);
|
||||||
|
public abstract bool RemoveElement(object element);
|
||||||
|
public abstract void ClearElements();
|
||||||
|
public abstract string CollectionTypeName { get; }
|
||||||
|
|
||||||
|
public abstract class HashMapItemBase : VisualElement
|
||||||
|
{
|
||||||
|
public Action OnClose;
|
||||||
|
public Action<object> OnValueChanged;
|
||||||
|
|
||||||
|
public abstract object Value { get; }
|
||||||
|
public abstract void Lock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4458165df128342d3a2a50b74dcc8c47
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
93
Alchemy/Assets/Alchemy/Editor/Elements/HashSetField.cs
Normal file
93
Alchemy/Assets/Alchemy/Editor/Elements/HashSetField.cs
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
public sealed class HashSetField : HashMapFieldBase
|
||||||
|
{
|
||||||
|
public HashSetField(object collection, string label, int depth) : base(collection, label, depth) { }
|
||||||
|
|
||||||
|
public override string CollectionTypeName => "HashSet";
|
||||||
|
|
||||||
|
public override bool CheckElement(object key)
|
||||||
|
{
|
||||||
|
return (bool)ReflectionHelper.Invoke(Collection, "Contains", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override object CreateElement()
|
||||||
|
{
|
||||||
|
return TypeHelper.CreateDefaultInstance(Collection.GetType().GenericTypeArguments[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void AddElement(object element)
|
||||||
|
{
|
||||||
|
ReflectionHelper.Invoke(Collection, "Add", element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool RemoveElement(object element)
|
||||||
|
{
|
||||||
|
return (bool)ReflectionHelper.Invoke(Collection, "Remove", element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ClearElements()
|
||||||
|
{
|
||||||
|
ReflectionHelper.Invoke(Collection, "Clear");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override HashMapItemBase CreateItem(object collection, object elementObj, string label, int depth)
|
||||||
|
{
|
||||||
|
return new Item(collection, elementObj, label, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Item : HashMapItemBase
|
||||||
|
{
|
||||||
|
public Item(object collection, object elementObj, string label, int depth)
|
||||||
|
{
|
||||||
|
var box = new Box()
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
marginBottom = 3.5f,
|
||||||
|
marginRight = -2f,
|
||||||
|
flexDirection = FlexDirection.Row
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var valueType = elementObj == null ? collection.GetType().GenericTypeArguments[0] : elementObj.GetType();
|
||||||
|
|
||||||
|
inputField = new GenericField(elementObj, valueType, label, depth);
|
||||||
|
inputField.style.flexGrow = 1f;
|
||||||
|
inputField.OnValueChanged += x =>
|
||||||
|
{
|
||||||
|
value = x;
|
||||||
|
OnValueChanged?.Invoke(x);
|
||||||
|
};
|
||||||
|
box.Add(inputField);
|
||||||
|
|
||||||
|
var closeButton = new Button(() => OnClose?.Invoke())
|
||||||
|
{
|
||||||
|
style = {
|
||||||
|
width = EditorGUIUtility.singleLineHeight,
|
||||||
|
height = EditorGUIUtility.singleLineHeight,
|
||||||
|
unityFontStyleAndWeight = FontStyle.Bold,
|
||||||
|
fontSize = 10f
|
||||||
|
},
|
||||||
|
text = "X",
|
||||||
|
};
|
||||||
|
box.Add(closeButton);
|
||||||
|
Add(box);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly GenericField inputField;
|
||||||
|
|
||||||
|
public override void Lock()
|
||||||
|
{
|
||||||
|
inputField.SetEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
object value;
|
||||||
|
public override object Value => value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Elements/HashSetField.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Elements/HashSetField.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ba0b1c41824514728bc50f6ff3d34315
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,95 @@
|
|||||||
|
using UnityEngine.Assertions;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visual Element that draws the ObjectField of the InlineEditor attribute
|
||||||
|
/// </summary>
|
||||||
|
public sealed class InlineEditorObjectField : BindableElement
|
||||||
|
{
|
||||||
|
public InlineEditorObjectField(SerializedProperty property, int depth)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(property.propertyType == SerializedPropertyType.ObjectReference);
|
||||||
|
|
||||||
|
style.minHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
|
||||||
|
foldout = new Foldout()
|
||||||
|
{
|
||||||
|
text = ObjectNames.NicifyVariableName(property.displayName)
|
||||||
|
};
|
||||||
|
var toggle = foldout.Q<Toggle>();
|
||||||
|
var clickable = InternalAPIHelper.GetClickable(toggle);
|
||||||
|
InternalAPIHelper.SetAcceptClicksIfDisabled(clickable, true);
|
||||||
|
|
||||||
|
foldout.BindProperty(property);
|
||||||
|
|
||||||
|
field = GUIHelper.CreateObjectField(property);
|
||||||
|
field.style.position = Position.Absolute;
|
||||||
|
field.style.width = Length.Percent(100f);
|
||||||
|
|
||||||
|
field.RegisterValueChangeCallback(x =>
|
||||||
|
{
|
||||||
|
isNull = x.changedProperty.objectReferenceValue == null;
|
||||||
|
if (!isNull) field.Q<Label>().text = string.Empty;
|
||||||
|
else field.Q<Label>().text = ObjectNames.NicifyVariableName(property.displayName);
|
||||||
|
|
||||||
|
field.pickingMode = PickingMode.Ignore;
|
||||||
|
var objectField = field.Q<ObjectField>();
|
||||||
|
objectField.pickingMode = PickingMode.Ignore;
|
||||||
|
var label = objectField.Q<Label>();
|
||||||
|
label.pickingMode = PickingMode.Ignore;
|
||||||
|
|
||||||
|
Build(x.changedProperty, depth);
|
||||||
|
});
|
||||||
|
|
||||||
|
Add(foldout);
|
||||||
|
Add(field);
|
||||||
|
|
||||||
|
Build(property, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly Foldout foldout;
|
||||||
|
readonly PropertyField field;
|
||||||
|
bool isNull;
|
||||||
|
|
||||||
|
public bool IsObjectNull => isNull;
|
||||||
|
|
||||||
|
public string Label
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (isNull) return field.Q<Label>().text;
|
||||||
|
else return foldout.text;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (isNull) field.Q<Label>().text = value;
|
||||||
|
else foldout.text = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Build(SerializedProperty property, int depth)
|
||||||
|
{
|
||||||
|
foldout.Clear();
|
||||||
|
var toggle = foldout.Q<Toggle>();
|
||||||
|
|
||||||
|
isNull = property.objectReferenceValue == null;
|
||||||
|
toggle.style.display = isNull ? DisplayStyle.None : DisplayStyle.Flex;
|
||||||
|
if (!isNull)
|
||||||
|
{
|
||||||
|
foldout.Add(new VisualElement() { style = { height = EditorGUIUtility.standardVerticalSpacing } });
|
||||||
|
var so = new SerializedObject(property.objectReferenceValue);
|
||||||
|
InspectorHelper.BuildElements(so, foldout, so.targetObject, name => so.FindProperty(name), depth);
|
||||||
|
this.Bind(so);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.Unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c644baa37186c4bbe9c006dee446d9a7
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
74
Alchemy/Assets/Alchemy/Editor/Elements/ListField.cs
Normal file
74
Alchemy/Assets/Alchemy/Editor/Elements/ListField.cs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visual Element that draws an IList
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ListField : VisualElement
|
||||||
|
{
|
||||||
|
sealed class Item : VisualElement
|
||||||
|
{
|
||||||
|
public int index;
|
||||||
|
}
|
||||||
|
|
||||||
|
const string ItemClassName = "unity-list-view__item";
|
||||||
|
|
||||||
|
public ListField(IList target, string label, int depth)
|
||||||
|
{
|
||||||
|
Assert.IsNotNull(target);
|
||||||
|
list = target;
|
||||||
|
|
||||||
|
listView = GUIHelper.CreateDefaultListView(label);
|
||||||
|
listView.makeItem = () => new Item();
|
||||||
|
listView.bindItem = (element, index) =>
|
||||||
|
{
|
||||||
|
((Item)element).index = index;
|
||||||
|
|
||||||
|
var value = list[index];
|
||||||
|
var listType = list.GetType();
|
||||||
|
var valueType = value != null ? value.GetType() : listType.IsGenericType ? listType.GenericTypeArguments[0] : typeof(object);
|
||||||
|
var fieldElement = new GenericField(value, valueType, label, depth);
|
||||||
|
element.Add(fieldElement);
|
||||||
|
var labelElement = fieldElement.Q<Label>();
|
||||||
|
if (labelElement != null) labelElement.text = "Element " + index;
|
||||||
|
|
||||||
|
fieldElement.OnValueChanged += x =>
|
||||||
|
{
|
||||||
|
list[((Item)element).index] = x;
|
||||||
|
NotifyOnValueChanged();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
listView.unbindItem = (element, index) =>
|
||||||
|
{
|
||||||
|
element.Clear();
|
||||||
|
};
|
||||||
|
listView.itemsSource = list;
|
||||||
|
listView.itemIndexChanged += (prevIndex, index) =>
|
||||||
|
{
|
||||||
|
var item = listView.Query<VisualElement>(className: ItemClassName).AtIndex(index).Q<Item>();
|
||||||
|
item.index = index;
|
||||||
|
NotifyOnValueChanged();
|
||||||
|
};
|
||||||
|
listView.itemsAdded += indexes => NotifyOnValueChanged();
|
||||||
|
|
||||||
|
listView.Q<Label>().style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
Add(listView);
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly IList list;
|
||||||
|
readonly ListView listView;
|
||||||
|
|
||||||
|
public event Action<object> OnValueChanged;
|
||||||
|
|
||||||
|
void NotifyOnValueChanged()
|
||||||
|
{
|
||||||
|
OnValueChanged?.Invoke(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Elements/ListField.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Elements/ListField.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d1b638bf9747241989ead75deeb13093
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
71
Alchemy/Assets/Alchemy/Editor/Elements/MethodButton.cs
Normal file
71
Alchemy/Assets/Alchemy/Editor/Elements/MethodButton.cs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
public sealed class MethodButton : VisualElement
|
||||||
|
{
|
||||||
|
const string ButtonLabelText = "Invoke";
|
||||||
|
|
||||||
|
public MethodButton(object target, MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
var parameters = methodInfo.GetParameters();
|
||||||
|
|
||||||
|
// Create parameterless button
|
||||||
|
if (parameters.Length == 0)
|
||||||
|
{
|
||||||
|
button = new Button((Action)methodInfo.CreateDelegate(typeof(Action), target))
|
||||||
|
{
|
||||||
|
text = methodInfo.Name
|
||||||
|
};
|
||||||
|
Add(button);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameterObjects = new object[parameters.Length];
|
||||||
|
|
||||||
|
var box = new HelpBox();
|
||||||
|
Add(box);
|
||||||
|
|
||||||
|
foldout = new Foldout()
|
||||||
|
{
|
||||||
|
text = methodInfo.Name,
|
||||||
|
value = false,
|
||||||
|
style = {
|
||||||
|
flexGrow = 1f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
button = new Button(() => methodInfo.Invoke(target, parameterObjects))
|
||||||
|
{
|
||||||
|
text = ButtonLabelText,
|
||||||
|
style = {
|
||||||
|
position = Position.Absolute,
|
||||||
|
right = 1f,
|
||||||
|
top = 1.5f,
|
||||||
|
width = 100f
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
box.Add(new VisualElement() { style = { width = 12f } });
|
||||||
|
box.Add(foldout);
|
||||||
|
box.Add(button);
|
||||||
|
|
||||||
|
for (int i = 0; i < parameters.Length; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
var parameter = parameters[index];
|
||||||
|
parameterObjects[index] = Activator.CreateInstance(parameter.ParameterType);
|
||||||
|
var element = new GenericField(parameterObjects[index], parameter.ParameterType, ObjectNames.NicifyVariableName(parameter.Name), 0);
|
||||||
|
element.OnValueChanged += x => parameterObjects[index] = x;
|
||||||
|
element.style.paddingRight = 4f;
|
||||||
|
foldout.Add(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly Foldout foldout;
|
||||||
|
readonly Button button;
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Elements/MethodButton.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Elements/MethodButton.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 098870ca8e0fc4d5b80c50ffa39f16f3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
39
Alchemy/Assets/Alchemy/Editor/Elements/PropertyListView.cs
Normal file
39
Alchemy/Assets/Alchemy/Editor/Elements/PropertyListView.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visual Element that draws SerializedProperty of Array or List
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PropertyListView : BindableElement
|
||||||
|
{
|
||||||
|
public PropertyListView(SerializedProperty property, int depth)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(property.isArray);
|
||||||
|
|
||||||
|
var listView = GUIHelper.CreateDefaultListView(ObjectNames.NicifyVariableName(property.displayName));
|
||||||
|
listView.bindItem = (element, index) =>
|
||||||
|
{
|
||||||
|
var arrayElement = property.GetArrayElementAtIndex(index);
|
||||||
|
var e = new AlchemyPropertyField(arrayElement, depth + 1);
|
||||||
|
element.Add(e);
|
||||||
|
element.Bind(arrayElement.serializedObject);
|
||||||
|
};
|
||||||
|
listView.unbindItem = (element, index) =>
|
||||||
|
{
|
||||||
|
element.Clear();
|
||||||
|
element.Unbind();
|
||||||
|
};
|
||||||
|
|
||||||
|
listView.Q<Label>().style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||||
|
|
||||||
|
listView.BindProperty(property);
|
||||||
|
Add(listView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f003dc4ef9602443c8a41555882e68b1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
85
Alchemy/Assets/Alchemy/Editor/Elements/ReflectionField.cs
Normal file
85
Alchemy/Assets/Alchemy/Editor/Elements/ReflectionField.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
public sealed class ReflectionField : VisualElement
|
||||||
|
{
|
||||||
|
public ReflectionField(object target, MemberInfo memberInfo, int depth)
|
||||||
|
{
|
||||||
|
Rebuild(target, memberInfo, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Rebuild(object target, MemberInfo memberInfo, int depth)
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
|
||||||
|
if (memberInfo is MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
if (methodInfo.HasCustomAttribute<ButtonAttribute>())
|
||||||
|
{
|
||||||
|
var button = new MethodButton(target, methodInfo);
|
||||||
|
Add(button);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
object value;
|
||||||
|
GenericField element;
|
||||||
|
switch (memberInfo)
|
||||||
|
{
|
||||||
|
default: return;
|
||||||
|
case FieldInfo fieldInfo:
|
||||||
|
value = fieldInfo.IsStatic ? fieldInfo.GetValue(null) : target == null ? TypeHelper.GetDefaultValue(fieldInfo.FieldType) : fieldInfo.GetValue(target);
|
||||||
|
var fieldType = target == null ? fieldInfo.FieldType : fieldInfo.GetValue(target)?.GetType() ?? fieldInfo.FieldType;
|
||||||
|
element = new GenericField(value, fieldType, ObjectNames.NicifyVariableName(memberInfo.Name), depth, true);
|
||||||
|
element.OnValueChanged += x =>
|
||||||
|
{
|
||||||
|
OnBeforeValueChange?.Invoke(target);
|
||||||
|
fieldInfo.SetValue(target, x);
|
||||||
|
|
||||||
|
// Force serialization
|
||||||
|
if (target is ISerializationCallbackReceiver receiver)
|
||||||
|
{
|
||||||
|
receiver.OnBeforeSerialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnValueChanged?.Invoke(target);
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
case PropertyInfo propertyInfo:
|
||||||
|
if (!propertyInfo.HasCustomAttribute<ShowInInspectorAttribute>()) return;
|
||||||
|
if (!propertyInfo.CanRead) return;
|
||||||
|
|
||||||
|
value = propertyInfo.GetMethod.IsStatic ? propertyInfo.GetValue(null) : target == null ? TypeHelper.GetDefaultValue(propertyInfo.PropertyType) : propertyInfo.GetValue(target);
|
||||||
|
var propertyType = target == null ? propertyInfo.PropertyType : propertyInfo.GetValue(target)?.GetType() ?? propertyInfo.PropertyType;
|
||||||
|
element = new GenericField(value, propertyType, ObjectNames.NicifyVariableName(memberInfo.Name), depth, true);
|
||||||
|
element.OnValueChanged += x =>
|
||||||
|
{
|
||||||
|
OnBeforeValueChange?.Invoke(target);
|
||||||
|
if (propertyInfo.CanWrite) propertyInfo.SetValue(target, x);
|
||||||
|
|
||||||
|
// Force serialization
|
||||||
|
if (target is ISerializationCallbackReceiver receiver)
|
||||||
|
{
|
||||||
|
receiver.OnBeforeSerialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
OnValueChanged?.Invoke(target);
|
||||||
|
};
|
||||||
|
element.SetEnabled(propertyInfo.CanWrite);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<object> OnBeforeValueChange;
|
||||||
|
public event Action<object> OnValueChanged;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e96e973f88c414f90b77487d1a59b89d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,127 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Editor.Internal;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Elements
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Draw properties marked with SerializeReference attribute
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SerializeReferenceField : VisualElement
|
||||||
|
{
|
||||||
|
public SerializeReferenceField(SerializedProperty property, int depth)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(property.propertyType == SerializedPropertyType.ManagedReference);
|
||||||
|
|
||||||
|
style.flexDirection = FlexDirection.Row;
|
||||||
|
style.minHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
|
||||||
|
|
||||||
|
foldout = new Foldout()
|
||||||
|
{
|
||||||
|
text = ObjectNames.NicifyVariableName(property.displayName)
|
||||||
|
};
|
||||||
|
foldout.style.flexGrow = 1f;
|
||||||
|
foldout.BindProperty(property);
|
||||||
|
Add(foldout);
|
||||||
|
|
||||||
|
buttonContainer = new IMGUIContainer(() =>
|
||||||
|
{
|
||||||
|
var position = EditorGUILayout.GetControlRect();
|
||||||
|
|
||||||
|
var dropdownRect = position;
|
||||||
|
dropdownRect.height = EditorGUIUtility.singleLineHeight;
|
||||||
|
|
||||||
|
var buttonLabel = EditorIcons.CsScriptIcon;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
buttonLabel.text = (property.managedReferenceValue == null ? "Null" : property.managedReferenceValue.GetType().Name) +
|
||||||
|
$" ({property.GetManagedReferenceFieldTypeName()})";
|
||||||
|
}
|
||||||
|
catch (InvalidOperationException)
|
||||||
|
{
|
||||||
|
// Ignoring exceptions when disposed (bad solution)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUI.Button(dropdownRect, buttonLabel, EditorStyles.objectField))
|
||||||
|
{
|
||||||
|
const int MaxTypePopupLineCount = 13;
|
||||||
|
|
||||||
|
var baseType = property.GetManagedReferenceFieldType();
|
||||||
|
SerializeReferenceDropdown dropdown = new(
|
||||||
|
TypeCache.GetTypesDerivedFrom(baseType).Append(baseType).Where(t =>
|
||||||
|
(t.IsPublic || t.IsNestedPublic) &&
|
||||||
|
!t.IsAbstract &&
|
||||||
|
!t.IsGenericType &&
|
||||||
|
!typeof(UnityEngine.Object).IsAssignableFrom(t) &&
|
||||||
|
t.IsSerializable
|
||||||
|
),
|
||||||
|
MaxTypePopupLineCount,
|
||||||
|
new AdvancedDropdownState()
|
||||||
|
);
|
||||||
|
|
||||||
|
dropdown.onItemSelected += item =>
|
||||||
|
{
|
||||||
|
property.SetManagedReferenceType(item.type);
|
||||||
|
property.isExpanded = true;
|
||||||
|
property.serializedObject.ApplyModifiedProperties();
|
||||||
|
property.serializedObject.Update();
|
||||||
|
|
||||||
|
Rebuild(property, depth);
|
||||||
|
};
|
||||||
|
|
||||||
|
dropdown.Show(position);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
schedule.Execute(() =>
|
||||||
|
{
|
||||||
|
VisualElement visualElement = GetFirstAncestorOfType<InspectorElement>();
|
||||||
|
visualElement.RegisterCallback<GeometryChangedEvent>(x =>
|
||||||
|
{
|
||||||
|
buttonContainer.style.width = GUIHelper.CalculateFieldWidth(buttonContainer, visualElement) -
|
||||||
|
(buttonContainer.GetFirstAncestorOfType<Foldout>() != null ? 18f : 0f);
|
||||||
|
});
|
||||||
|
buttonContainer.style.width = GUIHelper.CalculateFieldWidth(buttonContainer, visualElement) -
|
||||||
|
(buttonContainer.GetFirstAncestorOfType<Foldout>() != null ? 18f : 0f);
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonContainer.style.position = Position.Absolute;
|
||||||
|
buttonContainer.style.top = EditorGUIUtility.standardVerticalSpacing * 0.5f;
|
||||||
|
buttonContainer.style.right = 0f;
|
||||||
|
Add(buttonContainer);
|
||||||
|
|
||||||
|
Rebuild(property, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Foldout foldout;
|
||||||
|
public readonly IMGUIContainer buttonContainer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rebuild child elements
|
||||||
|
/// </summary>
|
||||||
|
void Rebuild(SerializedProperty property, int depth)
|
||||||
|
{
|
||||||
|
foldout.Clear();
|
||||||
|
|
||||||
|
if (property.managedReferenceValue == null)
|
||||||
|
{
|
||||||
|
var helpbox = new HelpBox("No type assigned.", HelpBoxMessageType.Info);
|
||||||
|
foldout.Add(helpbox);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
InspectorHelper.BuildElements(property.serializedObject, foldout, property.managedReferenceValue, x => property.FindPropertyRelative(x), depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Bind(property.serializedObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 03f2015425fd34b8cb49aea8e59f9745
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Editor/Internal.meta
Normal file
8
Alchemy/Assets/Alchemy/Editor/Internal.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9243210c5444145978ade99e7a7af0c1
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
10
Alchemy/Assets/Alchemy/Editor/Internal/EditorIcons.cs
Normal file
10
Alchemy/Assets/Alchemy/Editor/Internal/EditorIcons.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
internal static class EditorIcons
|
||||||
|
{
|
||||||
|
public static readonly GUIContent CsScriptIcon = EditorGUIUtility.IconContent("cs Script Icon");
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Internal/EditorIcons.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Internal/EditorIcons.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b5d3e43f293fb4a7ebcba1e3b2b91f2b
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
103
Alchemy/Assets/Alchemy/Editor/Internal/GUIHelper.cs
Normal file
103
Alchemy/Assets/Alchemy/Editor/Internal/GUIHelper.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using UnityEngine.Assertions;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
internal static class GUIHelper
|
||||||
|
{
|
||||||
|
public static Color LineColor => EditorGUIUtility.isProSkin ? new(0.4f, 0.4f, 0.4f) : new(0.6f, 0.6f, 0.6f);
|
||||||
|
public static Color SubtitleColor => new(0.5f, 0.5f, 0.5f);
|
||||||
|
public static Color TextColor => EditorGUIUtility.isProSkin ? new(0.725f, 0.725f, 0.725f) : new(0.141f, 0.141f, 0.141f);
|
||||||
|
|
||||||
|
public static float CalculateFieldWidth(VisualElement element, VisualElement root)
|
||||||
|
{
|
||||||
|
var labelWidth = CalculateLabelWidth(element, root);
|
||||||
|
return root.resolvedStyle.width - labelWidth - 27f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float CalculateLabelWidth(VisualElement element, VisualElement root)
|
||||||
|
{
|
||||||
|
// This code is a partial modification of the Label width calculation method actually used inside PropertyField.
|
||||||
|
var num = root.resolvedStyle.paddingLeft;
|
||||||
|
var num2 = 37f;
|
||||||
|
var num3 = 123f;
|
||||||
|
var num4 = element.GetFirstAncestorOfType<Foldout>() == null ? 0f : 15f;
|
||||||
|
|
||||||
|
var width = root.resolvedStyle.width;
|
||||||
|
var a = width * 0.45f - num2 - num - num4;
|
||||||
|
var b = Mathf.Max(num3 - num - num4, 0f);
|
||||||
|
|
||||||
|
return Mathf.Max(a, b) + 12f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ListView CreateDefaultListView(string label)
|
||||||
|
{
|
||||||
|
return new ListView()
|
||||||
|
{
|
||||||
|
reorderable = true,
|
||||||
|
reorderMode = ListViewReorderMode.Animated,
|
||||||
|
showBorder = true,
|
||||||
|
showFoldoutHeader = true,
|
||||||
|
headerTitle = label,
|
||||||
|
showAddRemoveFooter = true,
|
||||||
|
fixedItemHeight = 20f,
|
||||||
|
virtualizationMethod = CollectionVirtualizationMethod.DynamicHeight,
|
||||||
|
showAlternatingRowBackgrounds = AlternatingRowBackground.None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyField CreateObjectField(SerializedProperty property)
|
||||||
|
{
|
||||||
|
Assert.IsTrue(property.propertyType == SerializedPropertyType.ObjectReference);
|
||||||
|
|
||||||
|
var fieldInfo = property.GetFieldInfo();
|
||||||
|
var isAssetsOnly = fieldInfo.HasCustomAttribute<AssetsOnlyAttribute>();
|
||||||
|
|
||||||
|
var propertyField = new PropertyField(property);
|
||||||
|
propertyField.RegisterValueChangeCallback(x =>
|
||||||
|
{
|
||||||
|
var objectField = propertyField.Q<ObjectField>();
|
||||||
|
objectField.objectType = fieldInfo.FieldType;
|
||||||
|
objectField.allowSceneObjects = !isAssetsOnly;
|
||||||
|
});
|
||||||
|
|
||||||
|
return propertyField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ScheduleAdjustLabelWidth(VisualElement element)
|
||||||
|
{
|
||||||
|
void Adjust(VisualElement visualElement)
|
||||||
|
{
|
||||||
|
var label = element.Q<Label>();
|
||||||
|
if (label == null) return;
|
||||||
|
label.style.minWidth = 0f;
|
||||||
|
label.style.width = CalculateLabelWidth(element, visualElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust label width
|
||||||
|
element.schedule.Execute(() =>
|
||||||
|
{
|
||||||
|
VisualElement visualElement = element.GetFirstAncestorOfType<InspectorElement>();
|
||||||
|
visualElement.RegisterCallback<GeometryChangedEvent>(x => Adjust(visualElement));
|
||||||
|
Adjust(visualElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IMGUIContainer CreateLine(Color color, float height)
|
||||||
|
{
|
||||||
|
return new IMGUIContainer(() =>
|
||||||
|
{
|
||||||
|
var rect = EditorGUILayout.GetControlRect(false, height);
|
||||||
|
rect.xMin += 3f;
|
||||||
|
rect.y += rect.height * 0.5f;
|
||||||
|
rect.height = 1f;
|
||||||
|
EditorGUI.DrawRect(rect, color);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Internal/GUIHelper.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Internal/GUIHelper.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b23551d43228a43c9a9207cf8f150834
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
295
Alchemy/Assets/Alchemy/Editor/Internal/InspectorHelper.cs
Normal file
295
Alchemy/Assets/Alchemy/Editor/Internal/InspectorHelper.cs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.UIElements;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
using Alchemy.Inspector;
|
||||||
|
using Alchemy.Editor.Elements;
|
||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
using Alchemy.Serialization;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
public static class InspectorHelper
|
||||||
|
{
|
||||||
|
public const int MaxDepth = 15;
|
||||||
|
|
||||||
|
public sealed class GroupNode
|
||||||
|
{
|
||||||
|
public GroupNode(string name, PropertyGroupDrawer drawer)
|
||||||
|
{
|
||||||
|
this.name = name;
|
||||||
|
this.drawer = drawer;
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly string name;
|
||||||
|
readonly PropertyGroupDrawer drawer;
|
||||||
|
|
||||||
|
readonly List<MemberInfo> members = new();
|
||||||
|
readonly List<GroupNode> children = new();
|
||||||
|
|
||||||
|
public string Name => name;
|
||||||
|
public IEnumerable<MemberInfo> Members => members;
|
||||||
|
public PropertyGroupDrawer Drawer => drawer;
|
||||||
|
public VisualElement VisualElement { get; set; }
|
||||||
|
public GroupNode Parent { get; private set; }
|
||||||
|
|
||||||
|
public GroupNode Find(Func<GroupNode, bool> predicate)
|
||||||
|
{
|
||||||
|
return children.FirstOrDefault(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(GroupNode node)
|
||||||
|
{
|
||||||
|
children.Add(node);
|
||||||
|
node.Parent = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddMember(MemberInfo memberInfo)
|
||||||
|
{
|
||||||
|
members.Add(memberInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<GroupNode> DescendantsAndSelf()
|
||||||
|
{
|
||||||
|
yield return this;
|
||||||
|
foreach (var item in Descendants(children)) yield return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<GroupNode> Descendants(IEnumerable<GroupNode> source)
|
||||||
|
{
|
||||||
|
foreach (var item in source)
|
||||||
|
{
|
||||||
|
yield return item;
|
||||||
|
var e = Descendants(item.children).GetEnumerator();
|
||||||
|
while (e.MoveNext())
|
||||||
|
{
|
||||||
|
yield return e.Current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void BuildElements(SerializedObject serializedObject, VisualElement rootElement, object target, Func<string, SerializedProperty> findPropertyFunc, int depth)
|
||||||
|
{
|
||||||
|
if (depth >= MaxDepth) return;
|
||||||
|
if (target == null) return;
|
||||||
|
|
||||||
|
// Build node
|
||||||
|
var rootNode = BuildInspectorNode(target.GetType());
|
||||||
|
|
||||||
|
// Add elements
|
||||||
|
foreach (var node in rootNode.DescendantsAndSelf())
|
||||||
|
{
|
||||||
|
// Get or create group element
|
||||||
|
if (node.Parent == null)
|
||||||
|
{
|
||||||
|
node.VisualElement = rootElement;
|
||||||
|
}
|
||||||
|
else if (node.Drawer == null)
|
||||||
|
{
|
||||||
|
node.VisualElement = node.Parent.VisualElement;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.VisualElement = node.Drawer.CreateRootElement(node.Name);
|
||||||
|
node.Parent.VisualElement.Add(node.VisualElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add member elements
|
||||||
|
foreach (var member in node.Members.OrderByAttributeThenByMemberType())
|
||||||
|
{
|
||||||
|
// Exclude if member has HideInInspector attribute
|
||||||
|
if (member.HasCustomAttribute<HideInInspector>()) continue;
|
||||||
|
|
||||||
|
// Add default PropertyField if member has DisableAlchemyEditorAttribute
|
||||||
|
if (member.GetCustomAttribute<DisableAlchemyEditorAttribute>() != null)
|
||||||
|
{
|
||||||
|
var p = findPropertyFunc(member.Name);
|
||||||
|
if (p != null)
|
||||||
|
{
|
||||||
|
var propertyField = new PropertyField(p);
|
||||||
|
propertyField.style.width = Length.Percent(100f);
|
||||||
|
node.VisualElement.Add(propertyField);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
VisualElement element = null;
|
||||||
|
var property = findPropertyFunc(member.Name);
|
||||||
|
|
||||||
|
// Add default PropertyField if the property has a custom PropertyDrawer
|
||||||
|
if ((member is FieldInfo fieldInfo && InternalAPIHelper.GetDrawerTypeForType(fieldInfo.FieldType) != null) ||
|
||||||
|
(member is PropertyInfo propertyInfo && InternalAPIHelper.GetDrawerTypeForType(propertyInfo.PropertyType) != null))
|
||||||
|
{
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
element = new PropertyField(property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
element = CreateMemberElement(serializedObject, target, member, findPropertyFunc, depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element == null) continue;
|
||||||
|
element.style.width = Length.Percent(100f);
|
||||||
|
|
||||||
|
var e = node.Drawer?.GetGroupElement(
|
||||||
|
member.GetCustomAttributes<PropertyGroupAttribute>()
|
||||||
|
.OrderByDescending(x => x.GroupPath.Split('/').Length)
|
||||||
|
.FirstOrDefault()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (e == null) node.VisualElement.Add(element);
|
||||||
|
else e.Add(element);
|
||||||
|
PropertyProcessor.ExecuteProcessors(serializedObject, property, target, member, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GroupNode BuildInspectorNode(Type targetType)
|
||||||
|
{
|
||||||
|
var rootNode = new GroupNode("Inspector-Group-Root", null);
|
||||||
|
|
||||||
|
// Get all members
|
||||||
|
var members = targetType.GetMembers(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
|
||||||
|
// Build member nodes
|
||||||
|
foreach (var member in members)
|
||||||
|
{
|
||||||
|
var groupAttributes = member.GetCustomAttributes<PropertyGroupAttribute>(true);
|
||||||
|
if (groupAttributes.Count() == 0)
|
||||||
|
{
|
||||||
|
rootNode.AddMember(member);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentNode = rootNode;
|
||||||
|
|
||||||
|
foreach (var (groupAttribute, hierarchy) in groupAttributes
|
||||||
|
.Select(x => (x, x.GroupPath.Split('/')))
|
||||||
|
.OrderBy(x => x.Item2.Length))
|
||||||
|
{
|
||||||
|
parentNode = rootNode;
|
||||||
|
foreach (var groupName in hierarchy)
|
||||||
|
{
|
||||||
|
var next = parentNode.Find(x => x.Name == groupName);
|
||||||
|
if (next == null)
|
||||||
|
{
|
||||||
|
// Find drawer type
|
||||||
|
var drawerType = TypeCache.GetTypesWithAttribute<CustomPropertyGroupDrawerAttribute>()
|
||||||
|
.FirstOrDefault(x => x.GetCustomAttribute<CustomPropertyGroupDrawerAttribute>().targetAttributeType == groupAttribute.GetType());
|
||||||
|
|
||||||
|
var drawer = (PropertyGroupDrawer)Activator.CreateInstance(drawerType);
|
||||||
|
drawer._uniqueId = "AlchemyGroupId_" + targetType.FullName + "_" + groupAttribute.GroupPath;
|
||||||
|
|
||||||
|
next = new GroupNode(groupName, drawer);
|
||||||
|
parentNode.Add(next);
|
||||||
|
}
|
||||||
|
|
||||||
|
parentNode = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentNode.AddMember(member);
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VisualElement CreateMemberElement(SerializedObject serializedObject, object target, MemberInfo memberInfo, Func<string, SerializedProperty> findPropertyFunc, int depth)
|
||||||
|
{
|
||||||
|
if (depth > MaxDepth) return null;
|
||||||
|
|
||||||
|
switch (memberInfo)
|
||||||
|
{
|
||||||
|
case MethodInfo methodInfo:
|
||||||
|
if (methodInfo.HasCustomAttribute<ButtonAttribute>())
|
||||||
|
{
|
||||||
|
return new MethodButton(target, methodInfo);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case FieldInfo:
|
||||||
|
case PropertyInfo:
|
||||||
|
var property = findPropertyFunc?.Invoke(memberInfo.Name);
|
||||||
|
|
||||||
|
// Create property field
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
return new AlchemyPropertyField(property, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
if (serializedObject.targetObject.GetType().HasCustomAttribute<AlchemySerializeAttribute>() &&
|
||||||
|
memberInfo.HasCustomAttribute<AlchemySerializeFieldAttribute>())
|
||||||
|
{
|
||||||
|
var element = default(VisualElement);
|
||||||
|
if (memberInfo is FieldInfo fieldInfo)
|
||||||
|
{
|
||||||
|
SerializedProperty GetProperty() => findPropertyFunc?.Invoke("alchemySerializationData").FindPropertyRelative(memberInfo.Name);
|
||||||
|
|
||||||
|
var p = GetProperty();
|
||||||
|
if (p != null)
|
||||||
|
{
|
||||||
|
var field = new ReflectionField(target, fieldInfo, depth);
|
||||||
|
var foldout = field.Q<Foldout>();
|
||||||
|
foldout?.BindProperty(p);
|
||||||
|
field.TrackPropertyValue(p, p =>
|
||||||
|
{
|
||||||
|
field.Rebuild(target, memberInfo, depth);
|
||||||
|
var foldout = field.Q<Foldout>();
|
||||||
|
foldout?.BindProperty(p);
|
||||||
|
});
|
||||||
|
|
||||||
|
var undoName = "Modified:" + p.displayName;
|
||||||
|
field.OnBeforeValueChange += x =>
|
||||||
|
{
|
||||||
|
Undo.RegisterCompleteObjectUndo(GetProperty().serializedObject.targetObject, undoName);
|
||||||
|
};
|
||||||
|
|
||||||
|
element = field;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Supports editing of multiple objects
|
||||||
|
if (element != null && serializedObject.targetObjects.Length > 1)
|
||||||
|
{
|
||||||
|
element.SetEnabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Create element if member has ShowInInspector attribute
|
||||||
|
if (memberInfo.HasCustomAttribute<ShowInInspectorAttribute>())
|
||||||
|
{
|
||||||
|
return new ReflectionField(target, memberInfo, depth);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static IOrderedEnumerable<MemberInfo> OrderByAttributeThenByMemberType(this IEnumerable<MemberInfo> members)
|
||||||
|
{
|
||||||
|
return members
|
||||||
|
.OrderBy(x =>
|
||||||
|
{
|
||||||
|
var orderAttribute = x.GetCustomAttribute<OrderAttribute>();
|
||||||
|
if (orderAttribute == null) return 0;
|
||||||
|
return orderAttribute.Order;
|
||||||
|
})
|
||||||
|
.ThenBy(x =>
|
||||||
|
{
|
||||||
|
if (x is MethodInfo) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 664a7f04961bd4d6fb7fa0bcaf290687
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
53
Alchemy/Assets/Alchemy/Editor/Internal/InternalAPIHelper.cs
Normal file
53
Alchemy/Assets/Alchemy/Editor/Internal/InternalAPIHelper.cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
using Editor = UnityEditor.Editor;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Call the Unity internal API using reflection. This may not work depending on your Unity version.
|
||||||
|
/// </summary>
|
||||||
|
public static class InternalAPIHelper
|
||||||
|
{
|
||||||
|
static readonly Assembly EditorAssembly = Assembly.GetAssembly(typeof(Editor));
|
||||||
|
|
||||||
|
// ScriptAttributeUtility
|
||||||
|
// https://github.com/Unity-Technologies/UnityCsReference/blob/724ff727438a68d1bc05b342c693c1d481063fd3/Editor/Mono/Inspector/Core/ScriptAttributeGUI/ScriptAttributeUtility.cs
|
||||||
|
|
||||||
|
const string Name_ScriptAttributeUtility = "UnityEditor.ScriptAttributeUtility";
|
||||||
|
|
||||||
|
public static Type GetDrawerTypeForType(Type classType)
|
||||||
|
{
|
||||||
|
var instance = EditorAssembly.CreateInstance(Name_ScriptAttributeUtility);
|
||||||
|
var utilityType = instance.GetType();
|
||||||
|
|
||||||
|
var bindingFlags = BindingFlags.NonPublic | BindingFlags.Static;
|
||||||
|
var methodInfo = utilityType.GetMethod(nameof(GetDrawerTypeForType), bindingFlags);
|
||||||
|
|
||||||
|
return (Type)methodInfo.Invoke(instance, new object[] { classType });
|
||||||
|
}
|
||||||
|
|
||||||
|
const string Name_M_Clickable = "m_Clickable";
|
||||||
|
|
||||||
|
// BaseBoolField
|
||||||
|
// https://github.com/Unity-Technologies/UnityCsReference/blob/master/Modules/UIElements/Core/Controls/BaseBoolField.cs#L12
|
||||||
|
|
||||||
|
public static Clickable GetClickable(BaseBoolField boolField)
|
||||||
|
{
|
||||||
|
var clickable = ReflectionHelper.GetField(typeof(Toggle), Name_M_Clickable).GetValue(boolField);
|
||||||
|
return (Clickable)clickable;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clickable
|
||||||
|
// https://github.com/Unity-Technologies/UnityCsReference/blob/master/Modules/UIElements/Core/Clickable.cs#L12
|
||||||
|
|
||||||
|
const string Name_AcceptClicksIfDisabled = "acceptClicksIfDisabled";
|
||||||
|
|
||||||
|
public static void SetAcceptClicksIfDisabled(Clickable clickable, bool value)
|
||||||
|
{
|
||||||
|
ReflectionHelper.GetProperty(typeof(Clickable), Name_AcceptClicksIfDisabled).SetValue(clickable, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bd2e9148da43349219f92b909651fbdc
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
public static class MemberInfoExtensions
|
||||||
|
{
|
||||||
|
public static bool HasCustomAttribute<T>(this MemberInfo memberInfo) where T : Attribute
|
||||||
|
{
|
||||||
|
return memberInfo.GetCustomAttribute<T>() != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4786119bc20d840f7b77860f7b16e18c
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
298
Alchemy/Assets/Alchemy/Editor/Internal/ReflectionHelper.cs
Normal file
298
Alchemy/Assets/Alchemy/Editor/Internal/ReflectionHelper.cs
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
public static class ReflectionHelper
|
||||||
|
{
|
||||||
|
static readonly Dictionary<(Type, string, BindingFlags, bool), FieldInfo> cacheFieldInfo = new();
|
||||||
|
static readonly Dictionary<(Type, string, BindingFlags, bool), MethodInfo> cacheMethodInfo = new();
|
||||||
|
static readonly Dictionary<(Type, string, BindingFlags, bool), PropertyInfo> cachePropertyInfo = new();
|
||||||
|
static readonly Dictionary<(Type, BindingFlags, bool), MemberInfo[]> cacheAllMembers = new();
|
||||||
|
|
||||||
|
static readonly Dictionary<(Type, string), Func<object, object>> cacheGetFieldValue = new();
|
||||||
|
static readonly Dictionary<(Type, string), Func<object, object>> cacheGetPropertyValue = new();
|
||||||
|
static readonly Dictionary<(Type, string), Func<object, object>> cacheGetMethodValue = new();
|
||||||
|
|
||||||
|
public static Func<object, object> CreateGetter(FieldInfo fieldInfo)
|
||||||
|
{
|
||||||
|
if (fieldInfo == null) return null;
|
||||||
|
if (fieldInfo.IsStatic)
|
||||||
|
{
|
||||||
|
Expression body = Expression.Convert(Expression.MakeMemberAccess(null, fieldInfo), typeof(object));
|
||||||
|
var lambda = Expression.Lambda<Func<object>>(body).Compile();
|
||||||
|
return _ => lambda();
|
||||||
|
}
|
||||||
|
if (fieldInfo.DeclaringType != null)
|
||||||
|
{
|
||||||
|
var objParam = Expression.Parameter(typeof(object), "obj");
|
||||||
|
var tParam = Expression.Convert(objParam, fieldInfo.DeclaringType);
|
||||||
|
Expression body = Expression.Convert(Expression.MakeMemberAccess(tParam, fieldInfo), typeof(object));
|
||||||
|
return Expression.Lambda<Func<object, object>>(body, objParam).Compile();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<object, object> CreateGetter(PropertyInfo propertyInfo)
|
||||||
|
{
|
||||||
|
if (propertyInfo == null) return null;
|
||||||
|
if (propertyInfo.GetGetMethod(true).IsStatic)
|
||||||
|
{
|
||||||
|
Expression body = Expression.Convert(Expression.MakeMemberAccess(null, propertyInfo), typeof(object));
|
||||||
|
var lambda = Expression.Lambda<Func<object>>(body).Compile();
|
||||||
|
return _ => lambda();
|
||||||
|
}
|
||||||
|
if (propertyInfo.DeclaringType != null)
|
||||||
|
{
|
||||||
|
var objParam = Expression.Parameter(typeof(object), "obj");
|
||||||
|
var tParam = Expression.Convert(objParam, propertyInfo.DeclaringType);
|
||||||
|
Expression body = Expression.Convert(Expression.MakeMemberAccess(tParam, propertyInfo), typeof(object));
|
||||||
|
return Expression.Lambda<Func<object, object>>(body, objParam).Compile();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Func<object, object> CreateGetter(MethodInfo methodInfo)
|
||||||
|
{
|
||||||
|
if (methodInfo == null) return null;
|
||||||
|
if (methodInfo.IsStatic)
|
||||||
|
{
|
||||||
|
Expression body = Expression.Convert(Expression.Call(null, methodInfo), typeof(object));
|
||||||
|
var lambda = Expression.Lambda<Func<object>>(body).Compile();
|
||||||
|
return _ => lambda();
|
||||||
|
}
|
||||||
|
if (methodInfo.DeclaringType != null)
|
||||||
|
{
|
||||||
|
var objParam = Expression.Parameter(typeof(object), "obj");
|
||||||
|
var tParam = Expression.Convert(objParam, methodInfo.DeclaringType);
|
||||||
|
Expression body = Expression.Convert(Expression.Call(tParam, methodInfo), typeof(object));
|
||||||
|
return Expression.Lambda<Func<object, object>>(body, objParam).Compile();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetFieldValue(object target, Type type, string name, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
if (!cacheGetFieldValue.TryGetValue((type, name), out var value))
|
||||||
|
{
|
||||||
|
FieldInfo info = type.GetField(name, bindingAttr);
|
||||||
|
value = CreateGetter(info);
|
||||||
|
cacheGetFieldValue.Add((type, name), value);
|
||||||
|
}
|
||||||
|
return value?.Invoke(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetPropertyValue(object target, Type type, string name, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
if (!cacheGetPropertyValue.TryGetValue((type, name), out var value))
|
||||||
|
{
|
||||||
|
PropertyInfo info = type.GetProperty(name, bindingAttr);
|
||||||
|
value = CreateGetter(info);
|
||||||
|
cacheGetPropertyValue.Add((type, name), value);
|
||||||
|
}
|
||||||
|
return value?.Invoke(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetMethodValue(object target, Type type, string name, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
if (!cacheGetMethodValue.TryGetValue((type, name), out var value))
|
||||||
|
{
|
||||||
|
MethodInfo info = type.GetMethod(name, bindingAttr);
|
||||||
|
value = CreateGetter(info);
|
||||||
|
cacheGetMethodValue.Add((type, name), value);
|
||||||
|
}
|
||||||
|
return value?.Invoke(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static FieldInfo GetField(Type type, string name, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, bool inherit = false)
|
||||||
|
{
|
||||||
|
FieldInfo info;
|
||||||
|
if (cacheFieldInfo.ContainsKey((type, name, bindingAttr, inherit)))
|
||||||
|
{
|
||||||
|
info = cacheFieldInfo[(type, name, bindingAttr, inherit)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (inherit)
|
||||||
|
{
|
||||||
|
info = GetAllFieldsIncludingInherited(type, bindingAttr).FirstOrDefault(x => x.Name == name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info = type.GetField(name, bindingAttr);
|
||||||
|
}
|
||||||
|
cacheFieldInfo.Add((type, name, bindingAttr, inherit), info);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<FieldInfo> GetAllFieldsIncludingInherited(Type type, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
if (type == null) return Enumerable.Empty<FieldInfo>();
|
||||||
|
return type.GetFields(bindingAttr).Concat(GetAllFieldsIncludingInherited(type.BaseType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PropertyInfo GetProperty(Type type, string name, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, bool inherit = false)
|
||||||
|
{
|
||||||
|
PropertyInfo info;
|
||||||
|
if (cachePropertyInfo.ContainsKey((type, name, bindingAttr, inherit)))
|
||||||
|
{
|
||||||
|
info = cachePropertyInfo[(type, name, bindingAttr, inherit)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (inherit)
|
||||||
|
{
|
||||||
|
info = GetAllPropertiesIncludingInherited(type, bindingAttr).FirstOrDefault(x => x.Name == name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info = type.GetProperty(name, bindingAttr);
|
||||||
|
}
|
||||||
|
cachePropertyInfo.Add((type, name, bindingAttr, inherit), info);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<PropertyInfo> GetAllPropertiesIncludingInherited(Type type, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
if (type == null) return Enumerable.Empty<PropertyInfo>();
|
||||||
|
return type.GetProperties(bindingAttr).Concat(GetAllPropertiesIncludingInherited(type.BaseType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MethodInfo GetMethod(Type type, string name, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, bool inherit = false)
|
||||||
|
{
|
||||||
|
MethodInfo info;
|
||||||
|
if (cacheMethodInfo.ContainsKey((type, name, bindingAttr, inherit)))
|
||||||
|
{
|
||||||
|
info = cacheMethodInfo[(type, name, bindingAttr, inherit)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (inherit)
|
||||||
|
{
|
||||||
|
info = GetAllMethodsIncludingInherited(type, bindingAttr).FirstOrDefault(x => x.Name == name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info = type.GetMethods(bindingAttr).Where(x => x.Name == name).FirstOrDefault();
|
||||||
|
}
|
||||||
|
cacheMethodInfo.Add((type, name, bindingAttr, inherit), info);
|
||||||
|
}
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<MethodInfo> GetAllMethodsIncludingInherited(Type type, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
if (type == null) return Enumerable.Empty<MethodInfo>();
|
||||||
|
return type.GetMethods(bindingAttr).Concat(GetAllMethodsIncludingInherited(type.BaseType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetValue(object target, string name, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, bool allowProperty = true, bool allowMethod = true)
|
||||||
|
{
|
||||||
|
if (target == null) return null;
|
||||||
|
Type type = target.GetType();
|
||||||
|
object result;
|
||||||
|
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
result = GetFieldValue(target, type, name, bindingAttr);
|
||||||
|
if (result != null) return result;
|
||||||
|
|
||||||
|
if (allowProperty)
|
||||||
|
{
|
||||||
|
result = GetPropertyValue(target, type, name, bindingAttr);
|
||||||
|
if (result != null) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allowMethod)
|
||||||
|
{
|
||||||
|
result = GetMethodValue(target, type, name, bindingAttr);
|
||||||
|
if (result != null) return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetValue(object target, string name, int index)
|
||||||
|
{
|
||||||
|
if (GetValue(target, name, allowMethod: false) is not IEnumerable enumerable) return null;
|
||||||
|
IEnumerator enumerator = enumerable.GetEnumerator();
|
||||||
|
|
||||||
|
for (int i = 0; i <= index; i++)
|
||||||
|
{
|
||||||
|
if (!enumerator.MoveNext()) return null;
|
||||||
|
}
|
||||||
|
return enumerator.Current;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetValueBool(object target, string name)
|
||||||
|
{
|
||||||
|
if (GetValue(target, name) is bool cond)
|
||||||
|
{
|
||||||
|
return cond;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MemberInfo[] GetMembers(Type type, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static, bool inherit = false)
|
||||||
|
{
|
||||||
|
if (cacheAllMembers.ContainsKey((type, bindingAttr, inherit)))
|
||||||
|
{
|
||||||
|
return cacheAllMembers[(type, bindingAttr, inherit)];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MemberInfo[] memberInfos;
|
||||||
|
if (inherit)
|
||||||
|
{
|
||||||
|
memberInfos = GetMembersIncludingInherited(type, bindingAttr).ToArray();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memberInfos = type.GetMembers(bindingAttr);
|
||||||
|
}
|
||||||
|
cacheAllMembers.Add((type, bindingAttr, inherit), memberInfos);
|
||||||
|
return memberInfos;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static IEnumerable<MemberInfo> GetMembersIncludingInherited(Type type, BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
|
||||||
|
{
|
||||||
|
if (type == null) return Enumerable.Empty<MemberInfo>();
|
||||||
|
return type.GetMembers(bindingAttr).Concat(GetMembersIncludingInherited(type.BaseType));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object Invoke(object target, string name, params object[] parameters)
|
||||||
|
{
|
||||||
|
if (target == null) return false;
|
||||||
|
Type type = target.GetType();
|
||||||
|
BindingFlags bindingAttr = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static;
|
||||||
|
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
var m = GetMethod(type, name, bindingAttr);
|
||||||
|
if (m != null) return m.Invoke(m.IsStatic ? null : target, parameters);
|
||||||
|
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetCollectionValue(object target, int index)
|
||||||
|
{
|
||||||
|
var type = target.GetType();
|
||||||
|
|
||||||
|
if (type.IsArray) return ((Array)target).GetValue(index);
|
||||||
|
else if (target is IList list) return list[index];
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7ddc52eddda264fba8fea9844f3c0f61
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.IMGUI.Controls;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
public sealed class SerializeReferenceDropdownItem : AdvancedDropdownItem
|
||||||
|
{
|
||||||
|
public readonly Type type;
|
||||||
|
public SerializeReferenceDropdownItem(Type type, string name) : base(name)
|
||||||
|
{
|
||||||
|
this.type = type;
|
||||||
|
if (type != null) icon = (Texture2D)EditorIcons.CsScriptIcon.image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class SerializeReferenceDropdown : AdvancedDropdown
|
||||||
|
{
|
||||||
|
private static readonly float headerHeight = EditorGUIUtility.singleLineHeight * 2f;
|
||||||
|
private static readonly int maxNamespaceNestCount = 16;
|
||||||
|
private static readonly string nullDisplayName = "(Null)";
|
||||||
|
|
||||||
|
private Type[] types;
|
||||||
|
public event Action<SerializeReferenceDropdownItem> onItemSelected;
|
||||||
|
|
||||||
|
public static void AddTo(AdvancedDropdownItem root, IEnumerable<Type> types)
|
||||||
|
{
|
||||||
|
var itemCount = 0;
|
||||||
|
var nullItem = new SerializeReferenceDropdownItem(null, nullDisplayName)
|
||||||
|
{
|
||||||
|
id = itemCount++
|
||||||
|
};
|
||||||
|
root.AddChild(nullItem);
|
||||||
|
|
||||||
|
var typeArray = types.OrderBy(x => x.FullName);
|
||||||
|
|
||||||
|
var isSingleNamespace = true;
|
||||||
|
var namespaces = new string[maxNamespaceNestCount];
|
||||||
|
foreach (Type type in typeArray)
|
||||||
|
{
|
||||||
|
var splittedTypePath = GetSplittedTypePath(type);
|
||||||
|
if (splittedTypePath.Length <= 1) continue;
|
||||||
|
|
||||||
|
for (int i = 0; (splittedTypePath.Length - 1) > i; i++)
|
||||||
|
{
|
||||||
|
var ns = namespaces[i];
|
||||||
|
if (ns == null)
|
||||||
|
{
|
||||||
|
namespaces[i] = splittedTypePath[i];
|
||||||
|
}
|
||||||
|
else if (ns != splittedTypePath[i])
|
||||||
|
{
|
||||||
|
isSingleNamespace = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSingleNamespace)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var type in typeArray)
|
||||||
|
{
|
||||||
|
var splittedTypePath = GetSplittedTypePath(type);
|
||||||
|
if (splittedTypePath.Length == 0) continue;
|
||||||
|
|
||||||
|
var parent = root;
|
||||||
|
|
||||||
|
if (!isSingleNamespace)
|
||||||
|
{
|
||||||
|
for (int i = 0; (splittedTypePath.Length - 1) > i; i++)
|
||||||
|
{
|
||||||
|
var foundItem = GetItem(parent, splittedTypePath[i]);
|
||||||
|
if (foundItem != null)
|
||||||
|
{
|
||||||
|
parent = foundItem;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var newItem = new AdvancedDropdownItem(splittedTypePath[i])
|
||||||
|
{
|
||||||
|
id = itemCount++,
|
||||||
|
};
|
||||||
|
parent.AddChild(newItem);
|
||||||
|
parent = newItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var item = new SerializeReferenceDropdownItem(type, ObjectNames.NicifyVariableName(splittedTypePath[^1]))
|
||||||
|
{
|
||||||
|
id = itemCount++
|
||||||
|
};
|
||||||
|
parent.AddChild(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static AdvancedDropdownItem GetItem(AdvancedDropdownItem parent, string name)
|
||||||
|
{
|
||||||
|
foreach (AdvancedDropdownItem item in parent.children)
|
||||||
|
{
|
||||||
|
if (item.name == name) return item;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SerializeReferenceDropdown(IEnumerable<Type> types, int maxLineCount, AdvancedDropdownState state) : base(state)
|
||||||
|
{
|
||||||
|
SetTypes(types);
|
||||||
|
minimumSize = new(minimumSize.x, EditorGUIUtility.singleLineHeight * maxLineCount + headerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetTypes(IEnumerable<Type> types)
|
||||||
|
{
|
||||||
|
this.types = types.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override AdvancedDropdownItem BuildRoot()
|
||||||
|
{
|
||||||
|
var root = new AdvancedDropdownItem("Select Type");
|
||||||
|
AddTo(root, types);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ItemSelected(AdvancedDropdownItem item)
|
||||||
|
{
|
||||||
|
base.ItemSelected(item);
|
||||||
|
if (item is SerializeReferenceDropdownItem dropdownItem)
|
||||||
|
{
|
||||||
|
onItemSelected?.Invoke(dropdownItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static string[] GetSplittedTypePath(Type type)
|
||||||
|
{
|
||||||
|
int splitIndex = type.FullName.LastIndexOf('.');
|
||||||
|
if (splitIndex >= 0)
|
||||||
|
{
|
||||||
|
return new string[] { type.FullName[..splitIndex], type.FullName[(splitIndex + 1)..] };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new string[] { type.Name };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fc340cedf3cee4da19aae647ce6299ce
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
public static class SerializedObjectExtensions
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e70b6aca000bb4a698521391868e2f98
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,293 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEditor;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
public static class SerializedPropertyExtensions
|
||||||
|
{
|
||||||
|
public static bool TryGetAttribute<TAttribute>(this SerializedProperty property, out TAttribute result) where TAttribute : Attribute
|
||||||
|
{
|
||||||
|
return TryGetAttribute(property, false, out result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetAttribute<TAttribute>(this SerializedProperty property, bool inherit, out TAttribute result) where TAttribute : Attribute
|
||||||
|
{
|
||||||
|
TAttribute attribute = GetAttribute<TAttribute>(property, inherit);
|
||||||
|
result = attribute;
|
||||||
|
return attribute != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static TAttribute GetAttribute<TAttribute>(this SerializedProperty property, bool inherit = false) where TAttribute : Attribute
|
||||||
|
{
|
||||||
|
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||||
|
return property.GetFieldInfo().GetCustomAttribute<TAttribute>(inherit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<TAttribute> GetAttributes<TAttribute>(this SerializedProperty property, bool inherit) where TAttribute : Attribute
|
||||||
|
{
|
||||||
|
if (property == null) throw new ArgumentNullException(nameof(property));
|
||||||
|
return property.GetFieldInfo().GetCustomAttributes<TAttribute>(inherit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float GetHeight(this SerializedProperty property)
|
||||||
|
{
|
||||||
|
return EditorGUI.GetPropertyHeight(property, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float GetHeight(this SerializedProperty property, bool includeChildren)
|
||||||
|
{
|
||||||
|
return EditorGUI.GetPropertyHeight(property, includeChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float GetHeight(this SerializedProperty property, GUIContent label, bool includeChildren)
|
||||||
|
{
|
||||||
|
return EditorGUI.GetPropertyHeight(property, label, includeChildren);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T GetValue<T>(this SerializedProperty property)
|
||||||
|
{
|
||||||
|
return GetNestedObject<T>(property.propertyPath, GetSerializedPropertyRootObject(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool SetValue<T>(this SerializedProperty property, T value)
|
||||||
|
{
|
||||||
|
object obj = GetSerializedPropertyRootObject(property);
|
||||||
|
var fieldStructure = property.propertyPath.Split('.');
|
||||||
|
for (int i = 0; i < fieldStructure.Length - 1; i++)
|
||||||
|
{
|
||||||
|
obj = GetFieldOrPropertyValue<object>(fieldStructure[i], obj);
|
||||||
|
}
|
||||||
|
var fieldName = fieldStructure.Last();
|
||||||
|
|
||||||
|
return SetFieldOrPropertyValue(fieldName, obj, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static readonly Regex IndexerRegex = new(@"[^0-9]+");
|
||||||
|
|
||||||
|
public static FieldInfo GetFieldInfo(this SerializedProperty property)
|
||||||
|
{
|
||||||
|
object target = property.serializedObject.targetObject;
|
||||||
|
var splits = property.propertyPath.Split('.');
|
||||||
|
|
||||||
|
var fieldInfo = ReflectionHelper.GetField(target.GetType(), splits[0]);
|
||||||
|
target = fieldInfo.GetValue(target);
|
||||||
|
|
||||||
|
for (var i = 1; i < splits.Length; i++)
|
||||||
|
{
|
||||||
|
if (target == null) return null;
|
||||||
|
|
||||||
|
if (splits[i] == "Array")
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
if (i >= splits.Length) continue;
|
||||||
|
|
||||||
|
var index = int.Parse(IndexerRegex.Replace(splits[i], string.Empty));
|
||||||
|
var targetType = target.GetType();
|
||||||
|
|
||||||
|
if (targetType.IsArray)target = (target as Array).GetValue(index);
|
||||||
|
else target = (target as IList)[index];
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (i >= splits.Length) continue;
|
||||||
|
|
||||||
|
targetType = target.GetType();
|
||||||
|
fieldInfo = ReflectionHelper.GetField(targetType, splits[i]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var targetType = target.GetType();
|
||||||
|
fieldInfo = ReflectionHelper.GetField(targetType, splits[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
target = fieldInfo?.GetValue(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type GetPropertyType(this SerializedProperty property, bool isCollectionType = false)
|
||||||
|
{
|
||||||
|
var fieldInfo = property.GetFieldInfo();
|
||||||
|
|
||||||
|
if (isCollectionType && property.isArray && property.propertyType != SerializedPropertyType.String)
|
||||||
|
return fieldInfo.FieldType.IsArray ?
|
||||||
|
fieldInfo.FieldType.GetElementType() :
|
||||||
|
fieldInfo.FieldType.GetGenericArguments()[0];
|
||||||
|
return fieldInfo.FieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object SetManagedReferenceType(this SerializedProperty property, Type type)
|
||||||
|
{
|
||||||
|
var obj = (type != null) ? Activator.CreateInstance(type) : null;
|
||||||
|
property.managedReferenceValue = obj;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GetManagedReferenceFieldTypeName(this SerializedProperty property)
|
||||||
|
{
|
||||||
|
var typeName = property.managedReferenceFieldTypename;
|
||||||
|
var splitIndex = typeName.IndexOf(' ');
|
||||||
|
return typeName[(splitIndex + 1)..];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Type GetManagedReferenceFieldType(this SerializedProperty property)
|
||||||
|
{
|
||||||
|
var typeName = property.managedReferenceFieldTypename;
|
||||||
|
var splitIndex = typeName.IndexOf(' ');
|
||||||
|
var assembly = Assembly.Load(typeName[..splitIndex]);
|
||||||
|
return assembly.GetType(typeName[(splitIndex + 1)..]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static UnityEngine.Object GetSerializedPropertyRootObject(SerializedProperty property)
|
||||||
|
{
|
||||||
|
return property.serializedObject.targetObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
static T GetNestedObject<T>(string path, object obj, bool includeAllBases = false)
|
||||||
|
{
|
||||||
|
var parts = path.Split('.');
|
||||||
|
for (int i = 0; i < parts.Length; i++)
|
||||||
|
{
|
||||||
|
string part = parts[i];
|
||||||
|
|
||||||
|
if (part == "Array")
|
||||||
|
{
|
||||||
|
var regex = new Regex(@"[^0-9]");
|
||||||
|
var countText = regex.Replace(parts[i + 1], "");
|
||||||
|
if (!int.TryParse(countText, out var index))
|
||||||
|
{
|
||||||
|
index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj = GetElementAtOrDefault(obj, index);
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
obj = GetFieldOrPropertyValue<object>(part, obj, includeAllBases);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (T)obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static object GetElementAtOrDefault(object arrayOrListObj, int index)
|
||||||
|
{
|
||||||
|
if (arrayOrListObj is IEnumerable<object> referenceEnumerable)
|
||||||
|
{
|
||||||
|
return referenceEnumerable.ElementAtOrDefault(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arrayOrListObj is IList valueList)
|
||||||
|
{
|
||||||
|
object result;
|
||||||
|
if (index < 0 || index >= valueList.Count)
|
||||||
|
{
|
||||||
|
Type listType = valueList.GetType();
|
||||||
|
Type elementType = listType.IsArray ? listType.GetElementType() : listType.GetGenericArguments()[0];
|
||||||
|
result = Activator.CreateInstance(elementType);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = valueList[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ArgumentException($"Can't parse {arrayOrListObj.GetType()} as Array or List");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object GetParentObject(this SerializedProperty property)
|
||||||
|
{
|
||||||
|
if (property == null) return null;
|
||||||
|
|
||||||
|
var path = property.propertyPath.Replace(".Array.data[", "[");
|
||||||
|
object obj = property.serializedObject.targetObject;
|
||||||
|
var elements = path.Split('.');
|
||||||
|
foreach (var element in elements)
|
||||||
|
{
|
||||||
|
if (element.Contains("["))
|
||||||
|
{
|
||||||
|
var elementName = element[..element.IndexOf("[")];
|
||||||
|
var index = Convert.ToInt32(element[element.IndexOf("[")..].Replace("[", "").Replace("]", ""));
|
||||||
|
obj = ReflectionHelper.GetValue(obj, elementName, index);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
obj = ReflectionHelper.GetValue(obj, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static T GetFieldOrPropertyValue<T>(string fieldName, object obj, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
|
{
|
||||||
|
var field = obj.GetType().GetField(fieldName, bindings);
|
||||||
|
if (field != null) return (T)field.GetValue(obj);
|
||||||
|
|
||||||
|
var property = obj.GetType().GetProperty(fieldName, bindings);
|
||||||
|
if (property != null) return (T)property.GetValue(obj, null);
|
||||||
|
|
||||||
|
if (includeAllBases)
|
||||||
|
{
|
||||||
|
foreach (var type in TypeHelper.GetBaseClassesAndInterfaces(obj.GetType()))
|
||||||
|
{
|
||||||
|
field = type.GetField(fieldName, bindings);
|
||||||
|
if (field != null) return (T)field.GetValue(obj);
|
||||||
|
|
||||||
|
property = type.GetProperty(fieldName, bindings);
|
||||||
|
if (property != null) return (T)property.GetValue(obj, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool SetFieldOrPropertyValue(string fieldName, object obj, object value, bool includeAllBases = false, BindingFlags bindings = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
|
||||||
|
{
|
||||||
|
var field = obj.GetType().GetField(fieldName, bindings);
|
||||||
|
if (field != null)
|
||||||
|
{
|
||||||
|
field.SetValue(obj, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var property = obj.GetType().GetProperty(fieldName, bindings);
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
property.SetValue(obj, value, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeAllBases)
|
||||||
|
{
|
||||||
|
foreach (var type in TypeHelper.GetBaseClassesAndInterfaces(obj.GetType()))
|
||||||
|
{
|
||||||
|
field = type.GetField(fieldName, bindings);
|
||||||
|
if (field != null)
|
||||||
|
{
|
||||||
|
field.SetValue(obj, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
property = type.GetProperty(fieldName, bindings);
|
||||||
|
if (property != null)
|
||||||
|
{
|
||||||
|
property.SetValue(obj, value, null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 03e4ebe06bfe34cb69ed7fae43969cec
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
54
Alchemy/Assets/Alchemy/Editor/Internal/TypeHelper.cs
Normal file
54
Alchemy/Assets/Alchemy/Editor/Internal/TypeHelper.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Internal
|
||||||
|
{
|
||||||
|
internal static class TypeHelper
|
||||||
|
{
|
||||||
|
public static object GetDefaultValue(Type type)
|
||||||
|
{
|
||||||
|
if (!type.IsValueType)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object CreateDefaultInstance(Type type)
|
||||||
|
{
|
||||||
|
if (type == typeof(string)) return "";
|
||||||
|
if (type.IsSubclassOf(typeof(UnityEngine.Object))) return null;
|
||||||
|
return Activator.CreateInstance(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Type> GetBaseClassesAndInterfaces(Type type, bool includeSelf = false)
|
||||||
|
{
|
||||||
|
List<Type> allTypes = new();
|
||||||
|
|
||||||
|
if (includeSelf) allTypes.Add(type);
|
||||||
|
|
||||||
|
if (type.BaseType == typeof(object))
|
||||||
|
{
|
||||||
|
allTypes.AddRange(type.GetInterfaces());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
allTypes.AddRange(
|
||||||
|
Enumerable.Repeat(type.BaseType, 1)
|
||||||
|
.Concat(type.GetInterfaces())
|
||||||
|
.Concat(GetBaseClassesAndInterfaces(type.BaseType))
|
||||||
|
.Distinct()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return allTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool HasDefaultConstructor(Type type)
|
||||||
|
{
|
||||||
|
return type.GetConstructors().Any(t => t.GetParameters().Count() == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/Internal/TypeHelper.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/Internal/TypeHelper.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 154d82e962f674436ad4e4d39b9438a3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
14
Alchemy/Assets/Alchemy/Editor/PropertyGroupDrawer.cs
Normal file
14
Alchemy/Assets/Alchemy/Editor/PropertyGroupDrawer.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor
|
||||||
|
{
|
||||||
|
public abstract class PropertyGroupDrawer
|
||||||
|
{
|
||||||
|
public abstract VisualElement CreateRootElement(string label);
|
||||||
|
public virtual VisualElement GetGroupElement(Attribute attribute) => null;
|
||||||
|
|
||||||
|
public string UniqueId => _uniqueId;
|
||||||
|
internal string _uniqueId;
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/PropertyGroupDrawer.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/PropertyGroupDrawer.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6c870ede4caad4eb2accdd389ae0a436
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
48
Alchemy/Assets/Alchemy/Editor/PropertyProcessor.cs
Normal file
48
Alchemy/Assets/Alchemy/Editor/PropertyProcessor.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor
|
||||||
|
{
|
||||||
|
public abstract class PropertyProcessor
|
||||||
|
{
|
||||||
|
internal SerializedObject _serializedObject;
|
||||||
|
internal SerializedProperty _serializedProperty;
|
||||||
|
internal object _target;
|
||||||
|
internal MemberInfo _memberInfo;
|
||||||
|
internal Attribute _attribute;
|
||||||
|
internal VisualElement _element;
|
||||||
|
|
||||||
|
public SerializedObject SerializedObject => _serializedObject;
|
||||||
|
public SerializedProperty SerializedProperty => _serializedProperty;
|
||||||
|
public object Target => _target;
|
||||||
|
public MemberInfo MemberInfo => _memberInfo;
|
||||||
|
public Attribute Attribute => _attribute;
|
||||||
|
public VisualElement Element => _element;
|
||||||
|
|
||||||
|
public abstract void Execute();
|
||||||
|
|
||||||
|
internal static void ExecuteProcessors(SerializedObject serializedObject, SerializedProperty property, object target, MemberInfo memberInfo, VisualElement memberElement)
|
||||||
|
{
|
||||||
|
var attributes = memberInfo.GetCustomAttributes();
|
||||||
|
var processorTypes = TypeCache.GetTypesWithAttribute(typeof(CustomPropertyProcessorAttribute));
|
||||||
|
foreach (var attribute in attributes)
|
||||||
|
{
|
||||||
|
var processorType = processorTypes.FirstOrDefault(x => x.IsSubclassOf(typeof(PropertyProcessor)) && x.GetCustomAttribute<CustomPropertyProcessorAttribute>().targetAttributeType == attribute.GetType());
|
||||||
|
if (processorType == null) continue;
|
||||||
|
|
||||||
|
var processor = (PropertyProcessor)Activator.CreateInstance(processorType);
|
||||||
|
processor._serializedObject = serializedObject;
|
||||||
|
processor._serializedProperty = property;
|
||||||
|
processor._target = target;
|
||||||
|
processor._memberInfo = memberInfo;
|
||||||
|
processor._attribute = attribute;
|
||||||
|
processor._element = memberElement;
|
||||||
|
|
||||||
|
processor.Execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
Alchemy/Assets/Alchemy/Editor/PropertyProcessor.cs.meta
Normal file
11
Alchemy/Assets/Alchemy/Editor/PropertyProcessor.cs.meta
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c6e4cd4adfd574c4d8a27f6c20e60102
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,20 @@
|
|||||||
|
using UnityEditor.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Editor.Processors
|
||||||
|
{
|
||||||
|
public abstract class TrackSerializedObjectPropertyProcessor : PropertyProcessor
|
||||||
|
{
|
||||||
|
public override void Execute()
|
||||||
|
{
|
||||||
|
Element.TrackSerializedObjectValue(SerializedObject, x =>
|
||||||
|
{
|
||||||
|
OnInspectorChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
OnInspectorChanged();
|
||||||
|
Element.schedule.Execute(() => OnInspectorChanged());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void OnInspectorChanged();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c20e0e8f7324b4550ac2e5ba8589c972
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Generator.meta
Normal file
8
Alchemy/Assets/Alchemy/Generator.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3b82931c0193440458736fd4826c52de
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
BIN
Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll
Normal file
BIN
Alchemy/Assets/Alchemy/Generator/Alchemy.SourceGenerator.dll
Normal file
Binary file not shown.
@ -0,0 +1,90 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 28c3206aebe20494dbfa4cf9983b1cee
|
||||||
|
labels:
|
||||||
|
- RoslynAnalyzer
|
||||||
|
PluginImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
iconMap: {}
|
||||||
|
executionOrder: {}
|
||||||
|
defineConstraints: []
|
||||||
|
isPreloaded: 0
|
||||||
|
isOverridable: 0
|
||||||
|
isExplicitlyReferenced: 0
|
||||||
|
validateReferences: 1
|
||||||
|
platformData:
|
||||||
|
- first:
|
||||||
|
: Any
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
Exclude Android: 1
|
||||||
|
Exclude Editor: 1
|
||||||
|
Exclude Linux64: 1
|
||||||
|
Exclude OSXUniversal: 1
|
||||||
|
Exclude WebGL: 1
|
||||||
|
Exclude Win: 1
|
||||||
|
Exclude Win64: 1
|
||||||
|
Exclude iOS: 1
|
||||||
|
- first:
|
||||||
|
Android: Android
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
AndroidSharedLibraryType: Executable
|
||||||
|
CPU: ARMv7
|
||||||
|
- first:
|
||||||
|
Any:
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings: {}
|
||||||
|
- first:
|
||||||
|
Editor: Editor
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
CPU: AnyCPU
|
||||||
|
DefaultValueInitialized: true
|
||||||
|
OS: AnyOS
|
||||||
|
- first:
|
||||||
|
Standalone: Linux64
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
CPU: None
|
||||||
|
- first:
|
||||||
|
Standalone: OSXUniversal
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
CPU: None
|
||||||
|
- first:
|
||||||
|
Standalone: Win
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
CPU: None
|
||||||
|
- first:
|
||||||
|
Standalone: Win64
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
CPU: None
|
||||||
|
- first:
|
||||||
|
Windows Store Apps: WindowsStoreApps
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
CPU: AnyCPU
|
||||||
|
- first:
|
||||||
|
iPhone: iOS
|
||||||
|
second:
|
||||||
|
enabled: 0
|
||||||
|
settings:
|
||||||
|
AddToEmbeddedBinaries: false
|
||||||
|
CPU: AnyCPU
|
||||||
|
CompileFlags:
|
||||||
|
FrameworkDependencies:
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Runtime.meta
Normal file
8
Alchemy/Assets/Alchemy/Runtime.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2b496f89677ec4ca8882579908a7718e
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
23
Alchemy/Assets/Alchemy/Runtime/Alchemy.asmdef
Normal file
23
Alchemy/Assets/Alchemy/Runtime/Alchemy.asmdef
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "Alchemy",
|
||||||
|
"rootNamespace": "",
|
||||||
|
"references": [
|
||||||
|
"GUID:2765e68924a08a94ea0ea66b31c0168f",
|
||||||
|
"GUID:d8b63aba1907145bea998dd612889d6b"
|
||||||
|
],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": true,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [
|
||||||
|
{
|
||||||
|
"name": "com.unity.serialization",
|
||||||
|
"expression": "",
|
||||||
|
"define": "ALCHEMY_SUPPORT_SERIALIZATION"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
7
Alchemy/Assets/Alchemy/Runtime/Alchemy.asmdef.meta
Normal file
7
Alchemy/Assets/Alchemy/Runtime/Alchemy.asmdef.meta
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 88be65f96b86746888c927a5c8ff3534
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Runtime/Inspector.meta
Normal file
8
Alchemy/Assets/Alchemy/Runtime/Inspector.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b55a0053f22e5436bbd5290c70f8b038
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
33
Alchemy/Assets/Alchemy/Runtime/Inspector/GroupAttributes.cs
Normal file
33
Alchemy/Assets/Alchemy/Runtime/Inspector/GroupAttributes.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
namespace Alchemy.Inspector
|
||||||
|
{
|
||||||
|
public sealed class GroupAttribute : PropertyGroupAttribute
|
||||||
|
{
|
||||||
|
public GroupAttribute(string groupPath) : base(groupPath) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class BoxGroupAttribute : PropertyGroupAttribute
|
||||||
|
{
|
||||||
|
public BoxGroupAttribute(string groupPath) : base(groupPath) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class TabGroupAttribute : PropertyGroupAttribute
|
||||||
|
{
|
||||||
|
public TabGroupAttribute(string groupPath, string tabName) : base(groupPath)
|
||||||
|
{
|
||||||
|
TabName = tabName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TabName { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class FoldoutGroupAttribute : PropertyGroupAttribute
|
||||||
|
{
|
||||||
|
public FoldoutGroupAttribute(string groupPath) : base(groupPath) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class HorizontalGroupAttribute : PropertyGroupAttribute
|
||||||
|
{
|
||||||
|
public HorizontalGroupAttribute(string groupPath) : base(groupPath) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a66a786b5de34430bae43bb49a6b952f
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
185
Alchemy/Assets/Alchemy/Runtime/Inspector/InspectorAttributes.cs
Normal file
185
Alchemy/Assets/Alchemy/Runtime/Inspector/InspectorAttributes.cs
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UIElements;
|
||||||
|
|
||||||
|
namespace Alchemy.Inspector
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property)]
|
||||||
|
public sealed class DisableAlchemyEditorAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public sealed class HideScriptFieldAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class OrderAttribute : Attribute
|
||||||
|
{
|
||||||
|
public OrderAttribute(int order) => Order = order;
|
||||||
|
public int Order { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Method)]
|
||||||
|
public sealed class ButtonAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||||
|
public sealed class ShowInInspectorAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||||
|
public sealed class AssetsOnlyAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||||
|
public sealed class InlineEditorAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class IndentAttribute : Attribute
|
||||||
|
{
|
||||||
|
public IndentAttribute(int indent = 1) => this.indent = indent;
|
||||||
|
public readonly int indent;
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class ReadOnlyAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class HideInPlayModeAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class HideInEditModeAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class DisableInPlayModeAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class DisableInEditModeAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class HideLabelAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class LabelTextAttribute : Attribute
|
||||||
|
{
|
||||||
|
public LabelTextAttribute(string text) => Text = text;
|
||||||
|
public string Text { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class HideIfAttribute : Attribute
|
||||||
|
{
|
||||||
|
public HideIfAttribute(string condition) => Condition = condition;
|
||||||
|
public string Condition { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class ShowIfAttribute : Attribute
|
||||||
|
{
|
||||||
|
public ShowIfAttribute(string condition) => Condition = condition;
|
||||||
|
public string Condition { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class DisableIfAttribute : Attribute
|
||||||
|
{
|
||||||
|
public DisableIfAttribute(string condition) => Condition = condition;
|
||||||
|
public string Condition { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class EnableIfAttribute : Attribute
|
||||||
|
{
|
||||||
|
public EnableIfAttribute(string condition) => Condition = condition;
|
||||||
|
public string Condition { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||||
|
public sealed class RequiredAttribute : Attribute
|
||||||
|
{
|
||||||
|
public RequiredAttribute() => Message = null;
|
||||||
|
public RequiredAttribute(string message) => Message = message;
|
||||||
|
public string Message { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||||
|
public sealed class ValidateInputAttribute : Attribute
|
||||||
|
{
|
||||||
|
public ValidateInputAttribute(string condition)
|
||||||
|
{
|
||||||
|
Condition = condition;
|
||||||
|
Message = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValidateInputAttribute(string condition, string message)
|
||||||
|
{
|
||||||
|
Condition = condition;
|
||||||
|
Message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Condition { get; }
|
||||||
|
public string Message { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class HelpBoxAttribute : Attribute
|
||||||
|
{
|
||||||
|
public HelpBoxAttribute(string message, HelpBoxMessageType messageType = HelpBoxMessageType.Info)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
MessageType = messageType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Message { get; }
|
||||||
|
public HelpBoxMessageType MessageType { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class HorizontalLineAttribute : Attribute
|
||||||
|
{
|
||||||
|
public HorizontalLineAttribute()
|
||||||
|
{
|
||||||
|
Color = EditorGUIUtility.isProSkin ? new Color(0.4f, 0.4f, 0.4f) : new Color(0.6f, 0.6f, 0.6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HorizontalLineAttribute(float r, float g, float b)
|
||||||
|
{
|
||||||
|
Color = new Color(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HorizontalLineAttribute(float r, float g, float b, float a)
|
||||||
|
{
|
||||||
|
Color = new Color(r, g, b, a);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color Color { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class TitleAttribute : Attribute
|
||||||
|
{
|
||||||
|
public TitleAttribute(string titleText)
|
||||||
|
{
|
||||||
|
TitleText = titleText;
|
||||||
|
SubitleText = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TitleAttribute(string titleText, string subtitle)
|
||||||
|
{
|
||||||
|
TitleText = titleText;
|
||||||
|
SubitleText = subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public string TitleText { get; }
|
||||||
|
public string SubitleText { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Method)]
|
||||||
|
public sealed class BlockquoteAttribute : Attribute
|
||||||
|
{
|
||||||
|
public BlockquoteAttribute(string text)
|
||||||
|
{
|
||||||
|
Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Text { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 57ea17d9b1da64854af7ce0372ad77db
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,17 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Alchemy.Inspector
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class of attributes for creating Group on Inspector
|
||||||
|
/// </summary>
|
||||||
|
public abstract class PropertyGroupAttribute : Attribute
|
||||||
|
{
|
||||||
|
public PropertyGroupAttribute(string groupPath)
|
||||||
|
{
|
||||||
|
GroupPath = groupPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GroupPath { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 921947c074aa248eead17b72da8587ab
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Runtime/Serialization.meta
Normal file
8
Alchemy/Assets/Alchemy/Runtime/Serialization.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a6213c979732d44e9aa1b5339c5b85c8
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,10 @@
|
|||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
namespace Alchemy.Serialization
|
||||||
|
{
|
||||||
|
public interface IAlchemySerializationCallbackReceiver
|
||||||
|
{
|
||||||
|
void OnBeforeSerialize();
|
||||||
|
void OnAfterDeserialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6d700e58a5313415ab4ff85f38be75d2
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,15 @@
|
|||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Alchemy.Serialization
|
||||||
|
{
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
|
||||||
|
public sealed class AlchemySerializeAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
|
||||||
|
public sealed class ShowAlchemySerializationDataAttribute : Attribute { }
|
||||||
|
|
||||||
|
[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
|
||||||
|
public sealed class AlchemySerializeFieldAttribute : Attribute { }
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: face8c3c5e0a54daf99288ded0d16cc0
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,109 @@
|
|||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Serialization.Json;
|
||||||
|
using Unity.Mathematics;
|
||||||
|
|
||||||
|
namespace Alchemy.Serialization.Internal
|
||||||
|
{
|
||||||
|
public static class SerializationHelper
|
||||||
|
{
|
||||||
|
public static string ToJson<T>(T target, IList<UnityEngine.Object> unityObjectReferences)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsUnityObject<T>())
|
||||||
|
{
|
||||||
|
var index = unityObjectReferences.IndexOf(target as UnityEngine.Object);
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
unityObjectReferences.Add(target as UnityEngine.Object);
|
||||||
|
index = unityObjectReferences.Count - 1;
|
||||||
|
}
|
||||||
|
return index.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonSerialization.ToJson(target, new JsonSerializationParameters()
|
||||||
|
{
|
||||||
|
UserDefinedAdapters = new()
|
||||||
|
{
|
||||||
|
new UnityObjectAdapter(unityObjectReferences)
|
||||||
|
},
|
||||||
|
SerializedType = target.GetType()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T FromJson<T>(string json, IList<UnityEngine.Object> unityObjectReferences)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(json)) return default;
|
||||||
|
return ModifiedFromJson<T>(json, new JsonSerializationParameters()
|
||||||
|
{
|
||||||
|
UserDefinedAdapters = new()
|
||||||
|
{
|
||||||
|
new UnityObjectAdapter(unityObjectReferences)
|
||||||
|
},
|
||||||
|
SerializedType = typeof(T)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void FromJsonOverride<T>(string json, ref T container, IList<UnityEngine.Object> unityObjectReferences)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(json)) return;
|
||||||
|
ModifiedFromJsonOverride(json, ref container, new JsonSerializationParameters()
|
||||||
|
{
|
||||||
|
UserDefinedAdapters = new()
|
||||||
|
{
|
||||||
|
new UnityObjectAdapter(unityObjectReferences)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fixed a bug in the Unity.Serialization package (crash when executed at certain times)
|
||||||
|
/// Reference: https://forum.unity.com/threads/about-the-com-unity-serialization-package.1512431/
|
||||||
|
/// </summary>
|
||||||
|
static unsafe T ModifiedFromJson<T>(string json, JsonSerializationParameters parameters = default)
|
||||||
|
{
|
||||||
|
fixed (char* buffer = json)
|
||||||
|
{
|
||||||
|
using var reader = new SerializedObjectReader(buffer, json.Length, GetDefaultConfigurationForString(json, parameters));
|
||||||
|
reader.Read(out var view);
|
||||||
|
return JsonSerialization.FromJson<T>(view, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsafe void ModifiedFromJsonOverride<T>(string json, ref T container, JsonSerializationParameters parameters = default)
|
||||||
|
{
|
||||||
|
fixed (char* buffer = json)
|
||||||
|
{
|
||||||
|
using var reader = new SerializedObjectReader(buffer, json.Length, GetDefaultConfigurationForString(json, parameters));
|
||||||
|
reader.Read(out var view);
|
||||||
|
JsonSerialization.FromJsonOverride(view, ref container, parameters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// copied from internal method in JsonSerialization
|
||||||
|
static SerializedObjectReaderConfiguration GetDefaultConfigurationForString(string json, JsonSerializationParameters parameters = default)
|
||||||
|
{
|
||||||
|
var configuration = SerializedObjectReaderConfiguration.Default;
|
||||||
|
|
||||||
|
configuration.UseReadAsync = false;
|
||||||
|
configuration.ValidationType = parameters.DisableValidation ? JsonValidationType.None : parameters.Simplified ? JsonValidationType.Simple : JsonValidationType.Standard;
|
||||||
|
configuration.BlockBufferSize = math.max(json.Length * sizeof(char), 16);
|
||||||
|
configuration.TokenBufferSize = math.max(json.Length / 2, 16);
|
||||||
|
configuration.OutputBufferSize = math.max(json.Length * sizeof(char), 16);
|
||||||
|
configuration.StripStringEscapeCharacters = parameters.StringEscapeHandling;
|
||||||
|
|
||||||
|
return configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsUnityObject<T>()
|
||||||
|
{
|
||||||
|
var type = typeof(T);
|
||||||
|
return type == typeof(UnityEngine.Object) || type.IsSubclassOf(typeof(UnityEngine.Object));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a4c4286ac515f4ee9abc6244a1acda54
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,66 @@
|
|||||||
|
#if ALCHEMY_SUPPORT_SERIALIZATION
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using Unity.Serialization.Json;
|
||||||
|
|
||||||
|
namespace Alchemy.Serialization.Internal
|
||||||
|
{
|
||||||
|
public sealed class UnityObjectAdapter : IContravariantJsonAdapter<UnityEngine.Object>, IJsonAdapter<UnityEngine.Object>
|
||||||
|
{
|
||||||
|
public UnityObjectAdapter(IList<UnityEngine.Object> objectReferenceList)
|
||||||
|
{
|
||||||
|
this.ObjectReferenceList = objectReferenceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
IList<UnityEngine.Object> ObjectReferenceList { get; }
|
||||||
|
|
||||||
|
public object Deserialize(IJsonDeserializationContext context)
|
||||||
|
{
|
||||||
|
return DeserializeInternal(context.SerializedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnityEngine.Object Deserialize(in JsonDeserializationContext<UnityEngine.Object> context)
|
||||||
|
{
|
||||||
|
return DeserializeInternal(context.SerializedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Serialize(IJsonSerializationContext context, UnityEngine.Object value)
|
||||||
|
{
|
||||||
|
SerializeInternal(context.Writer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Serialize(in JsonSerializationContext<UnityEngine.Object> context, UnityEngine.Object value)
|
||||||
|
{
|
||||||
|
SerializeInternal(context.Writer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
void SerializeInternal(JsonWriter writer, UnityEngine.Object value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
writer.WriteNull();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = ObjectReferenceList.IndexOf(value);
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
ObjectReferenceList.Add(value);
|
||||||
|
index = ObjectReferenceList.Count - 1;
|
||||||
|
}
|
||||||
|
writer.WriteValue(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
UnityEngine.Object DeserializeInternal(SerializedValueView view)
|
||||||
|
{
|
||||||
|
if (view.IsNull()) return null;
|
||||||
|
|
||||||
|
var index = view.AsInt32();
|
||||||
|
if (index < 0 || index >= ObjectReferenceList.Count) return null;
|
||||||
|
return ObjectReferenceList[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: df462af0c52084630ae0e13dbc9596d3
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Samples~.meta
Normal file
8
Alchemy/Assets/Alchemy/Samples~.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 55766883e34d9443daef03bbdf57ea2f
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
8
Alchemy/Assets/Alchemy/Samples~/Samples.meta
Normal file
8
Alchemy/Assets/Alchemy/Samples~/Samples.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f5fa6db7b90db4598bada99fed111736
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
@ -0,0 +1,17 @@
|
|||||||
|
%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: 6655ad5b46ca640999921612370243df, type: 3}
|
||||||
|
m_Name: SampleScriptableObject
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
foo: 0
|
||||||
|
bar: {x: 0, y: 0}
|
||||||
|
baz: {fileID: 0}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user