├── Runtime ├── ObjectPicker.asmdef ├── ObjectSourceType.cs ├── PickerType.cs ├── ObjectPicker.asmdef.meta ├── ObjectProviderType.cs ├── AutoPickMode.cs.meta ├── PickerType.cs.meta ├── ObjectProviderType.cs.meta ├── ObjectSourceType.cs.meta ├── ObjectTypePair.cs.meta ├── PickleAttribute.cs.meta ├── ObjectTypePair.cs ├── AutoPickMode.cs └── PickleAttribute.cs ├── package.json ├── LICENSE.md.meta ├── README.MD.meta ├── package.json.meta ├── Editor.meta ├── Runtime.meta ├── Editor ├── Lookup Strategies.meta ├── ObjectPicker.Editor.asmdef.meta ├── Lookup Strategies │ ├── IObjectProvider.cs │ ├── IObjectProvider.cs.meta │ ├── AssetObjectProvider.cs.meta │ ├── ChildObjectsProvider.cs.meta │ ├── ObjectProviderUnion.cs.meta │ ├── RootChildrenObjectsProvider.cs │ ├── SceneObjectsProvider.cs.meta │ ├── ChildComponentsProvider.cs.meta │ ├── ObjectProviderUtilities.cs.meta │ ├── SceneComponentsProvider.cs.meta │ ├── PrefabComponentObjectProvider.cs.meta │ ├── RootChildrenComponentsProvider.cs.meta │ ├── RootChildrenObjectsProvider.cs.meta │ ├── RootChildrenComponentsProvider.cs │ ├── ObjectProviderUnion.cs │ ├── ChildComponentsProvider.cs │ ├── ChildObjectsProvider.cs │ ├── PrefabComponentObjectProvider.cs │ ├── SceneComponentsProvider.cs │ ├── SceneObjectsProvider.cs │ ├── AssetObjectProvider.cs │ └── ObjectProviderUtilities.cs ├── IObjectPicker.cs ├── IObjectPicker.cs.meta ├── PickleField.cs.meta ├── PickleSettings.cs.meta ├── ObjectFieldDrawer.cs.meta ├── ObjectPickerWindow.cs.meta ├── ObjectPickerDropdown.cs.meta ├── PickleAttributeDrawer.cs.meta ├── ObjectPickerWindowBuilder.cs.meta ├── ObjectPicker.Editor.asmdef ├── ObjectPickerWindowBuilder.cs ├── ObjectPickerDropdown.cs ├── PickleField.cs ├── ObjectFieldDrawer.cs ├── PickleAttributeDrawer.cs ├── PickleSettings.cs └── ObjectPickerWindow.cs ├── LICENSE.md └── README.MD /Runtime/ObjectPicker.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ObjectPicker" 3 | } 4 | -------------------------------------------------------------------------------- /Runtime/ObjectSourceType.cs: -------------------------------------------------------------------------------- 1 | namespace Pickle 2 | { 3 | public enum ObjectSourceType 4 | { 5 | Asset = 1 << 0, 6 | Scene = 1 << 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.mpozek.pickle", 3 | "version": "1.2.0", 4 | "unity": "2020.3", 5 | "displayName": "Pickle", 6 | "description": "A better Object picker for Unity" 7 | } -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eec33cfb4e66157429185751b0e1c689 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.MD.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d6d7f127a75626f479f7a2917c0e65e0 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Runtime/PickerType.cs: -------------------------------------------------------------------------------- 1 | namespace Pickle 2 | { 3 | public enum PickerType 4 | { 5 | Window = 0, 6 | Dropdown = 1, 7 | 8 | Default = -1 9 | } 10 | } -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 132a7fb0543220443bbdb2855287fef5 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 613187f0bf285154b8e76326b6c0bbc2 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fdbcb242643245047b91b39359af18bb 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/ObjectPicker.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ffbb120a9a4e71e4798eb7b8de54da41 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 11e842f4e3f6e7e49bb7e5bfe175b8d4 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/ObjectPicker.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f475354e85599294ca32fece69cf6c58 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/IObjectProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Pickle.ObjectProviders 4 | { 5 | public interface IObjectProvider 6 | { 7 | IEnumerator Lookup(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Runtime/ObjectProviderType.cs: -------------------------------------------------------------------------------- 1 | namespace Pickle 2 | { 3 | public enum ObjectProviderType 4 | { 5 | None = 0, 6 | Assets = 1 << 0, 7 | Scene = 1 << 1, 8 | Children = 1 << 2, 9 | RootChildren = 1 << 3, 10 | 11 | Default = 1 << 31, 12 | } 13 | } -------------------------------------------------------------------------------- /Editor/IObjectPicker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace Pickle.Editor 5 | { 6 | public interface IObjectPicker 7 | { 8 | event Action OnOptionPicked; 9 | void Show(Rect sourceRect, UnityEngine.Object selectedObject); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Editor/IObjectPicker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cddaedd7148be3245adef9beebfc3365 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PickleField.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4ee6bcf2687487c4fb4a8dbdf3ad7c80 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PickleSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a2e6911a0b273384b8b64b23d91873ed 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/AutoPickMode.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff33046c617c04143885e44ecb2bd889 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/PickerType.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5f820ebacb1a22e4f8388a224ca948cc 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ObjectFieldDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 565cb86bb4ab3684198e05366674021b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ObjectPickerWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fa2dd85f2488d2f4db143193f5e1e01c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ObjectProviderType.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 866e7d7ce55e81f42929208b1edd012f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ObjectSourceType.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5aee59de389137e409abfd8eb4bb5472 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/ObjectTypePair.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 869ff24bb02437e4bbc726ca23e2361b 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/PickleAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 441233dfd0465ba4f8d9ffc4d4cceeaf 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ObjectPickerDropdown.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aaaa0c5211c8d764a980800a2bd9ce54 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PickleAttributeDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 18d7a7b3f2c0e26429ae6c1597a9816f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/ObjectPickerWindowBuilder.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 45d280d377ceed64aa5f0a4f06985ade 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/IObjectProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 51c21a87e1ce2a547b8493c5e39465ab 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/AssetObjectProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2b6e5d77e4aa5c341ac7ce151b40ca83 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ChildObjectsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4fff60953856b4e48b8f64e2350e0ed8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ObjectProviderUnion.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2038245993486af4795c39f72ddc666a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/RootChildrenObjectsProvider.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Pickle.ObjectProviders 4 | { 5 | public class RootChildrenObjectsProvider : ChildObjectsProvider 6 | { 7 | public RootChildrenObjectsProvider(Transform parent) : base(parent.root) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/SceneObjectsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 59da41848c8f1b64aba4e485c9c4c462 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ChildComponentsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 293760431c634e347885b7f8a6ee1ca3 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ObjectProviderUtilities.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 105624163d35b4640b7ab40214be9b8f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/SceneComponentsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dfe5f4ca8813c5b4cbe70a3a753ba01f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/PrefabComponentObjectProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 016bc2400296df24f9896122510fe055 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/RootChildrenComponentsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab5be48feb46ea942b55347a90b9d204 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/RootChildrenObjectsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 588b644ac098c8d4eb44d99ad449273a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/RootChildrenComponentsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | namespace Pickle.ObjectProviders 5 | { 6 | 7 | public class RootChildrenComponentsProvider : ChildComponentsProvider 8 | { 9 | public RootChildrenComponentsProvider(Transform parent, Type componentType) : base(parent.root, componentType) 10 | { 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Editor/ObjectPicker.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ObjectPicker.Editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:ffbb120a9a4e71e4798eb7b8de54da41" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [], 17 | "noEngineReferences": false 18 | } -------------------------------------------------------------------------------- /Runtime/ObjectTypePair.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace Pickle 4 | { 5 | public struct ObjectTypePair 6 | { 7 | public ObjectSourceType Type; 8 | public UnityEngine.Object Object; 9 | 10 | #if UNITY_EDITOR 11 | public static ObjectTypePair EDITOR_ConstructPairFromObject(Object obj) 12 | { 13 | return new ObjectTypePair 14 | { 15 | Object = obj, 16 | Type = UnityEditor.EditorUtility.IsPersistent(obj) ? ObjectSourceType.Asset : ObjectSourceType.Scene 17 | }; 18 | } 19 | #endif 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ObjectProviderUnion.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Pickle.ObjectProviders 4 | { 5 | public class ObjectProviderUnion : IObjectProvider 6 | { 7 | private IObjectProvider[] _strategies; 8 | 9 | public ObjectProviderUnion(params IObjectProvider[] strategies) 10 | { 11 | _strategies = strategies; 12 | } 13 | 14 | public IEnumerator Lookup() 15 | { 16 | foreach (var strategy in _strategies) 17 | { 18 | var strategyResults = strategy.Lookup(); 19 | while (strategyResults.MoveNext()) 20 | yield return strategyResults.Current; 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ChildComponentsProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace Pickle.ObjectProviders 5 | { 6 | 7 | public class ChildComponentsProvider : IObjectProvider 8 | { 9 | private Transform _parent; 10 | private System.Type _componentType; 11 | 12 | public ChildComponentsProvider(Transform parent, System.Type componentType) 13 | { 14 | _parent = parent; 15 | _componentType = componentType; 16 | } 17 | 18 | public IEnumerator Lookup() 19 | { 20 | foreach (var component in _parent.GetComponentsInChildren(_componentType, true)) 21 | { 22 | yield return new ObjectTypePair 23 | { 24 | Object = component, 25 | Type = ObjectSourceType.Scene 26 | }; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ChildObjectsProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | namespace Pickle.ObjectProviders 5 | { 6 | public class ChildObjectsProvider : IObjectProvider 7 | { 8 | private Transform _parent; 9 | 10 | public ChildObjectsProvider(Transform parent) 11 | { 12 | _parent = parent; 13 | } 14 | 15 | public IEnumerator Lookup() 16 | { 17 | Queue openSet = new Queue(); 18 | openSet.Enqueue(_parent); 19 | 20 | while (openSet.Count > 0) 21 | { 22 | var cur = openSet.Dequeue(); 23 | 24 | yield return new ObjectTypePair { Object = cur.gameObject, Type = ObjectSourceType.Scene }; 25 | 26 | for (int i = 0; i < cur.childCount; i++) 27 | { 28 | openSet.Enqueue(cur.GetChild(i)); 29 | } 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 MPozek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Editor/ObjectPickerWindowBuilder.cs: -------------------------------------------------------------------------------- 1 | using Pickle.ObjectProviders; 2 | using System; 3 | using UnityEngine; 4 | 5 | namespace Pickle.Editor 6 | { 7 | public class ObjectPickerWindowBuilder : IObjectPicker 8 | { 9 | public event Action OnOptionPicked; 10 | 11 | private readonly string _title; 12 | private readonly IObjectProvider _lookupStrategy; 13 | private readonly Predicate _filter; 14 | 15 | public ObjectPickerWindowBuilder(string title, IObjectProvider lookupStrategy, Predicate filter) 16 | { 17 | _title = title; 18 | _lookupStrategy = lookupStrategy; 19 | _filter = filter; 20 | } 21 | 22 | public void Show(Rect sourceRect, UnityEngine.Object selectedObject) 23 | { 24 | ObjectPickerWindow.OpenCustomPicker(_title, OnOptionPickedListener, _lookupStrategy, _filter, selectedObject); 25 | } 26 | 27 | private void OnOptionPickedListener(UnityEngine.Object obj) 28 | { 29 | OnOptionPicked?.Invoke(obj); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/PrefabComponentObjectProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | 4 | #if UNITY_EDITOR 5 | using UnityEditor; 6 | #endif 7 | 8 | namespace Pickle.ObjectProviders 9 | { 10 | public class PrefabComponentObjectProvider : IObjectProvider 11 | { 12 | private readonly bool _allowPackageAssets; 13 | private readonly System.Type _type; 14 | 15 | public PrefabComponentObjectProvider(System.Type componentType, bool allowPackageAssets = false) 16 | { 17 | _allowPackageAssets = allowPackageAssets; 18 | _type = componentType; 19 | } 20 | 21 | public IEnumerator Lookup() 22 | { 23 | #if UNITY_EDITOR 24 | var guids = AssetDatabase.FindAssets($"t:prefab"); 25 | foreach (var guid in guids) 26 | { 27 | var path = AssetDatabase.GUIDToAssetPath(guid); 28 | 29 | if (!_allowPackageAssets && !path.StartsWith("Assets")) 30 | continue; 31 | 32 | var asset = AssetDatabase.LoadAssetAtPath(path); 33 | 34 | if (asset.TryGetComponent(_type, out var prefabComponent)) 35 | yield return new ObjectTypePair { Object = prefabComponent, Type = ObjectSourceType.Asset }; 36 | } 37 | #else 38 | return null; 39 | #endif 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/SceneComponentsProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.SceneManagement; 4 | 5 | namespace Pickle.ObjectProviders 6 | { 7 | 8 | public class SceneComponentsProvider : IObjectProvider 9 | { 10 | private static readonly List ROOT_OBJECT_CACHE = new List(); 11 | private static readonly List COMPONENT_RESULT_CACHE = new List(); 12 | 13 | private Scene _scene; 14 | private readonly System.Type _type; 15 | 16 | public SceneComponentsProvider(Scene scene, System.Type componentType) 17 | { 18 | _scene = scene; 19 | _type = componentType; 20 | } 21 | 22 | public IEnumerator Lookup() 23 | { 24 | _scene.GetRootGameObjects(ROOT_OBJECT_CACHE); 25 | foreach (var rootGameObject in ROOT_OBJECT_CACHE) 26 | { 27 | COMPONENT_RESULT_CACHE.AddRange(rootGameObject.GetComponentsInChildren(_type, true)); 28 | 29 | foreach (var component in COMPONENT_RESULT_CACHE) 30 | { 31 | yield return new ObjectTypePair { Object = component, Type = ObjectSourceType.Scene }; 32 | } 33 | 34 | COMPONENT_RESULT_CACHE.Clear(); 35 | } 36 | ROOT_OBJECT_CACHE.Clear(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/SceneObjectsProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.SceneManagement; 4 | 5 | namespace Pickle.ObjectProviders 6 | { 7 | public class SceneObjectsProvider : IObjectProvider 8 | { 9 | private static readonly List ROOT_OBJECT_CACHE = new List(); 10 | private Scene _scene; 11 | 12 | public SceneObjectsProvider(Scene scene) 13 | { 14 | _scene = scene; 15 | } 16 | 17 | public IEnumerator Lookup() 18 | { 19 | _scene.GetRootGameObjects(ROOT_OBJECT_CACHE); 20 | 21 | foreach (var rootGameObject in ROOT_OBJECT_CACHE) 22 | { 23 | var hierarchyEnumerator = TraverseHierarchy(rootGameObject); 24 | while (hierarchyEnumerator.MoveNext()) 25 | yield return new ObjectTypePair { Object = hierarchyEnumerator.Current, Type = ObjectSourceType.Scene }; 26 | } 27 | 28 | ROOT_OBJECT_CACHE.Clear(); 29 | } 30 | 31 | private IEnumerator TraverseHierarchy(GameObject root) 32 | { 33 | yield return root; 34 | 35 | var childCount = root.transform.childCount; 36 | for (int i = 0; i < childCount; i++) 37 | { 38 | var hierarchyEnumerator = TraverseHierarchy(root.transform.GetChild(i).gameObject); 39 | while (hierarchyEnumerator.MoveNext()) 40 | yield return hierarchyEnumerator.Current; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/AssetObjectProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | #if UNITY_EDITOR 6 | using UnityEditor; 7 | #endif 8 | 9 | namespace Pickle.ObjectProviders 10 | { 11 | public class AssetObjectProvider : IObjectProvider 12 | { 13 | private readonly bool _allowPackageAssets; 14 | private readonly System.Type _type; 15 | private readonly Predicate _additionalFilter; 16 | 17 | public AssetObjectProvider(System.Type assetType, Predicate additionalFilter = null, bool allowPackageAssets = false) 18 | { 19 | _allowPackageAssets = allowPackageAssets; 20 | _type = assetType; 21 | _additionalFilter = additionalFilter; 22 | } 23 | 24 | public IEnumerator Lookup() 25 | { 26 | #if UNITY_EDITOR 27 | var guids = AssetDatabase.FindAssets($"t:{_type.Name}"); 28 | foreach (var guid in guids) 29 | { 30 | var path = AssetDatabase.GUIDToAssetPath(guid); 31 | 32 | if (!_allowPackageAssets && !path.StartsWith("Assets")) 33 | continue; 34 | 35 | if (path.EndsWith(".unity")) 36 | { 37 | // it's invalid to use load all assets on scene objects 38 | var asset = AssetDatabase.LoadAssetAtPath(path, _type); 39 | 40 | if (_additionalFilter == null || _additionalFilter.Invoke(asset)) 41 | { 42 | yield return new ObjectTypePair { Object = asset, Type = ObjectSourceType.Asset }; 43 | } 44 | } 45 | else 46 | { 47 | foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(path)) 48 | { 49 | AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out var loadedGuid, out long _); 50 | 51 | if (guid == loadedGuid && _additionalFilter == null || _additionalFilter.Invoke(asset)) 52 | { 53 | yield return new ObjectTypePair { Object = asset, Type = ObjectSourceType.Asset }; 54 | } 55 | } 56 | } 57 | } 58 | #else 59 | return null; 60 | #endif 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Runtime/AutoPickMode.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Linq; 3 | 4 | namespace Pickle 5 | { 6 | public enum AutoPickMode 7 | { 8 | None = 0, 9 | GetComponent = 1, 10 | GetComponentInChildren = 2, 11 | FindObject = 3, 12 | GetComponentInParent = 4, 13 | 14 | Default = -1, 15 | } 16 | 17 | public static class AutoPickExtensions 18 | { 19 | public static Object DoAutoPick(this AutoPickMode mode, UnityEngine.Object fromObject, System.Type targetType) 20 | { 21 | switch (mode) 22 | { 23 | case AutoPickMode.GetComponent: 24 | return ((Component)fromObject).GetComponent(targetType); 25 | case AutoPickMode.GetComponentInChildren: 26 | return ((Component)fromObject).GetComponentInChildren(targetType); 27 | case AutoPickMode.GetComponentInParent: 28 | return ((Component)fromObject).GetComponentInParent(targetType); 29 | case AutoPickMode.FindObject: 30 | return GameObject.FindObjectOfType(targetType); 31 | } 32 | 33 | throw new System.NotImplementedException($"Auto picking for mode {mode} is not implemented!"); 34 | } 35 | 36 | public static Object DoAutoPick(this AutoPickMode mode, Object fromObject, System.Type targetType, System.Predicate filter) 37 | { 38 | if (filter == null) 39 | return mode.DoAutoPick(fromObject, targetType); 40 | 41 | System.Func predicate = (obj) => filter.Invoke(new ObjectTypePair { Object = obj, Type = ObjectSourceType.Scene }); 42 | 43 | switch (mode) 44 | { 45 | case AutoPickMode.GetComponent: 46 | return ((Component)fromObject).GetComponents(targetType).FirstOrDefault(predicate); 47 | case AutoPickMode.GetComponentInChildren: 48 | return ((Component)fromObject).GetComponentsInChildren(targetType).FirstOrDefault(predicate); 49 | case AutoPickMode.GetComponentInParent: 50 | return ((Component)fromObject).GetComponentsInParent(targetType).FirstOrDefault(predicate); 51 | case AutoPickMode.FindObject: 52 | return GameObject.FindObjectsOfType(targetType).FirstOrDefault(predicate); 53 | } 54 | 55 | throw new System.NotImplementedException($"Auto picking for mode {mode} is not implemented!"); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /Runtime/PickleAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | 4 | #if !PICKLE_IN_ROOT_NAMESPACE 5 | namespace Pickle 6 | { 7 | #else 8 | using Pickle; 9 | #endif 10 | 11 | [AttributeUsage(AttributeTargets.Field)] 12 | public class PickleAttribute : PropertyAttribute 13 | { 14 | public ObjectProviderType LookupType = ObjectProviderType.Default; 15 | public PickerType PickerType = PickerType.Default; 16 | public Type AdditionalTypeFilter; 17 | public string FilterMethodName = null; 18 | public AutoPickMode AutoPickMode = AutoPickMode.Default; 19 | public string CustomTypeName = null; 20 | 21 | public PickleAttribute() { } 22 | 23 | public PickleAttribute(string filterMethod) 24 | { 25 | FilterMethodName = filterMethod; 26 | } 27 | 28 | public PickleAttribute(ObjectProviderType providerType, string filterMethod = null) 29 | { 30 | LookupType = providerType; 31 | FilterMethodName = filterMethod; 32 | } 33 | 34 | public PickleAttribute(ObjectProviderType providerType, AutoPickMode autoPick, string filterMethod = null) 35 | { 36 | LookupType = providerType; 37 | AutoPickMode = autoPick; 38 | FilterMethodName = filterMethod; 39 | } 40 | 41 | public PickleAttribute(AutoPickMode autoPick, string filterMethod = null) 42 | { 43 | AutoPickMode = autoPick; 44 | FilterMethodName = filterMethod; 45 | } 46 | 47 | public PickleAttribute( 48 | Type additionalTypeFilter, 49 | ObjectProviderType providerType = ObjectProviderType.Default, 50 | AutoPickMode autoPick = AutoPickMode.Default, 51 | string filterMethod = null) 52 | { 53 | AdditionalTypeFilter = additionalTypeFilter; 54 | LookupType = providerType; 55 | AutoPickMode = autoPick; 56 | FilterMethodName = filterMethod; 57 | } 58 | 59 | public PickleAttribute(Type additionalTypeFilter, AutoPickMode autoPick, string filterMethod = null) 60 | { 61 | AdditionalTypeFilter = additionalTypeFilter; 62 | AutoPickMode = autoPick; 63 | FilterMethodName = filterMethod; 64 | } 65 | 66 | public PickleAttribute(Type additionalTypeFilter, string filterMethod) 67 | { 68 | AdditionalTypeFilter = additionalTypeFilter; 69 | FilterMethodName = filterMethod; 70 | } 71 | 72 | public PickleAttribute(Type additionalTypeFilter, ObjectProviderType providerType, string filterMethod = null) 73 | { 74 | AdditionalTypeFilter = additionalTypeFilter; 75 | LookupType = providerType; 76 | FilterMethodName = filterMethod; 77 | } 78 | } 79 | 80 | #if !PICKLE_IN_ROOT_NAMESPACE 81 | } 82 | #endif -------------------------------------------------------------------------------- /Editor/ObjectPickerDropdown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor.IMGUI.Controls; 3 | using UnityEditor; 4 | using System.Collections.Generic; 5 | using Pickle.ObjectProviders; 6 | using UnityEngine; 7 | 8 | namespace Pickle.Editor 9 | { 10 | public class ObjectPickerDropdown : AdvancedDropdown, IObjectPicker 11 | { 12 | public event Action OnOptionPicked; 13 | 14 | public string Title = ""; 15 | 16 | private List _objects = new List(); 17 | 18 | private readonly IObjectProvider _lookupStrategy; 19 | private readonly Predicate _filter; 20 | 21 | public ObjectPickerDropdown(IObjectProvider lookupStrategy, AdvancedDropdownState state, Predicate filter = null) : base(state) 22 | { 23 | _lookupStrategy = lookupStrategy; 24 | _filter = filter; 25 | 26 | var minSize = base.minimumSize; 27 | minSize.y = 300f; 28 | base.minimumSize = minSize; 29 | } 30 | 31 | public void Show(Rect sourceRect, UnityEngine.Object _) 32 | { 33 | Show(sourceRect); 34 | } 35 | 36 | protected override void ItemSelected(AdvancedDropdownItem item) 37 | { 38 | base.ItemSelected(item); 39 | 40 | if (item.enabled) 41 | { 42 | if (item.id >= 0) 43 | { 44 | OnOptionPicked?.Invoke(_objects[item.id]); 45 | } 46 | else 47 | { 48 | OnOptionPicked?.Invoke(null); 49 | } 50 | } 51 | } 52 | 53 | protected override AdvancedDropdownItem BuildRoot() 54 | { 55 | var root = new AdvancedDropdownItem(Title); 56 | 57 | int assetsCount = 0; 58 | int sceneCount = 0; 59 | var assets = new AdvancedDropdownItem("Assets"); 60 | var scene = new AdvancedDropdownItem("Scene"); 61 | 62 | var nullChoice = new AdvancedDropdownItem("None"); 63 | nullChoice.id = -1; 64 | root.AddChild(nullChoice); 65 | 66 | root.AddChild(assets); 67 | 68 | var iterator = _lookupStrategy.Lookup(); 69 | while (iterator.MoveNext()) 70 | { 71 | var cur = iterator.Current; 72 | 73 | if (_filter != null && !_filter.Invoke(cur)) 74 | continue; 75 | 76 | var item = new AdvancedDropdownItem(cur.Object.ToString()); 77 | item.icon = AssetPreview.GetMiniThumbnail(cur.Object); 78 | //item.icon = AssetPreview.GetMiniTypeThumbnail(cur.Object.GetType()); 79 | item.id = _objects.Count; 80 | if (cur.Type == ObjectSourceType.Asset) 81 | { 82 | assetsCount++; 83 | assets.AddChild(item); 84 | } 85 | else if (cur.Type == ObjectSourceType.Scene) 86 | { 87 | sceneCount++; 88 | root.AddChild(item); 89 | // scene.AddChild(item); 90 | } 91 | else 92 | { 93 | root.AddChild(item); 94 | } 95 | 96 | _objects.Add(cur.Object); 97 | } 98 | 99 | scene.enabled = sceneCount != 0; 100 | assets.enabled = assetsCount != 0; 101 | 102 | 103 | if (sceneCount == 0) 104 | { 105 | root = assets; 106 | root.name = Title; 107 | } 108 | 109 | return root; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # Pickle 2 | [![openupm](https://img.shields.io/npm/v/com.mpozek.pickle?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/com.mpozek.pickle/) 3 | 4 | A better Object picking solution for Unity. 5 | 6 | ![Pickle_Preview](https://user-images.githubusercontent.com/15526815/132820255-99ac69b3-c26c-4b11-9a70-a20b0e1ce266.gif) 7 | 8 | ## What is Pickle? 9 | Pickle is a collection of Editor scripts and Attributes that aim to replicate and extend the behaviour of the default UnityEngine.Object picker. 10 | 11 | ## Installation 12 | Using OpenUPM CLI: 13 | `openupm add com.mpozek.pickle` 14 | 15 | Or manually: 16 | 1. open the Unity Package Manager (Windows/Package Manager) 17 | 2. click the add package button and choose the "add package from git URL" option 18 | 19 | ![image](https://user-images.githubusercontent.com/15526815/130480877-e7b244be-7a24-4bf7-b008-ca214f090ba5.png) 20 | 21 | 3. paste the git url `https://github.com/MPozek/Pickle.git` and click Add 22 | 23 | That's it! Unity should handle the rest of downloading and adding Pickle to your project. 24 | 25 | ## Why use Pickle? 26 | - it correctly displays scene Components and prefabs in the picker if they match the field type 27 | - it allows you to choose if the object picker will display only assets, scene objects or both 28 | - it offers a way to define further filtering methods per attribute 29 | - it can open the Object selector in a window, or a dropdown, depending on which you find neater 30 | - it works on both fields and arrays/lists of types derived from `UnityEngine.Object` 31 | - it looks exactly like the built-in unity object picker so you can keep your editor UI feeling clean and natural 32 | - it's easy, just add the `[Pickle]` attribute above your field 33 | 34 | ## Usage example 35 | ```cs 36 | // it's easy 37 | [Pickle] 38 | public Rigidbody DefaultPicker; 39 | 40 | // open the picker as a dropdown, this is the default behaviour 41 | [Pickle(PickerType = PickerType.Dropdown)] 42 | public GameObject DropdownPicker; 43 | 44 | // open the picker in a new window 45 | [Pickle(PickerType = PickerType.Window)] 46 | public GameObject CustomWindowPicker; 47 | 48 | // supports both arrays and lists 49 | [Pickle] 50 | public Rigidbody[] RigidbodyArray; 51 | [Pickle] 52 | public List RigidbodyList; 53 | 54 | // narrow down the allowed objects on the field, the default is both assets and scene 55 | [Pickle(LookupType = ObjectProviderType.Scene)] 56 | public Transform OnlySceneObjects; 57 | 58 | [Pickle(LookupType = ObjectProviderType.Assets)] 59 | public Transform OnlyPrefabs; 60 | 61 | [Pickle(LookupType = ObjectProviderType.Children)] 62 | public Transform OnlyChildrenOfThisObject; 63 | 64 | [Pickle(LookupType = ObjectProviderType.RootChildren)] 65 | public Transform OnlyChildrenOfTheRootOfThisObject; 66 | 67 | [Pickle(LookupType = ObjectProviderType.Assets | ObjectProviderType.Scene)] 68 | public Transform AnyAssetOrSceneObject; 69 | 70 | // use a custom method to filter your results 71 | [Pickle(LookupType = ObjectProviderType.Scene, FilterMethodName = nameof(CustomFilter))] 72 | public UnityEngine.UI.Image ImagesWithCustomFilter; 73 | 74 | private bool CustomFilter(ObjectTypePair item) 75 | { 76 | return item.Object.name.StartsWith("UI_"); 77 | } 78 | 79 | [Header("Auto picking")] 80 | [Pickle(AutoPickMode = AutoPickMode.GetComponent)] 81 | public Transform MyTransform; 82 | 83 | [Pickle(AutoPickMode = AutoPickMode.GetComponentInChildren)] 84 | public MeshRenderer ChildRenderer; 85 | 86 | [Pickle(AutoPickMode = AutoPickMode.GetComponentInParent)] 87 | public Rigidbody ParentRigidbody; 88 | 89 | [Pickle(AutoPickMode = AutoPickMode.FindObject)] 90 | public Camera SceneCamera; 91 | 92 | // since version 1.2.0 you can add additional type filters to, for example, filter only interface implementers 93 | [Pickle(typeof(IMyInterface))] 94 | public UnityEngine.Object AnyObjectImplementingTheInterface; 95 | ``` 96 | -------------------------------------------------------------------------------- /Editor/Lookup Strategies/ObjectProviderUtilities.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEngine; 3 | using UnityEngine.SceneManagement; 4 | 5 | namespace Pickle.ObjectProviders 6 | { 7 | public static class ObjectProviderUtilities 8 | { 9 | public static IObjectProvider ResolveProviderTypeToProvider( 10 | this ObjectProviderType providerType, 11 | System.Type fieldType, 12 | UnityEngine.Object owner = null, 13 | bool allowPackages = false) 14 | { 15 | List strategiesForUnion = new List(); 16 | 17 | // find the owner gameobject 18 | // if the owner is not a gameobject or a component, we later skip looking at children / scene objects 19 | GameObject ownerGameObject = null; 20 | if (owner) 21 | { 22 | if (owner is Component comp) 23 | ownerGameObject = comp.gameObject; 24 | else if (owner is GameObject go) 25 | ownerGameObject = go; 26 | } 27 | 28 | var isFieldTypeObject = typeof(UnityEngine.Object) == fieldType; 29 | var isFieldTypeAComponent = typeof(Component).IsAssignableFrom(fieldType); 30 | var isFieldTypeASceneObject = isFieldTypeObject || typeof(GameObject) == fieldType || isFieldTypeAComponent; 31 | 32 | if ((providerType & ObjectProviderType.Assets) != 0) 33 | { 34 | if (isFieldTypeAComponent) 35 | { 36 | strategiesForUnion.Add(new PrefabComponentObjectProvider(fieldType, allowPackages)); 37 | } 38 | else 39 | { 40 | strategiesForUnion.Add(new AssetObjectProvider(fieldType, null, allowPackages)); 41 | } 42 | } 43 | 44 | if (ownerGameObject && isFieldTypeASceneObject) 45 | { 46 | if ((providerType & ObjectProviderType.Scene) != 0) 47 | { 48 | if (isFieldTypeAComponent) 49 | { 50 | strategiesForUnion.Add(new SceneComponentsProvider(ownerGameObject.scene, fieldType)); 51 | } 52 | else if (isFieldTypeObject) 53 | { 54 | strategiesForUnion.Add(new SceneComponentsProvider(ownerGameObject.scene, typeof(Component))); 55 | strategiesForUnion.Add(new SceneObjectsProvider(ownerGameObject.scene)); 56 | } 57 | else 58 | { 59 | strategiesForUnion.Add(new SceneObjectsProvider(ownerGameObject.scene)); 60 | } 61 | } 62 | else 63 | { 64 | if ((providerType & ObjectProviderType.RootChildren) != 0) 65 | { 66 | if (isFieldTypeAComponent) 67 | { 68 | strategiesForUnion.Add(new RootChildrenComponentsProvider(ownerGameObject.transform, fieldType)); 69 | } 70 | else if (isFieldTypeObject) 71 | { 72 | strategiesForUnion.Add(new RootChildrenComponentsProvider(ownerGameObject.transform, fieldType)); 73 | strategiesForUnion.Add(new RootChildrenObjectsProvider(ownerGameObject.transform)); 74 | } 75 | else 76 | { 77 | strategiesForUnion.Add(new RootChildrenObjectsProvider(ownerGameObject.transform)); 78 | } 79 | } 80 | else if ((providerType & ObjectProviderType.Children) != 0) 81 | { 82 | if (isFieldTypeAComponent) 83 | { 84 | strategiesForUnion.Add(new ChildComponentsProvider(ownerGameObject.transform, fieldType)); 85 | } 86 | else if (isFieldTypeObject) 87 | { 88 | strategiesForUnion.Add(new ChildComponentsProvider(ownerGameObject.transform, fieldType)); 89 | strategiesForUnion.Add(new ChildObjectsProvider(ownerGameObject.transform)); 90 | } 91 | else 92 | { 93 | strategiesForUnion.Add(new ChildObjectsProvider(ownerGameObject.transform)); 94 | } 95 | } 96 | } 97 | } 98 | 99 | return new ObjectProviderUnion(strategiesForUnion.ToArray()); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Editor/PickleField.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | using Pickle.ObjectProviders; 5 | using UnityEditor.IMGUI.Controls; 6 | 7 | namespace Pickle.Editor 8 | { 9 | public class PickleField 10 | { 11 | private const float AUTO_PICK_BUTTON_WIDTH = 20f; 12 | 13 | private bool _isValidField; 14 | private Type _fieldType; 15 | private ObjectFieldDrawer _objectFieldDrawer; 16 | private IObjectPicker _objectPicker; 17 | private SerializedProperty _property; 18 | private PickleFieldConfiguration _configuration; 19 | 20 | public PickleField( 21 | SerializedProperty property, 22 | Type fieldType, 23 | PickleFieldConfiguration configuration) 24 | { 25 | _configuration = configuration; 26 | _objectFieldDrawer = 27 | configuration.EmptyFieldLabel != null ? 28 | new ObjectFieldDrawer(CheckObjectType, configuration.EmptyFieldLabel) : 29 | new ObjectFieldDrawer(CheckObjectType, fieldType); 30 | _fieldType = fieldType; 31 | 32 | _isValidField = property.propertyType == SerializedPropertyType.ObjectReference; 33 | 34 | if (_isValidField) 35 | { 36 | var targetObject = property.serializedObject.targetObject; 37 | var targetObjectType = targetObject.GetType(); 38 | 39 | InitializePickerPopup(_configuration.ObjectProvider, _configuration.PickerType); 40 | 41 | _objectFieldDrawer.OnObjectPickerButtonClicked += OpenObjectPicker; 42 | _objectPicker.OnOptionPicked += ChangeObject; 43 | } 44 | } 45 | 46 | public float GetPropertyHeight(SerializedProperty property, GUIContent label) 47 | { 48 | return EditorGUI.GetPropertyHeight(property, label); 49 | } 50 | 51 | public void OnGUI(Rect position, SerializedProperty property, GUIContent label) 52 | { 53 | _property = property; 54 | 55 | if (!_isValidField) 56 | { 57 | EditorGUI.PropertyField(position, property, label, true); 58 | return; 59 | } 60 | 61 | if (_configuration.AutoPickMode != AutoPickMode.None) 62 | { 63 | var buttonRect = Rect.MinMaxRect(position.xMax - AUTO_PICK_BUTTON_WIDTH, position.yMin, position.xMax, position.yMax); 64 | position.width -= AUTO_PICK_BUTTON_WIDTH; 65 | 66 | if (GUI.Button(buttonRect, "A")) 67 | { 68 | ChangeObject(_configuration.AutoPickMode.DoAutoPick(_property.serializedObject.targetObject, _fieldType, _configuration.Filter)); 69 | } 70 | } 71 | 72 | var newReference = _objectFieldDrawer.Draw(position, label, property.objectReferenceValue); 73 | ChangeObject(newReference); 74 | } 75 | 76 | private void ChangeObject(UnityEngine.Object obj) 77 | { 78 | if (obj == _property.objectReferenceValue) 79 | return; 80 | 81 | if (typeof(Component).IsAssignableFrom(_fieldType) && obj is GameObject go) 82 | { 83 | obj = go.GetComponent(_fieldType); 84 | } 85 | 86 | if (_property.objectReferenceValue != obj) 87 | { 88 | _property.objectReferenceValue = obj; 89 | _property.serializedObject.ApplyModifiedProperties(); 90 | } 91 | } 92 | 93 | private void InitializePickerPopup(IObjectProvider objectProvider, PickerType pickerType) 94 | { 95 | if (pickerType == PickerType.Default) pickerType = PickerType.Dropdown; 96 | 97 | if (pickerType == PickerType.Dropdown) 98 | { 99 | _objectPicker = new ObjectPickerDropdown( 100 | objectProvider, 101 | new AdvancedDropdownState(), 102 | _configuration.Filter 103 | ); 104 | } 105 | else if (pickerType == PickerType.Window) 106 | { 107 | _objectPicker = new ObjectPickerWindowBuilder(_property.displayName, objectProvider, _configuration.Filter); 108 | } 109 | else 110 | { 111 | throw new System.NotImplementedException($"Picker type {pickerType} not implemented!"); 112 | } 113 | } 114 | 115 | private void OpenObjectPicker() 116 | { 117 | _objectPicker.Show(_objectFieldDrawer.FieldRect, _property.objectReferenceValue); 118 | } 119 | 120 | public bool CheckObjectType(UnityEngine.Object obj) 121 | { 122 | if (typeof(Component).IsAssignableFrom(_fieldType) && obj is GameObject go) 123 | { 124 | if (!go.TryGetComponent(_fieldType, out var component)) 125 | return false; 126 | 127 | obj = component; 128 | } 129 | 130 | return _fieldType.IsAssignableFrom(obj.GetType()) && (_configuration.Filter == null || _configuration.Filter.Invoke(ObjectTypePair.EDITOR_ConstructPairFromObject(obj))); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Editor/ObjectFieldDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Pickle.Editor 6 | { 7 | public class ObjectFieldDrawer 8 | { 9 | public event Action OnObjectPickerButtonClicked; 10 | public readonly Predicate IsObjectValidForField; 11 | private readonly Func _objectLabelGetter; 12 | 13 | public Rect FieldRect { get; private set; } 14 | 15 | public static GUIContent DefaultObjectLabelGetter(UnityEngine.Object obj, string typeName) 16 | { 17 | return obj ? new GUIContent(obj.ToString(), AssetPreview.GetMiniThumbnail(obj)) : new GUIContent($"None ({typeName})"); 18 | } 19 | 20 | public ObjectFieldDrawer(Predicate isObjectValidForField, Func objectLabelGetter) 21 | { 22 | IsObjectValidForField = isObjectValidForField; 23 | _objectLabelGetter = objectLabelGetter; 24 | } 25 | 26 | public ObjectFieldDrawer(Predicate isObjectValidForField, System.Type fieldType) 27 | { 28 | IsObjectValidForField = isObjectValidForField; 29 | _objectLabelGetter = (obj) => DefaultObjectLabelGetter(obj, fieldType.Name); 30 | } 31 | 32 | public ObjectFieldDrawer(Predicate isObjectValidForField, string fieldTypeName) 33 | { 34 | IsObjectValidForField = isObjectValidForField; 35 | _objectLabelGetter = (obj) => DefaultObjectLabelGetter(obj, fieldTypeName); 36 | } 37 | 38 | public UnityEngine.Object Draw( 39 | Rect position, GUIContent label, 40 | UnityEngine.Object activeObject) 41 | { 42 | var dropBoxRect = EditorGUI.PrefixLabel(position, label); 43 | var buttonRect = dropBoxRect; 44 | buttonRect.xMin = position.xMax - 20f; 45 | buttonRect = new RectOffset(-1, -1, -1, -1).Add(buttonRect); 46 | 47 | if (Event.current.type == EventType.Repaint) 48 | FieldRect = dropBoxRect; 49 | 50 | // we have to manually handle the mouse down events cause GUI.Button eats them 51 | if (GUI.enabled) 52 | { 53 | var ev = Event.current; 54 | 55 | var isMouseInsideControl = dropBoxRect.Contains(ev.mousePosition); 56 | 57 | if (isMouseInsideControl) 58 | { 59 | if (ev.type == EventType.MouseDown) 60 | { 61 | if (isMouseInsideControl) 62 | { 63 | var isMouseOverSelectButton = buttonRect.Contains(ev.mousePosition); 64 | 65 | if (isMouseOverSelectButton) 66 | { 67 | Event.current.Use(); 68 | OnObjectPickerButtonClicked?.Invoke(); 69 | } 70 | else 71 | { 72 | Event.current.Use(); 73 | if (activeObject != null) 74 | { 75 | EditorGUIUtility.PingObject(GetPingableObject(activeObject)); 76 | } 77 | } 78 | } 79 | } 80 | else if (HandleDragEvents(GetDraggedObjectIfValid(), ref activeObject)) 81 | { 82 | Event.current.Use(); 83 | } 84 | 85 | } 86 | } 87 | 88 | GUIContent activeObjectLabel = _objectLabelGetter(activeObject); 89 | 90 | if (activeObjectLabel.image) 91 | { 92 | GUI.Toggle(dropBoxRect, dropBoxRect.Contains(Event.current.mousePosition) && GetDraggedObjectIfValid(), GUIContent.none, EditorStyles.objectField); 93 | 94 | var iconRect = dropBoxRect; 95 | iconRect.center += Vector2.right * 3f; 96 | iconRect.width = 15f; 97 | 98 | var labelRect = dropBoxRect; 99 | labelRect.xMin += iconRect.width + 1f; 100 | 101 | var icon = activeObjectLabel.image; 102 | activeObjectLabel.image = null; 103 | 104 | var labelStyle = new GUIStyle(EditorStyles.objectField); 105 | labelStyle.normal.background = Texture2D.blackTexture; 106 | 107 | EditorGUI.LabelField(labelRect, activeObjectLabel, labelStyle); 108 | GUI.DrawTexture(iconRect, icon, ScaleMode.ScaleToFit); 109 | } 110 | else 111 | { 112 | GUI.Toggle(dropBoxRect, dropBoxRect.Contains(Event.current.mousePosition) && GetDraggedObjectIfValid(), activeObjectLabel, EditorStyles.objectField); 113 | } 114 | 115 | var objectFieldButtonStyle = new GUIStyle("ObjectFieldButton"); 116 | GUI.Button(buttonRect, new GUIContent(""), objectFieldButtonStyle); 117 | 118 | return activeObject; 119 | } 120 | 121 | private UnityEngine.Object GetPingableObject(UnityEngine.Object activeObject) 122 | { 123 | if (activeObject is Component component) 124 | { 125 | return component.gameObject; 126 | } 127 | else 128 | { 129 | return activeObject; 130 | } 131 | } 132 | 133 | private UnityEngine.Object GetDraggedObjectIfValid() 134 | { 135 | var draggedObjects = DragAndDrop.objectReferences; 136 | if (draggedObjects.Length != 1) return null; 137 | var obj = draggedObjects[0]; 138 | 139 | return IsObjectValidForField.Invoke(obj) ? obj : null; 140 | } 141 | 142 | private static bool HandleDragEvents(bool isValidObjectBeingDragged, ref UnityEngine.Object activeObject) 143 | { 144 | var ev = Event.current; 145 | if (ev.type == EventType.DragUpdated) 146 | { 147 | if (isValidObjectBeingDragged) 148 | { 149 | DragAndDrop.visualMode = DragAndDropVisualMode.Link; 150 | } 151 | else 152 | { 153 | DragAndDrop.visualMode = DragAndDropVisualMode.Rejected; 154 | } 155 | 156 | return true; 157 | } 158 | else if (ev.type == EventType.DragPerform) 159 | { 160 | if (isValidObjectBeingDragged) 161 | { 162 | DragAndDrop.AcceptDrag(); 163 | activeObject = DragAndDrop.objectReferences[0]; 164 | } 165 | 166 | return true; 167 | } 168 | else if (ev.type == EventType.DragExited) 169 | { 170 | return true; 171 | } 172 | return false; 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Editor/PickleAttributeDrawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEditor; 3 | using UnityEngine; 4 | using Pickle.ObjectProviders; 5 | using System.Collections; 6 | 7 | namespace Pickle.Editor 8 | { 9 | public struct PickleFieldConfiguration 10 | { 11 | public IObjectProvider ObjectProvider; 12 | public PickerType PickerType; 13 | public AutoPickMode AutoPickMode; 14 | public string EmptyFieldLabel; 15 | public Predicate Filter; 16 | } 17 | 18 | 19 | #if DEFAULT_TO_PICKLE 20 | [CustomPropertyDrawer(typeof(UnityEngine.Object), true)] 21 | #endif 22 | [CustomPropertyDrawer(typeof(PickleAttribute))] 23 | public class PickleAttributeDrawer : PropertyDrawer 24 | { 25 | private PickleField _fieldDrawer; 26 | private bool _isValidField; 27 | 28 | private void Initialize(SerializedProperty property) 29 | { 30 | if (_fieldDrawer != null) 31 | return; 32 | 33 | var fieldType = ExtractFieldType(fieldInfo); 34 | 35 | _isValidField = fieldType != null; 36 | 37 | var targetObject = property.serializedObject.targetObject; 38 | var targetObjectType = targetObject.GetType(); 39 | 40 | var configuration = new PickleFieldConfiguration(); 41 | configuration.Filter = null; 42 | 43 | configuration.PickerType = PickleSettings.GetDefaultPickerType(fieldType); 44 | 45 | if (base.attribute != null) 46 | { 47 | var attribute = (PickleAttribute)this.attribute; 48 | 49 | configuration = ExtractConfigurationFromAttribute(attribute, targetObject, targetObjectType, fieldType); 50 | 51 | if (attribute.CustomTypeName != null) 52 | { 53 | configuration.EmptyFieldLabel = attribute.CustomTypeName; 54 | } 55 | else if (attribute.AdditionalTypeFilter != null) 56 | { 57 | configuration.EmptyFieldLabel = $"{fieldType.Name}: {attribute.AdditionalTypeFilter.Name}"; 58 | } 59 | else 60 | { 61 | configuration.EmptyFieldLabel = null; 62 | } 63 | } 64 | else 65 | { 66 | configuration.EmptyFieldLabel = null; 67 | 68 | configuration.ObjectProvider = ObjectProviderUtilities.ResolveProviderTypeToProvider(PickleSettings.GetDefaultProviderType(), fieldType, targetObject, true); 69 | configuration.AutoPickMode = PickleSettings.DefaultAutoPickMode; 70 | configuration.PickerType = PickleSettings.GetDefaultPickerType(fieldType); 71 | } 72 | 73 | _fieldDrawer = new PickleField( 74 | property, 75 | fieldType, 76 | configuration 77 | ); 78 | } 79 | 80 | private static Type ExtractFieldType(System.Reflection.FieldInfo fieldInfo) 81 | { 82 | if (fieldInfo == null) 83 | return null; 84 | 85 | var fieldType = fieldInfo.FieldType; 86 | 87 | if (fieldType.IsArray) 88 | return fieldType.GetElementType(); 89 | 90 | if (typeof(IList).IsAssignableFrom(fieldType)) 91 | return fieldType.GetGenericArguments()[0]; 92 | 93 | return fieldType; 94 | } 95 | 96 | private static PickleFieldConfiguration ExtractConfigurationFromAttribute( 97 | PickleAttribute attribute, 98 | UnityEngine.Object targetObject, 99 | Type targetObjectType, 100 | Type fieldType) 101 | { 102 | var result = new PickleFieldConfiguration(); 103 | 104 | var lookupType = attribute.LookupType == ObjectProviderType.Default ? PickleSettings.GetDefaultProviderType() : attribute.LookupType; 105 | result.ObjectProvider = lookupType.ResolveProviderTypeToProvider(fieldType, targetObject); 106 | 107 | if (!string.IsNullOrEmpty(attribute.FilterMethodName)) 108 | { 109 | var filterMethodInfo = targetObjectType.GetMethod( 110 | attribute.FilterMethodName, 111 | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance 112 | ); 113 | 114 | if (filterMethodInfo != null) 115 | { 116 | var parameters = filterMethodInfo.GetParameters(); 117 | 118 | if (parameters.Length == 1 && parameters[0].ParameterType == typeof(ObjectTypePair)) 119 | { 120 | result.Filter = (Predicate)Delegate.CreateDelegate(typeof(Predicate), targetObject, filterMethodInfo); 121 | } 122 | else 123 | { 124 | Debug.LogError($"CustomPicker filter method with name {attribute.FilterMethodName} on object {targetObject} has wrong arguments!", targetObject); 125 | } 126 | } 127 | else 128 | { 129 | Debug.LogError($"CustomPicker filter method with name {attribute.FilterMethodName} on object {targetObject}:{targetObjectType} not found!", targetObject); 130 | } 131 | } 132 | 133 | if (attribute.AdditionalTypeFilter != null) 134 | { 135 | if (result.Filter == null) 136 | { 137 | result.Filter = (objectTypePair) => attribute.AdditionalTypeFilter.IsAssignableFrom(objectTypePair.Object.GetType()); 138 | } 139 | else 140 | { 141 | var customFilter = result.Filter; 142 | result.Filter = (objectTypePair) => 143 | attribute.AdditionalTypeFilter.IsAssignableFrom(objectTypePair.Object.GetType()) 144 | && customFilter(objectTypePair); 145 | } 146 | } 147 | 148 | result.PickerType = attribute.PickerType == PickerType.Default ? PickleSettings.GetDefaultPickerType(fieldType) : attribute.PickerType; 149 | 150 | result.AutoPickMode = attribute.AutoPickMode == AutoPickMode.Default ? PickleSettings.DefaultAutoPickMode : attribute.AutoPickMode; 151 | 152 | return result; 153 | } 154 | 155 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 156 | { 157 | Initialize(property); 158 | return _fieldDrawer.GetPropertyHeight(property, label); 159 | } 160 | 161 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 162 | { 163 | Initialize(property); 164 | 165 | if (!_isValidField) 166 | { 167 | EditorGUI.PropertyField(position, property, label, true); 168 | return; 169 | } 170 | 171 | _fieldDrawer.OnGUI(position, property, label); 172 | } 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Editor/PickleSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Pickle.Editor 6 | { 7 | public class PickleSettings : ScriptableObject 8 | { 9 | /// 10 | /// Editor preferences key for: 11 | /// Last known location of the pickle settings asset 12 | /// 13 | private const string PICKLE_SETTINGS_LAST_KNOWN_LOCATION = "PICKLE_SETTINGS_LAST_KNOWN_LOCATION"; 14 | 15 | private static PickleSettings m_instance; 16 | private static PickleSettings _instance 17 | { 18 | get 19 | { 20 | if (m_instance) 21 | return m_instance; 22 | 23 | var lastKnownLocation = EditorPrefs.GetString(PICKLE_SETTINGS_LAST_KNOWN_LOCATION, null); 24 | if (lastKnownLocation != null) 25 | { 26 | m_instance = AssetDatabase.LoadAssetAtPath(PICKLE_SETTINGS_LAST_KNOWN_LOCATION); 27 | if (m_instance) 28 | return m_instance; 29 | } 30 | 31 | var guids = AssetDatabase.FindAssets($"t:{nameof(PickleSettings)}"); 32 | if (guids.Length > 0) 33 | { 34 | var path = AssetDatabase.GUIDToAssetPath(guids[0]); 35 | m_instance = AssetDatabase.LoadAssetAtPath(path); 36 | 37 | lastKnownLocation = path; 38 | EditorPrefs.SetString(PICKLE_SETTINGS_LAST_KNOWN_LOCATION, lastKnownLocation); 39 | } 40 | return m_instance; 41 | } 42 | } 43 | 44 | public static AutoPickMode DefaultAutoPickMode => _instance ? _instance._defaultAutoPickMode : AutoPickMode.None; 45 | 46 | [InitializeOnLoadMethod] 47 | private static void InitializePickleSettings() 48 | { 49 | if (!_instance) 50 | { 51 | EditorUtility.DisplayDialog("Pickle installer", "Pickle settings asset does not exist, pick a location in project to create it.", "Ok"); 52 | 53 | var path = EditorUtility.SaveFilePanelInProject("Pickle installer", "PickleSettings", "asset", ""); 54 | 55 | if (string.IsNullOrEmpty(path) == false) 56 | { 57 | m_instance = ScriptableObject.CreateInstance(); 58 | AssetDatabase.CreateAsset(m_instance, path); 59 | AssetDatabase.SaveAssets(); 60 | AssetDatabase.Refresh(); 61 | } 62 | } 63 | } 64 | 65 | [Header("Defaults")] 66 | [SerializeField] private PickerType _defaultPickerType = PickerType.Dropdown; 67 | [SerializeField] private int _defaultObjectProvider = (int)(ObjectProviderType.Assets | ObjectProviderType.Scene); 68 | [SerializeField] private AutoPickMode _defaultAutoPickMode = AutoPickMode.None; 69 | 70 | [Header("Open in window types")] 71 | [SerializeField] 72 | private List _defaultToWindowTypeNames = new List() { typeof(Sprite).FullName, typeof(Texture2D).FullName, typeof(Mesh).FullName }; 73 | 74 | internal static PickerType GetDefaultPickerType(System.Type fieldType) 75 | { 76 | var instance = _instance; 77 | if (!instance) 78 | return PickerType.Dropdown; 79 | 80 | return instance._defaultToWindowTypeNames.Contains(fieldType.FullName) ? PickerType.Window : instance._defaultPickerType; 81 | } 82 | 83 | public static ObjectProviderType GetDefaultProviderType() 84 | { 85 | var instance = _instance; 86 | if (!instance) 87 | return (ObjectProviderType.Scene | ObjectProviderType.Assets); 88 | 89 | return (ObjectProviderType)instance._defaultObjectProvider; 90 | } 91 | 92 | [CustomEditor(typeof(PickleSettings))] 93 | public class PickleSettingsEditor : UnityEditor.Editor 94 | { 95 | private const string PICKLE_IS_DEFAULT = "DEFAULT_TO_PICKLE"; 96 | private const string PICKLE_IN_ROOT_NAMESPACE = "PICKLE_IN_ROOT_NAMESPACE"; 97 | 98 | private SerializedProperty _defaultPickerTypeProp; 99 | private SerializedProperty _defaultProviderTypeProp; 100 | private SerializedProperty _defaultAutoPickModeProp; 101 | private SerializedProperty _defaultToWindowTypesProp; 102 | 103 | private string[] _providerMaskDisplayNames; 104 | 105 | public override void OnInspectorGUI() 106 | { 107 | base.DrawHeader(); 108 | 109 | FetchProperties(); 110 | 111 | EditorGUILayout.PropertyField(_defaultPickerTypeProp); 112 | EditorGUILayout.PropertyField(_defaultAutoPickModeProp); 113 | 114 | int providerTypeMask = _defaultProviderTypeProp.intValue; 115 | 116 | var newTypeMask = EditorGUILayout.MaskField("Default Provider Type", providerTypeMask, _providerMaskDisplayNames); 117 | if (newTypeMask != providerTypeMask) 118 | { 119 | _defaultProviderTypeProp.intValue = newTypeMask; 120 | } 121 | 122 | 123 | EditorGUILayout.PropertyField(_defaultToWindowTypesProp); 124 | 125 | DrawScriptingDefineToggles(); 126 | } 127 | 128 | private void DrawScriptingDefineToggles() 129 | { 130 | EditorGUILayout.Space(); 131 | 132 | PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, out var defines); 133 | 134 | DrawDefineToggle(PICKLE_IS_DEFAULT, defines, "Set Pickle as default picker", "Unset Pickle as default picker"); 135 | DrawDefineToggle(PICKLE_IN_ROOT_NAMESPACE, defines, "Move attribute to default namespace", "Move attribute to Pickle namespace"); 136 | } 137 | 138 | private void DrawDefineToggle(string defineString, string[] defines, string positiveLabel, string negativeLabel) 139 | { 140 | var defineIndex = System.Array.IndexOf(defines, defineString); 141 | var isSymbolSet = defineIndex >= 0; 142 | var toggleButtonLabel = isSymbolSet ? negativeLabel : positiveLabel; 143 | 144 | var buttonStyle = (GUIStyle)"AC Button"; 145 | var size = buttonStyle.CalcSize(new GUIContent(toggleButtonLabel)); 146 | 147 | var buttonRect = EditorGUILayout.GetControlRect(false, GUILayout.ExpandWidth(true), GUILayout.Height(size.y)); 148 | buttonRect = Rect.MinMaxRect( 149 | buttonRect.center.x - size.x * 0.5f, 150 | buttonRect.center.y - size.y * 0.5f, 151 | buttonRect.center.x + size.x * 0.5f, 152 | buttonRect.center.y + size.y * 0.5f 153 | ); 154 | 155 | if (GUI.Button(buttonRect, toggleButtonLabel, buttonStyle)) 156 | { 157 | if (isSymbolSet) 158 | { 159 | defines[defineIndex] = defines[defines.Length - 1]; 160 | System.Array.Resize(ref defines, defines.Length - 1); 161 | } 162 | else 163 | { 164 | System.Array.Resize(ref defines, defines.Length + 1); 165 | defines[defines.Length - 1] = defineString; 166 | 167 | } 168 | 169 | PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, defines); 170 | 171 | Repaint(); 172 | } 173 | } 174 | 175 | private void FetchProperties() 176 | { 177 | if (_defaultPickerTypeProp == null) 178 | _defaultPickerTypeProp = serializedObject.FindProperty(nameof(_defaultPickerType)); 179 | 180 | if (_defaultProviderTypeProp == null) 181 | _defaultProviderTypeProp = serializedObject.FindProperty(nameof(_defaultObjectProvider)); 182 | 183 | if (_defaultAutoPickModeProp == null) 184 | _defaultAutoPickModeProp = serializedObject.FindProperty(nameof(_defaultAutoPickMode)); 185 | 186 | if (_defaultToWindowTypesProp == null) 187 | _defaultToWindowTypesProp = serializedObject.FindProperty(nameof(_defaultToWindowTypeNames)); 188 | 189 | if (_providerMaskDisplayNames == null || _providerMaskDisplayNames.Length == 0) 190 | { 191 | var values = (ObjectProviderType[]) System.Enum.GetValues(typeof(ObjectProviderType)); 192 | var displayNames = new List(); 193 | 194 | for (int i = 0; i < 32; i++) 195 | { 196 | var val = 1 << i; 197 | if (System.Array.IndexOf(values, (ObjectProviderType)val) >= 0) 198 | { 199 | displayNames.Add(((ObjectProviderType)val).ToString()); 200 | } 201 | else 202 | { 203 | displayNames.Add(i.ToString()); 204 | } 205 | } 206 | 207 | _providerMaskDisplayNames = displayNames.ToArray(); 208 | } 209 | } 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /Editor/ObjectPickerWindow.cs: -------------------------------------------------------------------------------- 1 | using Pickle.ObjectProviders; 2 | using System; 3 | using System.Collections.Generic; 4 | using UnityEditor; 5 | using UnityEditor.IMGUI.Controls; 6 | using UnityEngine; 7 | 8 | namespace Pickle.Editor 9 | { 10 | 11 | public class ObjectPickerWindow : EditorWindow 12 | { 13 | private static readonly Vector2 DEFAULT_SIZE = new Vector2(700f, 560f); 14 | private const float GRID_TILE_SIZE_MIN = 100f; 15 | 16 | private IObjectProvider _lookupStrategy; 17 | private Action _onPickCallback; 18 | private Predicate _filter; 19 | private SearchField _searchField; 20 | 21 | private int _selectedOptionIndex; 22 | private string _searchString; 23 | private Vector2 _scrollPosition; 24 | private List _options = new List(); 25 | private List _visibleOptionIndices = new List(); 26 | private bool _drawAsAList; 27 | 28 | public static void OpenCustomPicker( 29 | string title, 30 | Action onPick, 31 | IObjectProvider lookupStrategy, 32 | Predicate filter, 33 | UnityEngine.Object selectedObject = null) 34 | { 35 | var picker = GetWindow(true, title); 36 | 37 | picker._drawAsAList = false; 38 | picker._lookupStrategy = lookupStrategy; 39 | picker._onPickCallback = onPick; 40 | picker._filter = filter; 41 | picker.RefreshList(); 42 | 43 | picker._selectedOptionIndex = picker._options.FindIndex((option) => option.Object == selectedObject); 44 | } 45 | 46 | private void OnEnable() 47 | { 48 | _searchString = null; 49 | _selectedOptionIndex = -1; 50 | _scrollPosition = new Vector2(0f, 0f); 51 | 52 | _searchField = new SearchField(); 53 | _searchField.autoSetFocusOnFindCommand = true; 54 | _searchField.downOrUpArrowKeyPressed += SearchField_downOrUpArrowKeyPressed; 55 | 56 | var pos = position; 57 | pos.size = DEFAULT_SIZE; 58 | position = pos; 59 | } 60 | 61 | private void SearchField_downOrUpArrowKeyPressed() 62 | { 63 | // which one was it? 64 | var ev = Event.current; 65 | var isDown = ev.keyCode == KeyCode.DownArrow; 66 | Event.current.Use(); 67 | 68 | var displayIndexOfCurrentOption = _visibleOptionIndices.IndexOf(_selectedOptionIndex); 69 | displayIndexOfCurrentOption += isDown ? 1 : -1; 70 | 71 | // make sure the index is in range 72 | displayIndexOfCurrentOption = (displayIndexOfCurrentOption + _visibleOptionIndices.Count) % _visibleOptionIndices.Count; 73 | _selectedOptionIndex = _visibleOptionIndices[displayIndexOfCurrentOption]; 74 | 75 | Repaint(); 76 | } 77 | 78 | private void RefreshList() 79 | { 80 | _options.Clear(); 81 | 82 | var lookupEnumeration = _lookupStrategy.Lookup(); 83 | while (lookupEnumeration.MoveNext()) 84 | { 85 | var objectTypePair = lookupEnumeration.Current; 86 | var obj = objectTypePair.Object; 87 | 88 | if ((_filter?.Invoke(objectTypePair)).GetValueOrDefault(true) && (obj.hideFlags & HideFlags.HideInHierarchy) == 0) 89 | { 90 | _options.Add(objectTypePair); 91 | } 92 | } 93 | 94 | RefreshVisibleOptions(); 95 | 96 | Repaint(); 97 | } 98 | 99 | private void RefreshVisibleOptions() 100 | { 101 | _visibleOptionIndices.Clear(); 102 | var hasSearchString = !string.IsNullOrEmpty(_searchString); 103 | 104 | _visibleOptionIndices.Add(-1); 105 | for (int i = 0; i < _options.Count; i++) 106 | { 107 | UnityEngine.Object obj = _options[i].Object; 108 | if (!hasSearchString || obj.ToString().ToLowerInvariant().Contains(_searchString.ToLowerInvariant())) 109 | { 110 | _visibleOptionIndices.Add(i); 111 | } 112 | } 113 | } 114 | 115 | private void AcceptSelectionAndClose() 116 | { 117 | _onPickCallback?.Invoke(_selectedOptionIndex >= 0 ? _options[_selectedOptionIndex].Object : null); 118 | Close(); 119 | } 120 | 121 | private void OnGUI() 122 | { 123 | // handle enter and escape keys 124 | var ev = Event.current; 125 | if (ev.type == EventType.KeyDown) 126 | { 127 | if (ev.keyCode == KeyCode.KeypadEnter || ev.keyCode == KeyCode.Return) 128 | { 129 | Event.current.Use(); 130 | AcceptSelectionAndClose(); 131 | return; 132 | } 133 | else if (ev.keyCode == KeyCode.Escape) 134 | { 135 | Event.current.Use(); 136 | Close(); 137 | return; 138 | } 139 | } 140 | 141 | // search bar 142 | EditorGUI.BeginChangeCheck(); 143 | 144 | var toolbarRect = EditorGUILayout.GetControlRect(false); 145 | 146 | var searchRect = toolbarRect; 147 | searchRect.width -= 150f; 148 | 149 | _searchString = _searchField.OnGUI(searchRect, _searchString); 150 | _searchField.SetFocus(); 151 | 152 | var buttonsRect = toolbarRect; 153 | buttonsRect.width = 100f; 154 | buttonsRect.center += Vector2.right * (searchRect.width + 50f); 155 | 156 | var leftButton = buttonsRect; leftButton.width *= 0.5f; 157 | var rightButton = leftButton; rightButton.center += Vector2.right * rightButton.width; 158 | 159 | if (GUI.Toggle(leftButton, _drawAsAList, "list", EditorStyles.miniButtonLeft) != _drawAsAList) 160 | { 161 | _drawAsAList = true; 162 | } 163 | 164 | if (GUI.Toggle(rightButton, !_drawAsAList, "grid", EditorStyles.miniButtonRight) == _drawAsAList) 165 | { 166 | _drawAsAList = false; 167 | } 168 | 169 | if (EditorGUI.EndChangeCheck()) 170 | { 171 | RefreshVisibleOptions(); 172 | } 173 | 174 | EditorGUILayout.Space(); 175 | 176 | DrawOptionsGUI(); 177 | } 178 | 179 | private void DrawOptionsGUI() 180 | { 181 | // display 182 | _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); 183 | 184 | if (_drawAsAList) 185 | { 186 | DrawList(); 187 | } 188 | else 189 | { 190 | DrawGrid(); 191 | } 192 | 193 | EditorGUILayout.EndScrollView(); 194 | } 195 | 196 | private void DrawList() 197 | { 198 | for (int i = 0; i < _visibleOptionIndices.Count; i++) 199 | { 200 | DrawOptionListSelectionLabel(_visibleOptionIndices[i]); 201 | } 202 | } 203 | 204 | private void DrawGrid() 205 | { 206 | const float PADDING = 10f; 207 | 208 | var width = position.width; 209 | var columnCount = Mathf.FloorToInt(width / GRID_TILE_SIZE_MIN); 210 | var tileWidth = width / columnCount; 211 | var tileHeight = tileWidth + EditorGUIUtility.singleLineHeight * 2f; 212 | 213 | var rows = Mathf.CeilToInt(_visibleOptionIndices.Count * 1f / columnCount); 214 | var height = rows * tileHeight; 215 | 216 | var rect = EditorGUILayout.GetControlRect(false, height, GUILayout.ExpandWidth(true)); 217 | 218 | for (int i = 0; i < rows; i++) 219 | { 220 | for (int j = 0; j < columnCount; j++) 221 | { 222 | var tileRect = new Rect(rect.min + new Vector2(j * tileWidth, i * tileHeight), new Vector2(tileWidth, tileHeight)); 223 | tileRect = Rect.MinMaxRect(tileRect.xMin + PADDING, tileRect.yMin + PADDING, tileRect.xMax - PADDING, tileRect.yMax - PADDING); 224 | 225 | var idx = i * columnCount + j; 226 | 227 | if (_visibleOptionIndices.Count > idx) 228 | { 229 | DrawOptionImagesSelectionLabel(tileRect, _visibleOptionIndices[idx]); 230 | } 231 | } 232 | } 233 | } 234 | 235 | private void DrawOptionImagesSelectionLabel(Rect tileRect, int optionIndex) 236 | { 237 | if (GUI.Button(tileRect, "", GUIStyle.none)) 238 | { 239 | if (_selectedOptionIndex == optionIndex) 240 | { 241 | AcceptSelectionAndClose(); 242 | return; 243 | } 244 | 245 | _selectedOptionIndex = optionIndex; 246 | } 247 | 248 | var labelStyle = (GUIStyle)"GridListText"; 249 | labelStyle.wordWrap = true; 250 | labelStyle.alignment = TextAnchor.UpperCenter; 251 | 252 | EditorGUI.Toggle(tileRect, GUIContent.none, _selectedOptionIndex == optionIndex, labelStyle); 253 | if (optionIndex >= 0) 254 | { 255 | var option = _options[optionIndex]; 256 | 257 | var imageRect = tileRect; 258 | imageRect.height = imageRect.width; 259 | 260 | var preview = AssetPreview.GetAssetPreview(option.Object); 261 | if (!preview) 262 | { 263 | preview = AssetPreview.GetMiniThumbnail(option.Object); 264 | } 265 | var imageDrawRect = Rect.MinMaxRect(imageRect.xMin + 10f, imageRect.yMin + 10f, imageRect.xMax - 10f, imageRect.yMax - 10f); 266 | 267 | GUI.DrawTexture(imageDrawRect, preview, ScaleMode.ScaleToFit, true); 268 | 269 | var labelRect = imageRect; 270 | labelRect.height = tileRect.height - imageRect.height; 271 | labelRect.center += Vector2.up * imageRect.height; 272 | 273 | EditorGUI.LabelField(labelRect, option.Object.ToString(), labelStyle); 274 | } 275 | else 276 | { 277 | var labelRect = tileRect; 278 | labelRect.height = tileRect.height - tileRect.width; 279 | labelRect.center += Vector2.up * tileRect.width; 280 | EditorGUI.LabelField(labelRect, "None", labelStyle); 281 | } 282 | } 283 | 284 | private void DrawOptionListSelectionLabel(int optionIndex) 285 | { 286 | var obj = optionIndex >= 0 ? _options[optionIndex].Object : null; 287 | 288 | string name = optionIndex >= 0 ? _options[optionIndex].Object.ToString() : "None"; 289 | string tag = optionIndex >= 0 ? _options[optionIndex].Type.ToString() : ""; 290 | 291 | var previewTexture = obj == null ? null : AssetPreview.GetMiniThumbnail(obj); 292 | 293 | if (DrawSelectableLabel(name, tag, _selectedOptionIndex == optionIndex, previewTexture)) 294 | { 295 | if (_selectedOptionIndex == optionIndex) 296 | { 297 | AcceptSelectionAndClose(); 298 | return; 299 | } 300 | 301 | _selectedOptionIndex = optionIndex; 302 | } 303 | } 304 | 305 | private bool DrawSelectableLabel(string text, string tag, bool isSelected, Texture2D icon = null) 306 | { 307 | var labelStyle = (GUIStyle)"GridListText"; 308 | 309 | bool result = false; 310 | var r = EditorGUILayout.GetControlRect(GUILayout.ExpandWidth(true)); 311 | 312 | if (GUI.Button(r, "", GUIStyle.none)) 313 | { 314 | result = true; 315 | } 316 | 317 | EditorGUI.Toggle(r, isSelected, labelStyle); 318 | 319 | float indentation = r.height; 320 | r.xMin += indentation; 321 | 322 | float iconWidth = r.height; 323 | if (icon) 324 | { 325 | GUI.DrawTexture(new Rect(r.min, new Vector2(r.height, r.height)), icon); 326 | } 327 | 328 | r.xMin += iconWidth; 329 | r.width -= iconWidth; 330 | 331 | float columnWidth = r.width / 2f; 332 | 333 | var columnRect = r; r.width = columnWidth; 334 | EditorGUI.LabelField(columnRect, text, EditorStyles.whiteLabel); 335 | 336 | columnRect.x += columnWidth; 337 | EditorGUI.LabelField(columnRect, tag, EditorStyles.whiteLabel); 338 | 339 | return result; 340 | } 341 | } 342 | } 343 | --------------------------------------------------------------------------------