├── assets ├── Source │ ├── _assembly.asmdef │ ├── _assembly.asmdef.meta │ ├── Common.meta │ ├── Collections.meta │ ├── Collections │ │ ├── EditableEntry.cs.meta │ │ ├── OrderedDictionary.cs.meta │ │ ├── EditableEntryAttribute.cs.meta │ │ ├── EditableEntry{TKey,TValue}.cs.meta │ │ ├── OrderedDictionary{TKey,TValue}.cs.meta │ │ ├── OrderedDictionary{TKey,TValue}.Enumerator.cs.meta │ │ ├── OrderedDictionary{TKey,TValue}.KeyCollection.cs.meta │ │ ├── OrderedDictionary{TKey,TValue}.ValueCollection.cs.meta │ │ ├── EditableEntryAttribute.cs │ │ ├── EditableEntry.cs │ │ ├── EditableEntry{TKey,TValue}.cs │ │ ├── OrderedDictionary{TKey,TValue}.Enumerator.cs │ │ ├── OrderedDictionary.cs │ │ ├── OrderedDictionary{TKey,TValue}.KeyCollection.cs │ │ ├── OrderedDictionary{TKey,TValue}.ValueCollection.cs │ │ └── OrderedDictionary{TKey,TValue}.cs │ └── Common │ │ ├── EditableOrderedDictionary_string_Sprite.cs.meta │ │ ├── EditableOrderedDictionary_string_Texture.cs.meta │ │ ├── EditableOrderedDictionary_string_string.cs.meta │ │ ├── EditableOrderedDictionary_string_Texture2D.cs.meta │ │ ├── EditableOrderedDictionary_string_string.cs │ │ ├── EditableOrderedDictionary_string_Sprite.cs │ │ ├── EditableOrderedDictionary_string_Texture.cs │ │ └── EditableOrderedDictionary_string_Texture2D.cs ├── Editor │ ├── _assembly.asmdef.meta │ ├── Collections.meta │ ├── Collections │ │ ├── IOrderedDictionaryListAdaptor.cs.meta │ │ ├── OrderedDictionaryListAdaptor.cs.meta │ │ ├── OrderedDictionaryPropertyDrawer.cs.meta │ │ ├── IEditableOrderedDictionaryContext.cs.meta │ │ ├── OrderedDictionaryNewEntryManager.cs.meta │ │ ├── IOrderedDictionaryListAdaptor.cs │ │ ├── IEditableOrderedDictionaryContext.cs │ │ ├── OrderedDictionaryNewEntryManager.cs │ │ ├── OrderedDictionaryListAdaptor.cs │ │ └── OrderedDictionaryPropertyDrawer.cs │ └── _assembly.asmdef ├── Editor.meta └── Source.meta ├── screenshot.png ├── screenshot2.png ├── index.js ├── .editorconfig ├── package.json ├── .gitignore ├── LICENSE ├── .gitattributes └── README.md /assets/Source/_assembly.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rotorz.unity3d-ordered-dictionary" 3 | } 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotorz/unity3d-ordered-dictionary/HEAD/screenshot.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rotorz/unity3d-ordered-dictionary/HEAD/screenshot2.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | throw new Error("This package is not supposed to be used directly."); 5 | -------------------------------------------------------------------------------- /assets/Editor/_assembly.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5d0b2db64ad156946b22c01a4e0f1021 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /assets/Source/_assembly.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8578be3ad9d73394f9a6dd4094fea2f0 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /assets/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a458c3ead0ebed34dbd1da1dcaca8ef4 3 | folderAsset: yes 4 | timeCreated: 1487023990 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /assets/Source.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b34ced32e11f49049918f2e267ea6999 3 | folderAsset: yes 4 | timeCreated: 1487023990 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /assets/Source/Common.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ff17ab9a5747d8042a5437ae5592c595 3 | folderAsset: yes 4 | timeCreated: 1487044964 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /assets/Editor/Collections.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 022fb295db48e2b49bbe7e5b8fb533a4 3 | folderAsset: yes 4 | timeCreated: 1487044921 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /assets/Source/Collections.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ab4fce98a9a74ce4a9f363929055aeb9 3 | folderAsset: yes 4 | timeCreated: 1487044905 5 | licenseType: Pro 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /assets/Source/Collections/EditableEntry.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bc802a3efa95cbf40ab22850b4dccdc9 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 19a29261b9989ff43a24261551a4d6e1 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Collections/EditableEntryAttribute.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: aaba14ff07617e54fb69826f026e4106 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Collections/EditableEntry{TKey,TValue}.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8aa282f26d1672b4382afa2c522ad731 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Editor/Collections/IOrderedDictionaryListAdaptor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 619a24560573b46468632ee1accb1469 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Editor/Collections/OrderedDictionaryListAdaptor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6896a707d2259a349b1264c4fb43025b 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Editor/Collections/OrderedDictionaryPropertyDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ba7be57680bfec648b85531737eeb4ae 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9077c4a3478778540aedf115d9764937 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Editor/Collections/IEditableOrderedDictionaryContext.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 454adf930789bdc4bb27af9a2cdc0dd7 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Editor/Collections/OrderedDictionaryNewEntryManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 612f49b08bca3144b908bd6d19dc04a9 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_Sprite.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fa6d97adb8f8d1a4ea24eb432ccc1890 3 | timeCreated: 1483510231 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_Texture.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 56a765fa677501041bb3ced9c584a34e 3 | timeCreated: 1483509947 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_string.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 94ddce6038a8156429fc89350d3ef381 3 | timeCreated: 1484298612 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.Enumerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e20ad9a33c382fc4087384aeb1a865ff 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_Texture2D.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a9a9298cdd7d1b94e8ce0b5b89f17a9d 3 | timeCreated: 1483524383 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.KeyCollection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a93726b225093b0469996afafaad1a80 3 | timeCreated: 1487023991 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.ValueCollection.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: edce5f4531cda8f4f9d2acf37f39b71c 3 | timeCreated: 1487023992 4 | licenseType: Pro 5 | MonoImporter: 6 | serializedVersion: 2 7 | defaultReferences: [] 8 | executionOrder: 0 9 | icon: {instanceID: 0} 10 | userData: 11 | assetBundleName: 12 | assetBundleVariant: 13 | -------------------------------------------------------------------------------- /assets/Editor/_assembly.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rotorz.unity3d-ordered-dictionary.editor", 3 | "references": [ 4 | "rotorz.unity3d-ordered-dictionary", 5 | "rotorz.unity3d-reorderable-list", 6 | "rotorz.unity3d-reorderable-list.editor", 7 | "rotorz.unity3d-utils.editor" 8 | ], 9 | "optionalUnityReferences": [], 10 | "includePlatforms": [ 11 | "Editor" 12 | ], 13 | "excludePlatforms": [], 14 | "allowUnsafeCode": false 15 | } 16 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | ; Grab the EditorConfig extension for Visual Studio: 2 | ; https://visualstudiogallery.msdn.microsoft.com/c8bccfe2-650c-4b42-bc5c-845e21f96328 3 | 4 | ; Top-most EditorConfig file 5 | root = true 6 | 7 | ; Unix-style newlines with a newline ending every file 8 | [*] 9 | end_of_line = LF 10 | insert_final_newline = true 11 | indent_style = space 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | 15 | [*.{md,yaml}] 16 | trim_trailing_whitespace = false 17 | 18 | [*.{js,json,yaml,html,css,styl}] 19 | indent_size = 2 20 | 21 | [Makefile] 22 | indent_style = tab 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@rotorz/unity3d-ordered-dictionary", 3 | "version": "1.0.3", 4 | "description": "Library for adding ordered dictionaries to custom `ScriptableObject` and `MonoBehaviour` classes in a way that can be serialized by Unity provided that the key and value types are serializable.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/rotorz/unity3d-ordered-dictionary" 12 | }, 13 | "author": "Rotorz Limited", 14 | "license": "MIT", 15 | "keywords": [ 16 | "unity3d", 17 | "unity3d-package" 18 | ], 19 | "dependencies": { 20 | "@rotorz/unity3d-reorderable-list": "github:rotorz/unity3d-reorderable-list#semver:^1.0.2", 21 | "@rotorz/unity3d-utils": "github:rotorz/unity3d-utils#semver:^1.0.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Derived From: 2 | # http://kleber-swf.com/the-definitive-gitignore-for-unity-projects/ 3 | 4 | # ============= # 5 | # Working Files # 6 | # ============= # 7 | node_modules/ 8 | npm-debug.log 9 | 10 | # ===================== # 11 | # Working Files - Unity # 12 | # ===================== # 13 | /Temp/ 14 | /Library/ 15 | /Packages/ 16 | /ProjectSettings/ 17 | /assets/Plugins/ 18 | /assets/Plugins.meta 19 | /*.csproj 20 | /*.sln 21 | 22 | # ===================================== # 23 | # Visual Studio / MonoDevelop generated # 24 | # ===================================== # 25 | /.vs/ 26 | bin 27 | obj 28 | ExportedObj/ 29 | *.svd 30 | *.userprefs 31 | *.pidb 32 | *.suo 33 | *.user 34 | *.unityproj 35 | *.booproj 36 | *.pdb 37 | *.pdb.meta 38 | 39 | # ============ # 40 | # OS generated # 41 | # ============ # 42 | .DS_Store 43 | .DS_Store? 44 | *~ 45 | ._* 46 | .Spotlight-V100 47 | .Trashes 48 | Icon? 49 | ehthumbs.db 50 | Thumbs.db 51 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_string.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. 3 | 4 | using System; 5 | 6 | namespace Rotorz.Games.Collections 7 | { 8 | /// 9 | /// An object that allows users to edit 10 | /// objects when using the Unity inspector. 11 | /// 12 | public sealed class EditableOrderedDictionary_string_string : EditableEntry 13 | { 14 | } 15 | 16 | 17 | /// 18 | /// An ordered dictionary with keys mapped to other 19 | /// 21 | [Serializable, EditableEntry(typeof(EditableOrderedDictionary_string_string))] 22 | public sealed class OrderedDictionary_string_string : OrderedDictionary 23 | { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_Sprite.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. 3 | 4 | using System; 5 | using UnityEngine; 6 | 7 | namespace Rotorz.Games.Collections 8 | { 9 | /// 10 | /// An object that allows users to edit 11 | /// objects when using the Unity inspector. 12 | /// 13 | public sealed class EditableOrderedDictionary_string_Sprite : EditableEntry 14 | { 15 | } 16 | 17 | 18 | /// 19 | /// An ordered dictionary with keys mapped to 20 | /// values. 21 | /// 22 | [Serializable, EditableEntry(typeof(EditableOrderedDictionary_string_Sprite))] 23 | public sealed class OrderedDictionary_string_Sprite : OrderedDictionary 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_Texture.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. 3 | 4 | using System; 5 | using UnityEngine; 6 | 7 | namespace Rotorz.Games.Collections 8 | { 9 | /// 10 | /// An object that allows users to edit 11 | /// objects when using the Unity inspector. 12 | /// 13 | public sealed class EditableOrderedDictionary_string_Texture : EditableEntry 14 | { 15 | } 16 | 17 | 18 | /// 19 | /// An ordered dictionary with keys mapped to 20 | /// values. 21 | /// 22 | [Serializable, EditableEntry(typeof(EditableOrderedDictionary_string_Texture))] 23 | public sealed class OrderedDictionary_string_Texture : OrderedDictionary 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /assets/Source/Common/EditableOrderedDictionary_string_Texture2D.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. 3 | 4 | using System; 5 | using UnityEngine; 6 | 7 | namespace Rotorz.Games.Collections 8 | { 9 | /// 10 | /// An object that allows users to edit 11 | /// objects when using the Unity inspector. 12 | /// 13 | public sealed class EditableOrderedDictionary_string_Texture2D : EditableEntry 14 | { 15 | } 16 | 17 | 18 | /// 19 | /// An ordered dictionary with keys mapped to 20 | /// values. 21 | /// 22 | [Serializable, EditableEntry(typeof(EditableOrderedDictionary_string_Texture2D))] 23 | public sealed class OrderedDictionary_string_Texture2D : OrderedDictionary 24 | { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Rotorz Limited 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. 22 | -------------------------------------------------------------------------------- /assets/Source/Collections/EditableEntryAttribute.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using System; 5 | 6 | namespace Rotorz.Games.Collections 7 | { 8 | /// 9 | /// Associates a type of with a type of . 10 | /// 11 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] 12 | public sealed class EditableEntryAttribute : Attribute 13 | { 14 | /// 15 | /// Initializes a new instance of the class. 16 | /// 17 | /// The type of editable entry. 18 | public EditableEntryAttribute(Type editableEntryType) 19 | { 20 | this.EditableEntryType = editableEntryType; 21 | } 22 | 23 | 24 | /// 25 | /// Gets the type of that is to be associated with 26 | /// the class. 27 | /// 28 | public Type EditableEntryType { get; private set; } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assets/Editor/Collections/IOrderedDictionaryListAdaptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using UnityEditor; 5 | 6 | namespace Rotorz.Games.Collections 7 | { 8 | /// 9 | /// Interface represents the context of an editable entry. 10 | /// 11 | public interface IOrderedDictionaryListAdaptor : IReorderableListAdaptor 12 | { 13 | /// 14 | /// Gets a value indicating whether a null key error was encountered. 15 | /// 16 | bool HadNullKeyErrorOnLastRepaint { get; } 17 | 18 | 19 | /// 20 | /// Adds a new entry to the ordered dictionary. 21 | /// 22 | /// Key for the new entry (will be copied from 23 | /// another serialized object). 24 | /// Value for the new entry (will be copied from 25 | /// another serialized object). 26 | /// 27 | /// If entry cannot be added because the collection has an inconsistent quantity 28 | /// of keys and values. 29 | /// 30 | void Add(SerializedProperty inputKeyProperty, SerializedProperty inputValueProperty); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /assets/Source/Collections/EditableEntry.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using UnityEngine; 5 | 6 | namespace Rotorz.Games.Collections 7 | { 8 | /// 9 | /// A class with a field that can be used to draw 10 | /// an 'Add Entry' control below the entries of an actual 11 | /// in an editor interface (i.e. the inspector). 12 | /// 13 | /// 14 | public abstract class EditableEntry : ScriptableObject 15 | { 16 | /// 17 | /// Gets the that has exactly one key/value entry. 18 | /// 19 | /// 20 | /// This property is exposed so that a list adaptor can be constructed to 21 | /// draw the 'Add Entry' controls. 22 | /// 23 | public abstract OrderedDictionary Dictionary { get; } 24 | 25 | /// 26 | /// Gets the user input for the new entry key. 27 | /// 28 | public abstract object Key { get; } 29 | 30 | /// 31 | /// Gets the user input for the new entry value. 32 | /// 33 | public abstract object Value { get; } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /assets/Source/Collections/EditableEntry{TKey,TValue}.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using UnityEngine; 5 | 6 | namespace Rotorz.Games.Collections 7 | { 8 | /// 9 | /// A class with a field that can 10 | /// be used to draw an 'Add Entry' control below the entries of an actual 11 | /// in an editor interface (i.e. the inspector). 12 | /// 13 | /// 14 | /// Type of ordered dictionary that is to be edited. 15 | public abstract class EditableEntry : EditableEntry 16 | where TOrderedDictionary : OrderedDictionary, new() 17 | { 18 | [SerializeField] 19 | private TOrderedDictionary dictionary = new TOrderedDictionary(); 20 | 21 | 22 | /// 23 | public override OrderedDictionary Dictionary { 24 | get { return this.dictionary; } 25 | } 26 | 27 | /// 28 | public override object Key { 29 | get { return this.dictionary.GetKeyFromIndex(0); } 30 | } 31 | 32 | /// 33 | public override object Value { 34 | get { return this.dictionary.GetValueFromIndex(0); } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /assets/Editor/Collections/IEditableOrderedDictionaryContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using System; 5 | using UnityEditor; 6 | 7 | namespace Rotorz.Games.Collections 8 | { 9 | /// 10 | /// Interface represents the context of an editable entry. 11 | /// 12 | public interface IEditableOrderedDictionaryContext 13 | { 14 | /// 15 | /// Gets the unique value that identifies the control. 16 | /// 17 | Guid ControlID { get; } 18 | 19 | /// 20 | /// Gets the ordered dictionary that is being edited. 21 | /// 22 | OrderedDictionary OrderedDictionary { get; } 23 | 24 | /// 25 | /// Gets the type of the ordered dictionary. 26 | /// 27 | Type OrderedDictionaryType { get; } 28 | 29 | 30 | /// 31 | /// Creates a that will be used to draw and 32 | /// manipulate the list of ordered dictionary entries. 33 | /// 34 | /// Property representing the ordered dictionary. 35 | /// 36 | /// The new instance. 37 | /// 38 | IOrderedDictionaryListAdaptor CreateListAdaptor(SerializedProperty dictionaryProperty); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Apply native OS line-endings on checkout of these files... 2 | *.boo text 3 | *.c text 4 | *.cginc text 5 | *.config text 6 | *.contentproj text 7 | *.cpp text 8 | *.cs text 9 | *.css text 10 | *.dae text 11 | *.DAE text 12 | *.dtd text 13 | *.fx text 14 | *.glsl text 15 | *.h text 16 | *.htm text 17 | *.html text 18 | *.inc text 19 | *.ini text 20 | *.js text 21 | *.JSFL text 22 | *.jsfl text 23 | *.json text 24 | *.log text 25 | *.md text 26 | *.mel text 27 | *.php text 28 | *.po text 29 | *.shader text 30 | *.txt text 31 | *.TXT text 32 | *.xaml text 33 | *.xml text 34 | *.xsd text 35 | .gitattributes text 36 | .gitignore text 37 | COPYING text 38 | INSTALL* text 39 | KEYS* text 40 | LICENSE* text 41 | NEWS* text 42 | NOTICE* text 43 | README* text 44 | TODO* text 45 | WHATSNEW* text 46 | 47 | # Apply Unix-style LF line-endings on checkout for these files since Unity 48 | # project has been configured to force text output for them... 49 | *.anim text eol=lf 50 | *.asset text eol=lf 51 | *.controller text eol=lf 52 | *.cubemap text eol=lf 53 | *.guiskin text eol=lf 54 | *.mat text eol=lf 55 | *.prefab text eol=lf 56 | *.physicMaterial text eol=lf 57 | *.physicmaterial text eol=lf 58 | *.unity text eol=lf 59 | 60 | # Apply Unix-style LF line-endings on checkout of these files... 61 | *.meta text eol=lf 62 | *.sh text eol=lf 63 | *.vspscc text eol=lf 64 | .htaccess text eol=lf 65 | 66 | # Apply Windows/DOS-style CR-LF line-endings on checkout of these files... 67 | *.bat text eol=crlf 68 | *.cmd text eol=crlf 69 | *.csproj text eol=crlf 70 | *.sln text eol=crlf 71 | *.user text eol=crlf 72 | *.vcproj text eol=crlf 73 | 74 | # No end-of-line conversions are applied (i.e., "-text -diff") to these files... 75 | *.7z binary 76 | *.ai binary 77 | *.apk binary 78 | *.bin binary 79 | *.bmp binary 80 | *.BMP binary 81 | *.com binary 82 | *.COM binary 83 | *.dex binary 84 | *.dll binary 85 | *.DLL binary 86 | *.dylib binary 87 | *.eps binary 88 | *.exe binary 89 | *.EXE binary 90 | *.exr binary 91 | *.fbx binary 92 | *.FBX binary 93 | *.fla binary 94 | *.flare binary 95 | *.flv binary 96 | *.gif binary 97 | *.gz binary 98 | *.ht binary 99 | *.ico binary 100 | *.jpeg binary 101 | *.jpg binary 102 | *.keystore binary 103 | *.mask binary 104 | *.mb binary 105 | *.mo binary 106 | *.mp3 binary 107 | *.mp4 binary 108 | *.mpg binary 109 | *.ogg binary 110 | *.PCX binary 111 | *.pcx binary 112 | *.pdb binary 113 | *.pdf binary 114 | *.png binary 115 | *.ps binary 116 | *.psd binary 117 | *.qt binary 118 | *.so binary 119 | *.swf binary 120 | *.tga binary 121 | *.tif binary 122 | *.tiff binary 123 | *.ttf binary 124 | *.TTF binary 125 | *.unitypackage binary 126 | *.unityPackage binary 127 | *.wav binary 128 | *.wmv binary 129 | *.zip binary 130 | *.ZIP binary 131 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.Enumerator.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Rotorz.Games.Collections 9 | { 10 | public partial class OrderedDictionary 11 | { 12 | /// 13 | /// Enumerator for enumerating through the key/value pairs of an ordered dictionary. 14 | /// 15 | public struct Enumerator : IEnumerator>, IDictionaryEnumerator 16 | { 17 | private readonly OrderedDictionary dictionary; 18 | private readonly int version; 19 | private readonly bool returnDictionaryEntry; 20 | 21 | private int index; 22 | private KeyValuePair current; 23 | 24 | 25 | /// 26 | /// Initializes a new instance of the structure. 27 | /// 28 | /// The associated dictionary. 29 | public Enumerator(OrderedDictionary dictionary, bool returnDictionaryEntry) 30 | { 31 | this.dictionary = dictionary; 32 | this.version = dictionary.version; 33 | this.returnDictionaryEntry = returnDictionaryEntry; 34 | 35 | this.index = 0; 36 | this.current = default(KeyValuePair); 37 | } 38 | 39 | 40 | /// 41 | void IDisposable.Dispose() 42 | { 43 | } 44 | 45 | /// 46 | public KeyValuePair Current { 47 | get { return this.current; } 48 | } 49 | 50 | /// 51 | object IEnumerator.Current { 52 | get { 53 | if (this.returnDictionaryEntry) { 54 | return new DictionaryEntry(this.current.Key, this.current.Value); 55 | } 56 | else { 57 | return this.current; 58 | } 59 | } 60 | } 61 | 62 | /// 63 | /// Gets the key of the current entry. 64 | /// 65 | public TKey Key { 66 | get { return this.current.Key; } 67 | } 68 | 69 | /// 70 | /// Gets the value of the current entry. 71 | /// 72 | public TValue Value { 73 | get { return this.current.Value; } 74 | } 75 | 76 | /// 77 | DictionaryEntry IDictionaryEnumerator.Entry { 78 | get { return new DictionaryEntry(this.current.Key, this.current.Value); } 79 | } 80 | 81 | /// 82 | object IDictionaryEnumerator.Key { 83 | get { return this.current.Key; } 84 | } 85 | 86 | /// 87 | object IDictionaryEnumerator.Value { 88 | get { return this.current.Value; } 89 | } 90 | 91 | /// 92 | public bool MoveNext() 93 | { 94 | this.dictionary.CheckVersion(version); 95 | 96 | if (this.index < this.dictionary.Count) { 97 | this.current = new KeyValuePair(this.dictionary.keys[this.index], this.dictionary.values[this.index]); 98 | ++this.index; 99 | return true; 100 | } 101 | 102 | this.current = default(KeyValuePair); 103 | return false; 104 | } 105 | 106 | /// 107 | void IEnumerator.Reset() 108 | { 109 | this.dictionary.CheckVersion(version); 110 | 111 | this.index = 0; 112 | this.current = default(KeyValuePair); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unity3d-ordered-dictionary 2 | 3 | Library for adding ordered dictionaries to custom `ScriptableObject` and `MonoBehaviour` 4 | classes in a way that can be serialized by Unity provided that the key and value types 5 | are serializable. 6 | 7 | 8 | ```sh 9 | $ yarn add rotorz/unity3d-ordered-dictionary 10 | ``` 11 | 12 | This package is compatible with the [unity3d-package-syncer][tool] tool. Refer to the 13 | tools' [README][tool] for information on syncing packages into a Unity project. 14 | 15 | [tool]: https://github.com/rotorz/unity3d-package-syncer 16 | 17 | ![screenshot](screenshot.png) 18 | 19 | 20 | ## Features 21 | 22 | - Default inspector with intuitive interface. 23 | - Drop insertion for UnityEngine.Object's where name is used for key. 24 | - Have multiple dictionaries per `ScriptableObject` or `MonoBehaviour`! 25 | - Serializable dictionary asset files. 26 | - Supports any serializable key and value types. 27 | - Ordered dictionary. 28 | 29 | 30 | ## Installation 31 | 32 | The **unity3d-ordered-dictionary** library is designed to be installed into Unity projects 33 | using the **npm** package manager and then synchronized into the "Assets" directory using 34 | the **unity3d-package-syncer** utility. For more information regarding this workflow refer 35 | to the [unity3d-package-syncer](https://github.com/rotorz/unity3d-package-syncer) 36 | repository. 37 | 38 | Alternatively you can download the contents of this repository and add directly into your 39 | project, but you would also need to download the sources of other packages that this 40 | package is dependant upon. Refer to the `packages.json` file to see these. 41 | 42 | 43 | ## Boilerplate Generation Tool 44 | 45 | This is a simple client side (offline) tool that runs to quickly generate the boilerplate 46 | that Unity needs to make the Ordered Dictionary for Unity library work since Unity doesn't 47 | currently support serialization with generic types: 48 | 49 | http://rotorz.com/unity/misc/ordered-dictionary-boilerplate 50 | 51 | 52 | ## A couple of examples! 53 | 54 | ### Sprite Library 55 | 56 | ```csharp 57 | // StringSpriteDictionaryEditable.cs 58 | using Rotorz.Games.Collections; 59 | using System; 60 | using UnityEngine; 61 | 62 | // Script filename must match this class. 63 | public sealed class StringSpriteDictionaryEditable : EditableEntry 64 | { 65 | } 66 | 67 | // This class can exist in same file with any name. 68 | [Serializable, EditableEntry(typeof(StringSpriteDictionaryEditable))] 69 | public sealed class StringSpriteDictionary : OrderedDictionary 70 | { 71 | } 72 | 73 | 74 | // SpriteLibrary.cs 75 | using UnityEditor; 76 | using UnityEngine; 77 | 78 | [CreateAssetMenu] 79 | public class SpriteLibrary : ScriptableObject 80 | { 81 | public StringSpriteDictionary sprites; 82 | } 83 | ``` 84 | 85 | 86 | ### String Lookup Table 87 | 88 | ![screenshot2](screenshot2.png) 89 | 90 | ```csharp 91 | // StringStringDictionaryEditable.cs 92 | using Rotorz.Games.Collections; 93 | using System; 94 | using UnityEngine; 95 | 96 | // Script filename must match this class. 97 | public sealed class StringStringDictionaryEditable : EditableEntry 98 | { 99 | } 100 | 101 | // This class can exist in same file with any name. 102 | [Serializable, EditableEntry(typeof(StringStringDictionaryEditable))] 103 | public sealed class StringStringDictionary : OrderedDictionary 104 | { 105 | } 106 | 107 | 108 | // AchievementNamesBehaviour.cs 109 | using UnityEditor; 110 | using UnityEngine; 111 | 112 | public class AchievementNamesBehaviour : MonoBehaviour 113 | { 114 | public StringStringDictionary names; 115 | } 116 | ``` 117 | 118 | 119 | ## Contribution Agreement 120 | 121 | This project is licensed under the MIT license (see LICENSE). To be in the best 122 | position to enforce these licenses the copyright status of this project needs to 123 | be as simple as possible. To achieve this the following terms and conditions 124 | must be met: 125 | 126 | - All contributed content (including but not limited to source code, text, 127 | image, videos, bug reports, suggestions, ideas, etc.) must be the 128 | contributors own work. 129 | 130 | - The contributor disclaims all copyright and accepts that their contributed 131 | content will be released to the public domain. 132 | 133 | - The act of submitting a contribution indicates that the contributor agrees 134 | with this agreement. This includes (but is not limited to) pull requests, issues, 135 | tickets, e-mails, newsgroups, blogs, forums, etc. 136 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using UnityEngine; 7 | 8 | namespace Rotorz.Games.Collections 9 | { 10 | /// 11 | /// Base class for a serializable ordered dictionary asset. Custom dictionary asset 12 | /// classes should inherit from the 13 | /// generic class instead. 14 | /// 15 | public abstract class OrderedDictionary 16 | { 17 | /// 18 | [SerializeField, HideInInspector] 19 | public bool suppressErrors; 20 | 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// Data type of a key. 26 | /// Data type of a value. 27 | protected OrderedDictionary(Type keyType, Type valueType) 28 | { 29 | this.KeyType = keyType; 30 | this.ValueType = valueType; 31 | } 32 | 33 | 34 | /// 35 | /// Gets the data type of a key. 36 | /// 37 | /// 38 | public Type KeyType { get; private set; } 39 | 40 | /// 41 | /// Gets the data type of a value. 42 | /// 43 | /// 44 | public Type ValueType { get; private set; } 45 | 46 | 47 | /// 48 | /// Gets the total count of entries in the dictionary. 49 | /// 50 | public abstract int Count { get; } 51 | 52 | 53 | /// 54 | /// Gets the collection of keys that somehow have two or more associated values. 55 | /// 56 | public abstract IEnumerable KeysWithDuplicateValues { get; } 57 | 58 | 59 | /// 60 | /// Implements the public interface . 61 | /// 62 | /// Zero-based index of entry in ordered dictionary. 63 | /// 64 | /// The key. 65 | /// 66 | /// 67 | /// If is out of range. 68 | /// 69 | protected abstract object GetKeyFromIndexInternal(int index); 70 | 71 | /// 72 | /// Implements the public interface . 73 | /// 74 | /// Zero-based index of entry in ordered dictionary. 75 | /// 76 | /// The key. 77 | /// 78 | /// 79 | /// If is out of range. 80 | /// 81 | protected abstract object GetValueFromIndexInternal(int index); 82 | 83 | /// 84 | /// Gets the key of the entry at the specified index. 85 | /// 86 | /// Zero-based index of entry in ordered dictionary. 87 | /// 88 | /// The key. 89 | /// 90 | /// 91 | /// If is out of range. 92 | /// 93 | /// 94 | /// 95 | public object GetKeyFromIndex(int index) 96 | { 97 | return this.GetKeyFromIndexInternal(index); 98 | } 99 | 100 | /// 101 | /// Gets the value of the entry at the specified index. 102 | /// 103 | /// Zero-based index of entry in ordered dictionary. 104 | /// 105 | /// The key. 106 | /// 107 | /// 108 | /// If is out of range. 109 | /// 110 | /// 111 | /// 112 | public object GetValueFromIndex(int index) 113 | { 114 | return this.GetValueFromIndexInternal(index); 115 | } 116 | 117 | /// 118 | /// Determines whether the dictionary contains the untyped key. 119 | /// 120 | /// Key to lookup. 121 | /// 122 | /// A value of true if dictionary contains an entry with the specified key. 123 | /// Always returns a value of false if the specified key is not of the type 124 | /// . 125 | /// 126 | /// 127 | /// If is null. 128 | /// 129 | public abstract bool ContainsKey(object key); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /assets/Editor/Collections/OrderedDictionaryNewEntryManager.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using Rotorz.Games.UnityEditorExtensions; 5 | using System; 6 | using System.Linq; 7 | using UnityEditor; 8 | using UnityEngine; 9 | using Object = UnityEngine.Object; 10 | 11 | namespace Rotorz.Games.Collections 12 | { 13 | /// 14 | /// Manages the active ordered dictionary new entry editor. 15 | /// 16 | public static class OrderedDictionaryNewEntryManager 17 | { 18 | private static IEditableOrderedDictionaryContext s_ActiveContext; 19 | private static EditableEntry s_NewEntry; 20 | 21 | 22 | /// 23 | /// Gets the unique identifier of the active new entry control. 24 | /// 25 | public static Guid ActiveControlID { 26 | get { return s_ActiveContext != null ? s_ActiveContext.ControlID : Guid.Empty; } 27 | } 28 | 29 | /// 30 | /// Gets the ordered dictionary list adaptor that is used to draw the 'new entry' field. 31 | /// 32 | public static IOrderedDictionaryListAdaptor NewEntryListAdaptor { get; private set; } 33 | 34 | /// 35 | /// Gets a for the new entry object. 36 | /// 37 | public static SerializedObject NewEntryObject { get; private set; } 38 | /// 39 | /// Gets a for the key property of the new entry. 40 | /// 41 | public static SerializedProperty NewEntryKeyProperty { get; private set; } 42 | /// 43 | /// Gets a for the value property of the new entry. 44 | /// 45 | public static SerializedProperty NewEntryValueProperty { get; private set; } 46 | 47 | 48 | /// 49 | /// Activates the 'new entry' editor for a specified control. 50 | /// 51 | /// Context of the editable entry. 52 | public static void SetActiveNewEntry(IEditableOrderedDictionaryContext context) 53 | { 54 | if (context == null) { 55 | throw new ArgumentNullException("context"); 56 | } 57 | if (context.ControlID == Guid.Empty) { 58 | throw new ArgumentException("Invalid control identifier.", "context"); 59 | } 60 | if (!typeof(OrderedDictionary).IsAssignableFrom(context.OrderedDictionaryType)) { 61 | throw new ArgumentException("Not a valid ordered dictionary type.", "context"); 62 | } 63 | 64 | if (context.ControlID == ActiveControlID) { 65 | return; 66 | } 67 | 68 | DiscardActiveNewEntry(); 69 | 70 | s_NewEntry = CreateEditableEntryObject(context.OrderedDictionaryType); 71 | 72 | NewEntryObject = new SerializedObject(s_NewEntry); 73 | var dictionaryProperty = NewEntryObject.FindProperty("dictionary"); 74 | var keysProperty = dictionaryProperty.FindPropertyRelative("keys"); 75 | var valuesProperty = dictionaryProperty.FindPropertyRelative("values"); 76 | 77 | // Add a single key/value entry to the editable entry. 78 | NewEntryObject.Update(); 79 | keysProperty.arraySize = 1; 80 | valuesProperty.arraySize = 1; 81 | NewEntryObject.ApplyModifiedPropertiesWithoutUndo(); 82 | 83 | // Get a `SerializedProperty` for the key and value properties. 84 | NewEntryKeyProperty = keysProperty.GetArrayElementAtIndex(0); 85 | NewEntryValueProperty = valuesProperty.GetArrayElementAtIndex(0); 86 | 87 | NewEntryListAdaptor = context.CreateListAdaptor(dictionaryProperty); 88 | s_ActiveContext = context; 89 | } 90 | 91 | /// 92 | /// Discard any current active editable entry. 93 | /// 94 | public static void DiscardActiveNewEntry() 95 | { 96 | if (ActiveControlID == Guid.Empty) { 97 | return; 98 | } 99 | 100 | s_ActiveContext = null; 101 | 102 | Object.DestroyImmediate(s_NewEntry); 103 | s_NewEntry = null; 104 | 105 | NewEntryObject = null; 106 | NewEntryKeyProperty = null; 107 | NewEntryValueProperty = null; 108 | } 109 | 110 | /// 111 | /// Resets state of the new entry input controls. 112 | /// 113 | public static void ResetActiveNewEntry() 114 | { 115 | NewEntryObject.Update(); 116 | SerializedPropertyUtility.ResetValue(NewEntryKeyProperty); 117 | SerializedPropertyUtility.ResetValue(NewEntryValueProperty); 118 | NewEntryObject.ApplyModifiedProperties(); 119 | } 120 | 121 | 122 | /// 123 | /// Determines whether the current new entry can be added to the specified control. 124 | /// 125 | /// Unique identifier of the specified control. 126 | /// 127 | /// A value of true if the current new entry can be added to the specified 128 | /// control; otherwise, a value of false. 129 | /// 130 | public static bool CanAddNewEntry(Guid controlID) 131 | { 132 | if (controlID != ActiveControlID) { 133 | return false; 134 | } 135 | 136 | var newKeyValue = s_NewEntry.Key; 137 | 138 | bool isNullKey = newKeyValue == null; 139 | bool dictionaryAlreadyContainsKey = newKeyValue != null && s_ActiveContext.OrderedDictionary.ContainsKey(newKeyValue); 140 | 141 | return !dictionaryAlreadyContainsKey && !isNullKey; 142 | } 143 | 144 | 145 | private static EditableEntry CreateEditableEntryObject(Type orderedDictionaryType) 146 | { 147 | var editableEntryAttribute = GetEditableEntryAttribute(orderedDictionaryType); 148 | if (editableEntryAttribute == null) { 149 | throw new InvalidOperationException("Custom ordered dictionary type is not annotated with an `EditableEntryAttribute`."); 150 | } 151 | 152 | var editableEntry = (EditableEntry)ScriptableObject.CreateInstance(editableEntryAttribute.EditableEntryType); 153 | editableEntry.hideFlags = HideFlags.DontSave; 154 | editableEntry.Dictionary.suppressErrors = true; 155 | return editableEntry; 156 | } 157 | 158 | private static EditableEntryAttribute GetEditableEntryAttribute(Type orderedDictionaryType) 159 | { 160 | return orderedDictionaryType.GetCustomAttributes(typeof(EditableEntryAttribute), true) 161 | .Cast() 162 | .FirstOrDefault(); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.KeyCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Rotorz.Games.Collections 9 | { 10 | public partial class OrderedDictionary 11 | { 12 | /// 13 | /// A read-only ordered collection of keys from the associated instance. 14 | /// 15 | public sealed class KeyCollection : IList, ICollection 16 | { 17 | private readonly OrderedDictionary dictionary; 18 | 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The associated dictionary. 24 | public KeyCollection(OrderedDictionary dictionary) 25 | { 26 | this.dictionary = dictionary; 27 | } 28 | 29 | 30 | /// 31 | public int Count { 32 | get { return this.dictionary.keys.Count; } 33 | } 34 | 35 | /// 36 | bool ICollection.IsReadOnly { 37 | get { return true; } 38 | } 39 | 40 | /// 41 | bool ICollection.IsSynchronized { 42 | get { return (this.dictionary.dictionary as ICollection).IsSynchronized; } 43 | } 44 | 45 | /// 46 | object ICollection.SyncRoot { 47 | get { return (this.dictionary.dictionary as ICollection).SyncRoot; } 48 | } 49 | 50 | /// 51 | /// Gets key of entry at a specific index in the ordered dictionary. 52 | /// 53 | /// Zero-based index of entry. 54 | /// 55 | /// The . 56 | /// 57 | /// 58 | /// If is out of range of the collection. 59 | /// 60 | public TKey this[int index] { 61 | get { 62 | this.dictionary.CheckIndexArgument(index); 63 | 64 | return this.dictionary.keys[index]; 65 | } 66 | } 67 | 68 | /// 69 | TKey IList.this[int index] { 70 | get { return this[index]; } 71 | set { ThrowReadOnlyException(); } 72 | } 73 | 74 | /// 75 | public bool Contains(TKey item) 76 | { 77 | return this.dictionary.ContainsKey(item); 78 | } 79 | 80 | /// 81 | void ICollection.CopyTo(Array array, int index) 82 | { 83 | (this.dictionary.keys as ICollection).CopyTo(array, index); 84 | } 85 | 86 | /// 87 | public void CopyTo(TKey[] array, int arrayIndex) 88 | { 89 | this.dictionary.keys.CopyTo(array, arrayIndex); 90 | } 91 | 92 | /// 93 | /// Gets an object for enumerating over the ordered collection of keys. 94 | /// 95 | /// 96 | /// The new . 97 | /// 98 | public Enumerator GetEnumerator() 99 | { 100 | return new Enumerator(this.dictionary); 101 | } 102 | 103 | /// 104 | IEnumerator IEnumerable.GetEnumerator() 105 | { 106 | return this.GetEnumerator(); 107 | } 108 | 109 | /// 110 | IEnumerator IEnumerable.GetEnumerator() 111 | { 112 | return this.GetEnumerator(); 113 | } 114 | 115 | /// 116 | void ICollection.Add(TKey item) 117 | { 118 | ThrowReadOnlyException(); 119 | } 120 | 121 | /// 122 | bool ICollection.Remove(TKey item) 123 | { 124 | ThrowReadOnlyException(); 125 | return false; 126 | } 127 | 128 | /// 129 | void ICollection.Clear() 130 | { 131 | ThrowReadOnlyException(); 132 | } 133 | 134 | /// 135 | public int IndexOf(TKey item) 136 | { 137 | return this.dictionary.keys.IndexOf(item); 138 | } 139 | 140 | /// 141 | void IList.Insert(int index, TKey item) 142 | { 143 | ThrowReadOnlyException(); 144 | } 145 | 146 | /// 147 | void IList.RemoveAt(int index) 148 | { 149 | ThrowReadOnlyException(); 150 | } 151 | 152 | 153 | /// 154 | /// Enumerator for enumerating through the keys of an ordered dictionary. 155 | /// 156 | public struct Enumerator : IEnumerator 157 | { 158 | private readonly OrderedDictionary dictionary; 159 | private readonly int version; 160 | 161 | private int index; 162 | private TKey current; 163 | 164 | /// 165 | /// Initializes a new instance of the structure. 166 | /// 167 | /// The associated dictionary. 168 | public Enumerator(OrderedDictionary dictionary) 169 | { 170 | this.dictionary = dictionary; 171 | this.version = dictionary.version; 172 | 173 | this.index = 0; 174 | this.current = default(TKey); 175 | } 176 | 177 | /// 178 | void IDisposable.Dispose() 179 | { 180 | } 181 | 182 | /// 183 | public TKey Current { 184 | get { return this.current; } 185 | } 186 | 187 | /// 188 | object IEnumerator.Current { 189 | get { return this.current; } 190 | } 191 | 192 | /// 193 | public bool MoveNext() 194 | { 195 | this.dictionary.CheckVersion(this.version); 196 | 197 | if (this.index < this.dictionary.keys.Count) { 198 | this.current = this.dictionary.keys[this.index]; 199 | ++this.index; 200 | return true; 201 | } 202 | return false; 203 | } 204 | 205 | /// 206 | public void Reset() 207 | { 208 | this.dictionary.CheckVersion(this.version); 209 | 210 | this.current = default(TKey); 211 | this.index = 0; 212 | } 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.ValueCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | namespace Rotorz.Games.Collections 9 | { 10 | public partial class OrderedDictionary 11 | { 12 | /// 13 | /// A read-only ordered collection of values from the associated instance. 14 | /// 15 | public sealed class ValueCollection : IList, ICollection 16 | { 17 | private readonly OrderedDictionary dictionary; 18 | 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The associated dictionary. 24 | public ValueCollection(OrderedDictionary dictionary) 25 | { 26 | this.dictionary = dictionary; 27 | } 28 | 29 | 30 | /// 31 | public int Count { 32 | get { return this.dictionary.values.Count; } 33 | } 34 | 35 | /// 36 | bool ICollection.IsReadOnly { 37 | get { return true; } 38 | } 39 | 40 | /// 41 | bool ICollection.IsSynchronized { 42 | get { return (this.dictionary.dictionary as ICollection).IsSynchronized; } 43 | } 44 | 45 | /// 46 | object ICollection.SyncRoot { 47 | get { return (this.dictionary.dictionary as ICollection).SyncRoot; } 48 | } 49 | 50 | /// 51 | /// Gets value of entry at a specific index in the ordered dictionary. 52 | /// 53 | /// Zero-based index of entry. 54 | /// 55 | /// The . 56 | /// 57 | /// 58 | /// If is out of range of the collection. 59 | /// 60 | public TValue this[int index] { 61 | get { 62 | this.dictionary.CheckIndexArgument(index); 63 | 64 | return this.dictionary.values[index]; 65 | } 66 | } 67 | 68 | /// 69 | TValue IList.this[int index] { 70 | get { return this[index]; } 71 | set { ThrowReadOnlyException(); } 72 | } 73 | 74 | /// 75 | public bool Contains(TValue item) 76 | { 77 | return this.dictionary.values.Contains(item); 78 | } 79 | 80 | /// 81 | void ICollection.CopyTo(Array array, int index) 82 | { 83 | (this.dictionary.values as ICollection).CopyTo(array, index); 84 | } 85 | 86 | /// 87 | public void CopyTo(TValue[] array, int arrayIndex) 88 | { 89 | this.dictionary.values.CopyTo(array, arrayIndex); 90 | } 91 | 92 | /// 93 | /// Gets an object for enumerating over the ordered collection of values. 94 | /// 95 | /// 96 | /// The new . 97 | /// 98 | public Enumerator GetEnumerator() 99 | { 100 | return new Enumerator(this.dictionary); 101 | } 102 | 103 | /// 104 | IEnumerator IEnumerable.GetEnumerator() 105 | { 106 | return this.GetEnumerator(); 107 | } 108 | 109 | /// 110 | IEnumerator IEnumerable.GetEnumerator() 111 | { 112 | return this.GetEnumerator(); 113 | } 114 | 115 | /// 116 | void ICollection.Add(TValue item) 117 | { 118 | ThrowReadOnlyException(); 119 | } 120 | 121 | /// 122 | bool ICollection.Remove(TValue item) 123 | { 124 | ThrowReadOnlyException(); 125 | return false; 126 | } 127 | 128 | /// 129 | void ICollection.Clear() 130 | { 131 | ThrowReadOnlyException(); 132 | } 133 | 134 | /// 135 | public int IndexOf(TValue item) 136 | { 137 | return this.dictionary.values.IndexOf(item); 138 | } 139 | 140 | /// 141 | void IList.Insert(int index, TValue item) 142 | { 143 | ThrowReadOnlyException(); 144 | } 145 | 146 | /// 147 | void IList.RemoveAt(int index) 148 | { 149 | ThrowReadOnlyException(); 150 | } 151 | 152 | 153 | /// 154 | /// Enumerator for enumerating through the values of an ordered dictionary. 155 | /// 156 | public struct Enumerator : IEnumerator 157 | { 158 | private readonly OrderedDictionary dictionary; 159 | private readonly int version; 160 | 161 | private int index; 162 | private TValue current; 163 | 164 | 165 | /// 166 | /// Initializes a new instance of the structure. 167 | /// 168 | /// The associated dictionary. 169 | public Enumerator(OrderedDictionary dictionary) 170 | { 171 | this.dictionary = dictionary; 172 | this.version = dictionary.version; 173 | 174 | this.index = 0; 175 | this.current = default(TValue); 176 | } 177 | 178 | 179 | /// 180 | void IDisposable.Dispose() 181 | { 182 | } 183 | 184 | /// 185 | public TValue Current { 186 | get { return this.current; } 187 | } 188 | 189 | /// 190 | object IEnumerator.Current { 191 | get { return this.current; } 192 | } 193 | 194 | /// 195 | public bool MoveNext() 196 | { 197 | this.dictionary.CheckVersion(this.version); 198 | 199 | if (this.index < this.dictionary.values.Count) { 200 | this.current = this.dictionary.values[this.index]; 201 | ++this.index; 202 | return true; 203 | } 204 | return false; 205 | } 206 | 207 | /// 208 | public void Reset() 209 | { 210 | this.dictionary.CheckVersion(this.version); 211 | 212 | this.current = default(TValue); 213 | this.index = 0; 214 | } 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /assets/Editor/Collections/OrderedDictionaryListAdaptor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using Rotorz.Games.UnityEditorExtensions; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using UnityEditor; 9 | using UnityEngine; 10 | using Object = UnityEngine.Object; 11 | 12 | namespace Rotorz.Games.Collections 13 | { 14 | /// 15 | /// Ordered dictionary asset adaptor for a can 16 | /// be subclassed to override its behavior. 17 | /// 18 | /// 19 | /// The class can also be subclassed 20 | /// allowing you to initialize a custom 21 | /// subclass to be instantiated. 22 | /// 23 | public class OrderedDictionaryListAdaptor : IOrderedDictionaryListAdaptor, IReorderableListDropTarget 24 | { 25 | protected readonly OrderedDictionary Target; 26 | protected readonly SerializedPropertyAdaptor KeysPropertyAdaptor; 27 | protected readonly SerializedPropertyAdaptor ValuesPropertyAdaptor; 28 | 29 | 30 | /// 31 | /// Initializes a new instance of the class. 32 | /// 33 | /// The target object. 34 | /// The adaptor for the ordered dictionary's keys. 35 | /// The adaptor for the ordered dictionary's values. 36 | public OrderedDictionaryListAdaptor(OrderedDictionary target, SerializedPropertyAdaptor keysPropertyAdaptor, SerializedPropertyAdaptor valuesPropertyAdaptor) 37 | { 38 | this.Target = target; 39 | this.KeysPropertyAdaptor = keysPropertyAdaptor; 40 | this.ValuesPropertyAdaptor = valuesPropertyAdaptor; 41 | } 42 | 43 | 44 | #region Manipulation 45 | 46 | /// 47 | public virtual bool CanDrag(int index) 48 | { 49 | return this.KeysPropertyAdaptor.CanDrag(index) && this.ValuesPropertyAdaptor.CanDrag(index); 50 | } 51 | 52 | /// 53 | public virtual bool CanRemove(int index) 54 | { 55 | return this.KeysPropertyAdaptor.CanRemove(index) && this.ValuesPropertyAdaptor.CanRemove(index); 56 | } 57 | 58 | /// 59 | public int Count { 60 | get { return this.KeysPropertyAdaptor.Count; } 61 | } 62 | 63 | /// 64 | public virtual void Clear() 65 | { 66 | this.KeysPropertyAdaptor.Clear(); 67 | this.ValuesPropertyAdaptor.Clear(); 68 | } 69 | 70 | /// 71 | void IReorderableListAdaptor.Add() 72 | { 73 | throw new NotImplementedException(); 74 | } 75 | 76 | /// 77 | public virtual void Add(SerializedProperty inputKeyProperty, SerializedProperty inputValueProperty) 78 | { 79 | if (this.KeysPropertyAdaptor.ArrayProperty.arraySize != this.ValuesPropertyAdaptor.ArrayProperty.arraySize) { 80 | throw new InvalidOperationException("Cannot add entry because of inconsistent count of keys and values."); 81 | } 82 | 83 | int count = this.KeysPropertyAdaptor.ArrayProperty.arraySize + 1; 84 | this.KeysPropertyAdaptor.ArrayProperty.arraySize = count; 85 | this.ValuesPropertyAdaptor.ArrayProperty.arraySize = count; 86 | 87 | var addedKeyProperty = this.KeysPropertyAdaptor.ArrayProperty.GetArrayElementAtIndex(count - 1); 88 | var addedValueProperty = this.ValuesPropertyAdaptor.ArrayProperty.GetArrayElementAtIndex(count - 1); 89 | SerializedPropertyUtility.CopyPropertyValue(addedKeyProperty, inputKeyProperty); 90 | SerializedPropertyUtility.CopyPropertyValue(addedValueProperty, inputValueProperty); 91 | } 92 | 93 | /// 94 | void IReorderableListAdaptor.Duplicate(int index) 95 | { 96 | throw new NotImplementedException(); 97 | } 98 | 99 | /// 100 | void IReorderableListAdaptor.Insert(int index) 101 | { 102 | throw new NotImplementedException(); 103 | } 104 | 105 | /// 106 | public virtual void Move(int sourceIndex, int destIndex) 107 | { 108 | this.KeysPropertyAdaptor.Move(sourceIndex, destIndex); 109 | this.ValuesPropertyAdaptor.Move(sourceIndex, destIndex); 110 | } 111 | 112 | /// 113 | public virtual void Remove(int index) 114 | { 115 | this.KeysPropertyAdaptor.Remove(index); 116 | this.ValuesPropertyAdaptor.Remove(index); 117 | } 118 | 119 | #endregion 120 | 121 | #region Drawing 122 | 123 | /// 124 | public bool HadNullKeyErrorOnLastRepaint { get; private set; } 125 | 126 | /// 127 | public virtual float GetItemHeight(int index) 128 | { 129 | float keyHeight = this.KeysPropertyAdaptor.GetItemHeight(index); 130 | float valueHeight = this.ValuesPropertyAdaptor.GetItemHeight(index); 131 | return Mathf.Max(keyHeight, valueHeight); 132 | } 133 | 134 | /// 135 | public virtual void BeginGUI() 136 | { 137 | if (Event.current.type == EventType.Repaint) { 138 | this.HadNullKeyErrorOnLastRepaint = false; 139 | } 140 | } 141 | 142 | /// 143 | public virtual void EndGUI() 144 | { 145 | } 146 | 147 | /// 148 | public virtual void DrawItemBackground(Rect position, int index) 149 | { 150 | } 151 | 152 | /// 153 | public virtual void DrawItem(Rect position, int index) 154 | { 155 | // Intercept context click before sub-controls have a chance to avoid 156 | // revealing undesirable commands such as item insertion/removal. 157 | if (Event.current.type == EventType.ContextClick && position.Contains(Event.current.mousePosition)) { 158 | this.OnContextClickItem(index); 159 | Event.current.Use(); 160 | } 161 | 162 | Color restoreColor = GUI.color; 163 | if (!this.Target.suppressErrors) { 164 | var key = this.Target.GetKeyFromIndex(index); 165 | 166 | if (this.Target.KeysWithDuplicateValues.Contains(key)) 167 | GUI.color = Color.red; 168 | 169 | if (key == null) { 170 | this.HadNullKeyErrorOnLastRepaint = true; 171 | GUI.color = new Color(1f, 0f, 1f); 172 | } 173 | } 174 | 175 | Rect keyPosition = position; 176 | keyPosition.width /= 3f; 177 | keyPosition.height = this.KeysPropertyAdaptor.GetItemHeight(index); 178 | this.KeysPropertyAdaptor.DrawItem(keyPosition, index); 179 | 180 | GUI.color = restoreColor; 181 | 182 | Rect valuePosition = position; 183 | valuePosition.xMin = keyPosition.xMax + 5f; 184 | valuePosition.height = this.ValuesPropertyAdaptor.GetItemHeight(index); 185 | this.ValuesPropertyAdaptor.DrawItem(valuePosition, index); 186 | } 187 | 188 | /// 189 | /// Occurs allowing a list item to respond to a context click. 190 | /// 191 | /// Zero-based index of the list item. 192 | protected virtual void OnContextClickItem(int index) 193 | { 194 | } 195 | 196 | #endregion 197 | 198 | #region Drop Insertion 199 | 200 | private Rect DropTargetPosition { 201 | get { 202 | // Expand size of drop target slightly so that it is easier to drop. 203 | Rect dropPosition = ReorderableListGUI.CurrentListPosition; 204 | dropPosition.y -= 10; 205 | dropPosition.height += 15; 206 | return dropPosition; 207 | } 208 | } 209 | 210 | /// 211 | public bool CanDropInsert(int insertionIndex) 212 | { 213 | if (!typeof(string).IsAssignableFrom(this.Target.KeyType)) 214 | return false; 215 | if (!typeof(Object).IsAssignableFrom(this.Target.ValueType)) 216 | return false; 217 | if (!this.DropTargetPosition.Contains(Event.current.mousePosition)) 218 | return false; 219 | 220 | var valueType = this.Target.ValueType; 221 | 222 | foreach (var obj in DragAndDrop.objectReferences) { 223 | if (!EditorUtility.IsPersistent(obj)) { 224 | continue; 225 | } 226 | 227 | if (valueType.IsAssignableFrom(obj.GetType())) { 228 | return true; 229 | } 230 | else { 231 | string assetPath = AssetDatabase.GetAssetPath(obj); 232 | if (AssetDatabase.LoadAllAssetsAtPath(assetPath).Any(o => valueType.IsAssignableFrom(o.GetType()))) { 233 | return true; 234 | } 235 | } 236 | } 237 | 238 | return false; 239 | } 240 | 241 | /// 242 | public void ProcessDropInsertion(int insertionIndex) 243 | { 244 | if (Event.current.type == EventType.DragPerform) { 245 | foreach (var objectReference in this.GetDraggedObjectReferences()) { 246 | if (this.Target.ContainsKey(objectReference.name)) { 247 | continue; 248 | } 249 | this.InsertObjectReferenceEntry(insertionIndex++, objectReference); 250 | } 251 | } 252 | } 253 | 254 | private IEnumerable GetDraggedObjectReferences() 255 | { 256 | var objectReferences = new HashSet(); 257 | var valueType = this.Target.ValueType; 258 | 259 | foreach (var obj in DragAndDrop.objectReferences) { 260 | if (!EditorUtility.IsPersistent(obj)) { 261 | continue; 262 | } 263 | 264 | if (valueType.IsAssignableFrom(obj.GetType())) { 265 | objectReferences.Add(obj); 266 | } 267 | else { 268 | string assetPath = AssetDatabase.GetAssetPath(obj); 269 | objectReferences.UnionWith(AssetDatabase.LoadAllAssetsAtPath(assetPath).Where(o => valueType.IsAssignableFrom(o.GetType()))); 270 | } 271 | } 272 | 273 | return objectReferences.OrderBy(sprite => sprite.name); 274 | } 275 | 276 | private void InsertObjectReferenceEntry(int insertionIndex, Object objectReference) 277 | { 278 | this.KeysPropertyAdaptor.Insert(insertionIndex); 279 | this.ValuesPropertyAdaptor.Insert(insertionIndex); 280 | 281 | var keyProperty = this.KeysPropertyAdaptor.ArrayProperty.GetArrayElementAtIndex(insertionIndex); 282 | var valueProperty = this.ValuesPropertyAdaptor.ArrayProperty.GetArrayElementAtIndex(insertionIndex); 283 | 284 | keyProperty.stringValue = objectReference.name; 285 | valueProperty.objectReferenceValue = objectReference; 286 | } 287 | 288 | #endregion 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /assets/Editor/Collections/OrderedDictionaryPropertyDrawer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using Rotorz.Games.UnityEditorExtensions; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using UnityEditor; 10 | using UnityEngine; 11 | 12 | namespace Rotorz.Games.Collections 13 | { 14 | /// 15 | /// Inspector that is assumed by default for all 16 | /// subclasses but can be subclassed to override its behavior. 17 | /// 18 | /// 19 | [CustomPropertyDrawer(typeof(OrderedDictionary), useForChildren: true)] 20 | public class OrderedDictionaryPropertyDrawer : PropertyDrawer, IEditableOrderedDictionaryContext 21 | { 22 | protected const float TitleHeight = 21f; 23 | protected const float NewInputPlaceholderHeight = 23f; 24 | protected const float NewInputSpacing = 2f; 25 | protected const float EndSpacing = 5f; 26 | 27 | private const float AddButtonWidth = 32f; 28 | private const float VerticalPadding = 8f; 29 | private const float HalfVerticalPadding = VerticalPadding / 2f; 30 | 31 | 32 | private readonly Guid controlID; 33 | private readonly List errors = new List(); 34 | 35 | private SerializedProperty property; 36 | private bool deferFocusNewInput; 37 | 38 | 39 | /// 40 | /// Initializes a new instance of the class. 41 | /// 42 | public OrderedDictionaryPropertyDrawer() 43 | { 44 | this.controlID = Guid.NewGuid(); 45 | } 46 | 47 | 48 | /// 49 | public Guid ControlID { 50 | get { return this.controlID; } 51 | } 52 | 53 | /// 54 | public OrderedDictionary OrderedDictionary { get; private set; } 55 | 56 | /// 57 | public Type OrderedDictionaryType { get; private set; } 58 | 59 | 60 | /// 61 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) 62 | { 63 | this.Initialize(property); 64 | 65 | this.UpdateDictionaryErrors(); 66 | 67 | float listHeight = this.ListControl.CalculateListHeight(this.ListAdaptor); 68 | float newInputHeight = NewInputPlaceholderHeight; 69 | float errorOutputHeight = this.errors.Sum(error => EditorStyles.helpBox.CalcHeight(error, 0f)); 70 | 71 | if (this.controlID == OrderedDictionaryNewEntryManager.ActiveControlID) { 72 | newInputHeight = ReorderableListGUI.CalculateListFieldHeight(OrderedDictionaryNewEntryManager.NewEntryListAdaptor, this.ListControl.Flags); 73 | } 74 | 75 | return TitleHeight + listHeight + NewInputSpacing + newInputHeight + errorOutputHeight + EndSpacing; 76 | } 77 | 78 | /// 79 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) 80 | { 81 | EditorGUI.BeginProperty(position, label, property); 82 | 83 | this.Initialize(property); 84 | 85 | float newInputHeight = NewInputPlaceholderHeight; 86 | float errorOutputHeight = this.errors.Sum(error => EditorStyles.helpBox.CalcHeight(error, 0f)); 87 | 88 | if (this.controlID == OrderedDictionaryNewEntryManager.ActiveControlID) { 89 | newInputHeight = ReorderableListGUI.CalculateListFieldHeight(OrderedDictionaryNewEntryManager.NewEntryListAdaptor, this.ListControl.Flags); 90 | } 91 | 92 | Rect titlePosition = position; 93 | titlePosition.height = TitleHeight; 94 | ReorderableListGUI.Title(titlePosition, label); 95 | 96 | Rect listPosition = position; 97 | listPosition.yMin += TitleHeight - 1f; 98 | listPosition.height -= NewInputSpacing; 99 | listPosition.height -= newInputHeight; 100 | listPosition.height -= errorOutputHeight; 101 | listPosition.height -= EndSpacing; 102 | this.DrawDictionaryListControl(listPosition); 103 | 104 | Rect newInputPosition = position; 105 | newInputPosition.yMin = NewInputSpacing + listPosition.yMax; 106 | newInputPosition.height = newInputHeight; 107 | 108 | if (this.controlID == OrderedDictionaryNewEntryManager.ActiveControlID) { 109 | DrawDictionaryNewInput(newInputPosition); 110 | } 111 | else { 112 | int activateInputAreaControlID = EditorGUIUtility.GetControlID(FocusType.Passive); 113 | switch (Event.current.GetTypeForControl(activateInputAreaControlID)) { 114 | case EventType.MouseDown: 115 | if (Event.current.button == 0 && newInputPosition.Contains(Event.current.mousePosition)) { 116 | OrderedDictionaryNewEntryManager.SetActiveNewEntry(this); 117 | this.deferFocusNewInput = true; 118 | Event.current.Use(); 119 | } 120 | break; 121 | 122 | case EventType.Repaint: 123 | var style = new GUIStyle(GUI.skin.box); 124 | style.normal.textColor = GUI.skin.button.normal.textColor; 125 | style.Draw(newInputPosition, new GUIContent("« Click to add new entry »"), activateInputAreaControlID); 126 | break; 127 | } 128 | } 129 | 130 | Rect errorOutputPosition = newInputPosition; 131 | foreach (var error in this.errors) { 132 | errorOutputPosition.yMin = errorOutputPosition.yMax; 133 | errorOutputPosition.height = EditorStyles.helpBox.CalcHeight(error, 0f); 134 | EditorGUI.HelpBox(errorOutputPosition, error.text, MessageType.Error); 135 | } 136 | 137 | EditorGUI.EndProperty(); 138 | } 139 | 140 | 141 | private void Initialize(SerializedProperty property) 142 | { 143 | if (property == this.property) { 144 | return; 145 | } 146 | 147 | this.OrderedDictionary = this.FindTargetOrderedDictionary(property); 148 | this.property = property; 149 | 150 | this.OrderedDictionaryType = this.OrderedDictionary.GetType(); 151 | 152 | this.InitializeListControl(); 153 | } 154 | 155 | private OrderedDictionary FindTargetOrderedDictionary(SerializedProperty property) 156 | { 157 | var targetObject = property.serializedObject.targetObject; 158 | var propertyPath = property.propertyPath; 159 | 160 | var targetObjectType = targetObject.GetType(); 161 | var targetField = GetFieldDerived(targetObjectType, propertyPath, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 162 | 163 | return (OrderedDictionary)targetField.GetValue(targetObject); 164 | } 165 | 166 | private static FieldInfo GetFieldDerived(Type type, string name, BindingFlags bindingAttr) 167 | { 168 | while (type != null) { 169 | var field = type.GetField(name, bindingAttr); 170 | if (field != null) { 171 | return field; 172 | } 173 | type = type.BaseType; 174 | } 175 | return null; 176 | } 177 | 178 | 179 | #region Dictionary List Control 180 | 181 | protected ReorderableListControl ListControl { get; private set; } 182 | protected IOrderedDictionaryListAdaptor ListAdaptor { get; private set; } 183 | 184 | 185 | private void InitializeListControl() 186 | { 187 | this.ListControl = this.CreateListControl(); 188 | this.ListAdaptor = this.CreateListAdaptor(this.property); 189 | } 190 | 191 | /// 192 | /// Creates the that will be used to draw 193 | /// and manipulate the list of ordered dictionary entries. 194 | /// 195 | /// 196 | /// The new instance. 197 | /// 198 | protected virtual ReorderableListControl CreateListControl() 199 | { 200 | var flags = 201 | ReorderableListFlags.DisableDuplicateCommand 202 | | ReorderableListFlags.HideAddButton 203 | ; 204 | return new ReorderableListControl(flags); 205 | } 206 | 207 | /// 208 | public virtual IOrderedDictionaryListAdaptor CreateListAdaptor(SerializedProperty dictionaryProperty) 209 | { 210 | var target = this.FindTargetOrderedDictionary(dictionaryProperty); 211 | 212 | var keysProperty = dictionaryProperty.FindPropertyRelative("keys"); 213 | var valuesProperty = dictionaryProperty.FindPropertyRelative("values"); 214 | 215 | var keysAdaptor = new SerializedPropertyAdaptor(keysProperty); 216 | var valuesAdaptor = new SerializedPropertyAdaptor(valuesProperty); 217 | return new OrderedDictionaryListAdaptor(target, keysAdaptor, valuesAdaptor); 218 | } 219 | 220 | #endregion 221 | 222 | #region Dictionary GUI 223 | 224 | private static GUIStyle s_AddButtonStyle; 225 | 226 | private static GUIStyle AddButtonStyle { 227 | get { 228 | if (s_AddButtonStyle == null) { 229 | s_AddButtonStyle = new GUIStyle(ReorderableListStyles.Instance.FooterButton2); 230 | s_AddButtonStyle.fixedHeight = 0f; 231 | } 232 | return s_AddButtonStyle; 233 | } 234 | } 235 | 236 | /// 237 | /// Draws the using the . 238 | /// 239 | protected virtual void DrawDictionaryListControl(Rect position) 240 | { 241 | this.ListControl.Draw(position, this.ListAdaptor); 242 | } 243 | 244 | /// 245 | /// Draws new input controls. 246 | /// 247 | protected virtual void DrawDictionaryNewInput(Rect position) 248 | { 249 | OrderedDictionaryNewEntryManager.NewEntryObject.Update(); 250 | 251 | EditorGUI.BeginChangeCheck(); 252 | 253 | var adaptor = OrderedDictionaryNewEntryManager.NewEntryListAdaptor; 254 | 255 | // Intercept context click before sub-controls have a chance to avoid 256 | // revealing undesirable commands such as item insertion/removal. 257 | if (Event.current.type == EventType.ContextClick && position.Contains(Event.current.mousePosition)) { 258 | this.OnNewInputContextClick(); 259 | Event.current.Use(); 260 | } 261 | 262 | // Background behind input controls excluding add button. 263 | Rect backgroundPosition = position; 264 | backgroundPosition.width -= AddButtonWidth + 5f; 265 | this.DrawAddNewBackground(backgroundPosition); 266 | 267 | Rect newInputPosition = position; 268 | newInputPosition.xMin += 24f; 269 | newInputPosition.x -= 12f; 270 | newInputPosition.y += HalfVerticalPadding; 271 | newInputPosition.width -= AddButtonWidth; 272 | newInputPosition.height -= VerticalPadding; 273 | 274 | adaptor.BeginGUI(); 275 | adaptor.DrawItemBackground(newInputPosition, 0); 276 | GUI.SetNextControlName(this.controlID.ToString()); 277 | adaptor.DrawItem(newInputPosition, 0); 278 | adaptor.EndGUI(); 279 | 280 | Rect addButtonPosition = position; 281 | addButtonPosition.xMin = addButtonPosition.xMax - AddButtonWidth; 282 | addButtonPosition.xMax -= 1f; 283 | this.DrawAddNewInputButton(addButtonPosition); 284 | 285 | if (EditorGUI.EndChangeCheck()) { 286 | // This is necessary so that changes made to the 'new entry' field are 287 | // repainted immediately. 288 | EditorUtility.SetDirty(this.property.serializedObject.targetObject); 289 | } 290 | 291 | if (this.deferFocusNewInput) { 292 | this.deferFocusNewInput = false; 293 | GUI.FocusControl(this.controlID.ToString()); 294 | EditorGUIUtility.editingTextField = true; 295 | EditorWindow.focusedWindow.Repaint(); 296 | } 297 | 298 | OrderedDictionaryNewEntryManager.NewEntryObject.ApplyModifiedProperties(); 299 | } 300 | 301 | /// 302 | /// Draws background behind new input controls. 303 | /// 304 | /// Absolute position of the background. 305 | protected virtual void DrawAddNewBackground(Rect position) 306 | { 307 | if (Event.current.type == EventType.Repaint) { 308 | ReorderableListStyles.Instance.Container.Draw(position, GUIContent.none, false, false, false, false); 309 | } 310 | } 311 | 312 | /// 313 | /// Draws button for adding new input. 314 | /// 315 | /// Absolute position of button in GUI. 316 | protected virtual void DrawAddNewInputButton(Rect position) 317 | { 318 | EditorGUI.BeginDisabledGroup(!this.CanAddNewInput); 319 | 320 | var addButtonNormal = ReorderableListStyles.Skin.Icon_Add_Normal; 321 | var addButtonActive = ReorderableListStyles.Skin.Icon_Add_Active; 322 | if (ExtraEditorGUI.IconButton(position, addButtonNormal, addButtonActive, AddButtonStyle)) { 323 | this.OnAddNewInputButtonClick(); 324 | } 325 | 326 | EditorGUI.EndDisabledGroup(); 327 | } 328 | 329 | /// 330 | /// Gets a value indicating whether the user can click the add new input button. 331 | /// 332 | protected virtual bool CanAddNewInput { 333 | get { 334 | return OrderedDictionaryNewEntryManager.CanAddNewEntry(this.ControlID); 335 | } 336 | } 337 | 338 | /// 339 | /// Occurs when the add new input button is clicked. 340 | /// 341 | protected virtual void OnAddNewInputButtonClick() 342 | { 343 | this.ListAdaptor.Add(OrderedDictionaryNewEntryManager.NewEntryKeyProperty, OrderedDictionaryNewEntryManager.NewEntryValueProperty); 344 | OrderedDictionaryNewEntryManager.ResetActiveNewEntry(); 345 | } 346 | 347 | /// 348 | /// Occurrs when the user context clicks on the new input controls. 349 | /// 350 | /// 351 | /// Intercepts context click before sub-controls have a chance to display 352 | /// a context menu to avoid exposing undesirable commands which could otherwise 353 | /// cause corruption by allowing individual keys or values to be removed. 354 | /// 355 | protected virtual void OnNewInputContextClick() 356 | { 357 | var menu = new GenericMenu(); 358 | menu.AddItem(new GUIContent("Reset Input"), false, OrderedDictionaryNewEntryManager.ResetActiveNewEntry); 359 | menu.ShowAsContext(); 360 | } 361 | 362 | private bool hasNullKeyErrorOnLayout; 363 | 364 | /// 365 | /// Draws error feedback relating to the ordered dictionary. 366 | /// 367 | protected virtual void UpdateDictionaryErrors() 368 | { 369 | this.errors.Clear(); 370 | if (this.OrderedDictionary.KeysWithDuplicateValues.Any()) { 371 | this.errors.Add(new GUIContent("Multiple values have been assigned to the same key.")); 372 | } 373 | 374 | if (Event.current.type == EventType.Layout) { 375 | this.hasNullKeyErrorOnLayout = this.ListAdaptor.HadNullKeyErrorOnLastRepaint; 376 | } 377 | if (this.hasNullKeyErrorOnLayout) { 378 | this.errors.Add(new GUIContent("One or more null keys were encountered.")); 379 | } 380 | } 381 | 382 | #endregion 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /assets/Source/Collections/OrderedDictionary{TKey,TValue}.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) Rotorz Limited. All rights reserved. 2 | // Licensed under the MIT license. See LICENSE file in the project root. 3 | 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using UnityEngine; 8 | 9 | namespace Rotorz.Games.Collections 10 | { 11 | /// 12 | /// Base class for a serializable ordered dictionary asset. Custom dictionary asset 13 | /// classes should inherit from this class since Unity is currently unable to 14 | /// serialize instances of generic types. 15 | /// 16 | /// Type of key. 17 | /// Type of value. 18 | public abstract partial class OrderedDictionary : OrderedDictionary, ISerializationCallbackReceiver, IDictionary, IDictionary, IList> 19 | { 20 | [SerializeField, HideInInspector] 21 | private int version; 22 | [SerializeField] 23 | protected List keys = new List(); 24 | [SerializeField] 25 | protected List values = new List(); 26 | 27 | 28 | protected readonly Dictionary dictionary = new Dictionary(); 29 | protected readonly HashSet keysWithDuplicateValues = new HashSet(); 30 | 31 | 32 | /// 33 | /// Initializes a new instance of the class. 34 | /// 35 | public OrderedDictionary() : base(typeof(TKey), typeof(TValue)) 36 | { 37 | this.Keys = new KeyCollection(this); 38 | this.Values = new ValueCollection(this); 39 | } 40 | 41 | 42 | #region Unity Serialization 43 | 44 | void ISerializationCallbackReceiver.OnAfterDeserialize() 45 | { 46 | this.OnAfterDeserialize(); 47 | } 48 | 49 | void ISerializationCallbackReceiver.OnBeforeSerialize() 50 | { 51 | this.OnBeforeSerialize(); 52 | } 53 | 54 | protected virtual void OnAfterDeserialize() 55 | { 56 | if (this.keys.Count != this.values.Count) { 57 | Debug.LogError("Inconsistent quantity of keys and values."); 58 | } 59 | 60 | this.dictionary.Clear(); 61 | this.keysWithDuplicateValues.Clear(); 62 | 63 | int count = Mathf.Min(this.keys.Count, this.values.Count); 64 | for (int i = 0; i < count; ++i) { 65 | var key = this.keys[i]; 66 | 67 | if (key == null) { 68 | if (!this.suppressErrors) { 69 | // Only show these warning messages when playing. 70 | Debug.LogError("Encountered invalid null key."); 71 | } 72 | continue; 73 | } 74 | 75 | if (!this.dictionary.ContainsKey(key)) { 76 | this.dictionary.Add(key, this.values[i]); 77 | } 78 | else { 79 | this.keysWithDuplicateValues.Add(key); 80 | } 81 | } 82 | 83 | // Only show these warning messages when playing. 84 | foreach (var key in this.keysWithDuplicateValues) { 85 | Debug.Log(string.Format("Has multiple values for the key '{0}'.", key)); 86 | } 87 | } 88 | 89 | protected virtual void OnBeforeSerialize() 90 | { 91 | } 92 | 93 | #endregion 94 | 95 | #region Integrity Checks 96 | 97 | private void CheckVersion(int version) 98 | { 99 | if (version != this.version) { 100 | throw new InvalidOperationException("Cannot make changes to the collection whilst the collection is being enumerated."); 101 | } 102 | } 103 | 104 | private void CheckIndexArgument(int index) 105 | { 106 | if ((uint)index >= this.Count) { 107 | throw new ArgumentOutOfRangeException("index", index, null); 108 | } 109 | } 110 | 111 | private void CheckInsertIndexArgument(int index) 112 | { 113 | if ((uint)index > this.keys.Count) { 114 | throw new ArgumentOutOfRangeException("index", index, null); 115 | } 116 | } 117 | 118 | private void CheckKeyArgument(object key) 119 | { 120 | if (key == null) { 121 | throw new ArgumentNullException("key"); 122 | } 123 | if (!(key is TKey)) { 124 | throw new ArgumentException("Incompatible type.", "key"); 125 | } 126 | } 127 | 128 | private void CheckKeyArgument(TKey key) 129 | { 130 | if (key == null) { 131 | throw new ArgumentNullException("key"); 132 | } 133 | } 134 | 135 | private void CheckValueArgument(object value) 136 | { 137 | if (value is TValue || (value == null && !typeof(TValue).IsValueType)) { 138 | return; 139 | } 140 | 141 | throw new ArgumentException("Incompatible type.", "value"); 142 | } 143 | 144 | private void CheckUniqueKeyArgument(TKey key) 145 | { 146 | if (this.ContainsKey(key)) { 147 | throw new ArgumentException(string.Format("Already contains key '{0}'.", key), "key"); 148 | } 149 | } 150 | 151 | private static void ThrowReadOnlyException() 152 | { 153 | throw new InvalidOperationException("Collection is read-only."); 154 | } 155 | 156 | #endregion 157 | 158 | #region Non-Applicable Interfaces 159 | 160 | /// 161 | bool IDictionary.IsReadOnly { 162 | get { return false; } 163 | } 164 | 165 | /// 166 | bool ICollection>.IsReadOnly { 167 | get { return false; } 168 | } 169 | 170 | /// 171 | bool IDictionary.IsFixedSize { 172 | get { return false; } 173 | } 174 | 175 | /// 176 | object ICollection.SyncRoot { 177 | get { return (this.dictionary as ICollection).SyncRoot; } 178 | } 179 | 180 | /// 181 | bool ICollection.IsSynchronized { 182 | get { return (this.dictionary as ICollection).IsSynchronized; } 183 | } 184 | 185 | #endregion 186 | 187 | 188 | /// 189 | public sealed override int Count { 190 | get { return this.keys.Count; } 191 | } 192 | 193 | 194 | /// 195 | /// Gets the read-only ordered collection of keys. 196 | /// 197 | /// 198 | public KeyCollection Keys { get; private set; } 199 | 200 | /// 201 | /// Gets the read-only ordered collection of values. 202 | /// 203 | /// 204 | public ValueCollection Values { get; private set; } 205 | 206 | /// 207 | ICollection IDictionary.Keys { 208 | get { return this.Keys; } 209 | } 210 | 211 | /// 212 | ICollection IDictionary.Values { 213 | get { return this.Values; } 214 | } 215 | 216 | /// 217 | ICollection IDictionary.Keys { 218 | get { return this.Keys; } 219 | } 220 | 221 | /// 222 | ICollection IDictionary.Values { 223 | get { return this.Values; } 224 | } 225 | 226 | 227 | /// 228 | public TValue this[TKey key] { 229 | get { return this.dictionary[key]; } 230 | set { 231 | if (this.dictionary.ContainsKey(key)) { 232 | this.dictionary[key] = value; 233 | 234 | int index = this.IndexOf(key); 235 | if (index == -1) { 236 | throw new InvalidOperationException(); 237 | } 238 | this.values[index] = value; 239 | } 240 | else { 241 | this.dictionary.Add(key, value); 242 | this.keys.Add(key); 243 | this.values.Add(value); 244 | } 245 | } 246 | } 247 | 248 | /// 249 | object IDictionary.this[object key] { 250 | get { 251 | this.CheckKeyArgument(key); 252 | 253 | return this[(TKey)key]; 254 | } 255 | set { 256 | this.CheckKeyArgument(key); 257 | this.CheckValueArgument(value); 258 | 259 | this[(TKey)key] = (TValue)value; 260 | } 261 | } 262 | 263 | /// 264 | KeyValuePair IList>.this[int index] { 265 | get { 266 | return new KeyValuePair(this.keys[index], this.values[index]); 267 | } 268 | set { 269 | var existingKey = this.keys[index]; 270 | 271 | if (!EqualityComparer.Default.Equals(existingKey, value.Key)) { 272 | if (this.ContainsKey(value.Key)) { 273 | throw new InvalidOperationException(); 274 | } 275 | 276 | this.dictionary.Remove(existingKey); 277 | this.dictionary[value.Key] = value.Value; 278 | 279 | this.keys[index] = value.Key; 280 | } 281 | 282 | this.values[index] = value.Value; 283 | } 284 | } 285 | 286 | 287 | /// 288 | public override IEnumerable KeysWithDuplicateValues { 289 | get { return this.keysWithDuplicateValues; } 290 | } 291 | 292 | 293 | /// 294 | protected override object GetKeyFromIndexInternal(int index) 295 | { 296 | this.CheckIndexArgument(index); 297 | 298 | return this.keys[index]; 299 | } 300 | 301 | /// 302 | protected override object GetValueFromIndexInternal(int index) 303 | { 304 | this.CheckIndexArgument(index); 305 | 306 | return this.values[index]; 307 | } 308 | 309 | /// 310 | public sealed override bool ContainsKey(object key) 311 | { 312 | this.CheckKeyArgument(key); 313 | 314 | if (!this.KeyType.IsAssignableFrom(key.GetType())) { 315 | return false; 316 | } 317 | 318 | return this.dictionary.ContainsKey((TKey)key); 319 | } 320 | 321 | /// 322 | /// Gets the key of the entry at the specified index. 323 | /// 324 | /// Zero-based index of entry in ordered dictionary. 325 | /// 326 | /// The key. 327 | /// 328 | /// 329 | /// If is out of range. 330 | /// 331 | /// 332 | /// 333 | new public TKey GetKeyFromIndex(int index) 334 | { 335 | this.CheckIndexArgument(index); 336 | 337 | return this.keys[index]; 338 | } 339 | 340 | /// 341 | /// Gets the value of the entry at the specified index. 342 | /// 343 | /// Zero-based index of entry in ordered dictionary. 344 | /// 345 | /// The key. 346 | /// 347 | /// 348 | /// If is out of range. 349 | /// 350 | /// 351 | /// 352 | new public TValue GetValueFromIndex(int index) 353 | { 354 | this.CheckIndexArgument(index); 355 | 356 | return this.values[index]; 357 | } 358 | 359 | /// 360 | void IList>.Insert(int index, KeyValuePair item) 361 | { 362 | this.Insert(index, item.Key, item.Value); 363 | } 364 | 365 | /// 366 | /// Insert new entry into the ordered dictionary. 367 | /// 368 | /// Zero-based index at which to insert the new entry. 369 | /// Unique key for the new entry. 370 | /// Value for the new entry. 371 | /// 372 | /// If is out of the range of the ordered dictionary. 373 | /// 374 | /// 375 | /// If is null. 376 | /// 377 | /// 378 | /// If dictionary already contains an entry with the specified key. 379 | /// 380 | public void Insert(int index, TKey key, TValue value) 381 | { 382 | this.CheckInsertIndexArgument(index); 383 | this.CheckUniqueKeyArgument(key); 384 | 385 | ++this.version; 386 | 387 | this.dictionary.Add(key, value); 388 | this.keys.Insert(index, key); 389 | this.values.Insert(index, value); 390 | } 391 | 392 | /// 393 | public void Add(TKey key, TValue value) 394 | { 395 | this.Insert(Count, key, value); 396 | } 397 | 398 | /// 399 | void IDictionary.Add(object key, object value) 400 | { 401 | this.CheckKeyArgument(key); 402 | this.CheckValueArgument(value); 403 | 404 | var k = (TKey)key; 405 | 406 | if (this.ContainsKey(k)) { 407 | throw new ArgumentException(string.Format("Already contains key '{0}'", k), "key"); 408 | } 409 | 410 | this.Add(k, (TValue)value); 411 | } 412 | 413 | /// 414 | void ICollection>.Add(KeyValuePair item) 415 | { 416 | this.Add(item.Key, item.Value); 417 | } 418 | 419 | /// 420 | public void Clear() 421 | { 422 | ++this.version; 423 | 424 | this.keys.Clear(); 425 | this.values.Clear(); 426 | this.dictionary.Clear(); 427 | } 428 | 429 | /// 430 | public bool Remove(TKey key) 431 | { 432 | this.CheckKeyArgument(key); 433 | 434 | if (this.ContainsKey(key)) { 435 | this.RemoveAt(this.IndexOf(key)); 436 | return true; 437 | } 438 | return false; 439 | } 440 | 441 | /// 442 | bool ICollection>.Remove(KeyValuePair item) 443 | { 444 | return this.Remove(item.Key); 445 | } 446 | 447 | /// 448 | void IDictionary.Remove(object key) 449 | { 450 | this.CheckKeyArgument(key); 451 | 452 | this.Remove((TKey)key); 453 | } 454 | 455 | /// 456 | public void RemoveAt(int index) 457 | { 458 | this.CheckIndexArgument(index); 459 | 460 | ++this.version; 461 | 462 | this.dictionary.Remove(this.keys[index]); 463 | this.keys.RemoveAt(index); 464 | this.values.RemoveAt(index); 465 | } 466 | 467 | /// 468 | /// Gets an object for enumerating over the ordered collection of keys and values. 469 | /// 470 | /// 471 | /// The new . 472 | /// 473 | public Enumerator GetEnumerator() 474 | { 475 | return new Enumerator(this, false); 476 | } 477 | 478 | /// 479 | IEnumerator> IEnumerable>.GetEnumerator() 480 | { 481 | return this.GetEnumerator(); 482 | } 483 | 484 | /// 485 | IEnumerator IEnumerable.GetEnumerator() 486 | { 487 | return this.GetEnumerator(); 488 | } 489 | 490 | /// 491 | IDictionaryEnumerator IDictionary.GetEnumerator() 492 | { 493 | return new Enumerator(this, true); 494 | } 495 | 496 | /// 497 | public bool TryGetValue(TKey key, out TValue value) 498 | { 499 | return this.dictionary.TryGetValue(key, out value); 500 | } 501 | 502 | /// 503 | public bool ContainsKey(TKey key) 504 | { 505 | return this.dictionary.ContainsKey(key); 506 | } 507 | 508 | /// 509 | bool ICollection>.Contains(KeyValuePair item) 510 | { 511 | TValue value; 512 | return this.TryGetValue(item.Key, out value) && EqualityComparer.Default.Equals(item.Value, value); 513 | } 514 | 515 | /// 516 | bool IDictionary.Contains(object key) 517 | { 518 | if (key == null) { 519 | throw new ArgumentNullException("key"); 520 | } 521 | 522 | if (key is TKey) { 523 | return this.dictionary.ContainsKey((TKey)key); 524 | } 525 | else { 526 | return false; 527 | } 528 | } 529 | 530 | /// 531 | /// Determines the index of an item with a aspecific key in the . 532 | /// 533 | /// The key of the entry to locate in the . 534 | /// 535 | /// The zero-based index of the entry when found; otherwise, a value of -1. 536 | /// 537 | public int IndexOf(TKey key) 538 | { 539 | return this.keys.IndexOf(key); 540 | } 541 | 542 | /// 543 | int IList>.IndexOf(KeyValuePair item) 544 | { 545 | int index = this.IndexOf(item.Key); 546 | 547 | if (index != -1 && !EqualityComparer.Default.Equals(item.Value, this.values[index])) { 548 | return -1; 549 | } 550 | 551 | return index; 552 | } 553 | 554 | /// 555 | void ICollection.CopyTo(Array array, int index) 556 | { 557 | (this.dictionary as ICollection).CopyTo(array, index); 558 | } 559 | 560 | /// 561 | void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) 562 | { 563 | (this.dictionary as ICollection>).CopyTo(array, arrayIndex); 564 | } 565 | } 566 | } 567 | --------------------------------------------------------------------------------