using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEngine; namespace Unity.VisualScripting { public class ImplementationInspector : Inspector where T : class { public ImplementationInspector(Metadata metadata) : base(metadata) { } private bool compactSelector = false; private bool hideSelector = false; private bool indentImplementation = true; public Metadata implementationMetadata => metadata.value != null ? metadata.Cast(metadata.valueType) : null; protected virtual GUIContent nullSelectorLabel { get; } = new GUIContent("(Nothing)"); protected virtual GUIContent selectorLabel => new GUIContent(" " + metadata.value, metadata.valueType.Icon()?[IconSize.Small]); protected virtual EditorTexture typeSelectorIcon => implementationMetadata.definedType.Icon(); protected sealed override float GetHeight(float width, GUIContent label) { var height = 0f; var widthWithoutLabel = WidthWithoutLabel(metadata, width, label); if (metadata.value == null) { height = GetNullSelectorHeight(widthWithoutLabel); } else if (compactSelector) { height = Mathf.Max(GetCompactSelectorHeight(), GetImplementationHeight(GetCompactedImplementationWidth(widthWithoutLabel))); } else { height += GetSelectorHeight(widthWithoutLabel); height += EditorGUIUtility.standardVerticalSpacing; height += GetImplementationHeight(widthWithoutLabel); } return HeightWithLabel(metadata, width, height, label); } private float GetNullSelectorHeight(float width) { return EditorGUIUtility.singleLineHeight; } private float GetSelectorHeight(float width) { return EditorGUIUtility.singleLineHeight; } private float GetCompactSelectorHeight() { return Styles.compactSelector.fixedHeight; } private float GetImplementationHeight(float implementationWidth) { return LudiqGUI.GetInspectorHeight(this, implementationMetadata, implementationWidth, GUIContent.none); } private float GetCompactedImplementationWidth(float width) { return width - Styles.spaceAroundCompactSelector - compactSelectorWidth; } protected sealed override void OnGUI(Rect position, GUIContent label) { if (metadata.value == null) { position = BeginLabeledBlock(metadata, position, label); var nullSelectorPosition = new Rect ( position.x, y, position.width, GetNullSelectorHeight(position.width) ); OnNullSelectorGUI(nullSelectorPosition); } else if (hideSelector) { position = BeginLabeledBlock(metadata, position, label); OnImplementationGUI(position); } else if (compactSelector) { position = BeginLabeledBlock(metadata, position, label); var implementationWidth = GetCompactedImplementationWidth(position.width); var compactSelectorPosition = new Rect ( position.x, y, compactSelectorWidth, GetCompactSelectorHeight() ); var implementationPosition = new Rect ( compactSelectorPosition.xMax + Styles.spaceAroundCompactSelector, y, implementationWidth, GetImplementationHeight(implementationWidth) ); OnCompactSelectorGUI(compactSelectorPosition); OnImplementationGUI(implementationPosition); } else { if (indentImplementation) { position = BeginLabeledBlock(metadata, position, label); } else { position = BeginLabeledBlock(metadata, position, GUIContent.none); } var selectorPosition = position.VerticalSection(ref y, GetSelectorHeight(position.width)); if (!indentImplementation) { selectorPosition = PrefixLabel(metadata, selectorPosition, label); } y += EditorGUIUtility.standardVerticalSpacing; var implementationPosition = position.VerticalSection(ref y, GetImplementationHeight(position.width)); OnSelectorGUI(selectorPosition); OnImplementationGUI(implementationPosition); } EndBlock(metadata); } private void OnImplementationGUI(Rect implementationPosition) { LudiqGUI.Inspector(implementationMetadata, implementationPosition, GUIContent.none); } private void OnNullSelectorGUI(Rect nullSelectorPosition) { EditorGUI.BeginChangeCheck(); var newImplementation = LudiqGUI.FuzzyPopup ( nullSelectorPosition, GetImplementationOptions, null, nullSelectorLabel ); if (EditorGUI.EndChangeCheck()) { ChangeImplementation(newImplementation); } } private void OnSelectorGUI(Rect selectorPosition) { EditorGUI.BeginChangeCheck(); var newImplementation = LudiqGUI.FuzzyPopup ( selectorPosition, GetImplementationOptions, null, selectorLabel ); if (EditorGUI.EndChangeCheck()) { ChangeImplementation(newImplementation); } } private void OnCompactSelectorGUI(Rect selectorPosition) { EditorGUI.BeginChangeCheck(); var newImplementation = LudiqGUI.FuzzyPopup ( selectorPosition, GetImplementationOptions, null, Styles.showCompactSelectorIcon ? new GUIContent(typeSelectorIcon?[IconSize.Small]) : Styles.compactSelectorWithoutIconContent, Styles.compactSelector ); if (EditorGUI.EndChangeCheck()) { ChangeImplementation(newImplementation); } } private void ChangeImplementation(object newImplementation) { metadata.RecordUndo(); metadata.UnlinkChildren(); metadata.value = CreateImplementation(newImplementation); metadata.InferOwnerFromParent(); } protected virtual IFuzzyOptionTree GetImplementationOptions() { return new TypeOptionTree(implementors) { rootMode = TypeOptionTree.RootMode.Types }; } protected virtual T CreateImplementation(object option) { return (T)(((Type)option).Instantiate()); } static ImplementationInspector() { implementors = Codebase.editorTypes .Where(type => type.HasAttribute()) .OrderBy(type => type.GetAttribute()?.order ?? int.MaxValue) .SelectMany(type => type.GetAttributes()) .Select(attribute => attribute.type) .Where(type => type != typeof(T) && typeof(T).IsAssignableFrom(type)) .ToList(); } private static readonly List implementors; private static float compactSelectorWidth => Styles.compactSelector.fixedWidth; public static float compactSelectorSubtractedWidth => compactSelectorWidth + Styles.spaceAroundCompactSelector; public static class Styles { static Styles() { compactSelectorWithoutIcon = new GUIStyle { imagePosition = ImagePosition.ImageOnly, contentOffset = new Vector2(1, -1), clipping = TextClipping.Clip, alignment = TextAnchor.MiddleCenter, fixedWidth = 9, fixedHeight = EditorGUIUtility.singleLineHeight }; compactSelectorWithoutIconContent = new GUIContent(BoltCore.Resources.LoadTexture("TypeSelector.png", new TextureResolution[] { 9, 18 }, CreateTextureOptions.PixelPerfect)?[9]); compactSelectorWithIcon = new GUIStyle { imagePosition = ImagePosition.ImageOnly, contentOffset = new Vector2(0, 0), alignment = TextAnchor.MiddleCenter, fixedWidth = 16, fixedHeight = EditorGUIUtility.singleLineHeight }; } public static readonly GUIStyle compactSelectorWithoutIcon; public static readonly GUIStyle compactSelectorWithIcon; public static readonly GUIContent compactSelectorWithoutIconContent; public static readonly int spaceAroundCompactSelector = 3; public static readonly bool showCompactSelectorIcon = true; public static GUIStyle compactSelector => showCompactSelectorIcon ? compactSelectorWithIcon : compactSelectorWithoutIcon; } } }