diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5a211a5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,67 @@ +# Keep Unity's human-readable project files consistent across platforms. +* text=auto +.gitignore text eol=lf +.gitattributes text eol=lf +*.cs text eol=lf +*.shader text eol=lf +*.hlsl text eol=lf +*.cginc text eol=lf +*.compute text eol=lf +*.asmdef text eol=lf +*.asmref text eol=lf +*.json text eol=lf +*.xml text eol=lf +*.yaml text eol=lf +*.yml text eol=lf +*.uxml text eol=lf +*.uss text eol=lf +*.txt text eol=lf +*.md text eol=lf +*.meta text eol=lf + +# Unity SmartMerge for YAML-serialized assets. +*.unity text eol=lf merge=unityyamlmerge +*.prefab text eol=lf merge=unityyamlmerge +*.asset text eol=lf merge=unityyamlmerge +*.mat text eol=lf merge=unityyamlmerge +*.anim text eol=lf merge=unityyamlmerge +*.controller text eol=lf merge=unityyamlmerge +*.overrideController text eol=lf merge=unityyamlmerge +*.playable text eol=lf merge=unityyamlmerge +*.mask text eol=lf merge=unityyamlmerge +*.spriteatlas text eol=lf merge=unityyamlmerge +*.physicsMaterial2D text eol=lf merge=unityyamlmerge +*.physicMaterial text eol=lf merge=unityyamlmerge +*.terrainlayer text eol=lf merge=unityyamlmerge +*.renderTexture text eol=lf merge=unityyamlmerge +*.lighting text eol=lf merge=unityyamlmerge +*.preset text eol=lf merge=unityyamlmerge +*.guiskin text eol=lf merge=unityyamlmerge + +# Source-art and runtime media are binary and belong in Git LFS. +*.psd filter=lfs diff=lfs merge=lfs -text +*.psb filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.tga filter=lfs diff=lfs merge=lfs -text +*.tif filter=lfs diff=lfs merge=lfs -text +*.tiff filter=lfs diff=lfs merge=lfs -text +*.exr filter=lfs diff=lfs merge=lfs -text +*.hdr filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text +*.fbx filter=lfs diff=lfs merge=lfs -text +*.blend filter=lfs diff=lfs merge=lfs -text +*.glb filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.aif filter=lfs diff=lfs merge=lfs -text +*.aiff filter=lfs diff=lfs merge=lfs -text +*.flac filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.mov filter=lfs diff=lfs merge=lfs -text +*.avi filter=lfs diff=lfs merge=lfs -text +*.webm filter=lfs diff=lfs merge=lfs -text +*.ttf filter=lfs diff=lfs merge=lfs -text +*.otf filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 22e7db6..a0bbaeb 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ # Visual Studio cache directory .vs/ +.vscode/ # Gradle cache directory .gradle/ @@ -47,6 +48,8 @@ ExportedObj/ *.mdb *.opendb *.VC.db +*.VC.opendb +*.slnx # Unity3D generated meta files *.pidb.meta diff --git a/Assets/DefaultNetworkPrefabs.asset b/Assets/DefaultNetworkPrefabs.asset new file mode 100644 index 0000000..cfbcdb4 --- /dev/null +++ b/Assets/DefaultNetworkPrefabs.asset @@ -0,0 +1,21 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: e651dbb3fbac04af2b8f5abf007ddc23, type: 3} + m_Name: DefaultNetworkPrefabs + m_EditorClassIdentifier: + IsDefault: 1 + List: + - Override: 0 + Prefab: {fileID: 3439633038736912633, guid: 398aad09d8b2a47eba664a076763cdcc, type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} diff --git a/Assets/DefaultNetworkPrefabs.asset.meta b/Assets/DefaultNetworkPrefabs.asset.meta new file mode 100644 index 0000000..48df624 --- /dev/null +++ b/Assets/DefaultNetworkPrefabs.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ade67b675ab279949a8c1b7b0684ce60 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/InputSystem_Actions.inputactions b/Assets/InputSystem_Actions.inputactions new file mode 100644 index 0000000..1a12cb9 --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions @@ -0,0 +1,1057 @@ +{ + "name": "InputSystem_Actions", + "maps": [ + { + "name": "Player", + "id": "df70fa95-8a34-4494-b137-73ab6b9c7d37", + "actions": [ + { + "name": "Move", + "type": "Value", + "id": "351f2ccd-1f9f-44bf-9bec-d62ac5c5f408", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Look", + "type": "Value", + "id": "6b444451-8a00-4d00-a97e-f47457f736a8", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Attack", + "type": "Button", + "id": "6c2ab1b8-8984-453a-af3d-a3c78ae1679a", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Interact", + "type": "Button", + "id": "852140f2-7766-474d-8707-702459ba45f3", + "expectedControlType": "Button", + "processors": "", + "interactions": "Hold", + "initialStateCheck": false + }, + { + "name": "Crouch", + "type": "Button", + "id": "27c5f898-bc57-4ee1-8800-db469aca5fe3", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Jump", + "type": "Button", + "id": "f1ba0d36-48eb-4cd5-b651-1c94a6531f70", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Previous", + "type": "Button", + "id": "2776c80d-3c14-4091-8c56-d04ced07a2b0", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Next", + "type": "Button", + "id": "b7230bb6-fc9b-4f52-8b25-f5e19cb2c2ba", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Sprint", + "type": "Button", + "id": "641cd816-40e6-41b4-8c3d-04687c349290", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "", + "id": "978bfe49-cc26-4a3d-ab7b-7d7a29327403", + "path": "/leftStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "WASD", + "id": "00ca640b-d935-4593-8157-c05846ea39b3", + "path": "Dpad", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "e2062cb9-1b15-46a2-838c-2f8d72a0bdd9", + "path": "/w", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "8180e8bd-4097-4f4e-ab88-4523101a6ce9", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "320bffee-a40b-4347-ac70-c210eb8bc73a", + "path": "/s", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "1c5327b5-f71c-4f60-99c7-4e737386f1d1", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "d2581a9b-1d11-4566-b27d-b92aff5fabbc", + "path": "/a", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "2e46982e-44cc-431b-9f0b-c11910bf467a", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcfe95b8-67b9-4526-84b5-5d0bc98d6400", + "path": "/d", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "77bff152-3580-4b21-b6de-dcd0c7e41164", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "1635d3fe-58b6-4ba9-a4e2-f4b964f6b5c8", + "path": "/{Primary2DAxis}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3ea4d645-4504-4529-b061-ab81934c3752", + "path": "/stick", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c1f7a91b-d0fd-4a62-997e-7fb9b69bf235", + "path": "/rightStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8c8e490b-c610-4785-884f-f04217b23ca4", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse;Touch", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3e5f5442-8668-4b27-a940-df99bad7e831", + "path": "/{Hatswitch}", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "143bb1cd-cc10-4eca-a2f0-a3664166fe91", + "path": "/buttonWest", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "05f6913d-c316-48b2-a6bb-e225f14c7960", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "886e731e-7071-4ae4-95c0-e61739dad6fd", + "path": "/primaryTouch/tap", + "interactions": "", + "processors": "", + "groups": ";Touch", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "ee3d0cd2-254e-47a7-a8cb-bc94d9658c54", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8255d333-5683-4943-a58a-ccb207ff1dce", + "path": "/{PrimaryAction}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3c1c7f0-bd20-4ee7-a0f1-899b24bca6d7", + "path": "/enter", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Attack", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "cbac6039-9c09-46a1-b5f2-4e5124ccb5ed", + "path": "/2", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e15ca19d-e649-4852-97d5-7fe8ccc44e94", + "path": "/dpad/right", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Next", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "f2e9ba44-c423-42a7-ad56-f20975884794", + "path": "/leftShift", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8cbb2f4b-a784-49cc-8d5e-c010b8c7f4e6", + "path": "/leftStickPress", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "d8bf24bf-3f2f-4160-a97c-38ec1eb520ba", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Sprint", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "eb40bb66-4559-4dfa-9a2f-820438abb426", + "path": "/space", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "daba33a1-ad0c-4742-a909-43ad1cdfbeb6", + "path": "/buttonSouth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "603f3daf-40bd-4854-8724-93e8017f59e3", + "path": "/secondaryButton", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Jump", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1534dc16-a6aa-499d-9c3a-22b47347b52a", + "path": "/1", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "25060bbd-a3a6-476e-8fba-45ae484aad05", + "path": "/dpad/left", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Previous", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "1c04ea5f-b012-41d1-a6f7-02e963b52893", + "path": "/e", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "b3f66d0b-7751-423f-908b-a11c5bd95930", + "path": "/buttonNorth", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Interact", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4f4649ac-64a8-4a73-af11-b3faef356a4d", + "path": "/buttonEast", + "interactions": "", + "processors": "", + "groups": "Gamepad", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "36e52cba-0905-478e-a818-f4bfcb9f3b9a", + "path": "/c", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Crouch", + "isComposite": false, + "isPartOfComposite": false + } + ] + }, + { + "name": "UI", + "id": "272f6d14-89ba-496f-b7ff-215263d3219f", + "actions": [ + { + "name": "Navigate", + "type": "PassThrough", + "id": "c95b2375-e6d9-4b88-9c4c-c5e76515df4b", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Submit", + "type": "Button", + "id": "7607c7b6-cd76-4816-beef-bd0341cfe950", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Cancel", + "type": "Button", + "id": "15cef263-9014-4fd5-94d9-4e4a6234a6ef", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Point", + "type": "PassThrough", + "id": "32b35790-4ed0-4e9a-aa41-69ac6d629449", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Click", + "type": "PassThrough", + "id": "3c7022bf-7922-4f7c-a998-c437916075ad", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "RightClick", + "type": "PassThrough", + "id": "44b200b1-1557-4083-816c-b22cbdf77ddf", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "MiddleClick", + "type": "PassThrough", + "id": "dad70c86-b58c-4b17-88ad-f5e53adf419e", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "ScrollWheel", + "type": "PassThrough", + "id": "0489e84a-4833-4c40-bfae-cea84b696689", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDevicePosition", + "type": "PassThrough", + "id": "24908448-c609-4bc3-a128-ea258674378a", + "expectedControlType": "Vector3", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDeviceOrientation", + "type": "PassThrough", + "id": "9caa3d8a-6b2f-4e8e-8bad-6ede561bd9be", + "expectedControlType": "Quaternion", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "Gamepad", + "id": "809f371f-c5e2-4e7a-83a1-d867598f40dd", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "14a5d6e8-4aaf-4119-a9ef-34b8c2c548bf", + "path": "/leftStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "9144cbe6-05e1-4687-a6d7-24f99d23dd81", + "path": "/rightStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2db08d65-c5fb-421b-983f-c71163608d67", + "path": "/leftStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "58748904-2ea9-4a80-8579-b500e6a76df8", + "path": "/rightStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "8ba04515-75aa-45de-966d-393d9bbd1c14", + "path": "/leftStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "712e721c-bdfb-4b23-a86c-a0d9fcfea921", + "path": "/rightStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcd248ae-a788-4676-a12e-f4d81205600b", + "path": "/leftStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "1f04d9bc-c50b-41a1-bfcc-afb75475ec20", + "path": "/rightStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "fb8277d4-c5cd-4663-9dc7-ee3f0b506d90", + "path": "/dpad", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "Joystick", + "id": "e25d9774-381c-4a61-b47c-7b6b299ad9f9", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "3db53b26-6601-41be-9887-63ac74e79d19", + "path": "/stick/up", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "0cb3e13e-3d90-4178-8ae6-d9c5501d653f", + "path": "/stick/down", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "0392d399-f6dd-4c82-8062-c1e9c0d34835", + "path": "/stick/left", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "942a66d9-d42f-43d6-8d70-ecb4ba5363bc", + "path": "/stick/right", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "Keyboard", + "id": "ff527021-f211-4c02-933e-5976594c46ed", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "563fbfdd-0f09-408d-aa75-8642c4f08ef0", + "path": "/w", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "eb480147-c587-4a33-85ed-eb0ab9942c43", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2bf42165-60bc-42ca-8072-8c13ab40239b", + "path": "/s", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "85d264ad-e0a0-4565-b7ff-1a37edde51ac", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "74214943-c580-44e4-98eb-ad7eebe17902", + "path": "/a", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "cea9b045-a000-445b-95b8-0c171af70a3b", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "8607c725-d935-4808-84b1-8354e29bab63", + "path": "/d", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "4cda81dc-9edd-4e03-9d7c-a71a14345d0b", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "9e92bb26-7e3b-4ec4-b06b-3c8f8e498ddc", + "path": "*/{Submit}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Submit", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "82627dcc-3b13-4ba9-841d-e4b746d6553e", + "path": "*/{Cancel}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Cancel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c52c8e0b-8179-41d3-b8a1-d149033bbe86", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e1394cbc-336e-44ce-9ea8-6007ed6193f7", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "5693e57a-238a-46ed-b5ae-e64e6e574302", + "path": "/touch*/position", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4faf7dc9-b979-4210-aa8c-e808e1ef89f5", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8d66d5ba-88d7-48e6-b1cd-198bbfef7ace", + "path": "/tip", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "47c2a644-3ebc-4dae-a106-589b7ca75b59", + "path": "/touch*/press", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "bb9e6b34-44bf-4381-ac63-5aa15d19f677", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "38c99815-14ea-4617-8627-164d27641299", + "path": "/scroll", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "ScrollWheel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4c191405-5738-4d4b-a523-c6a301dbf754", + "path": "/rightButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "RightClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "24066f69-da47-44f3-a07e-0015fb02eb2e", + "path": "/middleButton", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "MiddleClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "7236c0d9-6ca3-47cf-a6ee-a97f5b59ea77", + "path": "/devicePosition", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDevicePosition", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "23e01e3a-f935-4948-8d8b-9bcac77714fb", + "path": "/deviceRotation", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDeviceOrientation", + "isComposite": false, + "isPartOfComposite": false + } + ] + } + ], + "controlSchemes": [ + { + "name": "Keyboard&Mouse", + "bindingGroup": "Keyboard&Mouse", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + }, + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Gamepad", + "bindingGroup": "Gamepad", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Touch", + "bindingGroup": "Touch", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Joystick", + "bindingGroup": "Joystick", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "XR", + "bindingGroup": "XR", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + } + ] +} \ No newline at end of file diff --git a/Assets/InputSystem_Actions.inputactions.meta b/Assets/InputSystem_Actions.inputactions.meta new file mode 100644 index 0000000..6b38b04 --- /dev/null +++ b/Assets/InputSystem_Actions.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 052faaac586de48259a63d0c4782560b +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 0 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/OtherAssets.meta b/Assets/OtherAssets.meta new file mode 100644 index 0000000..54b46c5 --- /dev/null +++ b/Assets/OtherAssets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e09ca9c29032caf40bebc8c816cf7971 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV.meta b/Assets/OtherAssets/DEV.meta new file mode 100644 index 0000000..f662604 --- /dev/null +++ b/Assets/OtherAssets/DEV.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5b89e375a14f4e049b207cb0f3daaadf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/FREE.meta b/Assets/OtherAssets/DEV/FREE.meta new file mode 100644 index 0000000..07e261a --- /dev/null +++ b/Assets/OtherAssets/DEV/FREE.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 240c6ee064ec9b34888740ebc2d7c2dc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/FREE/SceneSwitcher.meta b/Assets/OtherAssets/DEV/FREE/SceneSwitcher.meta new file mode 100644 index 0000000..000d4f4 --- /dev/null +++ b/Assets/OtherAssets/DEV/FREE/SceneSwitcher.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1d47210b6a0af2247b2ddbb08cc85e7d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor.meta b/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor.meta new file mode 100644 index 0000000..18beb7e --- /dev/null +++ b/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9d463107ae5974c4a8bffb4f6a04bf54 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor/SceneSwitcherToolbar.cs b/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor/SceneSwitcherToolbar.cs new file mode 100644 index 0000000..052f603 --- /dev/null +++ b/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor/SceneSwitcherToolbar.cs @@ -0,0 +1,269 @@ +#if UNITY_EDITOR +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.UIElements; +using System.Reflection; +using System.Linq; +using System.IO; +using UnityEngine.SceneManagement; + +[InitializeOnLoad] +public static class SceneSwitcherToolbar +{ + private static string[] sceneNames = new string[0]; + private static int selectedIndex = 0; + private static string lastActiveScene = ""; + private static VisualElement toolbarUI; + + private static float positionOffset = 180f; + private static float dropdownBoxHeight = 20f; + + private static bool fetchAllScenes + { + get => EditorPrefs.GetBool("SceneSwitcher_FetchAllScenes", false); + set => EditorPrefs.SetBool("SceneSwitcher_FetchAllScenes", value); + } + + static SceneSwitcherToolbar() + { + RefreshSceneList(); + SelectCurrentScene(); + + EditorSceneManager.activeSceneChangedInEditMode += (prev, current) => UpdateSceneSelection(); + EditorApplication.playModeStateChanged += OnPlayModeChanged; + + EditorApplication.delayCall += AddToolbarUI; + } + + static void AddToolbarUI() + { + var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar"); + if (toolbarType == null) return; + + var toolbars = Resources.FindObjectsOfTypeAll(toolbarType); + if (toolbars.Length == 0) return; + + var toolbar = toolbars[0]; + var rootField = toolbarType.GetField("m_Root", BindingFlags.NonPublic | BindingFlags.Instance); + if (rootField == null) return; + + var root = rootField.GetValue(toolbar) as VisualElement; + if (root == null) return; + + var leftContainer = root.Q("ToolbarZoneLeftAlign"); + if (leftContainer == null) return; + + if (toolbarUI != null) + { + leftContainer.Remove(toolbarUI); + } + + toolbarUI = new IMGUIContainer(OnGUI); + toolbarUI.style.marginLeft = positionOffset; + + leftContainer.Add(toolbarUI); + } + + static void OnGUI() + { + CheckAndRefreshScenes(); + + if (selectedIndex >= sceneNames.Length) + selectedIndex = 0; + + bool isPlaying = EditorApplication.isPlaying; + + GUILayout.BeginHorizontal(); + + // Fetch all scenes toggle button + EditorGUI.BeginDisabledGroup(isPlaying); + bool newFetchAllScenes = GUILayout.Toggle(fetchAllScenes, "All Scenes", "Button", GUILayout.Height(dropdownBoxHeight)); + if (newFetchAllScenes != fetchAllScenes) + { + fetchAllScenes = newFetchAllScenes; + RefreshSceneList(); + SelectCurrentScene(); + } + EditorGUI.EndDisabledGroup(); + + // Add Scene button + EditorGUI.BeginDisabledGroup(isPlaying || fetchAllScenes); + if (GUILayout.Button("Add Scene", GUILayout.Height(dropdownBoxHeight))) + { + AddCurrentSceneToBuildSettings(); + } + EditorGUI.EndDisabledGroup(); + + // Scene dropdown + EditorGUI.BeginDisabledGroup(isPlaying); + GUIStyle popupStyle = new GUIStyle(EditorStyles.popup) + { + fixedHeight = dropdownBoxHeight + }; + + int newIndex = EditorGUILayout.Popup(selectedIndex, sceneNames, popupStyle, GUILayout.Width(150), GUILayout.Height(dropdownBoxHeight)); + + if (newIndex != selectedIndex) + { + selectedIndex = newIndex; + LoadScene(sceneNames[selectedIndex]); + } + EditorGUI.EndDisabledGroup(); + + GUILayout.EndHorizontal(); + } + + static void AddCurrentSceneToBuildSettings() + { + string currentScenePath = EditorSceneManager.GetActiveScene().path; + + if (string.IsNullOrEmpty(currentScenePath)) + { + EditorUtility.DisplayDialog("Scene Not Saved", + "Please save the scene before adding it to Build Settings.", "OK"); + return; + } + + // Check if scene is already in build settings + var buildScenes = EditorBuildSettings.scenes.ToList(); + bool sceneExists = buildScenes.Any(s => s.path == currentScenePath); + + if (sceneExists) + { + // Enable the scene if it was disabled + var existingScene = buildScenes.Find(s => s.path == currentScenePath); + if (!existingScene.enabled) + { + existingScene.enabled = true; + EditorBuildSettings.scenes = buildScenes.ToArray(); + Debug.Log($"Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' enabled in Build Settings."); + RefreshSceneList(); + SelectCurrentScene(); + } + else + { + Debug.Log($"Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' is already in Build Settings."); + } + } + else + { + // Add new scene to build settings + buildScenes.Add(new EditorBuildSettingsScene(currentScenePath, true)); + EditorBuildSettings.scenes = buildScenes.ToArray(); + + Debug.Log($"Scene '{Path.GetFileNameWithoutExtension(currentScenePath)}' added to Build Settings."); + + RefreshSceneList(); + SelectCurrentScene(); + } + } + + static void RefreshSceneList() + { + if (fetchAllScenes) + { + sceneNames = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories) + .Select(path => Path.GetFileNameWithoutExtension(path)) + .ToArray(); + } + else + { + sceneNames = EditorBuildSettings.scenes + .Where(scene => scene.enabled) + .Select(scene => Path.GetFileNameWithoutExtension(scene.path)) + .ToArray(); + } + } + + static void CheckAndRefreshScenes() + { + string[] currentScenes; + if (fetchAllScenes) + { + currentScenes = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories) + .Select(path => Path.GetFileNameWithoutExtension(path)) + .ToArray(); + } + else + { + currentScenes = EditorBuildSettings.scenes + .Where(scene => scene.enabled) + .Select(scene => Path.GetFileNameWithoutExtension(scene.path)) + .ToArray(); + } + + if (!currentScenes.SequenceEqual(sceneNames)) + { + sceneNames = currentScenes; + SelectCurrentScene(); + } + } + + static void SelectCurrentScene() + { + string currentScene = Path.GetFileNameWithoutExtension(EditorSceneManager.GetActiveScene().path); + int index = System.Array.IndexOf(sceneNames, currentScene); + + if (index != -1) + { + selectedIndex = index; + lastActiveScene = currentScene; + } + else + { + string notInBuildName = currentScene + " (not in build index)"; + sceneNames = new[] { notInBuildName }.Concat(sceneNames).ToArray(); + selectedIndex = 0; + lastActiveScene = currentScene; + } + } + + static void UpdateSceneSelection() + { + string currentScene = Path.GetFileNameWithoutExtension(EditorSceneManager.GetActiveScene().path); + if (currentScene != lastActiveScene) + { + lastActiveScene = currentScene; + sceneNames = sceneNames.Where(name => !name.EndsWith(" (not in build index)")).ToArray(); + SelectCurrentScene(); + } + } + + static void LoadScene(string sceneName) + { + string scenePath; + + if (fetchAllScenes) + { + scenePath = Directory.GetFiles("Assets", "*.unity", SearchOption.AllDirectories) + .FirstOrDefault(path => Path.GetFileNameWithoutExtension(path) == sceneName); + } + else + { + scenePath = EditorBuildSettings.scenes + .FirstOrDefault(scene => scene.enabled && scene.path.Contains(sceneName))?.path; + } + + if (!string.IsNullOrEmpty(scenePath)) + { + if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) + { + EditorSceneManager.OpenScene(scenePath); + } + } + else + { + Debug.LogError("Scene not found: " + sceneName); + } + } + + static void OnPlayModeChanged(PlayModeStateChange state) + { + if (state == PlayModeStateChange.EnteredPlayMode || state == PlayModeStateChange.ExitingPlayMode) + { + EditorApplication.delayCall += () => AddToolbarUI(); + } + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor/SceneSwitcherToolbar.cs.meta b/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor/SceneSwitcherToolbar.cs.meta new file mode 100644 index 0000000..a60a905 --- /dev/null +++ b/Assets/OtherAssets/DEV/FREE/SceneSwitcher/Editor/SceneSwitcherToolbar.cs.meta @@ -0,0 +1,9 @@ +fileFormatVersion: 2 +guid: 09ae5cf30ab3ca54f92f91841e5f2c04 +AssetOrigin: + serializedVersion: 1 + productId: 313355 + packageName: Scene Switcher Pro + packageVersion: 1.0.0 + assetPath: Assets/SceneSwitcherPro/Editor/SceneSwitcherToolbar.cs + uploadId: 758164 diff --git a/Assets/OtherAssets/DEV/PAID.meta b/Assets/OtherAssets/DEV/PAID.meta new file mode 100644 index 0000000..aa89b4b --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 47cf9d3c724f25f4c80adada943326ba +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites.meta b/Assets/OtherAssets/DEV/PAID/vFavorites.meta new file mode 100644 index 0000000..e16c640 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d454140e53231466b8d485f9ff487b2e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/Manual.pdf b/Assets/OtherAssets/DEV/PAID/vFavorites/Manual.pdf new file mode 100644 index 0000000..84c4ea1 Binary files /dev/null and b/Assets/OtherAssets/DEV/PAID/vFavorites/Manual.pdf differ diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/Manual.pdf.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/Manual.pdf.meta new file mode 100644 index 0000000..a486e69 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/Manual.pdf.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 842468ccf23564770b4b1cb9f7eca30e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.asmdef b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.asmdef new file mode 100644 index 0000000..8897fa1 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.asmdef @@ -0,0 +1,16 @@ +{ + "name": "VFavorites", + "rootNamespace": "", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.asmdef.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.asmdef.meta new file mode 100644 index 0000000..d92c7c0 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d8266c7db84a045d3b706b23e60aa197 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.cs b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.cs new file mode 100644 index 0000000..128bd15 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.cs @@ -0,0 +1,1976 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using Type = System.Type; +using static VFavorites.Libs.VUtils; +using static VFavorites.Libs.VGUI; +using static VFavorites.VFavoritesData; + + +namespace VFavorites +{ + public static class VFavorites + { + static void WrappedOnGUI(object _) + { + if (wrappedBrowser?.GetType() != t_BrowserWindow) { originalBrowserGUI(); return; } + + + void createData() + { + if (data) return; + + data = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(data, GetScriptPath("VFavorites").GetParentPath().CombinePath("vFavorites Data.asset")); + + + + // // migrateDataFromV1 + + // if (!EditorPrefsCached.HasKey("vFavorites-guids-" + GetProjectId())) return; + // if (EditorPrefsCached.HasKey("vHierarchy-dataMigrationFromV1Attempted-" + GetProjectId())) return; + + // EditorPrefsCached.SetBool("vHierarchy-dataMigrationFromV1Attempted-" + GetProjectId(), true); + + + // if (!data.pages.Any()) + // data.pages.Add(new Page("Page 1")); + + + // var guidsFromV1 = EditorPrefsCached.GetString("vFavorites-guids-" + GetProjectId()).Split('-').Where(r => r != "").ToList(); + + // foreach (var guid in guidsFromV1) + // if (AssetDatabase.LoadAssetAtPath(guid.ToPath()) is Object obj) + // data.pages.First().items.Add(new Item(obj)); + + + // data.Dirty(); + // data.Save(); + + } + void closeNavbarSearch() + { + if (!curEvent.isLayout) return; + if (t_VFolders == null) return; + + + if (GUI.GetNameOfFocusedControl() == "navbar search field") + GUI.FocusControl(null); + + if (!wrappedBrowser.GetMemberValue("m_SearchFieldText").IsNullOrEmpty()) + { + wrappedBrowser.SetMemberValue("m_SearchFieldText", ""); + wrappedBrowser.GetMemberValue("m_SearchFilter").SetMemberValue("m_NameFilter", ""); + + wrappedBrowser.InvokeMethod("UpdateSearchDelayed"); + + GUIUtility.keyboardControl = 0; + } + + + // mouse input on vFavorites throws exceptions if search is active on vFolders navbar + // here we close search to avoid this + + } + + void pageScroll() + { + if (!curEvent.isScroll) return; + if (!VFavoritesMenu.pageScrollEnabled) return; + + + + var scrollDelta = curEvent.mouseDelta.y; + + if (scrollDelta == 0 && curEvent.holdingShift) + scrollDelta = curEvent.mouseDelta.x; + + if (scrollDelta < 0 && data.curPageIndex == 0) return; + + + + if (scrollDelta < 0) + { + data.curPageIndex--; + prevPageButtonBrightness = 2; + } + + if (scrollDelta > 0) + { + data.curPageIndex++; + nextPageButtonBrightness = 2; + + } + + CancelDragging(); + CancelRowAnimations(); + + curEvent.Use(); + + } + + void background() + { + var color = isDarkTheme ? Greyscale(.2f) : Greyscale(.78f); + + totalRect_groupSpace.Draw(color); + + } + void pages() + { + void page(Rect pageRect, Page page) + { + void findSelectedItem() + { + if (!curEvent.isLayout) return; + + foreach (var item in page.items) + item.isSelected = false; + + if (draggingItem) return; + if (mousePresesdOnItem) return; + if (page.lastItemDragTime_ticks > page.lastItemSelectTime_ticks) return; + + Item lastSelectedItem = null; + + foreach (var item in page.items) + { + if (!item.isLoadable) continue; + if (lastSelectedItem?.lastSelectTime_ticks > item.lastSelectTime_ticks) continue; + + + var isSelected = false; + + if (item.isFolder) + { + var targetBrowser = isWrappedBrowserLocked && isOneColumn ? allBrowsers.FirstOrDefault(r => !r.GetMemberValue("isLocked")) ?? wrappedBrowser + : wrappedBrowser; + + if (targetBrowser.GetFieldValue("m_ViewMode") == 1) + isSelected = targetBrowser.InvokeMethod("GetActiveFolderPath") == item.assetPath; + else + isSelected = Selection.activeObject == item.obj; + + } + + if (!item.isFolder) + isSelected = Selection.activeObject == item.obj; + + + if (isSelected) + lastSelectedItem = item; + + } + + if (lastSelectedItem != null) + lastSelectedItem.isSelected = true; + + } + + void rows() + { + void row(float y, Item item) + { + var rowRect = pageRect.SetHeight(rowHeight).SetY(y).SetX(0); + + var iconOffset = 6; + var iconSize = 25 * Mathf.Min(1, data.rowScale); + var nameOffset = 3; + var deletedOrNotLoadedLabelOffset = 1; + + float highlightAmount = 0f; + + + void set_highlightAmount() + { + if (animatingDroppedItem && item == droppedItem) + highlightAmount = droppedItemHighlightAmount; + + if (item.isSelected) + highlightAmount = 1; + + if (draggingItem && item == draggedItem) + highlightAmount = 1; + + if (mousePresesdOnItem && item == pressedItem) + highlightAmount = 1; + + } + + void shadow() + { + if (item != draggedItem && item != droppedItem) return; + + var amount = item == droppedItem ? droppedItemShadowAmount : 1; + + if (amount.Approx(0)) return; + + rowRect.AddWidthFromMid(30).DrawBlurred(Greyscale(0, .55f * amount), 22); + + } + void background() + { + var evenColor = isDarkTheme ? Greyscale(.249f) : Greyscale(.82f); + var oddColor = isDarkTheme ? Greyscale(.228f) : Greyscale(.85f); + var highlightedColor = isDarkTheme ? Greyscale(.335f) : Greyscale(.9f); + + var rowColor = Lerp(evenColor, oddColor, rowRect.y.PingPong(rowHeight) / rowHeight); + + Lerp(ref rowColor, highlightedColor, highlightAmount); + + rowRect.Draw(rowColor); + + } + void icon() + { + var iconRect = rowRect.MoveX(iconOffset).SetWidth(iconSize).SetHeightFromMid(iconSize); + + if (item.isSceneGameObject) + iconRect = iconRect.MoveX(1).Resize(.5f); + + void asset() + { + if (!item.isAsset) return; + + var iconTexture = item.isLoadable ? AssetPreview.GetAssetPreview(item.obj) ?? AssetPreview.GetMiniThumbnail(item.obj) : AssetPreview.GetMiniTypeThumbnail(item.type); + + GUI.DrawTexture(iconRect, iconTexture); + + } + void sceneGameObject() + { + if (!item.isSceneGameObject) return; + + void getIconNameFromAssetPreview() + { + if (!item.isLoadable) return; + + item.sceneGameObjectIconName = AssetPreview.GetMiniThumbnail(item.obj).name; + + } + void getIconNameFromVHierarchy() + { + if (!item.isLoadable) return; + if (!(item.obj is GameObject gameObject)) return; + if (mi_VHierarchy_GetIconName == null) return; + + var iconNameFromVHierarchy = (string)mi_VHierarchy_GetIconName.Invoke(null, new object[] { gameObject }); + + if (!iconNameFromVHierarchy.IsNullOrEmpty()) + item.sceneGameObjectIconName = iconNameFromVHierarchy; + + } + + getIconNameFromAssetPreview(); + getIconNameFromVHierarchy(); + + var iconTexture = EditorGUIUtility.IconContent(item.sceneGameObjectIconName.IsNullOrEmpty() ? "GameObject icon" : item.sceneGameObjectIconName).image; + + GUI.DrawTexture(iconRect, iconTexture); + + } + void folder() + { + if (!item.isFolder) return; + + iconRect = iconRect.Resize(-1.5f); + + void drawNormal() + { + if (isDarkTheme) + if (highlightAmount == 1) return; + + GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("Folder icon").image); + + } + void drawHighlighted() + { + if (!isDarkTheme) return; + if (highlightAmount != 1) return; + + SetGUIColor(Greyscale(.84f)); + + GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("Folder On icon").image); + + ResetGUIColor(); + + } + void drawViaVFolders() + { + mi_VFolders_DrawBigFolderIcon?.Invoke(null, new object[] { iconRect, item.globalId.guid }); + } + + drawNormal(); + drawHighlighted(); + drawViaVFolders(); + + } + + asset(); + sceneGameObject(); + folder(); + + } + void name() + { + var nameRect = rowRect.MoveX(iconOffset + iconSize + nameOffset).MoveY(-.5f).SetHeightFromMid(16); + + void normal() + { + if (isDarkTheme) + if (highlightAmount == 1) return; + + GUI.Label(nameRect, item.name); + + } + void highlighted() + { + if (!isDarkTheme) return; + if (highlightAmount != 1) return; + if (!curEvent.isRepaint) return; + + SetGUIColor(Greyscale(.91f)); + + GUI.skin.GetStyle("WhiteLabel").Draw(nameRect, item.name, false, false, false, false); + + ResetGUIColor(); + + } + + normal(); + highlighted(); + + } + void deletedOrNotLoaded() + { + var labelRect = rowRect.MoveX(iconOffset + iconSize + nameOffset + item.name.GetLabelWidth() + deletedOrNotLoadedLabelOffset).MoveY(.5f); + + SetGUIEnabled(false); + SetLabelFontSize(10); + + if (item.isDeleted) + GUI.Label(labelRect, "Deleted"); + + else if (!item.isLoadable) + GUI.Label(labelRect, "Not loaded"); + + ResetLabelStyle(); + ResetGUIEnabled(); + + } + void crossButton() + { + if (!rowRect.IsHovered()) return; + if (draggingItem) return; + // if (mousePresesdOnItem) return; // idk + + var buttonRect = rowRect.SetWidthFromRight(0).MoveX(-crossButtonOffsetFromRight).SetWidthFromMid(crossButtonSize); + var iconRect = buttonRect.SetSizeFromMid(16); + + var normalColor = Greyscale(item.isSelected ? .48f : .4f); + var hoveredColor = isDarkTheme ? Greyscale(.8f) : normalColor; + var pressedColor = Greyscale(.6f); + + SetGUIColor(buttonRect.IsHovered() ? (mousePressed ? pressedColor : hoveredColor) : normalColor); + GUI.Label(iconRect, EditorGUIUtility.IconContent("CrossIcon")); + ResetGUIColor(); + + buttonRect.MarkInteractive(); + + + if (!mousePressedOnCrossButtonArea) return; + if (!curEvent.isMouseUp) return; + if (!buttonRect.IsHovered()) return; + + CancelRowAnimations(); + data.curPage.rowGaps[data.curPage.items.IndexOf(item)] = rowHeight; + + data.curPage.items.Remove(item); + + data.Dirty(); + data.Save(); + + curEvent.Use(); + + } + void click() + { + if (!rowRect.IsHovered()) return; + if (!curEvent.isMouseUp) return; + + curEvent.Use(); + + if (draggingItem) return; + if (mouseDragDistance > 2) return; + if (!item.isLoadable) return; + + SelectItem(item); + + } + void doubleclick() + { + if (!rowRect.IsHovered()) return; + if (!doubleclickUnhandled) return; + + OpenItem(item); + + doubleclickUnhandled = false; + + } + + + rowRect.MarkInteractive(); + + set_highlightAmount(); + + shadow(); + background(); + icon(); + name(); + deletedOrNotLoaded(); + crossButton(); + click(); + doubleclick(); + + } + + void normalRow(int i) + { + Space(page.rowGaps[i]); + Space(rowHeight); + + if (page.items[i] == droppedItem && animatingDroppedItem && page == data.curPage) return; + + row(lastRect.y, page.items[i]); + + } + void draggedRow() + { + if (!draggingItem) return; + if (page != data.curPage) return; + + + row(draggedItemY_rowsSpace, draggedItem); + + } + void droppedRow() + { + if (!animatingDroppedItem) return; + if (page != data.curPage) return; + + row(droppedItemY_rowsSpace, droppedItem); + + } + + + if (curEvent.isRepaint && skipNextRepaint) { skipNextRepaint = false; return; } + + + GUILayout.BeginArea(pageRect); + page.scrollPos = EditorGUILayout.BeginScrollView(new Vector2(0, page.scrollPos), GUIStyle.none, GUIStyle.none).y; + + for (int i = 0; i < page.items.Count; i++) + normalRow(i); + + Space(page.rowGaps.Last()); + + Space(60); + + draggedRow(); + droppedRow(); + + EditorGUILayout.EndScrollView(); + GUILayout.EndArea(); + + + } + void curtains() + { + var height = 25; + var color = isDarkTheme ? Greyscale(.2f) : Greyscale(.78f); + + pageRect.SetHeight(height).DrawCurtainDown(color.SetAlpha((page.scrollPos / 20).Smoothstep())); + pageRect.SetHeightFromBottom(height).DrawCurtainUp(color); + + } + void tutor() + { + if (page.items.Any() || draggingItem) return; + + SetGUIEnabled(false); + SetLabelFontSize(11); + SetLabelAlignmentCenter(); + + GUI.Label(pageRect.MoveY(-13), "Drop folders, assets"); + GUI.Label(pageRect.MoveY(5), "or GameObjects"); + + ResetGUIEnabled(); + ResetLabelStyle(); + } + + + findSelectedItem(); + + rows(); + curtains(); + tutor(); + + } + + var spaceBetweenPages = 10; + + + if (pagesScrollPos == -1) + pagesScrollPos = data.curPageIndex; + + while (data.pages.Count <= pagesScrollPos.CeilToInt()) + data.pages.Add(new Page("Page " + (data.pages.Count + 1))); + + + for (int i = pagesScrollPos.FloorToInt(); i <= pagesScrollPos.CeilToInt(); i++) + page(totalRect_groupSpace.SetX((totalRect_groupSpace.width + spaceBetweenPages) * (i - pagesScrollPos)), data.pages[i]); + + } + void widget() + { + var widthToAdd = 48; + var height = 24; + var distToBottom = 13; + + var color = isDarkTheme ? Greyscale(.1f) : Greyscale(.85f); + var shadowSize = 10; + var shadowAlpha = .23f; + + var chevronSize = 15; + var chevronOffset = 12; + var chevronBrightness = .5f; + + var textSize = 11; + var textBrightness = .65f; + + widgetRect_groupSpace = totalRect_groupSpace.SetWidthFromMid(data.curPage.name.GetLabelWidth(textSize) + widthToAdd).SetHeightFromBottom(height).MoveY(-distToBottom); + + + void shadow() + { + widgetRect_groupSpace.Resize(1).DrawBlurred(Greyscale(0f, shadowAlpha), shadowSize); + } + void background() + { + widgetRect_groupSpace.DrawWithRoundedCorners(color, 1223); + } + + void nameLabel() + { + if (renamingPage) return; + + + var buttonRect = widgetRect_groupSpace.SetWidthFromMid(data.curPage.name.GetLabelWidth(textSize) - 2); + + buttonRect.MarkInteractive(); + + + var activated = curEvent.isMouseUp && buttonRect.IsHovered(); + + + var brightness = !buttonRect.IsHovered() ? textBrightness : (mousePressed ? .75f : 1); + + SetGUIColor(Greyscale(brightness)); + SetLabelAlignmentCenter(); + SetLabelFontSize(textSize); + SetLabelBold(); + + GUI.Label(widgetRect_groupSpace, data.curPage.name); + + ResetGUIColor(); + ResetLabelStyle(); + + + if (!activated) return; + + renamingPage = true; + prevPageName = data.curPage.name; + + curEvent.Use(); + + } + void leftButton() + { + if (renamingPage) return; + + var iconRect = widgetRect_groupSpace.SetWidth(chevronOffset * 2).SetSizeFromMid(chevronSize, chevronSize); + var buttonRect = Rect.zero.SetX(0).SetY(widgetRect_groupSpace.y).SetXMax(iconRect.xMax + 4).SetYMax(totalRect_groupSpace.yMax); + + buttonRect.MarkInteractive(); + + + var active = data.curPageIndex > 0; + var activated = curEvent.isMouseUp && buttonRect.IsHovered(); + + + var brightness = prevPageButtonBrightness * (!buttonRect.IsHovered() || !active ? chevronBrightness : (mousePressed ? .75f : 1)); + + SetGUIColor(Greyscale(brightness)); + + GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("NodeChevronLeft@2x").image); + + ResetGUIColor(); + + + + if (!activated) return; + + curEvent.Use(); + + + + if (!active) return; + + CancelDragging(); + CancelRowAnimations(); + + data.curPageIndex--; + + + } + void rightButton() + { + if (renamingPage) return; + + var iconRect = widgetRect_groupSpace.SetWidthFromRight(chevronOffset * 2).SetSizeFromMid(chevronSize, chevronSize); + var buttonRect = Rect.zero.SetX(iconRect.x - 6).SetY(widgetRect_groupSpace.y).SetXMax(totalRect_groupSpace.xMax).SetYMax(totalRect_groupSpace.yMax); + + buttonRect.MarkInteractive(); + + + var active = true;// data.curPageIndex < data.pages.Count - 1 && data.curPage.items.Any(); + var activated = curEvent.isMouseUp && buttonRect.IsHovered(); + + + var brightness = nextPageButtonBrightness * (!buttonRect.IsHovered() || !active ? chevronBrightness : (mousePressed ? .75f : 1)); + + SetGUIColor(Greyscale(brightness)); + + GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("NodeChevronRight@2x").image); + + ResetGUIColor(); + + + + if (!activated) return; + + curEvent.Use(); + + + + if (!active) return; + + CancelDragging(); + CancelRowAnimations(); + + data.curPageIndex++; + + } + + void nameTextField() + { + if (!renamingPage) return; + + var textFieldRect = widgetRect_groupSpace.AddHeightFromMid(-2).SetWidthFromMid(data.curPage.name.GetLabelWidth(textSize) + 4); + + var s = new GUIStyle(GUI.skin.textField); + s.alignment = TextAnchor.MiddleCenter; + s.fontSize = 11; + + EditorGUIUtility.AddCursorRect(textFieldRect, MouseCursor.CustomCursor); + + GUI.SetNextControlName("asdasdasd"); + EditorGUI.FocusTextInControl("asdasdasd"); + + + data.curPage.name = EditorGUI.TextField(textFieldRect, data.curPage.name, s); + + + if (data.curPage.name.IsNullOrEmpty()) + data.curPage.name = "Page " + (data.curPageIndex + 1); + + EditorGUIUtility.AddCursorRect(textFieldRect, MouseCursor.CustomCursor); + + + + } + void cancelRename_button() + { + if (!renamingPage) return; + + var iconRect = widgetRect_groupSpace.SetWidth(chevronOffset * 2).SetSizeFromMid(chevronSize - 2); + var buttonRect = iconRect.SetSizeFromMid(25, 25); + + buttonRect.MarkInteractive(); + + + var brightness = !buttonRect.IsHovered() ? chevronBrightness : (mousePressed ? .75f : 1); + + SetGUIColor(Greyscale(brightness)); + + GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("CrossIcon").image); + + ResetGUIColor(); + + + + if (!curEvent.isMouseUp) return; + if (!buttonRect.IsHovered()) return; + + curEvent.Use(); + + data.curPage.name = prevPageName; + + renamingPage = false; + + } + void acceptRename_button() + { + if (!renamingPage) return; + + var iconRect = widgetRect_groupSpace.SetWidthFromRight(chevronOffset * 2).SetSizeFromMid(chevronSize - 0); + var buttonRect = iconRect.SetSizeFromMid(25, 25); + + buttonRect.MarkInteractive(); + + + var brightness = !buttonRect.IsHovered() ? chevronBrightness : (mousePressed ? .75f : 1); + + SetGUIColor(Greyscale(brightness)); + + GUI.DrawTexture(iconRect, EditorGUIUtility.IconContent("check").image); + + ResetGUIColor(); + + + + if (!curEvent.isMouseUp) return; + if (!buttonRect.IsHovered()) return; + + curEvent.Use(); + + renamingPage = false; + + data.Dirty(); + data.Save(); + + } + void acceptRename_enterKey() + { + if (!renamingPage) return; + if (curEvent.keyCode != KeyCode.Return) return; + + renamingPage = false; + + data.Dirty(); + data.Save(); + + } + void cancelRename_escapeKey() + { + if (!renamingPage) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + data.curPage.name = prevPageName; + + renamingPage = false; + + } + + + shadow(); + background(); + + leftButton(); + rightButton(); + nameLabel(); + + nameTextField(); + + cancelRename_button(); + acceptRename_button(); + + acceptRename_enterKey(); + cancelRename_escapeKey(); + + } + void keys() + { + if (isWrappedBrowserLocked && !totalRect_browserSpace.IsHovered()) return; + + void prevPage() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.LeftArrow) return; + if (!VFavoritesMenu.arrowKeysEnabled) return; + if (data.curPageIndex == 0) return; + + CancelDragging(); + CancelRowAnimations(); + + data.curPageIndex--; + + prevPageButtonBrightness = 2; + + curEvent.Use(); + + } + void nextPage() + { + if (curEvent.keyCode != KeyCode.RightArrow) return; + if (!curEvent.isKeyDown) return; + if (!VFavoritesMenu.arrowKeysEnabled) return; + + CancelDragging(); + CancelRowAnimations(); + + data.curPageIndex++; + + nextPageButtonBrightness = 2; + + curEvent.Use(); + + } + void selectPrev() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.UpArrow) return; + if (!VFavoritesMenu.arrowKeysEnabled) return; + + var iToSelect = data.curPage.items.IndexOfFirst(r => r.isSelected) - 1; + + if (iToSelect < 0) + iToSelect = data.curPage.items.LastIndex(); + + if (iToSelect.IsInRangeOf(data.curPage.items)) + SelectItem(data.curPage.items[iToSelect]); + + curEvent.Use(); + + } + void selectNext() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.DownArrow) return; + if (!VFavoritesMenu.arrowKeysEnabled) return; + + var iToSelect = data.curPage.items.IndexOfFirst(r => r.isSelected) + 1; + + if (iToSelect >= data.curPage.items.Count) + iToSelect = 0; + + if (iToSelect.IsInRangeOf(data.curPage.items)) + SelectItem(data.curPage.items[iToSelect]); + + curEvent.Use(); + + } + void numberKeys() + { + if (!curEvent.isKeyDown) return; + if (!VFavoritesMenu.numberKeysEnabled) return; + if (EditorGUIUtility.editingTextField) return; + + + var i = ((int)curEvent.keyCode - 48); + + if (i == 0) i = 10; + + if (!i.IsInRange(1, 10)) return; + + + data.curPageIndex = i - 1; + + curEvent.Use(); + + + } + + prevPage(); + nextPage(); + selectPrev(); + selectNext(); + numberKeys(); + + } + + void doOriginalGUIFirst_() + { + originalBrowserGUI(); + + GUI.BeginGroup(totalRect_browserSpace); + GUI.color = GUI.color.SetAlpha(currentOpacity); + + background(); + pages(); + widget(); + + GUI.color = GUI.color.SetAlpha(1); + GUI.EndGroup(); + + } + void doVFavoritesGUIFirst_() + { + keys(); + pageScroll(); + + GUI.BeginGroup(totalRect_browserSpace); + + widget(); + pages(); + + GUI.EndGroup(); + + if (totalRect_browserSpace.IsHovered()) + if (curEvent.isMouseUp || curEvent.isMouseDrag || curEvent.isScroll) // prevents these events from reaching original gui + curEvent.Use(); + + originalBrowserGUI(); + + } + + void originalBrowserGUI() + { + if (origBrowserOnGUIDelegate.GetMethodInfo().DeclaringType.Name.Contains("VTabs")) return; + + if (isOneColumn && currentOpacity.Approx(1)) // to optimize locked one-column browser functioning as a dedicated favorites window + if (originalGUICalledOnce) // needs to be called once to init stuff so pinging object won't throw exceptions + return; + + + + if (origBrowserOnGUIDelegate.GetMethodInfo().IsStatic) // wrapped by vFolders + origBrowserOnGUIDelegate.GetMethodInfo().Invoke(null, new[] { wrappedBrowser }); + else + origBrowserOnGUIDelegate.GetMethodInfo().Invoke(wrappedBrowser, null); + + originalGUICalledOnce = true; + + } + + + + + createData(); + closeNavbarSearch(); + + UpdateMouseState(); + UpdateDragging(); + + + var doOriginalGUIFirst = VFavoritesMenu.pageScrollEnabled ? curEvent.isRepaint || curEvent.isLayout + : curEvent.isRepaint; + + if (renamingPage) + doOriginalGUIFirst = !curEvent.isMouseDown && !curEvent.isMouseUp; + + if (renamingPage && curEvent.isMouseDown) + curEvent.Use(); + + + if (doOriginalGUIFirst) + doOriginalGUIFirst_(); + else + doVFavoritesGUIFirst_(); + + + if (isWrappedBrowserLocked) + if (!totalRect_browserSpace.IsHovered() && !animatingDroppedItem && !animatingPageScroll) return; + + if (animatingOpacity || animatingDroppedItem || animatingPageScroll || animatingRowGaps) + wrappedBrowser.Repaint(); + + } + + static void SelectItem(Item item) + { + void openFolder(EditorWindow browser, string path) + { + var folderAsset = AssetDatabase.LoadAssetAtPath(path); + + if (browser.GetFieldValue("m_ViewMode") == 1) + browser.InvokeMethod("SetFolderSelection", new[] { folderAsset.GetInstanceID() }, false); + else + { + Selection.activeObject = folderAsset; + + browser.GetMemberValue("m_AssetTree")?.GetPropertyValue("data")?.InvokeMethod("SetExpanded", folderAsset.GetInstanceID(), true); + + } + + } + + void selectSceneObject() + { + if (!item.isSceneGameObject) return; + + Selection.activeObject = item.obj; + + } + void selectAsset_twoColumns() + { + if (!item.isAsset) return; + if (wrappedBrowser.GetFieldValue("m_ViewMode") != 1) return; + + openFolder(wrappedBrowser, item.assetPath.GetParentPath()); + + Selection.activeObject = item.obj; + + } + void selectAsset_oneColumn() + { + if (!item.isAsset) return; + if (wrappedBrowser.GetFieldValue("m_ViewMode") != 0) return; + + Selection.activeObject = item.obj; + + } + void openFolder_unlocked() + { + if (!item.isFolder) return; + if (isWrappedBrowserLocked) return; + + openFolder(wrappedBrowser, item.assetPath); + + } + void openFolder_locked() + { + if (!item.isFolder) return; + if (!isWrappedBrowserLocked) return; + + var unlockedBrowser = allBrowsers.FirstOrDefault(r => !r.GetMemberValue("isLocked")); + var browserToUse = isOneColumn ? unlockedBrowser : lockedBrowser; + + if (!browserToUse) return; + + openFolder(browserToUse, item.assetPath); + + } + + selectSceneObject(); + selectAsset_twoColumns(); + selectAsset_oneColumn(); + openFolder_unlocked(); + openFolder_locked(); + + item.lastSelectTime_ticks = data.curPage.lastItemSelectTime_ticks = System.DateTime.UtcNow.Ticks; + + } + static void OpenItem(Item item) + { + void openText() + { + if (item.assetPath.GetExtension() != ".cs" + && item.assetPath.GetExtension() != ".shader" + && item.assetPath.GetExtension() != ".compute" + && item.assetPath.GetExtension() != ".cginc" + && item.assetPath.GetExtension() != ".json") return; + + + AssetDatabase.OpenAsset(item.globalId.guid.LoadGuid()); + + } + void openPrefab() + { + if (item.type != typeof(GameObject)) return; + if (!item.isLoadable) return; + if ((item.obj as GameObject).scene.rootCount != 0) return; + + AssetDatabase.OpenAsset(item.obj); + + } + void openScene() + { + if (item.type != typeof(SceneAsset)) return; + + EditorSceneManager.SaveOpenScenes(); + EditorSceneManager.OpenScene(item.assetPath); + + } + void openSceneForNotLoadedGameObject() + { + if (!item.isSceneGameObject) return; + if (item.isLoadable) return; + if (item.isDeleted) return; + + EditorSceneManager.SaveOpenScenes(); + EditorSceneManager.OpenScene(item.assetPath); + + Selection.activeObject = item.obj; + + } + + openText(); + openPrefab(); + openScene(); + openSceneForNotLoadedGameObject(); + + } + + static float rowHeight => 44 * data.rowScale; + + static float crossButtonOffsetFromRight = 23; + static float crossButtonSize = 16; + + static Rect totalRect_browserSpace + { + get + { + if (isOneColumn) + return wrappedBrowser.position.SetPos(0, 0); + + + var treeViewRect = wrappedBrowser.GetFieldValue("m_TreeViewRect"); + + return wrappedBrowser.position.SetPos(0, 0).SetWidth(treeViewRect.width).SetHeightFromBottom(treeViewRect.height); + + } + } + static Rect totalRect_groupSpace => totalRect_browserSpace.SetPos(0, 0); + + static Rect widgetRect_browserSpace => widgetRect_groupSpace.MoveY(totalRect_browserSpace.y); + static Rect widgetRect_groupSpace; + + static bool renamingPage; + static string prevPageName; + + static int lastNumberKeyPressedIndex; + static long lastNumberKeyPressedTime_ticks; + + static bool isOneColumn => wrappedBrowser?.GetFieldValue("m_ViewMode") == 0; + + static bool skipNextRepaint; + static bool originalGUICalledOnce; + + + + + + + + static void UpdateMouseState() // called from WrappedOnGUI + { + if (!shortcutPressed && !isWrappedBrowserLocked) { setDefaultState(); return; } + if (!totalRect_browserSpace.IsHovered()) { setDefaultState(); return; } + + void setDefaultState() + { + mousePressed = false; + mousePressedOnCrossButtonArea = false; + mousePressedOnWidget = false; + pressedItem = null; + doubleclickUnhandled = false; + + } + + void position() + { + mousePosiion_browserSpace = curEvent.mousePosition; + } + void down() + { + if (!curEvent.isMouseDown) return; + + mousePressed = true; + mousePressedOnCrossButtonArea = totalRect_browserSpace.SetWidthFromRight(0).MoveX(-crossButtonOffsetFromRight).SetWidthFromMid(crossButtonSize).IsHovered(); + mousePressedOnWidget = curEvent.mousePosition.y >= widgetRect_browserSpace.y; + + mouseDownPosiion_browserSpace = curEvent.mousePosition; + + var pressedItemIndex = (mouseDownPosition_rowsSpace.y / rowHeight).FloorToInt(); + if (pressedItemIndex.IsInRangeOf(data.curPage.items) && !mousePressedOnCrossButtonArea && !mousePressedOnWidget) + pressedItem = data.curPage.items[pressedItemIndex]; + + doubleclickUnhandled = !mousePressedOnCrossButtonArea && curEvent.clickCount == 2; + + curEvent.Use(); + + } + void up() + { + if (!curEvent.isMouseUp) return; + + mousePressed = false; + doubleclickUnhandled = false; + pressedItem = null; + + } + + position(); + down(); + up(); + + } + + static bool mousePressed; + static bool mousePresesdOnItem => pressedItem != null; + static bool mousePressedOnCrossButtonArea; + static bool mousePressedOnWidget; + static bool doubleclickUnhandled; + + static float groupRectOffsetY => isOneColumn ? 0 : -20; + + static Vector2 mousePosiion_browserSpace; + static Vector2 mousePosition_groupSpace => mousePosiion_browserSpace.AddY(groupRectOffsetY); + static Vector2 mousePosition_rowsSpace => mousePosiion_browserSpace.AddY(groupRectOffsetY + data.curPage.scrollPos); + + static Vector2 mouseDownPosiion_browserSpace; + static Vector2 mouseDownPosition_groupSpace => mouseDownPosiion_browserSpace.AddY(groupRectOffsetY); + static Vector2 mouseDownPosition_rowsSpace => mouseDownPosiion_browserSpace.AddY(groupRectOffsetY + data.curPage.scrollPos); + + static float mouseDragDistance => (mousePosiion_browserSpace - mouseDownPosiion_browserSpace).magnitude; + + + static Item pressedItem; + + + + + + + static void UpdateAnimations() // Update + { + void calcDeltaTime() + { + deltaTime = (float)(EditorApplication.timeSinceStartup - lastLayoutTime); + + if (deltaTime > .05f) + deltaTime = .0166f; + + lastLayoutTime = EditorApplication.timeSinceStartup; + + } + void opacity() + { + if (!VFavoritesMenu.fadeAnimationsEnabled) { currentOpacity = targetOpacity; return; } + if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive) { currentOpacity = targetOpacity; return; } + + SmoothDamp(ref currentOpacity, targetOpacity, 11, ref currentOpacityDerivative, deltaTime); + + if (targetOpacity == 0 && currentOpacity < .04f) + currentOpacity = 0; + + } + void pagesScroll() + { + if (!data) return; + if (!VFavoritesMenu.pageScrollAnimationEnabled) { pagesScrollPos = data.curPageIndex; return; } + + if (pagesScrollPos == -1) + pagesScrollPos = data.curPageIndex; + + SmoothDamp(ref pagesScrollPos, data.curPageIndex, 5, ref pagesScrollDerivative, deltaTime); + + if (pagesScrollPos.DistanceTo(data.curPageIndex) < .001f) + pagesScrollPos = data.curPageIndex; + + } + void rowGaps() + { + if (!data) return; + + var lerpSpeed = 10; + + for (int i = 0; i < data.curPage.rowGaps.Count; i++) + data.curPage.rowGaps[i] = Lerp(data.curPage.rowGaps[i], draggingItem && i == insertDraggedItemAtIndex ? rowHeight : 0, lerpSpeed, deltaTime); + + + } + void droppedItem() + { + if (!animatingDroppedItem) return; + + var yLerpSpeed = 8; + var shadowLerpSpeed = 8; + var highlightLerpSpeed = 10; + + SmoothDamp(ref droppedItemY_rowsSpace, data.curPage.items.IndexOf(VFavorites.droppedItem) * rowHeight, yLerpSpeed, ref droppedItemYDerivative, deltaTime); + Lerp(ref droppedItemShadowAmount, 0, shadowLerpSpeed, deltaTime); + Lerp(ref droppedItemHighlightAmount, 0, highlightLerpSpeed, deltaTime); + + if (droppedItemShadowAmount < .01f) + animatingDroppedItem = false; + + } + void pageButtons() + { + if (!VFavoritesMenu.pageScrollAnimationEnabled) { prevPageButtonBrightness = nextPageButtonBrightness = 1; return; } + + var lerpSpeed = 7; + + Lerp(ref prevPageButtonBrightness, 1, lerpSpeed, deltaTime); + Lerp(ref nextPageButtonBrightness, 1, lerpSpeed, deltaTime); + + + } + + calcDeltaTime(); + opacity(); + pagesScroll(); + rowGaps(); + droppedItem(); + pageButtons(); + + } + + static void CancelRowAnimations() + { + for (int i = 0; i < data.curPage.rowGaps.Count; i++) + data.curPage.rowGaps[i] = 0; + + animatingDroppedItem = false; + droppedItem = null; + + } + + static float deltaTime; + static double lastLayoutTime; + + static bool animatingRowGaps => data.curPage.rowGaps.Any(r => r > .1f && r < rowHeight - .1f); + + static float pagesScrollPos = -1; + static float pagesScrollDerivative; + static bool animatingPageScroll => !pagesScrollPos.Approx(data ? data.curPageIndex : 0); + + static float droppedItemY_rowsSpace; + static float droppedItemYDerivative; + static float droppedItemShadowAmount; + static float droppedItemHighlightAmount; + static bool animatingDroppedItem; + + static float prevPageButtonBrightness = 1; + static float nextPageButtonBrightness = 1; + + static float currentOpacity; + static float currentOpacityDerivative; + static float targetOpacity => curEvent.holdingAlt || renamingPage || draggingItemFromPageToOutside || isWrappedBrowserLocked ? 1 : 0; // holdingAlt instead of shortcutPressed to prevent unwrapping due to incorrect event modifiers on key down on mac + static bool animatingOpacity => currentOpacity.DistanceTo(targetOpacity) > .01f; + + + + + + static void UpdateDragging() // called from WrappedOnGUI + { + void initFromOutside() + { + if (draggingItem) return; + if (!totalRect_browserSpace.IsHovered()) return; + if (!curEvent.isDragUpdate) return; + if (!DragAndDrop.objectReferences.FirstOrDefault()) return; + if (draggingItemFromPageToOutside) return; // to avoid duplication when dragging item from page to outside and back + + + animatingDroppedItem = false; + + draggingItem = true; + draggingItemFromPage = false; + + draggedItem = new Item(DragAndDrop.objectReferences.FirstOrDefault()); + draggedItemHoldOffset = 0; + + data.curPage.lastItemDragTime_ticks = System.DateTime.UtcNow.Ticks; + + } + void initFromPage() + { + if (draggingItem) return; + if (!totalRect_browserSpace.IsHovered()) return; + if (!curEvent.isMouseDrag) return; + if (mouseDragDistance < 2) return; + + + var i = (mouseDownPosition_rowsSpace.y / rowHeight).FloorToInt(); + + if (i >= data.curPage.items.Count) return; + if (i < 0) return; // somehow i = -1 with mouseDownPosition_rowsSpace.y = -20 when dragging in from outside quickly + + + animatingDroppedItem = false; + + draggingItem = true; + draggingItemFromPage = true; + draggingItemFromPageAtIndex = i; + + draggedItem = data.curPage.items[i]; + draggedItemHoldOffset = (i * rowHeight + rowHeight / 2) - mouseDownPosition_rowsSpace.y; + + data.curPage.lastItemDragTime_ticks = System.DateTime.UtcNow.Ticks; + + data.curPage.items.Remove(draggedItem); + data.curPage.rowGaps[draggingItemFromPageAtIndex] = rowHeight; + + data.Dirty(); + data.Save(); + + } + + void acceptFromOutside() + { + if (!draggingItem) return; + if (!curEvent.isDragPerform) return; + + DragAndDrop.AcceptDrag(); + curEvent.Use(); + + AcceptDragging(); + + } + void acceptFromPage() + { + if (!draggingItem) return; + if (!curEvent.isMouseUp) return; + + curEvent.Use(); + + AcceptDragging(); + } + + void cancelFromOutside() + { + if (!draggingItemFromOutside) return; + if (totalRect_browserSpace.IsHovered()) return; + + CancelDragging(); + + } + void cancelFromPageAndInitToOutside() + { + if (!curEvent.isMouseDrag) return; + if (!draggingItemFromPage) return; + if (totalRect_browserSpace.IsHovered()) return; + if (DragAndDrop.objectReferences.Any()) return; + + + DragAndDrop.PrepareStartDrag(); + DragAndDrop.objectReferences = new[] { draggedItem.obj }; + DragAndDrop.StartDrag(draggedItem.name); + + CancelDragging(); + + draggingItemFromPageToOutside = true; + + } + + void setVisualMode() + { + if (!draggingItem) return; + + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + + } + void setHotControl() + { + if (!draggingItem) return; + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + } + + void resetDraggingItemFromPageToOutside() // delayCall loop + { + if (!DragAndDrop.objectReferences.Any()) + draggingItemFromPageToOutside = false; + + EditorApplication.delayCall -= resetDraggingItemFromPageToOutside; + EditorApplication.delayCall += resetDraggingItemFromPageToOutside; + + // DragAndDrop.objectReferences is unreliable outside of delayCall + + } + + + initFromOutside(); + initFromPage(); + + acceptFromOutside(); + acceptFromPage(); + + cancelFromOutside(); + cancelFromPageAndInitToOutside(); + + setVisualMode(); + setHotControl(); + + EditorApplication.delayCall -= resetDraggingItemFromPageToOutside; + EditorApplication.delayCall += resetDraggingItemFromPageToOutside; + + } + + static void AcceptDragging() + { + draggingItem = false; + draggingItemFromPage = false; + mousePressed = false; + + data.curPage.items.AddAt(draggedItem, insertDraggedItemAtIndex); + + data.curPage.rowGaps[insertDraggedItemAtIndex] -= rowHeight; + data.curPage.rowGaps.AddAt(0, insertDraggedItemAtIndex); + + droppedItem = draggedItem; + + droppedItemY_rowsSpace = draggedItemY_groupSpace + data.curPage.scrollPos; + droppedItemYDerivative = 0; + droppedItemShadowAmount = droppedItemHighlightAmount = 1; + animatingDroppedItem = true; + + draggedItem = null; + + EditorGUIUtility.hotControl = 0; + + data.Dirty(); + data.Save(); + + } + static void CancelDragging() + { + if (!draggingItem) return; + + draggingItem = false; + mousePressed = false; + + + if (!draggingItemFromPage) { draggedItem = null; return; } + + data.curPage.items.AddAt(draggedItem, draggingItemFromPageAtIndex); + + data.curPage.rowGaps[draggingItemFromPageAtIndex] -= rowHeight; + droppedItem = draggedItem; + droppedItemY_rowsSpace = draggedItemY_groupSpace - data.curPage.scrollPos; + droppedItemShadowAmount = droppedItemHighlightAmount = 1; + animatingDroppedItem = true; + + draggingItemFromPage = false; + + draggedItem = null; + + EditorGUIUtility.hotControl = 0; + + data.Dirty(); + data.Save(); + + } + + static bool draggingItem; + static bool draggingItemFromPage; + static bool draggingItemFromPageToOutside; + static bool draggingItemFromOutside => draggingItem && !draggingItemFromPage; + static int draggingItemFromPageAtIndex; + static Item draggedItem; + static float draggedItemHoldOffset; + static float draggedItemY_groupSpace => (mousePosition_groupSpace.y - rowHeight / 2 + draggedItemHoldOffset).Clamp(0, 12321); + static float draggedItemY_rowsSpace => draggedItemY_groupSpace + data.curPage.scrollPos; + static int insertDraggedItemAtIndex => ((mousePosition_rowsSpace.y + draggedItemHoldOffset) / rowHeight).FloorToInt().Clamp(0, data.curPage.items.Count); + + static Item droppedItem; + + + + + + + + + + + + + + + static void UpdateGUIWrapping() // called from EditorApplicaton.update + { + void wrap() + { + if (wrappedBrowser) return; + if (!UnityEditorInternal.InternalEditorUtility.isApplicationActive) return; + if (!shortcutPressed) return; + if (EditorWindow.mouseOverWindow?.GetType() != t_BrowserWindow) return; + if (t_VTabs != null && !EditorPrefsCached.GetBool("vTabs-pluginDisabled", false) && EditorWindow.mouseOverWindow.GetMemberValue("isLocked")) return; + + + WrapBrowserGUI(EditorWindow.mouseOverWindow); + + wrappedBrowser = EditorWindow.mouseOverWindow; + + wrappedBrowser.Focus(); + wrappedBrowser.Repaint(); + + t_BrowserWindow.SetFieldValue("s_LastInteractedProjectBrowser", wrappedBrowser); // so vTabs can copy its layout setting + + } + void unwrap() + { + if (!wrappedBrowser) return; + if (shortcutPressed && wrappedBrowser.hasFocus) return; + if (currentOpacity > 0 && wrappedBrowser.hasFocus) return; + + + CancelDragging(); + CancelRowAnimations(); + + UnwrapBrowserGUI(); + + wrappedBrowser.Repaint(); + + wrappedBrowser = null; + + } + + unwrap(); + wrap(); + + } + + static void WrapBrowserGUI(EditorWindow browser) + { + var hostView = fi_m_Parent.GetValue(browser); + var newDelegate = mi_WrappedOnGUI.CreateDelegate(t_EditorWindowDelegate, hostView); + + origBrowserOnGUIDelegate = fi_m_OnGUI.GetValue(hostView) as System.Delegate; + fi_m_OnGUI.SetValue(hostView, newDelegate); + + } + static void UnwrapBrowserGUI() + { + if (wrappedBrowser.GetFieldValue("m_Parent").GetFieldValue("m_OnGUI").Method != mi_WrappedOnGUI) return; + + wrappedBrowser.GetFieldValue("m_Parent").SetFieldValue("m_OnGUI", origBrowserOnGUIDelegate); + + } + + static EditorWindow wrappedBrowser; + static System.Delegate origBrowserOnGUIDelegate; + + static bool shortcutPressed + { + get + { + if (VFavoritesMenu.activeOnAltEnabled) + return curEvent.holdingAlt; + + if (VFavoritesMenu.activeOnAltShiftEnabled) + return curEvent.modifiers == (EventModifiers.Alt | EventModifiers.Shift); + + if (VFavoritesMenu.activeOnCtrlAltEnabled) + if (Application.platform == RuntimePlatform.OSXEditor) + return curEvent.modifiers == (EventModifiers.Command | EventModifiers.Alt); + else + return curEvent.modifiers == (EventModifiers.Control | EventModifiers.Alt); + + return false; + } + } + + + + + + + static void UpdateLocking() // called from EditorApplicaton.update + { + void unsetWrappedBrowser() + { + if (!isWrappedBrowserLocked) return; + + if (wrappedBrowser.GetFieldValue("m_Parent").GetFieldValue("m_OnGUI").Method == mi_WrappedOnGUI) return; + + wrappedBrowser = null; + + // nulls wrappedBrowser if it was unwrapped externally + // ie when locked browser gets moved or maximized + + } + void markLockedBrowser() + { + if (!lockedBrowser) return; + + MarkAsLocked(lockedBrowser); + + // fixes marking getting reset on scene load + + } + void setMinWidthOnLockedBrowser() + { + if (!lockedBrowser) return; + if (lockedBrowser.minSize.x == 100) return; + + lockedBrowser.minSize = Vector2.one * 100; + + } + + void lock_() + { + if (lockedBrowser) return; + if (!wrappedBrowser) return; + if (!wrappedBrowser.GetMemberValue("isLocked")) return; + + lockedBrowser = wrappedBrowser; + + EditorPrefsCached.SetInt("vFavorites-lockedBrowserHash", lockedBrowser.GetHashCode()); + EditorPrefsCached.SetInt("vFavorites-lockedBrowserDockAreaInstanceId", lockedBrowser.GetMemberValue("m_Parent").GetInstanceID()); + + curEvent.Use(); + + } + void unlock() + { + if (!lockedBrowser) return; + if (lockedBrowser.GetMemberValue("isLocked")) return; + + + lockedBrowser = null; + + EditorPrefsCached.SetInt("vFavorites-lockedBrowserHash", 0); + EditorPrefsCached.SetInt("vFavorites-lockedBrowserDockAreaInstanceId", 0); + + curEvent.Use(); + + } + void wrap() + { + if (!lockedBrowser) return; + if (!lockedBrowser.hasFocus) return; + if (isWrappedBrowserLocked) return; + + WrapBrowserGUI(lockedBrowser); + + currentOpacity = 1; + + wrappedBrowser = lockedBrowser; + + } + + + unsetWrappedBrowser(); + markLockedBrowser(); + setMinWidthOnLockedBrowser(); + + lock_(); + unlock(); + wrap(); + + } + + static EditorWindow lockedBrowser + { + get + { + if (_lockedBrowser) return _lockedBrowser; + + + var lockedBrowserInstanceId = EditorPrefsCached.GetInt("vFavorites-lockedBrowserInstanceId", 0); + + if (lockedBrowserInstanceId == 0) return null; + + + + var window = Resources.InstanceIDToObject(lockedBrowserInstanceId) as EditorWindow; + + if (window && window.GetType() == t_BrowserWindow) // prevents iid collisions + _lockedBrowser = window; + + if (_lockedBrowser) return _lockedBrowser; + + + + _lockedBrowser = allBrowsers.FirstOrDefault(r => IsMarkedAsLocked(r)); + + if (_lockedBrowser) return _lockedBrowser; // todo set instanceid + + + + // EditorPrefsCached.SetInt("vFavorites-lockedBrowserInstanceId", 0); // one attempt to find the locked browser isn't enough after unmaximize + + return null; + + } + set + { + if (_lockedBrowser) + MarkAsUnlocked(_lockedBrowser); + + if (value == null) + { + EditorPrefsCached.SetInt("vFavorites-lockedBrowserInstanceId", 0); + + _lockedBrowser = null; + + } + else + { + EditorPrefsCached.SetInt("vFavorites-lockedBrowserInstanceId", value.GetInstanceID()); + + MarkAsLocked(value); + + _lockedBrowser = value; + + } + + } + } + static EditorWindow _lockedBrowser; + + static bool IsMarkedAsLocked(EditorWindow browser) => browser.GetMemberValue("m_SearchFilter")?.GetMemberValue("m_OriginalText") == "asd"; + static void MarkAsLocked(EditorWindow browser) => browser.GetMemberValue("m_SearchFilter")?.SetMemberValue("m_OriginalText", "asd"); + static void MarkAsUnlocked(EditorWindow browser) => browser.GetMemberValue("m_SearchFilter")?.SetMemberValue("m_OriginalText", ""); + + static bool isWrappedBrowserLocked => wrappedBrowser && wrappedBrowser == lockedBrowser; + + static bool CanBrowserBeWrapped_byVTabs(EditorWindow browser) => !IsMarkedAsLocked(browser); + + + + + static void RepaintOnAltUp() // Update + { + var lastEvent = typeof(Event).GetFieldValue("s_Current"); + + if (wasAlt && !lastEvent.alt) + if (EditorWindow.mouseOverWindow?.GetType() == t_BrowserWindow) + EditorApplication.RepaintProjectWindow(); + + wasAlt = lastEvent.alt; + + } + + static bool wasAlt; + + + + + + + + + + + + + [InitializeOnLoadMethod] + static void Init() + { + if (VFavoritesMenu.pluginDisabled) return; + + void subscribe() + { + EditorApplication.update -= UpdateGUIWrapping; + EditorApplication.update += UpdateGUIWrapping; + + EditorApplication.update -= UpdateLocking; + EditorApplication.update += UpdateLocking; + + EditorApplication.update -= UpdateAnimations; + EditorApplication.update += UpdateAnimations; + + EditorApplication.update -= RepaintOnAltUp; + EditorApplication.update += RepaintOnAltUp; + + + + EditorApplication.quitting -= VFavoritesState.Save; + EditorApplication.quitting += VFavoritesState.Save; + + } + void loadData() + { + data = AssetDatabase.LoadAssetAtPath(EditorPrefsCached.GetString("vFavorites-lastKnownDataPath-" + GetProjectId())); + + + if (data) return; + + data = AssetDatabase.FindAssets("t:VFavoritesData").Select(guid => AssetDatabase.LoadAssetAtPath(guid.ToPath())).FirstOrDefault(); + + + if (!data) return; + + EditorPrefsCached.SetString("vFavorites-lastKnownDataPath-" + GetProjectId(), data.GetPath()); + + } + void loadDataDelayed() + { + if (data) return; + + EditorApplication.delayCall += () => EditorApplication.delayCall += loadData; + + // AssetDatabase isn't up to date at this point (it gets updated after InitializeOnLoadMethod) + // and if current AssetDatabase state doesn't contain the data - it won't be loaded during Init() + // so here we schedule an additional, delayed attempt to load the data + // this addresses reports of data loss when trying to load it on a new machine + + } + + subscribe(); + loadData(); + loadDataDelayed(); + + } + + public static VFavoritesData data; + + + + + + + + static void BeforeWindowCreated_byVTabs(object dockArea) + { + if (!wrappedBrowser) return; + if (wrappedBrowser.GetFieldValue("m_Parent") != dockArea) return; + + CancelDragging(); + CancelRowAnimations(); + + UnwrapBrowserGUI(); + + draggingItemFromPageToOutside = false; + + } + + + + + + + static IEnumerable allBrowsers => _allBrowsers ??= t_BrowserWindow.GetFieldValue("s_ProjectBrowsers").Cast(); + static IEnumerable _allBrowsers; + + static Type t_BrowserWindow = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser"); + static Type t_HostView = typeof(Editor).Assembly.GetType("UnityEditor.HostView"); + static Type t_EditorWindowDelegate = t_HostView.GetNestedType("EditorWindowDelegate", maxBindingFlags); + static FieldInfo fi_m_Parent = typeof(EditorWindow).GetField("m_Parent", maxBindingFlags); + static FieldInfo fi_m_OnGUI = t_HostView.GetField("m_OnGUI", maxBindingFlags); + static MethodInfo mi_WrappedOnGUI = typeof(VFavorites).GetMethod(nameof(WrappedOnGUI), maxBindingFlags); + + + static Type t_VHierarchy = Type.GetType("VHierarchy.VHierarchy") ?? Type.GetType("VHierarchy.VHierarchy, VHierarchy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + static Type t_VFolders = Type.GetType("VFolders.VFolders") ?? Type.GetType("VFolders.VFolders, VFolders, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + static Type t_VTabs = Type.GetType("VTabs.VTabs") ?? Type.GetType("VTabs.VTabs, VTabs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + + static MethodInfo mi_VHierarchy_GetIconName = t_VHierarchy?.GetMethod("GetIconName_forVFavorites", maxBindingFlags); + static MethodInfo mi_VFolders_DrawBigFolderIcon = t_VFolders?.GetMethod("DrawBigFolderIcon_forVFavorites", maxBindingFlags); + + + + + + const string version = "2.0.10"; + + } +} +#endif diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.cs.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.cs.meta new file mode 100644 index 0000000..64e3f6a --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavorites.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6651039474d594cdd91489768cf293f6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesData.cs b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesData.cs new file mode 100644 index 0000000..c2dbe74 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesData.cs @@ -0,0 +1,225 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Serialization; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VFavorites.VFavoritesState; +using static VFavorites.Libs.VUtils; +using static VFavorites.Libs.VGUI; + + + +namespace VFavorites +{ + public class VFavoritesData : ScriptableObject + { + public List pages = new List(); + + public Page curPage + { + get + { + while (curPageIndex >= pages.Count - 1) + pages.Add(new Page("Page " + (pages.Count + 1))); + + return pages[curPageIndex]; + + } + } + + public int curPageIndex { get => VFavoritesState.instance.curPageIndex; set => VFavoritesState.instance.curPageIndex = value; } + + public float rowScale = 1; + + + [System.Serializable] + public class Page + { + public List items = new List(); + + public string name = ""; + + public Page(string name) => this.name = name; + + + + + [System.NonSerialized] + public List _rowGaps = new List(); + public List rowGaps + { + get + { + if (_rowGaps == null) + _rowGaps = new List(); + + while (_rowGaps.Count < items.Count + 1) _rowGaps.Add(0); + while (_rowGaps.Count > items.Count + 1) _rowGaps.RemoveLast(); + + return _rowGaps; + + } + } + + + public float scrollPos { get => state.scrollPos; set => state.scrollPos = value; } + + public long lastItemSelectTime_ticks { get => state.lastItemSelectTime_ticks; set => state.lastItemSelectTime_ticks = value; } + public long lastItemDragTime_ticks { get => state.lastItemDragTime_ticks; set => state.lastItemDragTime_ticks = value; } + + + public PageState state + { + get + { + if (!VFavoritesState.instance.pageStates_byPageId.ContainsKey(id)) + VFavoritesState.instance.pageStates_byPageId[id] = new PageState(); + + return VFavoritesState.instance.pageStates_byPageId[id]; + + } + } + + public int id + { + get + { + if (_id == 0) + _id = Random.value.GetHashCode(); + + return _id; + + } + } + public int _id = 0; + + } + + [System.Serializable] + public class Item + { + public GlobalID globalId; + + + public Type type => Type.GetType(_typeString) ?? typeof(DefaultAsset); + public string _typeString; + + public Object obj => _obj != null ? _obj : (_obj = globalId.GetObject()); + public Object _obj; + + + public bool isSceneGameObject; + public bool isFolder; + public bool isAsset; + + + public bool isLoadable => obj != null; + + public bool isDeleted + { + get + { + if (!isSceneGameObject) + return !isLoadable; + + if (isLoadable) + return false; + + if (!AssetDatabase.LoadAssetAtPath(globalId.guid.ToPath())) + return true; + + for (int i = 0; i < EditorSceneManager.sceneCount; i++) + if (EditorSceneManager.GetSceneAt(i).path == globalId.guid.ToPath()) + return true; + + return false; + + } + } + + public string assetPath => globalId.guid.ToPath(); + + + public Item(Object o) + { + globalId = o.GetGlobalID(); + + + isSceneGameObject = o is GameObject go && go.scene.rootCount != 0; + isFolder = AssetDatabase.IsValidFolder(o.GetPath()); + isAsset = !isSceneGameObject && !isFolder; + + _typeString = o.GetType().AssemblyQualifiedName; + + _name = o.name; + + } + + + + + + public string name + { + get + { + if (!isLoadable) return _name; + + if (assetPath.GetExtension() == ".cs") + _name = obj.name.Decamelcase(); + else + _name = obj.name; + + return _name; + + } + } + public string _name { get => state._name; set => state._name = value; } + + public string sceneGameObjectIconName { get => state.sceneGameObjectIconName; set => state.sceneGameObjectIconName = value; } + + public long lastSelectTime_ticks { get => state.lastSelectTime_ticks; set => state.lastSelectTime_ticks = value; } + public bool isSelected { get => state.isSelected; set => state.isSelected = value; } + + + + public ItemState state + { + get + { + if (!VFavoritesState.instance.itemStates_byItemId.ContainsKey(id)) + VFavoritesState.instance.itemStates_byItemId[id] = new ItemState(); + + return VFavoritesState.instance.itemStates_byItemId[id]; + + } + } + + public int id + { + get + { + if (_id == 0) + _id = Random.value.GetHashCode(); + + return _id; + + } + } + public int _id = 0; + + + } + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesData.cs.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesData.cs.meta new file mode 100644 index 0000000..92b497a --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 066cf82f8f80d408c856e48fc8f1127b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesLibs.cs b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesLibs.cs new file mode 100644 index 0000000..ad3851d --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesLibs.cs @@ -0,0 +1,1816 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Linq; +using UnityEngine.UIElements; +using Type = System.Type; +using static VFavorites.Libs.VUtils; + + +namespace VFavorites.Libs +{ + public static class VUtils + { + #region Text + + + public static string Decamelcase(this string str) => Regex.Replace(Regex.Replace(str, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"), @"(\p{Ll})(\P{Ll})", "$1 $2"); + + public static string Remove(this string s, string toRemove) + { + if (toRemove == "") return s; + return s.Replace(toRemove, ""); + } + + public static bool IsEmpty(this string s) => s == ""; + public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s); + + + + + + #endregion + + #region IEnumerables + + + public static T AddAt(this List l, T r, int i) + { + if (i < 0) i = 0; + if (i >= l.Count) + l.Add(r); + else + l.Insert(i, r); + return r; + } + public static T RemoveLast(this List l) + { + if (!l.Any()) return default; + + var r = l.Last(); + + l.RemoveAt(l.Count - 1); + + return r; + } + + public static void Add(this List list, params T[] items) + { + foreach (var r in items) + list.Add(r); + } + + public static int LastIndex(this List l) => l.Count - 1; // toremove + + // public static T GetAtWrapped(this List list, int i) // toremove + // { + // while (i < 0) i += list.Count; + // while (i >= list.Count) i -= list.Count; + + // return list[i]; + // } + + + + + + #endregion + + #region Linq + + + public static T NextTo(this IEnumerable e, T to) => e.SkipWhile(r => !r.Equals(to)).Skip(1).FirstOrDefault(); + public static T PreviousTo(this IEnumerable e, T to) => e.Reverse().SkipWhile(r => !r.Equals(to)).Skip(1).FirstOrDefault(); + public static T NextToOtFirst(this IEnumerable e, T to) => e.NextTo(to) ?? e.First(); + public static T PreviousToOrLast(this IEnumerable e, T to) => e.PreviousTo(to) ?? e.Last(); + + public static Dictionary MergeDictionaries(IEnumerable> dicts) + { + if (dicts.Count() == 0) return null; + if (dicts.Count() == 1) return dicts.First(); + + var mergedDict = new Dictionary(dicts.First()); + + foreach (var dict in dicts.Skip(1)) + foreach (var r in dict) + if (!mergedDict.ContainsKey(r.Key)) + mergedDict.Add(r.Key, r.Value); + + return mergedDict; + } + + public static IEnumerable InsertFirst(this IEnumerable ie, T t) => new[] { t }.Concat(ie); + + public static bool None(this IEnumerable ie, System.Func f) => !ie.Any(f); + public static bool None(this IEnumerable ie) => !ie.Any(); + + public static int IndexOfFirst(this List list, System.Func f) => list.FirstOrDefault(f) is T t ? list.IndexOf(t) : -1; + public static int IndexOfLast(this List list, System.Func f) => list.LastOrDefault(f) is T t ? list.IndexOf(t) : -1; + + public static void SortBy(this List list, System.Func keySelector) where T2 : System.IComparable => list.Sort((q, w) => keySelector(q).CompareTo(keySelector(w))); + + public static void RemoveValue(this IDictionary dictionary, TValue value) + { + if (dictionary.FirstOrDefault(r => r.Value.Equals(value)) is var kvp) + dictionary.Remove(kvp); + } + + + public static void ForEach(this IEnumerable sequence, System.Action action) { foreach (T item in sequence) action(item); } + + + + + + #endregion + + #region Reflection + + public static object GetFieldValue(this object o, string fieldName, bool exceptionIfNotFound = true) + { + var type = (o as Type) ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(fieldName) is FieldInfo fieldInfo) + return fieldInfo.GetValue(target); + + + if (exceptionIfNotFound) + throw new System.Exception($"Field '{fieldName}' not found in type '{type.Name}' and its parent types"); + + return null; + + } + public static object GetPropertyValue(this object o, string propertyName, bool exceptionIfNotFound = true) + { + var type = (o as Type) ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetPropertyInfo(propertyName) is PropertyInfo propertyInfo) + return propertyInfo.GetValue(target); + + + if (exceptionIfNotFound) + throw new System.Exception($"Property '{propertyName}' not found in type '{type.Name}' and its parent types"); + + return null; + + } + public static object GetMemberValue(this object o, string memberName, bool exceptionIfNotFound = true) + { + var type = (o as Type) ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(memberName) is FieldInfo fieldInfo) + return fieldInfo.GetValue(target); + + if (type.GetPropertyInfo(memberName) is PropertyInfo propertyInfo) + return propertyInfo.GetValue(target); + + + if (exceptionIfNotFound) + throw new System.Exception($"Member '{memberName}' not found in type '{type.Name}' and its parent types"); + + return null; + + } + + public static void SetFieldValue(this object o, string fieldName, object value, bool exceptionIfNotFound = true) + { + var type = (o as Type) ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(fieldName) is FieldInfo fieldInfo) + fieldInfo.SetValue(target, value); + + + else if (exceptionIfNotFound) + throw new System.Exception($"Field '{fieldName}' not found in type '{type.Name}' and its parent types"); + + } + public static void SetPropertyValue(this object o, string propertyName, object value, bool exceptionIfNotFound = true) + { + var type = (o as Type) ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetPropertyInfo(propertyName) is PropertyInfo propertyInfo) + propertyInfo.SetValue(target, value); + + + else if (exceptionIfNotFound) + throw new System.Exception($"Property '{propertyName}' not found in type '{type.Name}' and its parent types"); + + } + public static void SetMemberValue(this object o, string memberName, object value, bool exceptionIfNotFound = true) + { + var type = (o as Type) ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(memberName) is FieldInfo fieldInfo) + fieldInfo.SetValue(target, value); + + else if (type.GetPropertyInfo(memberName) is PropertyInfo propertyInfo) + propertyInfo.SetValue(target, value); + + + else if (exceptionIfNotFound) + throw new System.Exception($"Member '{memberName}' not found in type '{type.Name}' and its parent types"); + + } + + public static object InvokeMethod(this object o, string methodName, params object[] parameters) // todo handle null params (can't get their type) + { + var type = (o as Type) ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetMethodInfo(methodName, parameters.Select(r => r.GetType()).ToArray()) is MethodInfo methodInfo) + return methodInfo.Invoke(target, parameters); + + + throw new System.Exception($"Method '{methodName}' not found in type '{type.Name}', its parent types and interfaces"); + + } + + + + static FieldInfo GetFieldInfo(this Type type, string fieldName) + { + if (fieldInfoCache.TryGetValue(type, out var fieldInfosByNames)) + if (fieldInfosByNames.TryGetValue(fieldName, out var fieldInfo)) + return fieldInfo; + + + if (!fieldInfoCache.ContainsKey(type)) + fieldInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetField(fieldName, maxBindingFlags) is FieldInfo fieldInfo) + return fieldInfoCache[type][fieldName] = fieldInfo; + + + return fieldInfoCache[type][fieldName] = null; + + } + static Dictionary> fieldInfoCache = new Dictionary>(); + + static PropertyInfo GetPropertyInfo(this Type type, string propertyName) + { + if (propertyInfoCache.TryGetValue(type, out var propertyInfosByNames)) + if (propertyInfosByNames.TryGetValue(propertyName, out var propertyInfo)) + return propertyInfo; + + + if (!propertyInfoCache.ContainsKey(type)) + propertyInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetProperty(propertyName, maxBindingFlags) is PropertyInfo propertyInfo) + return propertyInfoCache[type][propertyName] = propertyInfo; + + + return propertyInfoCache[type][propertyName] = null; + + } + static Dictionary> propertyInfoCache = new Dictionary>(); + + static MethodInfo GetMethodInfo(this Type type, string methodName, params Type[] argumentTypes) + { + var methodHash = methodName.GetHashCode() ^ argumentTypes.Aggregate(0, (hash, r) => hash ^= r.GetHashCode()); + + + if (methodInfoCache.TryGetValue(type, out var methodInfosByHashes)) + if (methodInfosByHashes.TryGetValue(methodHash, out var methodInfo)) + return methodInfo; + + + + if (!methodInfoCache.ContainsKey(type)) + methodInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetMethod(methodName, maxBindingFlags, null, argumentTypes, null) is MethodInfo methodInfo) + return methodInfoCache[type][methodHash] = methodInfo; + + foreach (var interfaceType in type.GetInterfaces()) + if (interfaceType.GetMethod(methodName, maxBindingFlags, null, argumentTypes, null) is MethodInfo methodInfo) + return methodInfoCache[type][methodHash] = methodInfo; + + + + return methodInfoCache[type][methodHash] = null; + + } + static Dictionary> methodInfoCache = new Dictionary>(); + + + + public static T GetFieldValue(this object o, string fieldName, bool exceptionIfNotFound = true) => (T)o.GetFieldValue(fieldName, exceptionIfNotFound); + public static T GetPropertyValue(this object o, string propertyName, bool exceptionIfNotFound = true) => (T)o.GetPropertyValue(propertyName, exceptionIfNotFound); + public static T GetMemberValue(this object o, string memberName, bool exceptionIfNotFound = true) => (T)o.GetMemberValue(memberName, exceptionIfNotFound); + public static T InvokeMethod(this object o, string methodName, params object[] parameters) => (T)o.InvokeMethod(methodName, parameters); + + + + + + + public static List GetSubclasses(this Type t) => t.Assembly.GetTypes().Where(type => type.IsSubclassOf(t)).ToList(); + + public static object GetDefaultValue(this FieldInfo f, params object[] constructorVars) => f.GetValue(System.Activator.CreateInstance(((MemberInfo)f).ReflectedType, constructorVars)); + + public static object GetDefaultValue(this FieldInfo f) => f.GetValue(System.Activator.CreateInstance(((MemberInfo)f).ReflectedType)); + + + public static IEnumerable GetFieldsWithoutBase(this Type t) => t.GetFields().Where(r => !t.BaseType.GetFields().Any(rr => rr.Name == r.Name)); + + public static IEnumerable GetPropertiesWithoutBase(this Type t) => t.GetProperties().Where(r => !t.BaseType.GetProperties().Any(rr => rr.Name == r.Name)); + + + public const BindingFlags maxBindingFlags = (BindingFlags)62; + + + + + + + + #endregion + + #region Math + + + public static bool Approx(this float f1, float f2) => Mathf.Approximately(f1, f2); + public static float DistanceTo(this float f1, float f2) => Mathf.Abs(f1 - f2); + public static float DistanceTo(this Vector2 f1, Vector2 f2) => (f1 - f2).magnitude; + public static float DistanceTo(this Vector3 f1, Vector3 f2) => (f1 - f2).magnitude; + public static float Dist(float f1, float f2) => Mathf.Abs(f1 - f2); + public static float Avg(float f1, float f2) => (f1 + f2) / 2; + public static float Abs(this float f) => Mathf.Abs(f); + public static int Abs(this int f) => Mathf.Abs(f); + public static float Sign(this float f) => Mathf.Sign(f); + public static float Clamp(this float f, float f0, float f1) => Mathf.Clamp(f, f0, f1); + public static int Clamp(this int f, int f0, int f1) => Mathf.Clamp(f, f0, f1); + public static float Clamp01(this float f) => Mathf.Clamp(f, 0, 1); + public static Vector2 Clamp01(this Vector2 f) => new Vector2(f.x.Clamp01(), f.y.Clamp01()); + public static Vector3 Clamp01(this Vector3 f) => new Vector3(f.x.Clamp01(), f.y.Clamp01(), f.z.Clamp01()); + + + public static float Pow(this float f, float pow) => Mathf.Pow(f, pow); + public static int Pow(this int f, int pow) => (int)Mathf.Pow(f, pow); + + public static float Round(this float f) => Mathf.Round(f); + public static float Ceil(this float f) => Mathf.Ceil(f); + public static float Floor(this float f) => Mathf.Floor(f); + public static int RoundToInt(this float f) => Mathf.RoundToInt(f); + public static int CeilToInt(this float f) => Mathf.CeilToInt(f); + public static int FloorToInt(this float f) => Mathf.FloorToInt(f); + public static int ToInt(this float f) => (int)f; + public static float ToFloat(this int f) => (float)f; + public static float ToFloat(this double f) => (float)f; + + + public static float Sqrt(this float f) => Mathf.Sqrt(f); + + public static float Max(this float f, float ff) => Mathf.Max(f, ff); + public static float Min(this float f, float ff) => Mathf.Min(f, ff); + public static int Max(this int f, int ff) => Mathf.Max(f, ff); + public static int Min(this int f, int ff) => Mathf.Min(f, ff); + + public static float Loop(this float f, float boundMin, float boundMax) + { + while (f < boundMin) f += boundMax - boundMin; + while (f > boundMax) f -= boundMax - boundMin; + return f; + } + public static float Loop(this float f, float boundMax) => f.Loop(0, boundMax); + + public static float PingPong(this float f, float boundMin, float boundMax) => boundMin + Mathf.PingPong(f - boundMin, boundMax - boundMin); + public static float PingPong(this float f, float boundMax) => f.PingPong(0, boundMax); + + + public static float TriangleArea(Vector2 A, Vector2 B, Vector2 C) => Vector3.Cross(A - B, A - C).z.Abs() / 2; + public static Vector2 LineIntersection(Vector2 A, Vector2 B, Vector2 C, Vector2 D) + { + var a1 = B.y - A.y; + var b1 = A.x - B.x; + var c1 = a1 * A.x + b1 * A.y; + + var a2 = D.y - C.y; + var b2 = C.x - D.x; + var c2 = a2 * C.x + b2 * C.y; + + var d = a1 * b2 - a2 * b1; + + var x = (b2 * c1 - b1 * c2) / d; + var y = (a1 * c2 - a2 * c1) / d; + + return new Vector2(x, y); + + } + + public static float ProjectOn(this Vector2 v, Vector2 on) => Vector3.Project(v, on).magnitude; + public static float AngleTo(this Vector2 v, Vector2 to) => Vector2.Angle(v, to); + + public static Vector2 Rotate(this Vector2 v, float deg) => Quaternion.AngleAxis(deg, Vector3.forward) * v; + + public static float Smoothstep(this float f) { f = f.Clamp01(); return f * f * (3 - 2 * f); } + + public static float InverseLerp(this Vector2 v, Vector2 a, Vector2 b) + { + var ab = b - a; + var av = v - a; + return Vector2.Dot(av, ab) / Vector2.Dot(ab, ab); + } + + public static bool IsOdd(this int i) => i % 2 == 1; + public static bool IsEven(this int i) => i % 2 == 0; + + public static bool IsInRange(this int i, int a, int b) => i >= a && i <= b; + public static bool IsInRange(this float i, float a, float b) => i >= a && i <= b; + + public static bool IsInRangeOf(this int i, IList list) => i.IsInRange(0, list.Count - 1); + public static bool IsInRangeOf(this int i, T[] array) => i.IsInRange(0, array.Length - 1); + + + + + + + + #endregion + + #region Lerping + + + public static float LerpT(float lerpSpeed, float deltaTime) => 1 - Mathf.Exp(-lerpSpeed * 2f * deltaTime); + public static float LerpT(float lerpSpeed) => LerpT(lerpSpeed, Time.deltaTime); + + public static float Lerp(float f1, float f2, float t) => Mathf.LerpUnclamped(f1, f2, t); + public static float Lerp(ref float f1, float f2, float t) => f1 = Lerp(f1, f2, t); + + public static Vector2 Lerp(Vector2 f1, Vector2 f2, float t) => Vector2.LerpUnclamped(f1, f2, t); + public static Vector2 Lerp(ref Vector2 f1, Vector2 f2, float t) => f1 = Lerp(f1, f2, t); + + public static Vector3 Lerp(Vector3 f1, Vector3 f2, float t) => Vector3.LerpUnclamped(f1, f2, t); + public static Vector3 Lerp(ref Vector3 f1, Vector3 f2, float t) => f1 = Lerp(f1, f2, t); + + public static Color Lerp(Color f1, Color f2, float t) => Color.LerpUnclamped(f1, f2, t); + public static Color Lerp(ref Color f1, Color f2, float t) => f1 = Lerp(f1, f2, t); + + + public static float Lerp(float current, float target, float speed, float deltaTime) => Mathf.Lerp(current, target, LerpT(speed, deltaTime)); + public static float Lerp(ref float current, float target, float speed, float deltaTime) => current = Lerp(current, target, speed, deltaTime); + + public static Vector2 Lerp(Vector2 current, Vector2 target, float speed, float deltaTime) => Vector2.Lerp(current, target, LerpT(speed, deltaTime)); + public static Vector2 Lerp(ref Vector2 current, Vector2 target, float speed, float deltaTime) => current = Lerp(current, target, speed, deltaTime); + + public static Vector3 Lerp(Vector3 current, Vector3 target, float speed, float deltaTime) => Vector3.Lerp(current, target, LerpT(speed, deltaTime)); + public static Vector3 Lerp(ref Vector3 current, Vector3 target, float speed, float deltaTime) => current = Lerp(current, target, speed, deltaTime); + + public static float SmoothDamp(float current, float target, float speed, ref float derivative, float deltaTime) => Mathf.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative, float deltaTime) => current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + public static float SmoothDamp(float current, float target, float speed, ref float derivative) => SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative) => current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) => Vector2.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) => current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative) => SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative) => current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) => Vector3.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) => current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative) => SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative) => current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + + + + + + + #endregion + + #region Colors + + + public static Color HSLToRGB(float h, float s, float l) + { + float hue2Rgb(float v1, float v2, float vH) + { + if (vH < 0f) + vH += 1f; + + if (vH > 1f) + vH -= 1f; + + if (6f * vH < 1f) + return v1 + (v2 - v1) * 6f * vH; + + if (2f * vH < 1f) + return v2; + + if (3f * vH < 2f) + return v1 + (v2 - v1) * (2f / 3f - vH) * 6f; + + return v1; + } + + if (s.Approx(0)) return new Color(l, l, l); + + float k1; + + if (l < .5f) + k1 = l * (1f + s); + else + k1 = l + s - s * l; + + + var k2 = 2f * l - k1; + + float r, g, b; + r = hue2Rgb(k2, k1, h + 1f / 3); + g = hue2Rgb(k2, k1, h); + b = hue2Rgb(k2, k1, h - 1f / 3); + + return new Color(r, g, b); + } + public static Color LCHtoRGB(float l, float c, float h) + { + l *= 100; + c *= 100; + h *= 360; + + double xw = 0.948110; + double yw = 1.00000; + double zw = 1.07304; + + float a = c * Mathf.Cos(Mathf.Deg2Rad * h); + float b = c * Mathf.Sin(Mathf.Deg2Rad * h); + + float fy = (l + 16) / 116; + float fx = fy + (a / 500); + float fz = fy - (b / 200); + + float x = (float)System.Math.Round(xw * ((System.Math.Pow(fx, 3) > 0.008856) ? System.Math.Pow(fx, 3) : ((fx - 16 / 116) / 7.787)), 5); + float y = (float)System.Math.Round(yw * ((System.Math.Pow(fy, 3) > 0.008856) ? System.Math.Pow(fy, 3) : ((fy - 16 / 116) / 7.787)), 5); + float z = (float)System.Math.Round(zw * ((System.Math.Pow(fz, 3) > 0.008856) ? System.Math.Pow(fz, 3) : ((fz - 16 / 116) / 7.787)), 5); + + float r = x * 3.2406f - y * 1.5372f - z * 0.4986f; + float g = -x * 0.9689f + y * 1.8758f + z * 0.0415f; + float bValue = x * 0.0557f - y * 0.2040f + z * 1.0570f; + + r = r > 0.0031308f ? 1.055f * (float)System.Math.Pow(r, 1 / 2.4) - 0.055f : r * 12.92f; + g = g > 0.0031308f ? 1.055f * (float)System.Math.Pow(g, 1 / 2.4) - 0.055f : g * 12.92f; + bValue = bValue > 0.0031308f ? 1.055f * (float)System.Math.Pow(bValue, 1 / 2.4) - 0.055f : bValue * 12.92f; + + // r = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, r))); + // g = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, g))); + // bValue = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, bValue))); + + return new Color(r, g, bValue); + + } + + + + public static Color Greyscale(float brightness, float alpha = 1) => new Color(brightness, brightness, brightness, alpha); + + public static Color SetAlpha(this Color color, float alpha) { color.a = alpha; return color; } + + public static Color MultiplyAlpha(this Color color, float k) { color.a *= k; return color; } + + + + + + #endregion + + #region Rects + + + public static Rect Resize(this Rect rect, float px) { rect.x += px; rect.y += px; rect.width -= px * 2; rect.height -= px * 2; return rect; } + + public static Rect SetPos(this Rect rect, Vector2 v) => rect.SetPos(v.x, v.y); + public static Rect SetPos(this Rect rect, float x, float y) { rect.x = x; rect.y = y; return rect; } + + public static Rect SetX(this Rect rect, float x) => rect.SetPos(x, rect.y); + public static Rect SetY(this Rect rect, float y) => rect.SetPos(rect.x, y); + public static Rect SetXMax(this Rect rect, float xMax) { rect.xMax = xMax; return rect; } + public static Rect SetYMax(this Rect rect, float yMax) { rect.yMax = yMax; return rect; } + + public static Rect SetMidPos(this Rect r, Vector2 v) => r.SetPos(v).MoveX(-r.width / 2).MoveY(-r.height / 2); + + public static Rect Move(this Rect rect, Vector2 v) { rect.position += v; return rect; } + public static Rect Move(this Rect rect, float x, float y) { rect.x += x; rect.y += y; return rect; } + public static Rect MoveX(this Rect rect, float px) { rect.x += px; return rect; } + public static Rect MoveY(this Rect rect, float px) { rect.y += px; return rect; } + + public static Rect SetWidth(this Rect rect, float f) { rect.width = f; return rect; } + public static Rect SetWidthFromMid(this Rect rect, float px) { rect.x += rect.width / 2; rect.width = px; rect.x -= rect.width / 2; return rect; } + public static Rect SetWidthFromRight(this Rect rect, float px) { rect.x += rect.width; rect.width = px; rect.x -= rect.width; return rect; } + + public static Rect SetHeight(this Rect rect, float f) { rect.height = f; return rect; } + public static Rect SetHeightFromMid(this Rect rect, float px) { rect.y += rect.height / 2; rect.height = px; rect.y -= rect.height / 2; return rect; } + public static Rect SetHeightFromBottom(this Rect rect, float px) { rect.y += rect.height; rect.height = px; rect.y -= rect.height; return rect; } + + public static Rect AddWidth(this Rect rect, float f) => rect.SetWidth(rect.width + f); + public static Rect AddWidthFromMid(this Rect rect, float f) => rect.SetWidthFromMid(rect.width + f); + public static Rect AddWidthFromRight(this Rect rect, float f) => rect.SetWidthFromRight(rect.width + f); + + public static Rect AddHeight(this Rect rect, float f) => rect.SetHeight(rect.height + f); + public static Rect AddHeightFromMid(this Rect rect, float f) => rect.SetHeightFromMid(rect.height + f); + public static Rect AddHeightFromBottom(this Rect rect, float f) => rect.SetHeightFromBottom(rect.height + f); + + public static Rect SetSize(this Rect rect, Vector2 v) => rect.SetWidth(v.x).SetHeight(v.y); + public static Rect SetSize(this Rect rect, float w, float h) => rect.SetWidth(w).SetHeight(h); + public static Rect SetSize(this Rect rect, float f) { rect.height = rect.width = f; return rect; } + + public static Rect SetSizeFromMid(this Rect r, Vector2 v) => r.Move(r.size / 2).SetSize(v).Move(-v / 2); + public static Rect SetSizeFromMid(this Rect r, float x, float y) => r.SetSizeFromMid(new Vector2(x, y)); + public static Rect SetSizeFromMid(this Rect r, float f) => r.SetSizeFromMid(new Vector2(f, f)); + + public static Rect AlignToPixelGrid(this Rect r) => GUIUtility.AlignRectToDevice(r); + + + + + + #endregion + + #region Vectors + + + public static Vector2 AddX(this Vector2 v, float f) => new Vector2(v.x + f, v.y + 0); + public static Vector2 AddY(this Vector2 v, float f) => new Vector2(v.x + 0, v.y + f); + + public static Vector3 AddX(this Vector3 v, float f) => new Vector3(v.x + f, v.y + 0, v.z + 0); + public static Vector3 AddY(this Vector3 v, float f) => new Vector3(v.x + 0, v.y + f, v.z + 0); + public static Vector3 AddZ(this Vector3 v, float f) => new Vector3(v.x + 0, v.y + 0, v.z + f); + + public static Vector2 xx(this Vector3 v) { return new Vector2(v.x, v.x); } + public static Vector2 xy(this Vector3 v) { return new Vector2(v.x, v.y); } + public static Vector2 xz(this Vector3 v) { return new Vector2(v.x, v.z); } + public static Vector2 yx(this Vector3 v) { return new Vector2(v.y, v.x); } + public static Vector2 yy(this Vector3 v) { return new Vector2(v.y, v.y); } + public static Vector2 yz(this Vector3 v) { return new Vector2(v.y, v.z); } + public static Vector2 zx(this Vector3 v) { return new Vector2(v.z, v.x); } + public static Vector2 zy(this Vector3 v) { return new Vector2(v.z, v.y); } + public static Vector2 zz(this Vector3 v) { return new Vector2(v.z, v.z); } + + + + + + #endregion + + #region Compute + + + [System.Serializable] + public class GaussianKernel + { + public GaussianKernel(bool isEvenSize = false, int radius = 7, float sharpness = .5f) + { + this.isEvenSize = isEvenSize; + this.radius = radius; + this.sharpness = sharpness; + } + + public bool isEvenSize = false; + + public int radius = 7; + public float sharpness = .5f; + + public int size => radius * 2 + (isEvenSize ? 0 : 1); + public float sigma => 1 - Mathf.Pow(sharpness, .1f) * .99999f; + + public float[,] Array2d() + { + float[,] kr = new float[size, size]; + + if (size == 1) { kr[0, 0] = 1; return kr; } + + var a = -2f * radius * radius / Mathf.Log(sigma); + var sum = 0f; + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + var rX = size % 2 == 1 ? (x - radius) : (x - radius) + .5f; + var rY = size % 2 == 1 ? (y - radius) : (y - radius) + .5f; + var dist = Mathf.Sqrt(rX * rX + rY * rY); + kr[x, y] = Mathf.Exp(-dist * dist / a); + sum += kr[x, y]; + } + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + kr[x, y] /= sum; + + return kr; + } + public float[] ArrayFlat() + { + var gk = Array2d(); + float[] flat = new float[size * size]; + + for (int i = 0; i < size; i++) + for (int j = 0; j < size; j++) + flat[(i * size + j)] = gk[i, j]; + + return flat; + } + } + + + + + + #endregion + + #region Objects + + + public static Object[] FindObjects(Type type) + { +#if UNITY_2023_1_OR_NEWER + return Object.FindObjectsByType(type, FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(type); +#endif + } + public static T[] FindObjects() where T : Object + { +#if UNITY_2023_1_OR_NEWER + return Object.FindObjectsByType(FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(); +#endif + } + + public static void Destroy(this Object r) + { + if (Application.isPlaying) + Object.Destroy(r); + else + Object.DestroyImmediate(r); + + } + + public static void DestroyImmediate(this Object o) => Object.DestroyImmediate(o); + + + + + + #endregion + + #region GlobalID + +#if UNITY_EDITOR + + [System.Serializable] + public struct GlobalID : System.IEquatable + { + public Object GetObject() => GlobalObjectId.GlobalObjectIdentifierToObjectSlow(globalObjectId); + public int GetObjectInstanceId() => GlobalObjectId.GlobalObjectIdentifierToInstanceIDSlow(globalObjectId); + + + public string guid => globalObjectId.assetGUID.ToString(); + public ulong fileId => globalObjectId.targetObjectId; + + public bool isNull => globalObjectId.identifierType == 0; + public bool isAsset => globalObjectId.identifierType == 1; + public bool isSceneObject => globalObjectId.identifierType == 2; + + public GlobalObjectId globalObjectId => _globalObjectId.Equals(default) && globalObjectIdString != null && GlobalObjectId.TryParse(globalObjectIdString, out var r) ? _globalObjectId = r : _globalObjectId; + public GlobalObjectId _globalObjectId; + + public GlobalID(Object o) => globalObjectIdString = (_globalObjectId = GlobalObjectId.GetGlobalObjectIdSlow(o)).ToString(); + public GlobalID(string s) => globalObjectIdString = GlobalObjectId.TryParse(s, out _globalObjectId) ? s : s; + + public string globalObjectIdString; + + + + public bool Equals(GlobalID other) => this.globalObjectIdString.Equals(other.globalObjectIdString); + + public static bool operator ==(GlobalID a, GlobalID b) => a.Equals(b); + public static bool operator !=(GlobalID a, GlobalID b) => !a.Equals(b); + + public override bool Equals(object other) => other is GlobalID otherglobalID && this.Equals(otherglobalID); + public override int GetHashCode() => globalObjectIdString == null ? 0 : globalObjectIdString.GetHashCode(); + + + public override string ToString() => globalObjectIdString; + + } + + public static GlobalID GetGlobalID(this Object o) => new GlobalID(o); + + public static int[] GetObjectInstanceIds(this IEnumerable globalIDs) + { + var goids = globalIDs.Select(r => r.globalObjectId).ToArray(); + + var iids = new int[goids.Length]; + + GlobalObjectId.GlobalObjectIdentifiersToInstanceIDsSlow(goids, iids); + + return iids; + } + + +#endif + + + + + #endregion + + #region Paths + + + public static string GetParentPath(this string path) => path.Substring(0, path.LastIndexOf('/')); + public static bool HasParentPath(this string path) => path.Contains('/') && path.GetParentPath() != ""; + + public static string ToGlobalPath(this string localPath) => Application.dataPath + "/" + localPath.Substring(0, localPath.Length - 1); + public static string ToLocalPath(this string globalPath) => "Assets" + globalPath.Remove(Application.dataPath); + + + + public static string CombinePath(this string p, string p2) => Path.Combine(p, p2); + + public static bool IsSubpathOf(this string path, string of) => path.StartsWith(of + "/") || of == ""; + + public static string GetDirectory(this string pathOrDirectory) + { + var directory = pathOrDirectory.Contains('.') ? pathOrDirectory.Substring(0, pathOrDirectory.LastIndexOf('/')) : pathOrDirectory; + + if (directory.Contains('.')) + directory = directory.Substring(0, directory.LastIndexOf('/')); + + return directory; + + } + + public static bool DirectoryExists(this string pathOrDirectory) => Directory.Exists(pathOrDirectory.GetDirectory()); + + public static string EnsureDirExists(this string pathOrDirectory) // todo to EnsureDirectoryExists + { + var directory = pathOrDirectory.GetDirectory(); + + if (directory.HasParentPath() && !Directory.Exists(directory.GetParentPath())) + EnsureDirExists(directory.GetParentPath()); + + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + return pathOrDirectory; + + } + + + + public static string ClearDir(this string dir) + { + if (!Directory.Exists(dir)) return dir; + + var diri = new DirectoryInfo(dir); + foreach (var r in diri.EnumerateFiles()) r.Delete(); + foreach (var r in diri.EnumerateDirectories()) r.Delete(true); + + return dir; + } + + + + + + +#if UNITY_EDITOR + + public static string EnsurePathIsUnique(this string path) + { + if (!path.DirectoryExists()) return path; + + var s = AssetDatabase.GenerateUniqueAssetPath(path); // returns empty if parent dir doesnt exist + + return s == "" ? path : s; + + } + + public static void EnsureDirExistsAndRevealInFinder(string dir) + { + EnsureDirExists(dir); + UnityEditor.EditorUtility.OpenWithDefaultApp(dir); + } + +#endif + + + + #endregion + + #region AssetDatabase + +#if UNITY_EDITOR + + public static AssetImporter GetImporter(this Object t) => AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(t)); + + public static string ToPath(this string guid) => AssetDatabase.GUIDToAssetPath(guid); // returns empty string if not found + public static List ToPaths(this IEnumerable guids) => guids.Select(r => r.ToPath()).ToList(); + + public static string GetFilename(this string path, bool withExtension = false) => withExtension ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path); // prev GetName + public static string GetExtension(this string path) => Path.GetExtension(path); + + + public static string ToGuid(this string pathInProject) => AssetDatabase.AssetPathToGUID(pathInProject); + public static List ToGuids(this IEnumerable pathsInProject) => pathsInProject.Select(r => r.ToGuid()).ToList(); + + public static string GetPath(this Object o) => AssetDatabase.GetAssetPath(o); + public static string GetGuid(this Object o) => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o)); + + public static string GetScriptPath(string scriptName) => AssetDatabase.FindAssets("t: script " + scriptName, null).FirstOrDefault()?.ToPath() ?? "scirpt not found"; + + + public static bool IsValidGuid(this string guid) => AssetDatabase.AssetPathToGUID(AssetDatabase.GUIDToAssetPath(guid), AssetPathToGUIDOptions.OnlyExistingAssets) != ""; + + + + // toremove + public static Object LoadGuid(this string guid) => AssetDatabase.LoadAssetAtPath(guid.ToPath(), typeof(Object)); + public static T LoadGuid(this string guid) where T : Object => AssetDatabase.LoadAssetAtPath(guid.ToPath()); + + + + + public static List FindAllAssetsOfType_guids(Type type) => AssetDatabase.FindAssets("t:" + type.Name).ToList(); + public static List FindAllAssetsOfType_guids(Type type, string path) => AssetDatabase.FindAssets("t:" + type.Name, new[] { path }).ToList(); + public static List FindAllAssetsOfType() where T : Object => FindAllAssetsOfType_guids(typeof(T)).Select(r => (T)r.LoadGuid()).ToList(); + public static List FindAllAssetsOfType(string path) where T : Object => FindAllAssetsOfType_guids(typeof(T), path).Select(r => (T)r.LoadGuid()).ToList(); + + public static T Reimport(this T t) where T : Object { AssetDatabase.ImportAsset(t.GetPath(), ImportAssetOptions.ForceUpdate); return t; } + +#endif + + + + + + #endregion + + #region Serialization + + [System.Serializable] + public class SerializableDictionary : Dictionary, ISerializationCallbackReceiver + { + [SerializeField] List keys = new List(); + [SerializeField] List values = new List(); + + public void OnBeforeSerialize() + { + keys.Clear(); + values.Clear(); + + foreach (KeyValuePair kvp in this) + { + keys.Add(kvp.Key); + values.Add(kvp.Value); + } + + } + public void OnAfterDeserialize() + { + this.Clear(); + + for (int i = 0; i < keys.Count; i++) + this[keys[i]] = values[i]; + + } + + } + + + #endregion + + #region Editor + +#if UNITY_EDITOR + + public static void ToggleDefineDisabledInScript(Type scriptType) + { + var path = GetScriptPath(scriptType.Name); + + var lines = File.ReadAllLines(path); + if (lines.First().StartsWith("#define DISABLED")) + File.WriteAllLines(path, lines.Skip(1)); + else + File.WriteAllLines(path, lines.Prepend("#define DISABLED // this line was added by VUtils.ToggleDefineDisabledInScript")); + + AssetDatabase.ImportAsset(path); + } + public static bool ScriptHasDefineDisabled(Type scriptType) => File.ReadLines(GetScriptPath(scriptType.Name)).First().StartsWith("#define DISABLED"); + public static void SetDefineDisabledInScript(Type scriptType, bool defineDisabled) + { + if (ScriptHasDefineDisabled(scriptType) != defineDisabled) + ToggleDefineDisabledInScript(scriptType); + + } + + public static int GetProjectId() => Application.dataPath.GetHashCode(); + + public static void PingObject(Object o, bool select = false, bool focusProjectWindow = true) + { + if (select) + { + Selection.activeObject = null; + Selection.activeObject = o; + } + if (focusProjectWindow) EditorUtility.FocusProjectWindow(); + EditorGUIUtility.PingObject(o); + + } + public static void PingObject(string guid, bool select = false, bool focusProjectWindow = true) => PingObject(AssetDatabase.LoadAssetAtPath(guid.ToPath())); + + + public static void OpenFolder(string path) + { + var folder = AssetDatabase.LoadAssetAtPath(path, typeof(Object)); + + var t = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser"); + var w = (EditorWindow)t.GetField("s_LastInteractedProjectBrowser").GetValue(null); + + var m_ListAreaState = t.GetField("m_ListAreaState", maxBindingFlags).GetValue(w); + + m_ListAreaState.GetType().GetField("m_SelectedInstanceIDs").SetValue(m_ListAreaState, new List { folder.GetInstanceID() }); + + t.GetMethod("OpenSelectedFolders", maxBindingFlags).Invoke(null, null); + + } + + public static void Dirty(this Object o) => UnityEditor.EditorUtility.SetDirty(o); + public static void Save(this Object o) => AssetDatabase.SaveAssetIfDirty(o); + public static void RecordUndo(this Object o) => Undo.RecordObject(o, ""); + + + public static EditorWindow OpenObjectPicker(Object obj = null, bool allowSceneObjects = false, string searchFilter = "", int controlID = 0) where T : Object + { + EditorGUIUtility.ShowObjectPicker(obj, allowSceneObjects, searchFilter, controlID); + + return Resources.FindObjectsOfTypeAll(typeof(Editor).Assembly.GetType("UnityEditor.ObjectSelector")).FirstOrDefault() as EditorWindow; + + } + public static EditorWindow OpenColorPicker(System.Action colorChangedCallback, Color color, bool showAlpha = true, bool hdr = false) + { + typeof(Editor).Assembly.GetType("UnityEditor.ColorPicker").InvokeMethod("Show", colorChangedCallback, color, showAlpha, hdr); + + return typeof(Editor).Assembly.GetType("UnityEditor.ColorPicker").GetPropertyValue("instance"); + + } + + public static void MoveTo(this EditorWindow window, Vector2 position, bool ensureFitsOnScreen = true) + { + if (!ensureFitsOnScreen) { window.position = window.position.SetPos(position); return; } + + var windowRect = window.position; + var unityWindowRect = EditorGUIUtility.GetMainWindowPosition(); + + position.x = position.x.Max(unityWindowRect.position.x); + position.y = position.y.Max(unityWindowRect.position.y); + + position.x = position.x.Min(unityWindowRect.xMax - windowRect.width); + position.y = position.y.Min(unityWindowRect.yMax - windowRect.height); + + window.position = windowRect.SetPos(position); + + } + + + + public static void RemoveEditorErrors() => removeEditorErrorsMethod.Invoke(null, new object[] { 1 }); + static MethodInfo removeEditorErrorsMethod = System.AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(r => r.GetName().ToString().Contains("UnityEditor.CoreModule")).GetTypes().First(r => r.Name.Contains("LogEntry")).GetMethod("RemoveLogEntriesByMode", BindingFlags.Static | BindingFlags.NonPublic); + + + + public static class EditorPrefsCached + { + public static bool GetBool(string key, bool defaultValue = false) + { + if (bools_byKey.ContainsKey(key)) + return bools_byKey[key]; + else + return bools_byKey[key] = EditorPrefs.GetBool(key, defaultValue); + + } + public static float GetFloat(string key, float defaultValue = 0) + { + if (floats_byKey.ContainsKey(key)) + return floats_byKey[key]; + else + return floats_byKey[key] = EditorPrefs.GetFloat(key, defaultValue); + + } + public static int GetInt(string key, int defaultValue = 0) + { + if (ints_byKey.ContainsKey(key)) + return ints_byKey[key]; + else + return ints_byKey[key] = EditorPrefs.GetInt(key, defaultValue); + + } + public static string GetString(string key, string defaultValue = "") + { + if (strings_byKey.ContainsKey(key)) + return strings_byKey[key]; + else + return strings_byKey[key] = EditorPrefs.GetString(key, defaultValue); + + } + + + public static void SetBool(string key, bool value) + { + bools_byKey[key] = value; + + EditorPrefs.SetBool(key, value); + + } + public static void SetFloat(string key, float value) + { + floats_byKey[key] = value; + + EditorPrefs.SetFloat(key, value); + + } + public static void SetInt(string key, int value) + { + ints_byKey[key] = value; + + EditorPrefs.SetInt(key, value); + + } + public static void SetString(string key, string value) + { + strings_byKey[key] = value; + + EditorPrefs.SetString(key, value); + + } + + + static Dictionary bools_byKey = new(); + static Dictionary floats_byKey = new(); + static Dictionary ints_byKey = new(); + static Dictionary strings_byKey = new(); + + } + + +#endif + + + + + + #endregion + + } + + public static partial class VGUI + { + #region Colors + + public static class GUIColors + { + public static Color windowBackground => isDarkTheme ? Greyscale(.22f) : Greyscale(.78f); // prev backgroundCol + public static Color pressedButtonBackground => isDarkTheme ? new Color(.48f, .76f, 1f, 1f) * 1.4f : new Color(.48f, .7f, 1f, 1f) * 1.2f; // prev pressedButtonCol + public static Color greyedOutTint => Greyscale(.7f); + public static Color selectedBackground => isDarkTheme ? new Color(.17f, .365f, .535f) : new Color(.2f, .375f, .555f) * 1.2f; + } + + + #endregion + + #region Shortcuts + + public static Rect lastRect => GUILayoutUtility.GetLastRect(); + + public static bool isDarkTheme => EditorGUIUtility.isProSkin; + + public static float GetLabelWidth(this string s) => GUI.skin.label.CalcSize(new GUIContent(s)).x; + public static float GetLabelWidth(this string s, int fotSize) + { + SetLabelFontSize(fotSize); + + var r = s.GetLabelWidth(); + + ResetLabelStyle(); + + return r; + + } + public static float GetLabelWidth(this string s, bool isBold) + { + if (isBold) + SetLabelBold(); + + var r = s.GetLabelWidth(); + + if (isBold) + ResetLabelStyle(); + + return r; + + } + + public static void SetGUIEnabled(bool enabled) { _prevGuiEnabled = GUI.enabled; GUI.enabled = enabled; } + public static void ResetGUIEnabled() => GUI.enabled = _prevGuiEnabled; + static bool _prevGuiEnabled = true; + + public static void SetLabelFontSize(int size) => GUI.skin.label.fontSize = size; + public static void SetLabelBold() => GUI.skin.label.fontStyle = FontStyle.Bold; + public static void SetLabelAlignmentCenter() => GUI.skin.label.alignment = TextAnchor.MiddleCenter; + public static void ResetLabelStyle() + { + GUI.skin.label.fontSize = 0; + GUI.skin.label.fontStyle = FontStyle.Normal; + GUI.skin.label.alignment = TextAnchor.MiddleLeft; + } + + + public static void SetGUIColor(Color c) + { + if (!_guiColorModified) + _defaultGuiColor = GUI.color; + + _guiColorModified = true; + + GUI.color = _defaultGuiColor * c; + + } + public static void ResetGUIColor() + { + GUI.color = _guiColorModified ? _defaultGuiColor : Color.white; + + _guiColorModified = false; + + } + static bool _guiColorModified; + static Color _defaultGuiColor; + + + + #endregion + + #region Events + + + public class WrappedEvent + { + public Event e; + + public bool isRepaint => e.type == EventType.Repaint; + public bool isLayout => e.type == EventType.Layout; + public bool isUsed => e.type == EventType.Used; + public bool isMouseLeaveWindow => e.type == EventType.MouseLeaveWindow; + public bool isMouseEnterWindow => e.type == EventType.MouseEnterWindow; + public bool isContextClick => e.type == EventType.ContextClick; + + public bool isKeyDown => e.type == EventType.KeyDown; + public bool isKeyUp => e.type == EventType.KeyUp; + public KeyCode keyCode => e.keyCode; + public char characted => e.character; + + public bool isExecuteCommand => e.type == EventType.ExecuteCommand; + public string commandName => e.commandName; + + public bool isMouse => e.isMouse; + public bool isMouseDown => e.type == EventType.MouseDown; + public bool isMouseUp => e.type == EventType.MouseUp; + public bool isMouseDrag => e.type == EventType.MouseDrag; + public bool isMouseMove => e.type == EventType.MouseMove; + public bool isScroll => e.type == EventType.ScrollWheel; + public int mouseButton => e.button; + public int clickCount => e.clickCount; + public Vector2 mousePosition => e.mousePosition; + public Vector2 mousePosition_screenSpace => GUIUtility.GUIToScreenPoint(e.mousePosition); + public Vector2 mouseDelta => e.delta; + + public bool isDragUpdate => e.type == EventType.DragUpdated; + public bool isDragPerform => e.type == EventType.DragPerform; + public bool isDragExit => e.type == EventType.DragExited; + + public EventModifiers modifiers => e.modifiers; + public bool holdingAnyModifierKey => modifiers != EventModifiers.None; + + public bool holdingAlt => e.alt; + public bool holdingShift => e.shift; + public bool holdingCtrl => e.control; + public bool holdingCmd => e.command; + public bool holdingCmdOrCtrl => e.command || e.control; + + public bool holdingAltOnly => e.modifiers == EventModifiers.Alt; // in some sessions FunctionKey is always pressed? + public bool holdingShiftOnly => e.modifiers == EventModifiers.Shift; // in some sessions FunctionKey is always pressed? + public bool holdingCtrlOnly => e.modifiers == EventModifiers.Control; + public bool holdingCmdOnly => e.modifiers == EventModifiers.Command; + public bool holdingCmdOrCtrlOnly => (e.modifiers == EventModifiers.Command || e.modifiers == EventModifiers.Control); + + public EventType type => e.type; + + public void Use() => e?.Use(); + + + public WrappedEvent(Event e) => this.e = e; + + public override string ToString() => e.ToString(); + + } + + public static WrappedEvent Wrap(this Event e) => new(e); + + public static WrappedEvent curEvent => _curEvent ??= typeof(Event).GetFieldValue("s_Current").Wrap(); + static WrappedEvent _curEvent; + + + + + + #endregion + + #region Layout + + + + public static void BeginPanel(string title, float height, System.Action onClose = null, System.Action onApply = null) + { + + void bg() + { + GUI.enabled = false; + SetGUIColor(Greyscale(.75f)); + var r = ExpandWidthLabelRect(0).SetHeight(height); + GUI.Button(r, ""); + GUI.Button(r, ""); + GUI.Button(r, ""); + GUI.Button(r, ""); + GUI.Button(r, ""); + GUI.Button(r, ""); + GUI.Button(r, ""); + GUI.Button(r, ""); + ResetGUIColor(); + GUI.enabled = true; + } + + void layout() + { + + GUILayout.BeginHorizontal(); + Space(7); + EditorGUIUtility.labelWidth -= 7; + GUILayout.BeginVertical(); + + } + + void title_() + { + Space(5); + EditorGUI.PrefixLabel(ExpandWidthLabelRect(), new GUIContent(title)); + // GUI.skin.label.fontStyle = FontStyle.Bold; + // GUILayout.Label(title); + + } + + void buttons() + { + var rClose = lastRect.SetWidthFromRight(16).MoveX(1).MoveY(-1); + + // if() + GUI.color = Greyscale(rClose.IsHovered() ? .9f : .6f); + GUI.Label(rClose, EditorGUIUtility.IconContent("CrossIcon")); + + GUI.color = Color.clear; + if (GUI.Button(rClose, "")) + onClose?.Invoke(); + + + var rApply = rClose.MoveX(-19).Resize(-1); + + GUI.color = Greyscale(rApply.IsHovered() ? .9f : .6f); + GUI.Label(rApply, EditorGUIUtility.IconContent("check")); + + GUI.color = Color.clear; + if (GUI.Button(rApply, "")) + onApply?.Invoke(); + + + GUI.color = Color.white; + + } + + + + bg(); + layout(); + title_(); + buttons(); + + Space(5); + + } + + public static void EndPanel() + { + GUILayout.EndVertical(); + Space(7); + EditorGUIUtility.labelWidth = 0; + GUILayout.EndHorizontal(); + } + + + + public static void BeginIndent(float f) + { + GUILayout.BeginHorizontal(); + GUILayout.Space(f); + GUILayout.BeginVertical(); + + _indentLabelWidthStack.Push(EditorGUIUtility.labelWidth); + + EditorGUIUtility.labelWidth -= f; + } + + public static void EndIndent(float f = 0) + { + GUILayout.EndVertical(); + GUILayout.Space(f); + GUILayout.EndHorizontal(); + + EditorGUIUtility.labelWidth = _indentLabelWidthStack.Pop(); + } + static Stack _indentLabelWidthStack = new Stack(); + + + + + #endregion + + #region Drawing + + public static Rect Draw(this Rect r) { EditorGUI.DrawRect(r, Color.black); return r; } + public static Rect Draw(this Rect r, Color c) { EditorGUI.DrawRect(r, c); return r; } + + + + public static Rect DrawWithRoundedCorners(this Rect rect, Color color, int cornerRadius) + { + if (!curEvent.isRepaint) return rect; + + cornerRadius = cornerRadius.Min((rect.height / 2).FloorToInt()).Min((rect.width / 2).FloorToInt()); + + GUIStyle style; + + void getStyle() + { + if (_roundedStylesByCornerRadius.TryGetValue(cornerRadius, out style)) return; + + var pixelsPerPoint = 2; + + var res = cornerRadius * 2 * pixelsPerPoint; + var pixels = new Color[res * res]; + + var white = Greyscale(1, 1); + var clear = Greyscale(1, 0); + var halfRes = res / 2; + + for (int x = 0; x < res; x++) + for (int y = 0; y < res; y++) + { + var sqrMagnitude = (new Vector2(x - halfRes + .5f, y - halfRes + .5f)).sqrMagnitude; + pixels[x + y * res] = sqrMagnitude <= halfRes * halfRes ? white : clear; + } + + var texture = new Texture2D(res, res); + texture.SetPropertyValue("pixelsPerPoint", pixelsPerPoint); + texture.hideFlags = HideFlags.DontSave; + texture.SetPixels(pixels); + texture.Apply(); + + + + style = new GUIStyle(); + style.normal.background = texture; + style.alignment = TextAnchor.MiddleCenter; + style.border = new RectOffset(cornerRadius, cornerRadius, cornerRadius, cornerRadius); + + + _roundedStylesByCornerRadius[cornerRadius] = style; + + } + void draw() + { + SetGUIColor(color); + + style.Draw(rect, false, false, false, false); + + ResetGUIColor(); + + } + + getStyle(); + draw(); + + return rect; + + } + public static Rect DrawWithRoundedCorners(this Rect rect, Color color, float cornerRadius) => rect.DrawWithRoundedCorners(color, cornerRadius.RoundToInt()); + static Dictionary _roundedStylesByCornerRadius = new Dictionary(); + + + public static Rect DrawBlurred(this Rect rect, Color color, int blurRadius) + { + if (!curEvent.isRepaint) return rect; + + var pixelsPerPoint = .5f; + // var pixelsPerPoint = 1f; + + var blurRadiusScaled = (blurRadius * pixelsPerPoint).RoundToInt().Max(1).Min(123); + + var croppedRectWidth = (rect.width * pixelsPerPoint).RoundToInt().Min(blurRadiusScaled * 2); + var croppedRectHeight = (rect.height * pixelsPerPoint).RoundToInt().Min(blurRadiusScaled * 2); + + var textureWidth = croppedRectWidth + blurRadiusScaled * 2; + var textureHeight = croppedRectHeight + blurRadiusScaled * 2; + + GUIStyle style; + + void getStyle() + { + if (_blurredStylesByTextureSize.TryGetValue((textureWidth, textureHeight), out style)) return; + + // VDebug.LogStart(blurRadius + ""); + + var pixels = new Color[textureWidth * textureHeight]; + var kernel = new GaussianKernel(false, blurRadiusScaled).Array2d(); + + for (int x = 0; x < textureWidth; x++) + for (int y = 0; y < textureHeight; y++) + { + var sum = 0f; + + for (int xSample = (x - blurRadiusScaled).Max(blurRadiusScaled); xSample <= (x + blurRadiusScaled).Min(textureWidth - 1 - blurRadiusScaled); xSample++) + for (int ySample = (y - blurRadiusScaled).Max(blurRadiusScaled); ySample <= (y + blurRadiusScaled).Min(textureHeight - 1 - blurRadiusScaled); ySample++) + sum += kernel[blurRadiusScaled + xSample - x, blurRadiusScaled + ySample - y]; + + pixels[x + y * textureWidth] = Greyscale(1, sum); + + } + + var texture = new Texture2D(textureWidth, textureHeight); + texture.SetPropertyValue("pixelsPerPoint", pixelsPerPoint); + texture.hideFlags = HideFlags.DontSave; + texture.SetPixels(pixels); + texture.Apply(); + + + style = new GUIStyle(); + style.normal.background = texture; + style.alignment = TextAnchor.MiddleCenter; + + var borderX = ((textureWidth / 2f - 1) / pixelsPerPoint).FloorToInt(); + var borderY = ((textureHeight / 2f - 1) / pixelsPerPoint).FloorToInt(); + style.border = new RectOffset(borderX, borderX, borderY, borderY); + + _blurredStylesByTextureSize[(textureWidth, textureHeight)] = style; + + // VDebug.LogFinish(); + + } + void draw() + { + SetGUIColor(color); + + style.Draw(rect.SetSizeFromMid(rect.width + blurRadius * 2, rect.height + blurRadius * 2), false, false, false, false); + + ResetGUIColor(); + + } + + getStyle(); + draw(); + + return rect; + + } + public static Rect DrawBlurred(this Rect rect, Color color, float blurRadius) => rect.DrawBlurred(color, blurRadius.RoundToInt()); + static Dictionary<(int, int), GUIStyle> _blurredStylesByTextureSize = new Dictionary<(int, int), GUIStyle>(); + + + static void DrawCurtain(this Rect rect, Color color, int dir) + { + void genTextures() + { + if (_gradientTextures != null) return; + + _gradientTextures = new Texture2D[4]; + + // var pixels = Enumerable.Range(0, 256).Select(r => Greyscale(1, r / 255f)); + var pixels = Enumerable.Range(0, 256).Select(r => Greyscale(1, (r / 255f).Smoothstep())); + + var up = new Texture2D(1, 256); + up.SetPixels(pixels.Reverse().ToArray()); + up.Apply(); + up.hideFlags = HideFlags.DontSave; + up.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[0] = up; + + var down = new Texture2D(1, 256); + down.SetPixels(pixels.ToArray()); + down.Apply(); + down.hideFlags = HideFlags.DontSave; + down.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[1] = down; + + var left = new Texture2D(256, 1); + left.SetPixels(pixels.ToArray()); + left.Apply(); + left.hideFlags = HideFlags.DontSave; + left.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[2] = left; + + var right = new Texture2D(256, 1); + right.SetPixels(pixels.Reverse().ToArray()); + right.Apply(); + right.hideFlags = HideFlags.DontSave; + right.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[3] = right; + + } + void draw() + { + SetGUIColor(color); + + GUI.DrawTexture(rect, _gradientTextures[dir]); + + ResetGUIColor(); + + } + + genTextures(); + draw(); + + } + public static void DrawCurtainUp(this Rect rect, Color color) => rect.DrawCurtain(color, 0); + public static void DrawCurtainDown(this Rect rect, Color color) => rect.DrawCurtain(color, 1); + public static void DrawCurtainLeft(this Rect rect, Color color) => rect.DrawCurtain(color, 2); + public static void DrawCurtainRight(this Rect rect, Color color) => rect.DrawCurtain(color, 3); + static Texture2D[] _gradientTextures; + + + + public static bool IsHovered(this Rect r) => r.Contains(curEvent.mousePosition); + + #endregion + + #region Spacing + + public static void Space(float px = 6) => GUILayout.Space(px); + + public static void Divider(float space = 15, float yOffset = 0) + { + GUILayout.Label("", GUILayout.Height(space), GUILayout.ExpandWidth(true)); + lastRect.SetHeightFromMid(1).SetWidthFromMid(lastRect.width - 16).MoveY(yOffset).Draw(isDarkTheme ? Color.white * .42f : Color.white * .72f); + } + + public static Rect ExpandSpace() { GUILayout.Label("", GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true)); return lastRect; } + + public static Rect ExpandWidthLabelRect() { GUILayout.Label(""/* , GUILayout.Height(0) */, GUILayout.ExpandWidth(true)); return lastRect; } + public static Rect ExpandWidthLabelRect(float height) { GUILayout.Label("", GUILayout.Height(height), GUILayout.ExpandWidth(true)); return lastRect; } + + + #endregion + + #region Icons + + public static class EditorIcons + { + public static Texture2D GetIcon(string iconNameOrPath) + { + if (icons_byName.TryGetValue(iconNameOrPath, out var cachedResult)) return cachedResult; + + var icon = typeof(EditorGUIUtility).InvokeMethod("LoadIcon", iconNameOrPath) as Texture2D; + + return icons_byName[iconNameOrPath] = icon; + + } + + static Dictionary icons_byName = new Dictionary(); + } + + + + + // toremove: + + public static void DrawIcon(Rect rect, string icon, Color? col = null) + { + if (icon == "") return; + Texture2D tex = (Texture2D)EditorGUIUtility.FindTexture(icon); + if (!tex) tex = (Texture2D)EditorGUIUtility.Load(icon); + if (!tex) tex = (Texture2D)Resources.Load(icon); + DrawIcon(rect, tex, col); + } + public static void DrawIcon(Rect rect, string icon, bool greyedOut) => DrawIcon(rect, icon, greyedOut ? GUIColors.greyedOutTint : (Color?)null); + + public static void DrawIcon(Rect rect, Texture2D tex, Color? col = null) + { + var color = Color.white; + if (col != null) color = col.GetValueOrDefault(); + + GUI.DrawTexture(rect, tex, ScaleMode.ScaleToFit, true, 0, color, 0, 0); + } + public static void DrawIcon(Rect rect, Texture2D tex, bool greyedOut) => DrawIcon(rect, tex, greyedOut ? GUIColors.greyedOutTint : (Color?)null); + + public static void DrawIconForGuid(Rect rect, string guid, bool greyedOut = false) + { + if (!guid.IsValidGuid()) return; + + var type = AssetDatabase.GetMainAssetTypeAtPath(guid.ToPath()); + + if (AssetDatabase.IsValidFolder(guid.ToPath())) + DrawIcon(rect, "Folder Icon", greyedOut); + + else if (guid.ToPath().GetExtension() == ".cs") + DrawIcon(rect, "cs Script Icon", greyedOut); + + else DrawIcon(rect, AssetPreview.GetMiniTypeThumbnail(type), greyedOut); + } + + // public static void DrawIcon(string icon, bool greyedOut = false, bool pressed = false) => DrawIcon(lastRect, icon, greyedOut, pressed); + // public static void DrawIcon(Rect rect, string icon, bool greyedOut = false, bool pressed = false) + // { + // if (icon == "") return; + // Texture2D tex = (Texture2D)EditorGUIUtility.FindTexture(icon); + // if (!tex) tex = (Texture2D)EditorGUIUtility.Load(icon); + // if (!tex) tex = (Texture2D)Resources.Load(icon); + // DrawIcon(rect, tex, greyedOut, pressed); + // } + // public static void DrawIcon(Rect rect, GUIContent tex, bool greyedOut = false) //some icons get fucked up if drawn differently + // { + // var r = GUI.enabled; + // if (greyedOut) GUI.enabled = false; + // var s = new GUIStyle(); + // GUI.Label(rect.Resize(0), tex, s); + // GUI.enabled = r; + // } + // public static void DrawIcon(Rect rect, Texture2D tex, bool greyedOut = false, bool pressed = false) + // { + // var col = Color.white; + // if (greyedOut) col *= greyedOutColor; + // if (pressed) col *= pressedCol; + // GUI.DrawTexture(rect, tex, ScaleMode.ScaleToFit, true, 0, col, 0, 0); + // } + // public static void DrawIcon(Rect rect, string icon, Color col) => DrawIcon(lastRect, EditorGUIUtility.FindTexture(icon), col); + // public static void DrawIcon(Rect rect, Texture2D tex, Color col) => GUI.DrawTexture(rect, tex, ScaleMode.ScaleToFit, true, 0, col, 0, 0); + + + + #endregion + + #region Other + + public static void MarkInteractive(this Rect rect) + { + if (!curEvent.isRepaint) return; + + var unclippedRect = (Rect)_mi_GUIClip_UnclipToWindow.Invoke(null, new object[] { rect }); + + var curGuiView = _pi_GUIView_current.GetValue(null); + + _mi_GUIView_MarkHotRegion.Invoke(curGuiView, new object[] { unclippedRect }); + + } + static PropertyInfo _pi_GUIView_current = typeof(Editor).Assembly.GetType("UnityEditor.GUIView").GetProperty("current", maxBindingFlags); + static MethodInfo _mi_GUIView_MarkHotRegion = typeof(Editor).Assembly.GetType("UnityEditor.GUIView").GetMethod("MarkHotRegion", maxBindingFlags); + static MethodInfo _mi_GUIClip_UnclipToWindow = typeof(GUI).Assembly.GetType("UnityEngine.GUIClip").GetMethod("UnclipToWindow", maxBindingFlags, null, new[] { typeof(Rect) }, null); + + + + #endregion + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesLibs.cs.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesLibs.cs.meta new file mode 100644 index 0000000..a0d7507 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesLibs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 38092f82a4c054086826261d88277942 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenu.cs b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenu.cs new file mode 100644 index 0000000..ba14769 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenu.cs @@ -0,0 +1,140 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEditor; +using static VFavorites.Libs.VUtils; +using static VFavorites.Libs.VGUI; + + + +namespace VFavorites +{ + public class VFavoritesMenu + { + + public static bool pageScrollEnabled { get => EditorPrefsCached.GetBool("vFavorites-pageScrollEnabled", true); set => EditorPrefsCached.SetBool("vFavorites-pageScrollEnabled", value); } + public static bool numberKeysEnabled { get => EditorPrefsCached.GetBool("vFavorites-numberKeysEnabled", true); set => EditorPrefsCached.SetBool("vFavorites-numberKeysEnabled", value); } + public static bool arrowKeysEnabled { get => EditorPrefsCached.GetBool("vFavorites-arrowKeysEnabled", true); set => EditorPrefsCached.SetBool("vFavorites-arrowKeysEnabled", value); } + + public static bool fadeAnimationsEnabled { get => EditorPrefsCached.GetBool("vFavorites-fadeAnimationsEnabled", true); set => EditorPrefsCached.SetBool("vFavorites-fadeAnimationsEnabled", value); } + public static bool pageScrollAnimationEnabled { get => EditorPrefsCached.GetBool("vFavorites-pageScrollAnimationEnabled", true); set => EditorPrefsCached.SetBool("vFavorites-pageScrollAnimationEnabled", value); } + + public static int activeOnKeyCombination { get => EditorPrefsCached.GetInt("vFavorites-activeOnKeyCombination", 0); set => EditorPrefsCached.SetInt("vFavorites-activeOnKeyCombination", value); } + public static bool activeOnAltEnabled { get => activeOnKeyCombination == 0; set => activeOnKeyCombination = 0; } + public static bool activeOnAltShiftEnabled { get => activeOnKeyCombination == 1; set => activeOnKeyCombination = 1; } + public static bool activeOnCtrlAltEnabled { get => activeOnKeyCombination == 2; set => activeOnKeyCombination = 2; } + + public static bool pluginDisabled { get => EditorPrefsCached.GetBool("vFavorites-pluginDisabled", false); set => EditorPrefsCached.SetBool("vFavorites-pluginDisabled", value); } + + + + + const string dir = "Tools/vFavorites/"; + + const string pageScroll = dir + "Scroll to change page"; + const string numberKeys = dir + "1-9 keys to change page"; + const string arrowKeys = dir + "Arrow keys to change page or selection "; + + const string fadeAnimations = dir + "Fade animations"; + const string pageScrollAnimation = dir + "Page scroll animation"; + + const string activeOnAlt = dir + "Holding Alt"; + const string activeOnAltShift = dir + "Holding Alt and Shift"; +#if UNITY_EDITOR_OSX + const string activeOnCtrlAlt = dir + "Holding Cmd and Alt"; +#else + const string activeOnCtrlAlt = dir + "Holding Ctrl and Alt"; + +#endif + + const string disablePlugin = dir + "Disable vFavorites"; + + + + + + + [MenuItem(dir + "Shortcuts", false, 1)] static void dadsas() { } + [MenuItem(dir + "Shortcuts", true, 1)] static bool dadsas123() => false; + + [MenuItem(pageScroll, false, 2)] static void dadsadasadsdadsas() => pageScrollEnabled = !pageScrollEnabled; + [MenuItem(pageScroll, true, 2)] static bool dadsadasdadsdasadsas() { Menu.SetChecked(pageScroll, pageScrollEnabled); return !pluginDisabled; } + + [MenuItem(numberKeys, false, 4)] static void dadsadadsas() => numberKeysEnabled = !numberKeysEnabled; + [MenuItem(numberKeys, true, 4)] static bool dadsaddasadsas() { Menu.SetChecked(numberKeys, numberKeysEnabled); return !pluginDisabled; } + + [MenuItem(arrowKeys, false, 5)] static void dadsadaddassas() => arrowKeysEnabled = !arrowKeysEnabled; + [MenuItem(arrowKeys, true, 5)] static bool dadadssaddasadsas() { Menu.SetChecked(arrowKeys, arrowKeysEnabled); return !pluginDisabled; } + + + + + + [MenuItem(dir + "Animations", false, 101)] static void dadsadsas() { } + [MenuItem(dir + "Animations", true, 101)] static bool dadadssas123() => false; + + [MenuItem(fadeAnimations, false, 102)] static void dadsdasadadsas() => fadeAnimationsEnabled = !fadeAnimationsEnabled; + [MenuItem(fadeAnimations, true, 102)] static bool dadsadadsadsdasadsas() { Menu.SetChecked(fadeAnimations, fadeAnimationsEnabled); return !pluginDisabled; } + + [MenuItem(pageScrollAnimation, false, 103)] static void dadsdasdasadadsas() => pageScrollAnimationEnabled = !pageScrollAnimationEnabled; + [MenuItem(pageScrollAnimation, true, 103)] static bool dadsadaddassadsdasadsas() { Menu.SetChecked(pageScrollAnimation, pageScrollAnimationEnabled); return !pluginDisabled; } + + + + + [MenuItem(dir + "Open when", false, 1001)] static void dadsaddssas() { } + [MenuItem(dir + "Open when", true, 1001)] static bool dadadsssas123() => false; + + [MenuItem(activeOnAlt, false, 1002)] static void dadsdasasdadsas() => activeOnAltEnabled = !activeOnAltEnabled; + [MenuItem(activeOnAlt, true, 1002)] static bool dadsadadssdadsdasadsas() { Menu.SetChecked(activeOnAlt, activeOnAltEnabled); return !pluginDisabled; } + + [MenuItem(activeOnAltShift, false, 1003)] static void dadsdasasdadsadsas() => activeOnAltShiftEnabled = !activeOnAltShiftEnabled; + [MenuItem(activeOnAltShift, true, 1003)] static bool dadsadadssdasdadsdasadsas() { Menu.SetChecked(activeOnAltShift, activeOnAltShiftEnabled); return !pluginDisabled; } + + [MenuItem(activeOnCtrlAlt, false, 1004)] static void dadsdasadasadssdadsas() => activeOnCtrlAltEnabled = !activeOnCtrlAltEnabled; + [MenuItem(activeOnCtrlAlt, true, 1004)] static bool dadsadadsadssdadsdasadsas() { Menu.SetChecked(activeOnCtrlAlt, activeOnCtrlAltEnabled); return !pluginDisabled; } + + + + + + [MenuItem(dir + "More", false, 10001)] static void daasadsddsas() { } + [MenuItem(dir + "More", true, 10001)] static bool dadsadsdasas123() => false; + + // [MenuItem(dir + "Open manual", false, 10002)] + // static void dadadssadsas() => AssetDatabase.OpenAsset(AssetDatabase.LoadAssetAtPath(GetScriptPath("VFavorites").GetParentPath().CombinePath("Manual.pdf"))); + + [MenuItem(dir + "Join our Discord", false, 10002)] + static void dadasdsas() => Application.OpenURL("https://discord.gg/pUektnZeJT"); + + + + + + [MenuItem(dir + "Deals ending soon/Get vHierarchy 2 at 60% off", false, 10003)] + static void dadadssadasdsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/253397?aid=1100lGLBn&pubref=deal60menuvfav"); + + [MenuItem(dir + "Deals ending soon/Get vFolders 2 at 60% off", false, 10004)] + static void dadadssasddsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/255470?aid=1100lGLBn&pubref=deal60menuvfav"); + + [MenuItem(dir + "Deals ending soon/Get vInspector 2 at 60% off", false, 10005)] + static void dadadadsssadsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/252297?aid=1100lGLBn&pubref=deal60menuvfav"); + + [MenuItem(dir + "Deals ending soon/Get vTabs 2 at 60% off", false, 10006)] + static void dadadadsssadsadsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/253396?aid=1100lGLBn&pubref=deal60menuvfav"); + + + + + + + + + [MenuItem(disablePlugin, false, 100001)] static void dadsadsdasadasdasdsadadsas() { pluginDisabled = !pluginDisabled; UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation(); } + [MenuItem(disablePlugin, true, 100001)] static bool dadsaddssdaasadsadadsdasadsas() { Menu.SetChecked(disablePlugin, pluginDisabled); return true; } + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenu.cs.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenu.cs.meta new file mode 100644 index 0000000..c4ab387 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65f8f6586ea4740238f667f8337cf6fb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenuItems.cs b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenuItems.cs new file mode 100644 index 0000000..8fb6b18 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenuItems.cs @@ -0,0 +1,6 @@ + + +// this file was present in a previus version and is supposed to be deleted now +// but asset store update delivery system doesn't allow deleting files +// so instead this file is now emptied +// feel free to delete it if you want diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenuItems.cs.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenuItems.cs.meta new file mode 100644 index 0000000..cef6260 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesMenuItems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e1b1b30cd63e14da9a295742be768842 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesState.cs b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesState.cs new file mode 100644 index 0000000..c4d1a52 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesState.cs @@ -0,0 +1,57 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Serialization; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VFavorites.Libs.VUtils; +using static VFavorites.Libs.VGUI; + + +namespace VFavorites +{ + [FilePath("Library/vFavorites State.asset", FilePathAttribute.Location.ProjectFolder)] + public class VFavoritesState : ScriptableSingleton + { + public int curPageIndex; + + public SerializableDictionary pageStates_byPageId = new SerializableDictionary(); + + public SerializableDictionary itemStates_byItemId = new SerializableDictionary(); + + + [System.Serializable] + public class PageState + { + public long lastItemSelectTime_ticks; + public long lastItemDragTime_ticks; + + public float scrollPos; + + } + + [System.Serializable] + public class ItemState + { + public string _name; + + public string sceneGameObjectIconName; + + public long lastSelectTime_ticks; + public bool isSelected; + + } + + + public static void Save() => instance.Save(true); + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesState.cs.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesState.cs.meta new file mode 100644 index 0000000..a326150 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/VFavoritesState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 362e220c378db4e97ae41fa35a6fb58f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/vFavorites Data.asset b/Assets/OtherAssets/DEV/PAID/vFavorites/vFavorites Data.asset new file mode 100644 index 0000000..3f90086 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/vFavorites Data.asset @@ -0,0 +1,22 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 066cf82f8f80d408c856e48fc8f1127b, type: 3} + m_Name: vFavorites Data + m_EditorClassIdentifier: + pages: + - items: [] + name: Page 1 + _id: 1039256194 + - items: [] + name: Page 2 + _id: 0 + rowScale: 1 diff --git a/Assets/OtherAssets/DEV/PAID/vFavorites/vFavorites Data.asset.meta b/Assets/OtherAssets/DEV/PAID/vFavorites/vFavorites Data.asset.meta new file mode 100644 index 0000000..9a2b93b --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFavorites/vFavorites Data.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c421be6dfd25bbe458970dad85cc77f4 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders.meta b/Assets/OtherAssets/DEV/PAID/vFolders.meta new file mode 100644 index 0000000..34b31e0 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 109ccfb08b9fd4bf5a9d8deee2ace2ed +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/Read me.pdf b/Assets/OtherAssets/DEV/PAID/vFolders/Read me.pdf new file mode 100644 index 0000000..48106d2 Binary files /dev/null and b/Assets/OtherAssets/DEV/PAID/vFolders/Read me.pdf differ diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/Read me.pdf.meta b/Assets/OtherAssets/DEV/PAID/vFolders/Read me.pdf.meta new file mode 100644 index 0000000..90fb259 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/Read me.pdf.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 6c1da0cc833e64f4c96ba170387ea7a6 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.asmdef b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.asmdef new file mode 100644 index 0000000..d3d34ab --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.asmdef @@ -0,0 +1,16 @@ +{ + "name": "VFolders", + "rootNamespace": "", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.asmdef.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.asmdef.meta new file mode 100644 index 0000000..a4528fa --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3331bc437f3a24586b7e86e7bdd62b5e +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.cs new file mode 100644 index 0000000..8b4cff2 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.cs @@ -0,0 +1,1677 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; +using static VFolders.VFoldersData; +using static VFolders.VFoldersCache; + + + +namespace VFolders +{ + public static class VFolders + { + + static void WrappedGUI(EditorWindow window) + { + var navbarHeight = 26; + + var isOneColumn = window.GetMemberValue("m_ViewMode") == 0; + + var prevSelection = Selection.objects; + + + void navbarGui() + { + if (!navbars_byWindow.ContainsKey(window)) + navbars_byWindow[window] = new VFoldersNavbar(window); + + var navbarRect = window.position.SetPos(0, 0).SetHeight(navbarHeight); + + navbars_byWindow[window].OnGUI(navbarRect); + + + } + void hideDefaultTopBar() + { + if (curEvent.isLayout || typeof(GUILayoutUtility).GetMemberValue("current")?.GetMemberValue("topLevel")?.GetMemberValue("entries").Count != 1) // prevents exception on focus by vTabs shift-scroll // doesnt always work. tbh not sure why it works at all + Space(-123); + + } + void defaultGuiWithOffset() + { + var defaultTopBarHeight = 20; + var topOffset = navbarHeight - defaultTopBarHeight; + + var m_Pos_original = window.GetFieldValue("m_Pos"); + + + + + GUI.BeginGroup(m_Pos_original.SetPos(0, 0).AddHeightFromBottom(-topOffset)); + + window.SetFieldValue("m_Pos", m_Pos_original.AddHeightFromBottom(-topOffset)); + + + try { window.InvokeMethod("OnGUI"); } + catch (System.Exception exception) + { + if (exception.InnerException is ExitGUIException) + throw exception.InnerException; + else + throw exception; + + // GUIUtility.ExitGUI() works by throwing ExitGUIException, which just exits imgui loop and doesn't appear in console + // but if ExitGUI is called from a reflected method (OnGUI in this case), the exception becomes TargetInvokationException + // which gets logged to console (only if debugger is attached, for some reason) + // so here in such cases we rethrow the original ExitGUIException + + } + + + window.SetFieldValue("m_Pos", m_Pos_original); + + GUI.EndGroup(); + + } + void treeViewShadow() + { + if (!curEvent.isRepaint) return; + + var shadowLength = 30; + var shadowPos = 21; + var shadowGreyscale = isDarkTheme ? .1f : .28f; + var shadowAlpha = .35f; + + var minScrollPos = 10; + var maxScrollPos = 20; + + + var scrollPos = window.GetMemberValue(isOneColumn ? "m_AssetTree" : "m_FolderTree").GetMemberValue("state").scrollPos.y; + + var opacity = ((scrollPos - minScrollPos) / (maxScrollPos - minScrollPos)).Clamp01(); + + + var rectWidth = isOneColumn ? window.position.width : window.GetMemberValue("m_TreeViewRect").width;// - 12; + + var rect = window.position.SetPos(0, 0).MoveY(shadowPos).SetHeight(shadowLength).SetWidth(rectWidth); + + + + var clipAtY = navbarHeight + 1; + + GUI.BeginClip(window.position.SetPos(0, clipAtY)); + + rect.MoveY(-clipAtY).DrawCurtainDown(Greyscale(shadowGreyscale, shadowAlpha * opacity)); + + GUI.EndClip(); + + + + + if (isOneColumn) return; + + var dividerRect = window.GetMemberValue("m_TreeViewRect").SetWidthFromRight(1).MoveX(1); + + var dividerColor = Greyscale(.16f); + + dividerRect.Draw(dividerColor); + + + } + void listAreaShadow() + { + if (isOneColumn) return; + if (!curEvent.isRepaint) return; + + + var shadowLength = 30; + var shadowPos = navbarHeight + 17; + var shadowGreyscale = isDarkTheme ? .1f : .28f; + var shadowAlpha = .35f; + + var minScrollPos = 10; + var maxScrollPos = 20; + + + var scrollPos = window.GetMemberValue("m_ListAreaState").GetMemberValue("m_ScrollPosition").y; + + var opacity = ((scrollPos - minScrollPos) / (maxScrollPos - minScrollPos)).Clamp01(); + + + var rectX = window.GetMemberValue("m_TreeViewRect").width + 1; + var rectWidth = window.position.width - rectX;// - 12; + + var rect = window.position.SetPos(rectX, 0).MoveY(shadowPos).SetHeight(shadowLength).SetWidth(rectWidth); + + + + var clipAtY = navbarHeight + 22; + + GUI.BeginClip(window.position.SetPos(0, clipAtY)); + + rect.MoveY(-clipAtY).DrawCurtainDown(Greyscale(shadowGreyscale, shadowAlpha * opacity)); + + GUI.EndClip(); + + + + if (isDarkTheme) + window.position.SetPos(rectX, clipAtY - 1).SetSize(12321, 1).Draw(Greyscale(.175f)); // line under breadcrumbs + + } + void preventSelectionChangeInOtherBrowsers() + { + if (Selection.objects.SequenceEqual(prevSelection)) return; + + + allBrowsers.ForEach(r => r?.SetMemberValue("m_InternalSelectionChange", true)); + + EditorApplication.delayCall += () => + allBrowsers.ForEach(r => r?.SetMemberValue("m_InternalSelectionChange", false)); + + } + + + + + + + var doNavbarFirst = GUI.GetNameOfFocusedControl() == "navbar search field" || curEvent.keyCode == KeyCode.Escape; + + if (doNavbarFirst) + navbarGui(); + + hideDefaultTopBar(); + defaultGuiWithOffset(); + treeViewShadow(); + listAreaShadow(); + preventSelectionChangeInOtherBrowsers(); + + if (!doNavbarFirst) + navbarGui(); + + } + + static Dictionary navbars_byWindow = new(); + + + + static void UpdateGUIWrapping(EditorWindow window) + { + if (!window.hasFocus) return; + + var isLocked = window.GetMemberValue("isLocked"); + var isVTabsActive = t_VTabs != null && !EditorPrefsCached.GetBool("vTabs-pluginDisabled", false); + + var curOnGUIMethod = window.GetMemberValue("m_Parent").GetMemberValue("m_OnGUI").Method; + + var isWrapped = curOnGUIMethod == mi_WrappedBrowserOnGUI; + var shouldBeWrapped = VFoldersMenu.navigationBarEnabled && !(isVTabsActive && isLocked) && curOnGUIMethod != mi_VFavorites_WrappedOnGUI; + + void wrap() + { + var hostView = window.GetMemberValue("m_Parent"); + + var newDelegate = typeof(VFolders).GetMethod(nameof(WrappedGUI), maxBindingFlags).CreateDelegate(t_EditorWindowDelegate, window); + + hostView.SetMemberValue("m_OnGUI", newDelegate); + + window.Repaint(); + + } + void unwrap() + { + var hostView = window.GetMemberValue("m_Parent"); + + var originalDelegate = hostView.InvokeMethod("CreateDelegate", "OnGUI"); + + hostView.SetMemberValue("m_OnGUI", originalDelegate); + + window.Repaint(); + + } + + + if (shouldBeWrapped && !isWrapped) + wrap(); + + if (!shouldBeWrapped && isWrapped) + unwrap(); + + } + static void UpdateGUIWrappingForAllBrowsers() => allBrowsers.ForEach(r => UpdateGUIWrapping(r)); + + static void OnDomainReloaded() => toCallInGUI += UpdateGUIWrappingForAllBrowsers; + + static void OnWindowUnmaximized() => UpdateGUIWrappingForAllBrowsers(); + + static void OnBrowserFocused() => UpdateGUIWrapping(EditorWindow.focusedWindow); + + static void OnDelayCall() => UpdateGUIWrappingForAllBrowsers(); + + + + + + static void CheckIfFocusedWindowChanged() + { + if (prevFocusedWindow != EditorWindow.focusedWindow) + if (EditorWindow.focusedWindow?.GetType() == t_ProjectBrowser) + OnBrowserFocused(); + + prevFocusedWindow = EditorWindow.focusedWindow; + + } + + static EditorWindow prevFocusedWindow; + + + + static void CheckIfWindowWasUnmaximized() + { + var isMaximized = EditorWindow.focusedWindow?.maximized == true; + + if (!isMaximized && wasMaximized) + OnWindowUnmaximized(); + + wasMaximized = isMaximized; + + } + + static bool wasMaximized; + + + + static void OnSomeGUI() + { + toCallInGUI?.Invoke(); + toCallInGUI = null; + + CheckIfFocusedWindowChanged(); + + } + + static void ProjectWindowItemOnGUI(string _, Rect __) => OnSomeGUI(); + static void HierarchyWindowItemOnGUI(int _, Rect __) => OnSomeGUI(); + + static System.Action toCallInGUI; + + + + static void DelayCallLoop() + { + OnDelayCall(); + + EditorApplication.delayCall -= DelayCallLoop; + EditorApplication.delayCall += DelayCallLoop; + + } + + + + + + + + + + + + + + + static void ItemGUI(Rect itemRect, string guid, int instanceId) + { + EditorWindow window; + + void findWindow() + { + if (allBrowsers.Count() == 1) { window = allBrowsers.First(); return; } + + + var pointInsideWindow = EditorGUIUtility.GUIToScreenPoint(itemRect.center); + + window = allBrowsers.FirstOrDefault(r => r.position.AddHeight(30).Contains(pointInsideWindow) && r.hasFocus); + + } + void updateWindow() + { + if (!window) return; // happens on half-visible rows during expand animation + + if (curEvent.isLayout && !lastEventWasLayout) + UpdateWindow_Layout(window); + + if (curEvent.isRepaint && !lastEventWasRepaint) + UpdateWindow_Repaint(window); + + + lastEventWasLayout = curEvent.isLayout; + lastEventWasRepaint = curEvent.isRepaint; + + } + void catchScrollInputForController() + { + if (!window) return; + if (!controllers_byWindow.ContainsKey(window)) return; + + if (curEvent.isScroll) + controllers_byWindow[window].animatingScroll = false; + + } + void callGUI() + { + if (!window) return; + if (!guis_byWindow.ContainsKey(window)) return; + + + var gui = guis_byWindow[window]; + + if (itemRect.height == 16) + gui.RowGUI(itemRect, guid, instanceId); + else + gui.CellGUI(itemRect, guid, instanceId); + + } + + findWindow(); + updateWindow(); + catchScrollInputForController(); + callGUI(); + + } + + static void ItemGUI_2021_3_and_older(string guid, Rect itemRect) + { + var instanceId = typeof(AssetDatabase).InvokeMethod("GetMainAssetOrInProgressProxyInstanceID", guid.ToPath()); + + ItemGUI(itemRect, guid, instanceId); + + } + static void ItemGUI_2022_1_and_newer(int instanceId, Rect itemRect) + { + var guid = AssetDatabase.GetAssetPath(instanceId).ToGuid(); + + ItemGUI(itemRect, guid, instanceId); + + } + + static bool lastEventWasLayout; + static bool lastEventWasRepaint; + + + + static void UpdateWindow_Layout(EditorWindow window) + { + if (!guis_byWindow.TryGetValue(window, out var gui)) + gui = guis_byWindow[window] = new(window); + + if (!controllers_byWindow.TryGetValue(window, out var controller)) + controller = controllers_byWindow[window] = new(window); + + if (!histories_byWindow.TryGetValue(window, out var history)) + history = histories_byWindow[window] = new(window); + + + gui.UpdateState_Layout(); + gui.UpdateFoldersFirst(); + + controller.UpdateState(); + controller.UpdateExpandQueue(); + controller.UpdateScrollAnimation(); + controller.UpdateHighlightAnimation(); + + history.UpdateState(); + history.CheckTreeStateChange(); + history.CheckFolderPathChange(); + + } + static void UpdateWindow_Repaint(EditorWindow window) + { + if (guis_byWindow.ContainsKey(window)) + guis_byWindow[window].UpdateState_Repaint(); + } + + public static Dictionary guis_byWindow = new(); + public static Dictionary controllers_byWindow = new(); + public static Dictionary histories_byWindow => VFoldersHistorySingleton.instance.histories_byWindow; + + + + + + + + + public static Texture2D GetSmallFolderIcon(FolderInfo folderInfo, bool removeColor = false) + { + var hasColor = folderInfo.hasColor && !removeColor; + var hasIcon = folderInfo.hasIcon; + + var color = hasColor ? folderInfo.color : default; + var iconNameOrPath = hasIcon ? folderInfo.iconNameOrPath : ""; + + var isEmpty = folderInfo.folderState.isEmpty; + + var key = new object[] { iconNameOrPath, color, isEmpty, isDarkTheme }.Aggregate(0, (hash, r) => (hash * 2) ^ r.GetHashCode()); + + + Texture2D icon = null; + + void getCached() + { + if (!cache.HasIcon(key)) return; + + icon = cache.GetIcon(key); + + } + void generateAndCache() + { + if (icon != null) return; + if (Event.current != null) return; // interactions with gpu in OnGUI may interfere with gui rendering + + var iconSizeX = hasIcon ? 36 : 32; + var iconSizeY = 32; + + var assetIconSize = 20; // 20 21 + var assetIconOffsetX = 16; + var assetIconOffsetY = -2; // -2 -3 + + var folderIconSize = iconSizeY; + var folderIconOffsetY_ifHasAssetIcon = 1; + + Color[] iconPixels; + + Texture2D folderIcon; + Color[] folderIconPixels; + + + void createIcon() + { + icon = new Texture2D(iconSizeX, iconSizeY, TextureFormat.RGBA32, 1, false); + icon.hideFlags = HideFlags.DontSave; + icon.SetPropertyValue("pixelsPerPoint", 2); + + iconPixels = new Color[iconSizeX * iconSizeY]; + + } + void createFolderIcon() + { + var folderIconName = hasColor ? (isEmpty ? "FolderEmpty On Icon" : "Folder On Icon") : + (isEmpty ? "FolderEmpty Icon" : "Folder Icon"); + + + folderIcon = EditorGUIUtility.FindTexture(folderIconName); + + if (folderIcon.width != folderIconSize) + folderIcon = folderIcon.CreateResizedCopy(folderIconSize, folderIconSize); + else + folderIcon = folderIcon.CreateCopy(); + + folderIconPixels = folderIcon.GetPixels(0); + + } + void copyFolderIcon() + { + if (!hasIcon) { iconPixels = folderIconPixels; return; } + + for (int x = 0; x < folderIcon.width; x++) + for (int y = 0; y < folderIcon.height - folderIconOffsetY_ifHasAssetIcon; y++) + iconPixels[x + (y + folderIconOffsetY_ifHasAssetIcon) * icon.width] = folderIconPixels[x + y * folderIcon.width]; + + } + void applyColor() + { + if (!hasColor) return; + + for (int i = 0; i < iconPixels.Length; i++) + iconPixels[i] *= (color * 1.06f).SetAlpha(1); + + } + void insertAssetIcon() + { + if (!hasIcon) return; + + + var assetIconOriginal = EditorIcons.GetIcon(iconNameOrPath); + + if (!assetIconOriginal) return; + + + var prevFilter = assetIconOriginal.filterMode; + + assetIconOriginal.filterMode = FilterMode.Bilinear; + var assetIconPixels_bilinear = assetIconOriginal.CreateResizedCopy(assetIconSize, assetIconSize).GetPixels(); + + assetIconOriginal.filterMode = FilterMode.Point; + var assetIconPixels_point = assetIconOriginal.CreateResizedCopy(assetIconSize, assetIconSize).GetPixels(); + + + assetIconOriginal.filterMode = prevFilter; + + + for (int x = 0; x < iconSizeX; x++) + for (int y = 0; y < iconSizeY; y++) + { + var xAssetIcon = x - assetIconOffsetX; + var yAssetIcon = y - assetIconOffsetY - folderIconOffsetY_ifHasAssetIcon; + + if (!xAssetIcon.IsInRange(0, assetIconSize - 1)) continue; + if (!yAssetIcon.IsInRange(0, assetIconSize - 1)) continue; + + + var innerRadius = (iconNameOrPath == "AudioClip Icon" ? .2f : .4f); + var isInnerPixel = (new Vector2(xAssetIcon, yAssetIcon) / (assetIconSize - 1) - Vector2.one * .5f).magnitude < innerRadius; + + var isOutlinePixel = false; + var outlineRadius = isInnerPixel ? 2 : 1; + for (int xx = xAssetIcon - outlineRadius; xx <= xAssetIcon + outlineRadius; xx++) + if (!isOutlinePixel) + for (int yy = yAssetIcon - outlineRadius; yy <= yAssetIcon + outlineRadius; yy++) + if (!isOutlinePixel) + if (xx.IsInRange(0, assetIconSize - 1) && yy.IsInRange(0, assetIconSize - 1)) + if (assetIconPixels_bilinear[xx + yy * assetIconSize].a > .2f) + isOutlinePixel = true; + + + var pxBilinear = assetIconPixels_bilinear[xAssetIcon + yAssetIcon * assetIconSize]; + var pxPoint = assetIconPixels_point[xAssetIcon + yAssetIcon * assetIconSize]; + var pxCombined = new Color(pxPoint.r, pxPoint.g, pxPoint.b, pxBilinear.a); + + if (pxCombined.a == 0 && !isOutlinePixel) continue; + + iconPixels[x + y * iconSizeX] = pxCombined; + + } + + } + + + createIcon(); + createFolderIcon(); + copyFolderIcon(); + applyColor(); + insertAssetIcon(); + + icon.SetPixels(iconPixels); + icon.Apply(); + + cache.AddIcon(key, icon); + + } + void queueGeneration() + { + if (icon != null) return; + + toGenerateInUpdate.Add(generateAndCache); + + } + + + getCached(); + generateAndCache(); + queueGeneration(); + + return icon ?? EditorGUIUtility.FindTexture(isEmpty ? "FolderEmpty Icon" : "Project@2x"); + + } + + static void GenerateIconsInUpdate() + { + foreach (var r in toGenerateInUpdate) + r.Invoke(); + + toGenerateInUpdate.Clear(); + + } + + static List toGenerateInUpdate = new(); + + + + public static void DrawBigFolderIcon(Rect rect, FolderInfo folderInfo) + { + Rect folderIconRect; + Rect assetIconRect; + + + void calcRects() + { + folderIconRect = rect.SetHeight(rect.width); + +#if !UNITY_2022_3_OR_NEWER + if (Application.platform == RuntimePlatform.OSXEditor) + if (folderIconRect.width > 64) + folderIconRect = folderIconRect.SetSizeFromMid(64, 64); +#endif + + + var assetIconOffsetMin = new Vector2(4.5f, 3.5f); + var assetIconSizeMin = 10; + + var assetIconOffsetMax = new Vector2(19, 15); + var assetIconSizeMax = 24.5f; // 25 + + var t = ((folderIconRect.width - 16) / (64 - 16)); + +#if !UNITY_2022_3_OR_NEWER + if (Application.platform == RuntimePlatform.OSXEditor) + t = t.Clamp01(); +#endif + + var assetIconOffset = MathUtil.Lerp(assetIconOffsetMin, assetIconOffsetMax, t); + var assetIconSize = MathUtil.Lerp(assetIconSizeMin, assetIconSizeMax, t); + + assetIconRect = folderIconRect.Move(assetIconOffset).SetSizeFromMid(assetIconSize, assetIconSize).AlignToPixelGrid(); + + } + + void color() + { + if (!folderInfo.hasColor) return; + + + SetGUIColor(folderInfo.color.SetAlpha(1)); + + GUI.DrawTexture(folderIconRect, EditorGUIUtility.FindTexture(folderInfo.folderState.isEmpty ? "FolderEmpty On Icon" : "Folder On Icon")); + + ResetGUIColor(); + + } + void assetIcon() + { + if (!folderInfo.hasIcon) return; + + + var texture = EditorIcons.GetIcon(folderInfo.iconNameOrPath); + + if (!texture) return; + + if (texture.width < texture.height) assetIconRect = assetIconRect.SetWidthFromMid(assetIconRect.height * texture.width / texture.height); + if (texture.height < texture.width) assetIconRect = assetIconRect.SetHeightFromMid(assetIconRect.width * texture.height / texture.width); + + + + void material() + { + if (!outlineMaterial) + outlineMaterial = new Material(Shader.Find("Hidden/Internal-GUITextureClipText")); + + outlineMaterial.color = isDarkTheme ? Greyscale(.2f, .6f) : Greyscale(.75f); + + // .color needs to be updated continiously because it gets reset on some editor events + // only happens when internal shader is used + + } + + void shadow() + { + var contrast = isDarkTheme ? .6f : .2f; // was .65 then .6 + + assetIconRect.SetSizeFromMid(assetIconRect.width * .8f).DrawBlurred(Greyscale(.2f, contrast), assetIconRect.width * .55f); + + } + void outline() + { + var outlineRect = assetIconRect.Resize(rect.height >= 70 && EditorGUIUtility.pixelsPerPoint >= 2 ? -1f / EditorGUIUtility.pixelsPerPoint : 0).AlignToPixelGrid(); + + EditorGUI.DrawPreviewTexture(outlineRect.Move(-1, -1), texture, outlineMaterial); + EditorGUI.DrawPreviewTexture(outlineRect.Move(-1, 1), texture, outlineMaterial); + EditorGUI.DrawPreviewTexture(outlineRect.Move(1, 1), texture, outlineMaterial); + EditorGUI.DrawPreviewTexture(outlineRect.Move(1, -1), texture, outlineMaterial); + + } + void background() + { + for (int i = 0; i < assetIconRect.size.x; i++) + EditorGUI.DrawPreviewTexture(assetIconRect.Resize(i * .5f + 1), texture, outlineMaterial); + + } + void icon() + { + GUI.DrawTexture(assetIconRect, texture); + } + + material(); + + shadow(); + outline(); + background(); + icon(); + + } + + + calcRects(); + + color(); + assetIcon(); + + } + + static Material outlineMaterial; + + + + + + + + + + + + + + + + + + public static Texture2D GetSmallFolderIcon_forVTabs(string folderGuid) + { + var folderInfo = GetFolderInfo(folderGuid); + + if (folderInfo.hasColor || folderInfo.hasIcon) + return GetSmallFolderIcon(folderInfo); + + return null; + + } + + public static void DrawBigFolderIcon_forVFavorites(Rect rect, string folderGuid) + { + DrawBigFolderIcon(rect, GetFolderInfo(folderGuid)); + } + + public static void SetIcon(string folderPath, string iconName, bool recursive = false) + { + var folderData = GetFolderData(folderPath.ToGuid(), createDataIfDoesntExist: true); + + folderData.iconNameOrGuid = iconName ?? ""; + folderData.isIconRecursive = recursive; + + + folderInfoCache.Clear(); + + EditorApplication.RepaintProjectWindow(); + + } + public static void SetColor(string folderPath, int colorIndex, bool recursive = false) + { + var folderData = GetFolderData(folderPath.ToGuid(), createDataIfDoesntExist: true); + + folderData.colorIndex = colorIndex; + folderData.isColorRecursive = recursive; + + + folderInfoCache.Clear(); + + EditorApplication.RepaintProjectWindow(); + + } + + + + + + + + + + + + + + + + + + static void Shortcuts() // globalEventHandler + { + if (EditorWindow.mouseOverWindow is not EditorWindow hoveredWindow) return; + if (hoveredWindow.GetType() != t_ProjectBrowser) return; + + void toggleExpanded() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.E) return; + if (curEvent.holdingAnyModifierKey) return; + if (!VFoldersMenu.toggleExpandedEnabled) return; + + if (lastHoveredTreeItem == null) return; + if (!lastHoveredRowRect_screenSpace.Contains(lastKnownMousePosition_screenSpace)) return; + + curEvent.Use(); + + if (lastHoveredTreeItem.children == null) return; + if (lastHoveredTreeItem.children.Count == 0) return; + + + controllers_byWindow[hoveredWindow].ToggleExpanded(lastHoveredTreeItem); + + } + void collapseEverything() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.E) return; + if (curEvent.modifiers != (EventModifiers.Shift | EventModifiers.Command) && curEvent.modifiers != (EventModifiers.Shift | EventModifiers.Control)) return; + if (!VFoldersMenu.collapseEverythingEnabled) return; + + curEvent.Use(); + + + controllers_byWindow[hoveredWindow].CollapseAll(); + + } + void collapseEverythingElse() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.E) return; + if (curEvent.modifiers != EventModifiers.Shift) return; + if (!VFoldersMenu.collapseEverythingElseEnabled) return; + + if (lastHoveredTreeItem == null) return; + if (!lastHoveredRowRect_screenSpace.Contains(lastKnownMousePosition_screenSpace)) return; + + curEvent.Use(); + + if (lastHoveredTreeItem.children == null) return; + if (lastHoveredTreeItem.children.Count == 0) return; + + + controllers_byWindow[hoveredWindow].Isolate(lastHoveredTreeItem); + + } + void moveBack() + { + if (!curEvent.isMouseDown) return; + if (curEvent.mouseButton != 3) return; + + + var isOneColumn = hoveredWindow.GetMemberValue("m_ViewMode") == 0; + + if (isOneColumn) return; + + + + if (!histories_byWindow.ContainsKey(hoveredWindow)) + histories_byWindow[hoveredWindow] = new(hoveredWindow); + + var history = histories_byWindow[hoveredWindow]; + + if (!history.prevFolderPaths.Any()) return; + + + history.MoveBack_TwoColumns(); + + } + void moveForward() + { + if (!curEvent.isMouseDown) return; + if (curEvent.mouseButton != 4) return; + + + var isOneColumn = hoveredWindow.GetMemberValue("m_ViewMode") == 0; + + if (isOneColumn) return; + + + + if (!histories_byWindow.ContainsKey(hoveredWindow)) + histories_byWindow[hoveredWindow] = new(hoveredWindow); + + var history = histories_byWindow[hoveredWindow]; + + if (!history.nextFolderPaths.Any()) return; + + + history.MoveForward_TwoColumns(); + + } + + toggleExpanded(); + collapseEverything(); + collapseEverythingElse(); + moveBack(); + moveForward(); + + } + + public static TreeViewItem lastHoveredTreeItem; + + public static Rect lastHoveredRowRect_screenSpace; + + public static Vector2 lastKnownMousePosition_screenSpace; + + + + + + + + + + + + + + + + + + public static FolderInfo GetFolderInfo(string guid) + { + if (folderInfoCache.TryGetValue(guid, out var cachedFolderInfo)) return cachedFolderInfo; + + + var folderInfo = new FolderInfo(); + + var folderData = folderInfo.folderData = GetFolderData(guid, createDataIfDoesntExist: false); + var folderState = folderInfo.folderState = GetFolderState(guid); + + + var recursiveIconNameOrGuid = ""; + var recursiveColorIndex = 0; + + var ruledIconNameOrGuid = ""; + var ruledColorIndex = 0; + + void checkRules() + { + if (rules == null) + rules = TypeCache.GetMethodsWithAttribute() + .Where(r => r.IsStatic + && r.GetParameters().Count() == 1 + && r.GetParameters().First().ParameterType == typeof(Folder)).ToList(); + + if (!rules.Any()) return; + + + + var folder = new Folder(guid); + + foreach (var rule in rules) + rule.Invoke(null, new[] { folder }); + + + ruledIconNameOrGuid = folder.icon; + ruledColorIndex = folder.color; + + + } + void checkRecursion(string path, int depth) + { + if (!path.HasParentPath()) return; + + var parentFolderData = GetFolderData(path.GetParentPath().ToGuid(), createDataIfDoesntExist: false); + + if (parentFolderData != null) + { + + if (parentFolderData.isIconRecursive && parentFolderData.iconNameOrGuid != "") + if (recursiveIconNameOrGuid == "") + recursiveIconNameOrGuid = parentFolderData.iconNameOrGuid; + + if (parentFolderData.isColorRecursive && parentFolderData.colorIndex != 0) + if (recursiveColorIndex == 0) + recursiveColorIndex = parentFolderData.colorIndex; + + + if (parentFolderData.isColorRecursive && parentFolderData.colorIndex != 0) + folderInfo.maxColorRecursionDepth = depth + 1; + + } + + + + checkRecursion(path.GetParentPath(), depth + 1); + + } + void setIcon() + { + var iconNameOrGuid = ""; + + if (folderData != null && folderData.iconNameOrGuid != "") + iconNameOrGuid = folderData.iconNameOrGuid; + + else if (recursiveIconNameOrGuid != "") + iconNameOrGuid = recursiveIconNameOrGuid; + + else if (ruledIconNameOrGuid != "") + iconNameOrGuid = ruledIconNameOrGuid; + + else if (VFoldersMenu.autoIconsEnabled && folderState.autoIconName != "" && folderData?.isIconRecursive != true) + iconNameOrGuid = folderState.autoIconName; + + + + if (iconNameOrGuid == "" || iconNameOrGuid == "none") { folderInfo.hasIcon = false; return; } + + folderInfo.hasIcon = true; + folderInfo.hasIconByRecursion = recursiveIconNameOrGuid != ""; + + folderInfo.iconNameOrPath = iconNameOrGuid.Length == 32 ? iconNameOrGuid.ToPath() + : iconNameOrGuid; + + } + void setColor() + { + var colorIndex = 0; + + if (folderData != null && folderData.colorIndex > 0) + colorIndex = folderData.colorIndex; + + else if (recursiveColorIndex != 0) + colorIndex = recursiveColorIndex; + + else if (ruledColorIndex != 0) + colorIndex = ruledColorIndex; + + + + if (colorIndex == 0) { folderInfo.hasColor = false; return; } + + folderInfo.hasColor = true; + folderInfo.hasColorByRecursion = recursiveColorIndex != 0; + + + + + var brightness = palette?.colorBrightness ?? 1; + var saturation = palette?.colorSaturation ?? 1; + + + var rawColor = palette ? palette.colors[colorIndex - 1] : VFoldersPalette.GetDefaultColor(colorIndex - 1); + + var brightenedColor = MathUtil.Lerp(Greyscale(.2f), rawColor, brightness); + + Color.RGBToHSV(brightenedColor, out float h, out float s, out float v); + var saturatedColor = Color.HSVToRGB(h, s * saturation, v); + + + folderInfo.color = saturatedColor; + + } + + checkRules(); + checkRecursion(guid.ToPath(), 0); + setIcon(); + setColor(); + + + return folderInfoCache[guid] = folderInfo; + + } + + public class FolderInfo + { + public string iconNameOrPath = ""; + public bool hasIcon; + public bool hasIconByRecursion; + + public Color color; + public bool hasColor; + public bool hasColorByRecursion; + public int maxColorRecursionDepth; + + + public FolderData folderData; + public FolderState folderState; + + } + + public static Dictionary folderInfoCache = new(); + + public static List rules = null; + + + + public static FolderData GetFolderData(string guid, bool createDataIfDoesntExist) + { + if (!data) return null; + + FolderData folderData = null; + + void fromScripableObject() + { + if (VFoldersData.storeDataInMetaFiles) return; + + data.folderDatas_byGuid.TryGetValue(guid, out folderData); + + + if (folderData != null || !createDataIfDoesntExist) return; + + folderData = new FolderData(); + + data.folderDatas_byGuid[guid] = folderData; + + } + void fromMetaFile() + { + if (!VFoldersData.storeDataInMetaFiles) return; + + folderDatasFromMetaFiles_byGuid.TryGetValue(guid, out folderData); + + + + if (folderData != null) return; + + var importer = AssetImporter.GetAtPath(guid.ToPath()); + + try { folderData = JsonUtility.FromJson(importer.userData); } catch { } + + folderDatasFromMetaFiles_byGuid[guid] = folderData; + + + + if (folderData != null || !createDataIfDoesntExist) return; + + folderData = new FolderData(); + + folderDatasFromMetaFiles_byGuid[guid] = folderData; + + } + + fromScripableObject(); + fromMetaFile(); + + return folderData; + + } + + public static Dictionary folderDatasFromMetaFiles_byGuid = new(); + + + + public static FolderState GetFolderState(string guid) + { + FolderState folderState = null; + + void getCached() + { + cache.folderStates_byGuid.TryGetValue(guid, out folderState); + } + void create() + { + if (folderState != null) return; + + folderState = new FolderState(); + + folderState.needsUpdate = true; + + cache.folderStates_byGuid[guid] = folderState; + + } + void update() + { + if (!folderState.needsUpdate) return; + if (!Directory.Exists(guid.ToPath())) { folderState.needsUpdate = false; return; } + + + var typesInFolder = Directory.GetFiles(guid.ToPath(), "*.*").Select(r => AssetDatabase.GetMainAssetTypeAtPath(r)).Where(r => r != null); + + void isEmpty() + { + folderState.isEmpty = !Directory.EnumerateFileSystemEntries(guid.ToPath()).Any(); + } + void contentMinimap() + { + var iconNames = new List(); + + void fill() + { + foreach (var type in typesInFolder) + + if (type == typeof(Texture2D)) + iconNames.Add("Texture Icon"); + + else if (type == typeof(GameObject)) + iconNames.Add("Prefab Icon"); + + else if (type.BaseType == typeof(ScriptableObject) || type.BaseType?.BaseType == typeof(ScriptableObject)) + iconNames.Add("ScriptableObject Icon"); + + else if (type == typeof(MonoScript)) + iconNames.Add("cs Script Icon"); + + else if (AssetPreview.GetMiniTypeThumbnail(type)?.name is string iconName) + iconNames.Add(iconName); + + } + void filter() + { + iconNames = iconNames.Distinct().ToList(); + + + for (int i = 0; i < iconNames.Count; i++) + if (iconNames[i].StartsWith("d_")) + iconNames[i] = iconNames[i].Substring(2); + + + + iconNames.Remove("DefaultAsset Icon"); + iconNames.Remove("TextAsset Icon"); + + + + if (iconNames.Contains("cs Script Icon")) + iconNames.Remove("AssemblyDefinitionAsset Icon"); + + if (iconNames.Contains("Shader Icon")) + iconNames.Remove("ShaderInclude Icon"); + + } + void order() + { + var order = new List + { + + "SceneAsset Icon", + + + "Prefab Icon", + "Mesh Icon", + "Material Icon", + "Texture Icon", + + + "cs Script Icon", + "Shader Icon", + "ComputeShader Icon", + "ShaderInclude Icon", + + + "ScriptableObject Icon", + + }; + + iconNames = iconNames.OrderBy(r => order.IndexOf(r) is int i && i != -1 ? i : 1232) + .ThenBy(r => r) + .ToList(); + } + + fill(); + filter(); + order(); + + folderState.contentMinimapIconNames = iconNames; + + } + void autoIcon() + { + folderState.autoIconName = ""; + + + if (!typesInFolder.Any()) return; + if (!typesInFolder.All(r => r == typesInFolder.First()) && !typesInFolder.All(r => typeof(ScriptableObject).IsAssignableFrom(r))) return; + + var type = typesInFolder.First(); + + + if (type == typeof(SceneAsset)) + folderState.autoIconName = "SceneAsset Icon"; + + else if (type == typeof(GameObject)) + folderState.autoIconName = "Prefab Icon"; + + else if (type == typeof(Material)) + folderState.autoIconName = "Material Icon"; + + else if (type == typeof(Texture)) + folderState.autoIconName = "Texture Icon"; + + else if (type == typeof(TerrainData)) + folderState.autoIconName = "TerrainData Icon"; + + else if (type == typeof(AudioClip)) + folderState.autoIconName = "AudioClip Icon"; + + else if (type == typeof(Shader)) + folderState.autoIconName = "Shader Icon"; + + else if (type == typeof(ComputeShader)) + folderState.autoIconName = "ComputeShader Icon"; + + else if (type == typeof(MonoScript) || type == typeof(UnityEditorInternal.AssemblyDefinitionAsset) || type == typeof(UnityEditorInternal.AssemblyDefinitionReferenceAsset)) + folderState.autoIconName = "cs Script Icon"; + + else if (typeof(ScriptableObject).IsAssignableFrom(type)) + folderState.autoIconName = "ScriptableObject Icon"; + + } + + + isEmpty(); + contentMinimap(); + autoIcon(); + + folderState.needsUpdate = false; + + } + + getCached(); + create(); + update(); + + return folderState; + + } + + class FolderStateChangeDetector : AssetPostprocessor + { +#if UNITY_2021_1_OR_NEWER + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload) +#else + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) +#endif + { + foreach (var path in importedAssets.Concat(deletedAssets).Concat(movedAssets).Concat(movedFromAssetPaths)) + if (path.HasParentPath()) + if (cache.folderStates_byGuid.TryGetValue(path.GetParentPath().ToGuid(), out var folderState)) + folderState.needsUpdate = true; // todo only clear folderinfo cache here? + } + } + + public static VFoldersCache cache => VFoldersCache.instance; + + + + public static void OnProjectChanged() => folderInfoCache.Clear(); + public static void OnDataSerialization() => folderInfoCache.Clear(); + + + + + + + + + + + + + + + + + [InitializeOnLoadMethod] + static void Init() + { + if (VFoldersMenu.pluginDisabled) return; + + void subscribe() + { + + // gui + +#if UNITY_2022_1_OR_NEWER + EditorApplication.projectWindowItemInstanceOnGUI -= ItemGUI_2022_1_and_newer; + EditorApplication.projectWindowItemInstanceOnGUI = ItemGUI_2022_1_and_newer + EditorApplication.projectWindowItemInstanceOnGUI; +#else + EditorApplication.projectWindowItemOnGUI -= ItemGUI_2021_3_and_older; + EditorApplication.projectWindowItemOnGUI = ItemGUI_2021_3_and_older + EditorApplication.projectWindowItemOnGUI; +#endif + + + + // wrapping updaters + + EditorApplication.projectWindowItemOnGUI -= ProjectWindowItemOnGUI; + EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUI; + + EditorApplication.hierarchyWindowItemOnGUI -= HierarchyWindowItemOnGUI; + EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI; + + EditorApplication.delayCall -= DelayCallLoop; + EditorApplication.delayCall += DelayCallLoop; + + EditorApplication.update -= CheckIfFocusedWindowChanged; + EditorApplication.update += CheckIfFocusedWindowChanged; + + + + // shortcuts + + var globalEventHandler = typeof(EditorApplication).GetFieldValue("globalEventHandler"); + typeof(EditorApplication).SetFieldValue("globalEventHandler", Shortcuts + (globalEventHandler - Shortcuts)); + + + + // other + + EditorApplication.update -= GenerateIconsInUpdate; + EditorApplication.update += GenerateIconsInUpdate; + + EditorApplication.projectChanged -= OnProjectChanged; + EditorApplication.projectChanged += OnProjectChanged; + + + } + void loadData() + { + data = AssetDatabase.LoadAssetAtPath(ProjectPrefs.GetString("vFolders-lastKnownDataPath")); + + + if (data) return; + + data = AssetDatabase.FindAssets("t:VFoldersData").Select(guid => AssetDatabase.LoadAssetAtPath(guid.ToPath())).FirstOrDefault(); + + + if (!data) return; + + ProjectPrefs.SetString("vFolders-lastKnownDataPath", data.GetPath()); + + } + void loadPalette() + { + palette = AssetDatabase.LoadAssetAtPath(ProjectPrefs.GetString("vFolders-lastKnownPalettePath")); + + + if (palette) return; + + palette = AssetDatabase.FindAssets("t:VFoldersPalette").Select(guid => AssetDatabase.LoadAssetAtPath(guid.ToPath())).FirstOrDefault(); + + + if (!palette) return; + + ProjectPrefs.SetString("vFolders-lastKnownPalettePath", palette.GetPath()); + + } + void loadDataAndPaletteDelayed() + { + if (!data) + EditorApplication.delayCall += () => EditorApplication.delayCall += loadData; + + if (!palette) + EditorApplication.delayCall += () => EditorApplication.delayCall += loadPalette; + + // AssetDatabase isn't up to date at this point (it gets updated after InitializeOnLoadMethod) + // and if current AssetDatabase state doesn't contain the data - it won't be loaded during Init() + // so here we schedule an additional, delayed attempt to load the data + // this addresses reports of data loss when trying to load it on a new machine + + } + void migrateDataFromV1() + { + if (!data) return; + if (ProjectPrefs.GetBool("vFolders-dataMigrationFromV1Attempted", false)) return; + + ProjectPrefs.SetBool("vFolders-dataMigrationFromV1Attempted", true); + + var lines = System.IO.File.ReadAllLines(data.GetPath()); + + if (lines.Length < 15 || !lines[14].Contains("folderDatasByGuid")) return; + + var guids = new List(); + var icons = new List(); + var colors = new List(); + + void parseGudis() + { + for (int i = 16; i < lines.Length; i++) + { + if (lines[i].Contains("values:")) break; + + var startIndex = lines[i].IndexOf("- ") + 2; + + if (startIndex < lines[i].Length) + guids.Add(lines[i].Substring(startIndex)); + else + guids.Add(""); + + } + + } + void parseIcons() + { + for (int i = 0; i < guids.Count; i++) + if (lines[29 + i * 5 + 3] is string line) + if (line.Length > line.IndexOf(": ") + 2) + icons.Add(line.Substring(line.IndexOf(": ") + 2)); + else + icons.Add(""); + + } + void parseColors() + { + for (int i = 0; i < guids.Count; i++) + if (lines[29 + i * 5 + 1] is string line) + if (line.Length > line.IndexOf(": ") + 2) + colors.Add(int.Parse(line.Substring(line.IndexOf(": ") + 2))); + else + colors.Add(0); + + } + + void remapColors() + { + for (int i = 0; i < colors.Count; i++) + if (colors[i] == 10) + colors[i] = 1; + else if (colors[i] != 0) + colors[i]++; + + } + void fillData() + { + for (int i = 0; i < guids.Count; i++) + if (icons[i] != "" || colors[i] != 0) + data.folderDatas_byGuid[guids[i]] = new FolderData { iconNameOrGuid = icons[i], colorIndex = colors[i] }; + + data.Dirty(); + data.Save(); + + } + + + try + { + parseGudis(); + parseIcons(); + parseColors(); + + remapColors(); + fillData(); + + } + catch { } + + } + void fixIconNamesForUnity6() + { + if (!Application.unityVersion.Contains("6000")) return; + if (ProjectPrefs.GetBool("vFolders-iconNamesForUnity6Fixed", false)) return; + if (!palette) return; + if (!data) return; + + foreach (var iconRow in palette.iconRows) + if (iconRow.builtinIcons.Contains("PhysicMaterial Icon")) + iconRow.builtinIcons[iconRow.builtinIcons.IndexOf("PhysicMaterial Icon")] = "PhysicsMaterial Icon"; + + foreach (var folderData in data.folderDatas_byGuid.Values) + if (folderData.iconNameOrGuid == "PhysicMaterial Icon") + folderData.iconNameOrGuid = "PhysicsMaterial Icon"; + + ProjectPrefs.SetBool("vFolders-iconNamesForUnity6Fixed", true); + + } + + subscribe(); + loadData(); + loadPalette(); + loadDataAndPaletteDelayed(); + migrateDataFromV1(); + fixIconNamesForUnity6(); + + OnDomainReloaded(); + + } + + public static VFoldersData data; + public static VFoldersPalette palette; + + + + + + static IEnumerable allBrowsers => _allBrowsers ??= t_ProjectBrowser.GetFieldValue("s_ProjectBrowsers").Cast(); + static IEnumerable _allBrowsers; + + static Type t_ProjectBrowser = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser"); + static Type t_HostView = typeof(Editor).Assembly.GetType("UnityEditor.HostView"); + static Type t_EditorWindowDelegate = t_HostView.GetNestedType("EditorWindowDelegate", maxBindingFlags); + + static Type t_VTabs = Type.GetType("VTabs.VTabs") ?? Type.GetType("VTabs.VTabs, VTabs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + static Type t_VFavorites = Type.GetType("VFavorites.VFavorites") ?? Type.GetType("VFavorites.VFavorites, VFavorites, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + + static MethodInfo mi_WrappedBrowserOnGUI = typeof(VFolders).GetMethod(nameof(WrappedGUI), maxBindingFlags); + static MethodInfo mi_VFavorites_WrappedOnGUI = t_VFavorites?.GetMethod("WrappedOnGUI", maxBindingFlags); + + + + + + const string version = "2.1.6"; + + } + + #region Rules + + public class RuleAttribute : System.Attribute { } + + public class Folder + { + public string path => guid.ToPath(); + public string name => path.Split('/').Last(); + + public int color = 0; + public string icon = ""; + + + + public Folder(string guid) => this.guid = guid; + + string guid; + + + } + + + + #endregion + +} +#endif diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.cs.meta new file mode 100644 index 0000000..726886f --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFolders.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cb4ae53db31ef4fdfbfc47484afb8192 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersCache.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersCache.cs new file mode 100644 index 0000000..87086b4 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersCache.cs @@ -0,0 +1,102 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; + +namespace VFolders +{ + [FilePath("Library/vFolders Cache.asset", FilePathAttribute.Location.ProjectFolder)] + public class VFoldersCache : ScriptableSingleton + { + + public Texture2D GetIcon(int key) + { + if (instance.iconTextures_byKey.ContainsKey(key)) + return instance.iconTextures_byKey[key]; + + if (instance.iconTextureDatas_byKey.ContainsKey(key)) + return instance.iconTextures_byKey[key] = instance.iconTextureDatas_byKey[key].GetTexture(); + + return null; + + } + + public void AddIcon(int key, Texture2D icon) + { + instance.iconTextures_byKey[key] = icon; + instance.iconTextureDatas_byKey[key] = new TextureData(icon); + + instance.Save(true); + + } + public bool HasIcon(int key) => instance.iconTextureDatas_byKey.ContainsKey(key); + + public Dictionary iconTextures_byKey = new(); + public SerializableDictionary iconTextureDatas_byKey = new(); + + [System.Serializable] + public class TextureData + { + public byte[] rawData; + public int width; + public int height; + public TextureFormat format; + public int mipCount; + public float pixelsPerPoint; + + public TextureData(Texture2D t) + { + rawData = t.GetRawTextureData(); + width = t.width; + height = t.height; + format = t.format; + mipCount = t.mipmapCount; + pixelsPerPoint = t.GetPropertyValue("pixelsPerPoint"); + + } + + public Texture2D GetTexture() + { + var t = new Texture2D(width, height, format, mipCount, false); + t.LoadRawTextureData(rawData); + t.SetPropertyValue("pixelsPerPoint", pixelsPerPoint); + t.hideFlags = HideFlags.DontSave; + t.Apply(); + + return t; + + } + + } + + + + + public SerializableDictionary folderStates_byGuid = new(); + + [System.Serializable] + public class FolderState + { + public bool isEmpty; + + public List contentMinimapIconNames = new(); + + public string autoIconName = ""; + + public bool needsUpdate; + + } + + } +} +#endif diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersCache.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersCache.cs.meta new file mode 100644 index 0000000..8cdb893 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69c14c9fceb5b4c968f55fe5786a0bee +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersController.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersController.cs new file mode 100644 index 0000000..d15ecb3 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersController.cs @@ -0,0 +1,521 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; +using static VFolders.VFolders; +using static VFolders.VFoldersData; +using static VFolders.VFoldersCache; + + + +namespace VFolders +{ + public class VFoldersController + { + + public void UpdateExpandQueue() + { + if (treeViewAnimatesExpansion) return; + + if (!expandQueue_toAnimate.Any()) + { + if (!expandQueue_toCollapseAfterAnimation.Any()) return; + + foreach (var id in expandQueue_toCollapseAfterAnimation) + SetExpanded_withoutAnimation(id, false); + + expandQueue_toCollapseAfterAnimation.Clear(); + + return; + } + + + var iid = expandQueue_toAnimate.First().id; + var expand = expandQueue_toAnimate.First().expand; + + + if (expandedIds.Contains(iid) != expand) + SetExpanded_withAnimation(iid, expand); + + expandQueue_toAnimate.RemoveAt(0); + + + window.Repaint(); + + } + + public List expandQueue_toAnimate = new(); + public List expandQueue_toCollapseAfterAnimation = new(); + + public struct ExpandQueueEntry { public int id; public bool expand; } + + public bool animatingExpansion => expandQueue_toAnimate.Any() || expandQueue_toCollapseAfterAnimation.Any(); + + + + + + + public void UpdateScrollAnimation() + { + if (!animatingScroll) return; + + + var lerpSpeed = 10; + + var lerpedScrollPos = MathUtil.SmoothDamp(currentScrollPos, targetScrollPos, lerpSpeed, ref scrollPosDerivative, editorDeltaTime); + + SetScrollPos(lerpedScrollPos); + + window.Repaint(); + + + + if (lerpedScrollPos.DistanceTo(targetScrollPos) > .4f) return; + + SetScrollPos(targetScrollPos); + + animatingScroll = false; + + + } + + public float targetScrollPos; + public float scrollPosDerivative; + + public bool animatingScroll; + + + + + + + + public void UpdateHighlightAnimation() + { + if (!animatingHighlight) return; + + + var lerpSpeed = 1.2f; + + MathUtil.SmoothDamp(ref highlightAmount, 0, lerpSpeed, ref highlightDerivative, editorDeltaTime); + + window.Repaint(); + + + + if (highlightAmount > .05f) return; + + highlightAmount = 0; + + animatingHighlight = false; + + + } + + public float highlightAmount; + public float highlightDerivative; + + public bool animatingHighlight; + + public string folderToHighlight; + + + + + + + + public void UpdateState() + { + isOneColumn = window.GetFieldValue("m_ViewMode") == 0; + + treeViewController = window.GetFieldValue(isOneColumn ? "m_AssetTree" : "m_FolderTree"); + treeViewControllerData = treeViewController?.GetPropertyValue("data"); + + + + var treeViewState = treeViewController?.GetPropertyValue("state"); + + currentScrollPos = treeViewState?.scrollPos.y ?? 0; + + expandedIds = treeViewState?.expandedIDs ?? new List(); + + + + var treeViewAnimator = treeViewController?.GetMemberValue("m_ExpansionAnimator"); + var treeViewAnimatorSetup = treeViewAnimator?.GetMemberValue("m_Setup"); + + treeViewAnimatesScroll = treeViewController?.GetMemberValue("m_FramingAnimFloat").isAnimating ?? false; + + treeViewAnimatesExpansion = treeViewAnimator?.GetMemberValue("isAnimating") ?? false; + + } + + bool isOneColumn; + + object treeViewController; + object treeViewControllerData; + + public float currentScrollPos; + + public List expandedIds = new(); + + public bool treeViewAnimatesScroll; + public bool treeViewAnimatesExpansion; + + public int GetRowIndex(int instanceId) + { + return treeViewControllerData.InvokeMethod("GetRow", instanceId); + } + + + + + + + + + + + + + + + + + public void ToggleExpanded(TreeViewItem item) + { + SetExpanded_withAnimation(item.id, !expandedIds.Contains(item.id)); + + window.Repaint(); + + } + + public void CollapseAll() + { + + var idsToCollapse_roots = expandedIds.Where(id => EditorUtility.InstanceIDToObject(id).GetPath() is string path && + path.HasParentPath() && + (path.GetParentPath() == "Assets" || path.GetParentPath() == "Packages")); + + + var idsToCollapse_children = expandedIds.Where(id => EditorUtility.InstanceIDToObject(id).GetPath() is string path && + !path.IsNullOrEmpty() && + path != "Assets" && + path != "Packages" && + !idsToCollapse_roots.Contains(id)); + + + expandQueue_toCollapseAfterAnimation = idsToCollapse_children.ToList(); + + expandQueue_toAnimate = idsToCollapse_roots.Select(id => new ExpandQueueEntry { id = id, expand = false }) + .OrderBy(row => GetRowIndex(row.id)).ToList(); + + + StartScrollAnimation(targetScrollPos: 0); + + + window.Repaint(); + + } + + public void Isolate(TreeViewItem targetItem) + { + + List getParents(TreeViewItem item) + { + var parents = new List(); + + while (item.parent != null) + parents.Add(item = item.parent); + + return parents; + + } + + var targetItemParents = getParents(targetItem); + + + + var expandedVisibleItems = new List(); + + foreach (var expandedId in expandedIds) + if (GetRowIndex(expandedId) is int rowIndex && rowIndex != -1) + expandedVisibleItems.Add(treeViewControllerData.InvokeMethod("GetItem", rowIndex)); + + + + var itemsToCollapse = expandedVisibleItems.ToList(); + + itemsToCollapse.Remove(targetItem); + itemsToCollapse.RemoveAll(r => targetItemParents.Contains(r)); + itemsToCollapse.RemoveAll(r => itemsToCollapse.Intersect(getParents(r)).Any()); + + + + expandQueue_toAnimate = itemsToCollapse.Select(item => new ExpandQueueEntry { id = item.id, expand = false }) + .Append(new ExpandQueueEntry { id = targetItem.id, expand = true }) + .OrderBy(r => GetRowIndex(r.id)).ToList(); + + + window.Repaint(); + + } + + + + + + public void StartExpandAnimation(List targetExpandedIds) + { + + var toExpand = targetExpandedIds.Except(expandedIds).ToHashSet(); + var toCollapse = expandedIds.Except(targetExpandedIds).ToHashSet(); + + + + // hanlde non-animated expansions/collapses + + bool hasParentToCollapse(int id) + { + var o = Resources.InstanceIDToObject(id); + + if (!o) return false; + + + var assetPath = AssetDatabase.GetAssetPath(o); + + if (!assetPath.HasParentPath()) return false; + if (assetPath == "Assets") return false; + + + var parentAsset = AssetDatabase.LoadAssetAtPath(assetPath.GetParentPath()); + + if (!parentAsset) return false; // packages item + + + var parentId = parentAsset.GetInstanceID(); + + return toCollapse.Contains(parentId) + || hasParentToCollapse(parentId); + + } + bool areAllParentsExpanded(int id) + { + var o = Resources.InstanceIDToObject(id); + + if (!o) return true; + + + var assetPath = AssetDatabase.GetAssetPath(o); + + if (!assetPath.HasParentPath()) return true; + if (assetPath == "Assets") return true; + + + var parentAsset = AssetDatabase.LoadAssetAtPath(assetPath.GetParentPath()); + + if (!parentAsset) return true; // packages item + + + var parentId = parentAsset.GetInstanceID(); + + return expandedIds.Contains(parentId) + && areAllParentsExpanded(parentId); + + } + + var toExpand_beforeAnimation = toExpand.Where(id => !areAllParentsExpanded(id)).ToHashSet(); + var toCollapse_afterAnimation = toCollapse.Where(id => hasParentToCollapse(id)).ToHashSet(); + + + foreach (var id in toExpand_beforeAnimation) + SetExpanded_withoutAnimation(id, true); + + foreach (var id in toCollapse_afterAnimation) + expandQueue_toCollapseAfterAnimation.Add(id); + + + toExpand.ExceptWith(toExpand_beforeAnimation); + toCollapse.ExceptWith(toCollapse_afterAnimation); + + + + + // setup animation + + expandQueue_toAnimate = toCollapse.Select(id => new ExpandQueueEntry { id = id, expand = false }) + .Concat(toExpand.Select(id => new ExpandQueueEntry { id = id, expand = true })) + .OrderBy(r => GetRowIndex(r.id)).ToList(); + + } + + public void SetExpandedIds(List targetExpandedIds) + { + treeViewControllerData.InvokeMethod("SetExpandedIDs", targetExpandedIds.ToArray()); + } + public void SetExpanded_withAnimation(int instanceId, bool expanded) + { + treeViewController.InvokeMethod("ChangeFoldingForSingleItem", instanceId, expanded); + } + public void SetExpanded_withoutAnimation(int instanceId, bool expanded) + { + treeViewControllerData.InvokeMethod("SetExpanded", instanceId, expanded); + } + + + + public void StartScrollAnimation(float targetScrollPos) + { + if (targetScrollPos.DistanceTo(currentScrollPos) < .05f) return; + + this.targetScrollPos = targetScrollPos; + + animatingScroll = true; + + } + + public void SetScrollPos(float targetScrollPos) + { + treeViewController.GetPropertyValue("state").scrollPos = Vector2.up * targetScrollPos; + } + + + + + public void RevealFolder(string path, bool expand, bool highlight, bool snapToTopMargin) + { + + int getId(string path) => AssetDatabase.LoadAssetAtPath(path).GetInstanceID(); + + + var idsToExpand = new List(); + + if (expand) + idsToExpand.Add(getId(path)); + + var cur = path; + while (!(cur = cur.GetParentPath()).IsNullOrEmpty()) + idsToExpand.Add(getId(cur)); + + idsToExpand.RemoveAll(r => expandedIds.Contains(r)); + + + + + foreach (var id in idsToExpand.SkipLast(1)) + SetExpanded_withoutAnimation(id, true); + + if (idsToExpand.Any()) + SetExpanded_withAnimation(idsToExpand.Last(), true); + + + + + var rowCount = treeViewControllerData.GetMemberValue("m_Rows").Count; + var maxScrollPos = rowCount * 16 - window.position.height + (isOneColumn ? 49.9f : 45.9f); + + var rowIndex = treeViewControllerData.InvokeMethod("GetRow", getId(path)); + var rowPos = rowIndex * 16f + (isOneColumn ? 11 : 23); + + var scrollAreaHeight = window.GetMemberValue("m_TreeViewRect").height; + + + + + var margin = 48; + + var targetScrollPos = 0f; + + if (snapToTopMargin) + targetScrollPos = (rowPos - margin).Min(maxScrollPos) + .Max(0); + else + targetScrollPos = currentScrollPos.Min(rowPos - margin) + .Max(rowPos - scrollAreaHeight + margin) + .Min(maxScrollPos) + .Max(0); + if (targetScrollPos < 25) + targetScrollPos = 0; + + StartScrollAnimation(targetScrollPos); + + + + + + if (!highlight) return; + + highlightAmount = 2.2f; + + animatingHighlight = true; + + folderToHighlight = path; + + + } + + public void OpenFolder(string path) + { + // update search + window.GetMemberValue("m_SearchFilter").InvokeMethod("ClearSearch"); + window.GetMemberValue("m_SearchFilter").SetMemberValue("folders", new[] { path }); + + + // update folder tree + window.GetMemberValue("m_FolderTree").InvokeMethod("SetSelection", new[] { AssetDatabase.LoadAssetAtPath(path).GetInstanceID() }, false); + + + // update list area + var listAreaRect = window.GetMemberValue("m_ListAreaRect"); + var searchFilter = window.GetMemberValue("m_SearchFilter"); + var checkThumbnails = false; + var assetToInstanceId = (System.Func)((s) => typeof(AssetDatabase).InvokeMethod("GetMainAssetInstanceID", s)); + + window.GetMemberValue("m_ListArea")?.InvokeMethod("InitForSearch", listAreaRect, HierarchyType.Assets, searchFilter, checkThumbnails, assetToInstanceId); + + + // updat breadcrumbs + window.GetMemberValue("m_BreadCrumbs").Clear(); + + + + // pretty much the same as ProjectBrowser.ShowFolderContents() + // but without m_FolderTree.SetSelection() + + } + + + + + + + + + + + + + public VFoldersController(EditorWindow window) => this.window = window; + + public EditorWindow window; + + public VFoldersGUI gui => VFolders.guis_byWindow[window]; + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersController.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersController.cs.meta new file mode 100644 index 0000000..290180f --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 380edf2cfb553464d9f7ec73602e93d1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersData.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersData.cs new file mode 100644 index 0000000..630bf5c --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersData.cs @@ -0,0 +1,145 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Serialization; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VFolders +{ + public class VFoldersData : ScriptableObject, ISerializationCallbackReceiver + { + + public SerializableDictionary folderDatas_byGuid = new(); + + [System.Serializable] + public class FolderData + { + public string iconNameOrGuid = ""; + public int colorIndex = 0; + + public bool isIconRecursive; + public bool isColorRecursive; + + } + + public void OnBeforeSerialize() => VFolders.OnDataSerialization(); + public void OnAfterDeserialize() { } + + + + + public List bookmarks = new(); + + [System.Serializable] + public class Bookmark + { + + public string name => isDeleted ? "Deleted" : guid.ToPath().GetFilename(); + + + public bool isDeleted => !AssetDatabase.IsValidFolder(guid.ToPath()); + + + + public Bookmark(Object o) => guid = o.GetGuid(); + + public string guid; + + } + + + + + + + + + [CustomEditor(typeof(VFoldersData))] + class Editor : UnityEditor.Editor + { + public override void OnInspectorGUI() + { + var style = new GUIStyle(EditorStyles.label) { wordWrap = true }; + + void normal() + { + if (storeDataInMetaFiles) return; + + SetGUIEnabled(false); + BeginIndent(0); + + Space(10); + EditorGUILayout.LabelField("This file stores data about which icons and colors are assigned to folders, along with bookmarks from navigation bar.", style); + + Space(6); + GUILayout.Label("If there are multiple people working on the project, it's better to store icon and color data in .meta files of folders to avoid merge conflicts. To do that, click the ⋮ button at the top right corner and enable Team Mode.", style); + + EndIndent(10); + ResetGUIEnabled(); + } + void meta() + { + if (!storeDataInMetaFiles) return; + + SetGUIEnabled(false); + BeginIndent(0); + + Space(10); + EditorGUILayout.LabelField("Icon and color data is currently stored in folders .meta files of folders, and this file only contains bookmarks from navigation bar.", style); + + Space(6); + GUILayout.Label("If you want all data to be stored in this file, click the ⋮ button at the top right corner and disable Team Mode.", style); + + EndIndent(10); + ResetGUIEnabled(); + } + + normal(); + meta(); + + } + } + + public static bool storeDataInMetaFiles { get => EditorPrefsCached.GetBool("vFolders-teamModeEnabled", false); set => EditorPrefsCached.SetBool("vFolders-teamModeEnabled", value); } + + + + [ContextMenu("Enable Team Mode", isValidateFunction: false, priority: 1)] + public void EnableTeamMode() + { + var option = EditorUtility.DisplayDialogComplex("Licensing notice", + "To use vFolders 2 within a team, licenses must be purchased for each individual user as per the Asset Store EULA.\n\n Sharing one license across the team is illegal and considered piracy.", + "Acknowledge", + "Cancel", + "Purchase more seats"); + if (option == 0) + storeDataInMetaFiles = true; + + if (option == 2) + Application.OpenURL("https://prf.hn/click/camref:1100lGLBn/pubref:teammode/destination:https://assetstore.unity.com/packages/tools/utilities/vfolders-2-255470"); + // Application.OpenURL("https://assetstore.unity.com/packages/slug/255470"); + + } + + [ContextMenu("Disable Team Mode", isValidateFunction: false, priority: 2)] + public void DisableTeamMode() => storeDataInMetaFiles = false; + + [ContextMenu("Enable Team Mode", isValidateFunction: true, priority: 1)] bool asd() => !storeDataInMetaFiles; + [ContextMenu("Disable Team Mode", isValidateFunction: true, priority: 2)] bool ads() => storeDataInMetaFiles; + + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersData.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersData.cs.meta new file mode 100644 index 0000000..30bf11c --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: adb9c12a8c6c949f7af83a3d238359d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersGUI.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersGUI.cs new file mode 100644 index 0000000..b483950 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersGUI.cs @@ -0,0 +1,1325 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; +using static VFolders.VFolders; +using static VFolders.VFoldersData; +using static VFolders.VFoldersCache; + + + +namespace VFolders +{ + public class VFoldersGUI + { + + public void RowGUI(Rect rowRect, string guid, int instanceId) + { + var fullRowRect = rowRect.AddWidthFromRight(rowRect.x); + + var isRowHovered = fullRowRect.IsHovered(); + + var isFolder = AssetDatabase.IsValidFolder(guid.ToPath()); + var isAsset = !isFolder && !guid.IsNullOrEmpty(); + var isFavorite = !isFolder && !isAsset && rowRect.x != 16; + + var isFavoritesRoot = rowRect.x == 16 && !isFolder && rowRect.y == 0; + var isAssetsRoot = rowRect.x == 16 && isFolder && guid.ToPath() == "Assets"; + var isPackagesRoot = rowRect.x == 16 && !isFavoritesRoot && !isAssetsRoot && guid.IsNullOrEmpty(); + + var isListArea = rowRect.x == 14 || !isFolder; + + var useMinimalMode = VFoldersMenu.minimalModeEnabled && !isListArea; + var useBackgroundColors = (VFoldersMenu.backgroundColorsEnabled || useMinimalMode) && !isListArea; + var useHierarchyLines = VFoldersMenu.hierarchyLinesEnabled && !isListArea; + var useContentMinimap = VFoldersMenu.contentMinimapEnabled; + var useZebraStriping = VFoldersMenu.zebraStripingEnabled; + + + + TreeViewItem treeItem = null; + + var isRowSelected = false; + var isRowBeingRenamed = false; + + void setObjects() + { + if (isListArea) return; + + if (!curEvent.isRepaint) // only needed for drawing, + if (!curEvent.isMouseUp) // altClick, + if (!isRowHovered) // and setting last hovered tree item + return; + + + void set_treeItem_byRect() + { + if (treeViewAnimatesExpansion) return; + + var offest = isTwoColumns ? -15 : -4; + + + if ((rowRect.y + offest) % 16 != 0) return; + + var rowIndex = ((rowRect.y + offest) / 16).ToInt(); + + + if (rowIndex < 0 || rowIndex >= rows.Count) return; + if (rows == null) return; + + treeItem = rows[rowIndex]; + + } + void set_treeItem_byInstanceId() + { + if (treeItem != null) return; + if (isFavorite || isFavoritesRoot) return; + + treeItem = treeViewController?.InvokeMethod("FindItem", instanceId); + + } + + set_treeItem_byRect(); + set_treeItem_byInstanceId(); + + } + void setState() + { + void set_isRowSelected_oneColumn() + { + if (!curEvent.isRepaint) return; + if (isListArea) return; + if (treeItem == null) return; + if (isTwoColumns) return; + + + var dragging = treeView_dragSelectionList?.Any() == true; + + isRowSelected = dragging ? (treeView_dragSelectionList.Contains(treeItem.id)) : Selection.Contains(treeItem.id); + + } + void set_isRowSelected_twoColumns() + { + if (!curEvent.isRepaint) return; + if (isListArea) return; + if (treeItem == null) return; + if (!isTwoColumns) return; + + + var dragging = treeView_dragSelectionList != null + && treeView_dragSelectionList.Any(); + + isRowSelected = dragging ? (treeView_dragSelectionList.Contains(treeItem.id)) : treeView_normalSelectionList != null && treeView_normalSelectionList.Contains(treeItem.id); + + } + void set_lastHovered() + { + if (isListArea) return; + if (!isRowHovered) return; + + lastHoveredRowRect_screenSpace = EditorGUIUtility.GUIToScreenRect(fullRowRect); + lastHoveredTreeItem = treeItem; + + } + void set_lastKnownMousePosition() + { + if (!curEvent.isRepaint) return; + + lastKnownMousePosition_screenSpace = curEvent.mousePosition_screenSpace; + + } + + set_isRowSelected_oneColumn(); + set_isRowSelected_twoColumns(); + set_lastHovered(); + set_lastKnownMousePosition(); + + isRowBeingRenamed = renamingRow && isRowSelected; + + } + + void drawing() + { + if (!curEvent.isRepaint) { hierarchyLines_isFirstRowDrawn = false; return; } + + + + var folderInfo = GetFolderInfo(guid); + + var rowHasColor = useBackgroundColors && folderInfo.hasColor; + + var rowHasCustomIcon = !isFolder ? false + : useBackgroundColors ? folderInfo.hasIcon + : folderInfo.hasIcon || folderInfo.hasColor; + var rowHasIcon = !useMinimalMode ? true + : rowHasCustomIcon || isAsset || isFavorite || isRowBeingRenamed; + + + var hideName = rowHasColor || !rowHasIcon; + var hideDefaultIcon = rowHasCustomIcon || !rowHasIcon; + + var drawTriangle = rowHasColor && treeItem?.hasChildren == true; + var drawName = rowHasColor || !rowHasIcon; + var drawDefaultIcon = (rowHasColor && !rowHasCustomIcon) && rowHasIcon; + + var makeTriangleBrighter = rowHasColor && isDarkTheme; + var makeNameBrighter = rowHasColor && isDarkTheme; + var makeIconBrighter = rowHasColor && isFolder; + var makeExtraSpaceForVCSIcons = isVersionControlEnabled; + + + var defaultBackground = isListArea ? Greyscale(isDarkTheme ? .2f : .75f) + : GUIColors.windowBackground; + if (isRowSelected) + if (!isRowBeingRenamed) + defaultBackground = isTreeFocused ? GUIColors.selectedBackground + : Greyscale(isDarkTheme ? .3f : .68f); + + + + void hideName_() + { + if (!hideName) return; + if (isFavorite) return; + + var name = isListArea ? guid.ToPath().GetFilename() : treeItem != null ? treeItem.displayName : "Favorites"; + + var nameRect = rowRect.SetWidth(name.GetLabelWidth(isBold: isFavorite || isAssetsRoot || isPackagesRoot) + 6).MoveX(16).MoveX(isListArea ? 3 : 0); + + nameRect.Draw(defaultBackground); + + } + void hideDefaultIcon_() + { + if (!hideDefaultIcon) return; + + var iconRect = rowRect.SetWidth(16).MoveX(isListArea ? 3 : 0); + + if (makeExtraSpaceForVCSIcons) + iconRect = iconRect.MoveX(8); + + + if (isListArea) + { + SetGUIColor(defaultBackground); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon("Folder On Icon")); + + ResetGUIColor(); + + } + else + iconRect.Draw(defaultBackground); + + } + + void color() + { + if (!rowHasColor) return; + + + + var color = (isDarkTheme ? Color.Lerp(folderInfo.color, Greyscale(.05f), .42f) + : Color.Lerp(folderInfo.color, Greyscale(.8f), .5f)).SetAlpha(1); + if (isRowHovered) + color *= isDarkTheme ? 1.1f : .92f; + + if (isRowSelected) + color *= isDarkTheme ? 1.2f : .8f; + + if (palette?.colorGradientsEnabled == false) + color = MathUtil.Lerp(color, Greyscale(.2f), isDarkTheme ? .25f : .03f); + + if (folderInfo.hasColorByRecursion) + color = MathUtil.Lerp(color, Greyscale(isDarkTheme ? .24f : .8f), .45f); + + + + + + + var colorRect = rowRect.AddWidthFromRight(30).AddWidth(16); + + if (folderInfo.hasColorByRecursion) + colorRect = colorRect.AddWidthFromRight(folderInfo.maxColorRecursionDepth * 14); + + if (!isRowSelected && !folderInfo.hasColorByRecursion) + colorRect = colorRect.AddHeightFromMid(EditorGUIUtility.pixelsPerPoint >= 2 ? -.5f : -1); + + if (folderInfo.hasColorByRecursion) + colorRect = colorRect.MoveY(EditorGUIUtility.pixelsPerPoint >= 2 ? -.25f : -.5f); + + if (palette?.colorGradientsEnabled == false) { colorRect.Draw(color); return; } + + + var hasLeftGradient = colorRect.x > 4; + + if (hasLeftGradient) + colorRect = colorRect.AddWidthFromRight(3); + + + + var leftGradientWith = hasLeftGradient ? 22 : 0; + var rightGradientWidth = (fullRowRect.width * .77f).Min(colorRect.width - leftGradientWith); + + var leftGradientRect = colorRect.SetWidth(leftGradientWith); + var rightGradientRect = colorRect.SetWidthFromRight(rightGradientWidth); + + var flatColorRect = colorRect.SetX(leftGradientRect.xMax).SetXMax(rightGradientRect.x); + + + + + + + leftGradientRect.AddWidth(1).DrawCurtainLeft(color); + + flatColorRect.AddWidth(1).Draw(color); + + rightGradientRect.Draw(color.MultiplyAlpha(.1f)); + rightGradientRect.DrawCurtainRight(color); + + + } + void triangle() + { + if (!drawTriangle) return; + + + var triangleRect = rowRect.MoveX(-15).SetWidth(16).Resize(-1); + + GUI.Label(triangleRect, EditorGUIUtility.IconContent(expandedIds.Contains(treeItem.id) ? "IN_foldout_on" : "IN_foldout")); + + + if (!makeTriangleBrighter) return; + + GUI.Label(triangleRect, EditorGUIUtility.IconContent(expandedIds.Contains(treeItem.id) ? "IN_foldout_on" : "IN_foldout")); + + } + void name() + { + if (!drawName) return; + if (isRowBeingRenamed) return; + + + var nameRect = rowRect.MoveX(18).AddHeight(1); ; + + if (isListArea) + nameRect = nameRect.MoveX(3); + + if (!rowHasIcon) + nameRect = nameRect.MoveX(-17); + + if (makeNameBrighter) + nameRect = nameRect.MoveX(-2).MoveY(-.5f); + + if (makeExtraSpaceForVCSIcons) + nameRect = nameRect.MoveX(16); + + + + var styleName = makeNameBrighter ? "WhiteLabel" : "TV Line"; + + if (isFavoritesRoot || isAssetsRoot || isPackagesRoot) + styleName = "BoldLabel"; + + + + var name = isFavoritesRoot ? "Favorites" : + isPackagesRoot ? "Packages" : + isListArea || treeItem == null ? guid.ToPath().GetFilename() : + treeItem.displayName; + + + + if (makeNameBrighter) + SetGUIColor(Greyscale(isRowSelected ? 1 : .93f)); + + GUI.skin.GetStyle(styleName).Draw(nameRect, name, false, false, isRowSelected, isTreeFocused && styleName != "BoldLabel"); + + if (makeNameBrighter) + ResetGUIColor(); + + } + void defaultIcon() + { + if (!drawDefaultIcon) return; + + + var iconRect = rowRect.SetWidth(16).MoveX(isListArea ? 3 : 0); + + if (makeExtraSpaceForVCSIcons) + iconRect = iconRect.MoveX(8); + + + var icon = isAsset ? AssetDatabase.GetCachedIcon(guid.ToPath()) + : makeIconBrighter ? EditorIcons.GetIcon(folderInfo.folderState.isEmpty ? "FolderEmpty On Icon" : "Folder On Icon") + : EditorIcons.GetIcon(folderInfo.folderState.isEmpty ? "FolderEmpty Icon" : "Folder Icon"); + + + SetLabelAlignmentCenter(); + + if (makeIconBrighter) + SetGUIColor(Greyscale(.88f)); + + GUI.DrawTexture(iconRect, icon); + + if (makeIconBrighter) + ResetGUIColor(); + + ResetLabelStyle(); + + } + void customIcon() + { + if (!rowHasCustomIcon) return; + + + var icon = GetSmallFolderIcon(folderInfo, removeColor: useBackgroundColors); + + if (useMinimalMode && folderInfo.hasIcon) + icon = EditorIcons.GetIcon(folderInfo.iconNameOrPath); + + if (!icon) return; + + + var iconRect = rowRect.SetWidth(16).MoveX(isListArea ? 3 : 0); + + iconRect = iconRect.SetWidth(iconRect.height / icon.height * icon.width); + + if (makeExtraSpaceForVCSIcons) + iconRect = iconRect.MoveX(7); + + + GUI.DrawTexture(iconRect, icon); + + } + void versionControlOverlay() + { + if (!isFolder) return; + if (!hideDefaultIcon && !rowHasColor) return; + + if (!isVersionControlEnabled) return; + + typeof(Editor).Assembly.GetType("UnityEditorInternal.VersionControl.ProjectHooks").InvokeMethod("HandleVersionControlOverlays", guid, rowRect.MoveX(3)); + + } + + void hierarchyLines() + { + if (!useHierarchyLines) return; + if (isListArea) return; + if (treeItem == null) return; + + + var lineThickness = 1f; + var lineColor = isDarkTheme ? Greyscale(1, .165f) : Greyscale(0, .23f); + + var depth = ((rowRect.x - 16) / 14).RoundToInt(); + + bool isLastChild(TreeViewItem item) => item.parent?.children?.LastOrDefault() == item; + bool hasChilren(TreeViewItem item) => item.children != null && item.children.Count > 0; + + void calcVerticalGaps_beforeFirstRowDrawn() + { + if (hierarchyLines_isFirstRowDrawn) return; + + hierarchyLines_verticalGaps.Clear(); + + var curItem = treeItem.parent; + var curDepth = depth - 1; + + while (curItem != null && curItem.parent != null) + { + if (isLastChild(curItem)) + hierarchyLines_verticalGaps.Add(curDepth - 1); + + curItem = curItem.parent; + curDepth--; + } + + } + void updateVerticalGaps_beforeNextRowDrawn() + { + if (isLastChild(treeItem)) + hierarchyLines_verticalGaps.Add(depth - 1); + + if (depth < hierarchyLines_prevRowDepth) + hierarchyLines_verticalGaps.RemoveAll(r => r >= depth); + + } + + void drawVerticals() + { + for (int i = 1; i < depth; i++) + if (!hierarchyLines_verticalGaps.Contains(i)) + rowRect.SetX(9 + i * 14 - lineThickness / 2) + .SetWidth(lineThickness) + .SetHeight(isLastChild(treeItem) && i == depth - 1 ? 8 + lineThickness / 2 : 16) + .Draw(lineColor); + + } + void drawHorizontals() + { + if (depth == 0) return; + if (depth == 1) return; + + rowRect.MoveX(-21) + .SetHeightFromMid(lineThickness) + .SetWidth(hasChilren(treeItem) ? 7 : 17) + .AddWidthFromRight(-lineThickness / 2f) + .Draw(lineColor); + + } + + + + calcVerticalGaps_beforeFirstRowDrawn(); + + drawVerticals(); + drawHorizontals(); + + updateVerticalGaps_beforeNextRowDrawn(); + + hierarchyLines_prevRowDepth = depth; + hierarchyLines_isFirstRowDrawn = true; + + } + void zebraStriping_() + { + if (!useZebraStriping) return; + + + var contrast = isDarkTheme ? .033f : .05f; + + + var firstRowY = isOneColumn ? 4 : -1; + + var t = 1 - (rowRect.y - firstRowY).PingPong(16f) / 16f; + + if (isRowHovered || isRowSelected) + if (!VFoldersPaletteWindow.instance || VFoldersPaletteWindow.instance.guids.Contains(guid)) + t = 1; + + if (t.Approx(0)) return; + + + + fullRowRect.Draw(Greyscale(isDarkTheme ? 1 : 0, contrast * t)); + + + } + void highlight_() + { + if (!controller.animatingHighlight) return; + if (guid.ToPath() != controller.folderToHighlight) return; + + + var highlightBrightness = isDarkTheme ? .16f : .35f; + + + var highlightAmount = controller.highlightAmount.Clamp01(); + + highlightAmount = highlightAmount * highlightAmount * (3 - 2 * highlightAmount); + + + fullRowRect.AddWidthFromRight(123).Draw(Greyscale(1, highlightBrightness * highlightAmount)); + + } + void hoverHighlight() + { + if (!VFoldersPaletteWindow.instance) + if (!rowRect.IsHovered()) return; + + if (VFoldersPaletteWindow.instance) + if (VFoldersPaletteWindow.instance.guids.Count > 1 || VFoldersPaletteWindow.instance.guids.First() != guid || !paletteOpenedOnRow) return; + + + fullRowRect.Draw(Greyscale(isDarkTheme ? 1 : 0, .06f)); + + } + + void contentMinimap() + { + if (!isFolder) return; + if (!useContentMinimap) return; + if (guid.IsNullOrEmpty()) return; + + void icon(Rect rect, string name) + { + var icon = EditorIcons.GetIcon(name); + + if (!icon) return; + + + SetGUIColor(Greyscale(1, isDarkTheme ? .5f : .7f)); + + GUI.DrawTexture(rect, icon); + + ResetGUIColor(); + + } + + + var iconDistance = 13; + var minButtonX = rowRect.x + guid.ToPath().GetFilename().GetLabelWidth() + iconDistance + 2; + var iconRect = fullRowRect.SetWidthFromRight(iconDistance).SetSizeFromMid(12, 12).MoveX(-1.5f); + + foreach (var iconName in folderInfo.folderState.contentMinimapIconNames) + { + if (iconRect.x < minButtonX) continue; + + icon(iconRect, iconName); + + iconRect = iconRect.MoveX(-iconDistance); + + } + + } + + + + fullRowRect.MarkInteractive(); + + hideName_(); + hideDefaultIcon_(); + + color(); + triangle(); + defaultIcon(); + customIcon(); + versionControlOverlay(); + name(); + + hierarchyLines(); + zebraStriping_(); + highlight_(); + hoverHighlight(); + + contentMinimap(); + + } + + void altDrag() + { + if (!curEvent.holdingAlt) return; + + void mouseDown() + { + if (!curEvent.isMouseDown) return; + if (!isRowHovered) return; + + mouseDownPos = curEvent.mousePosition; + + } + void mouseDrag() + { + if (!curEvent.isMouseDrag) return; + if ((curEvent.mousePosition - mouseDownPos).magnitude < 5) return; + if (!rowRect.Contains(mouseDownPos)) return; + if (!rowRect.Contains(curEvent.mousePosition - curEvent.mouseDelta)) return; + if (DragAndDrop.objectReferences.Any()) return; + + DragAndDrop.PrepareStartDrag(); + DragAndDrop.objectReferences = new[] { AssetDatabase.LoadAssetAtPath(guid.ToPath()) }; + DragAndDrop.StartDrag(guid.ToPath().GetFilename()); + + } + + mouseDown(); + mouseDrag(); + + // altdrag has to be set up manually before altClick + // because altClick will use() mouseDown event to prevent selection change + + } + void altClick() + { + if (!isRowHovered) return; + if (!curEvent.holdingAlt) return; + if (!isFolder) return; + + void mouseDown() + { + if (!curEvent.isMouseDown) return; + + curEvent.Use(); + + } + void mouseUp() + { + if (!curEvent.isMouseUp) return; + + var selectedGuids = isListArea + ? + Selection.objects.Where(r => r is DefaultAsset).Select(r => r.GetPath().ToGuid()) + : +#if UNITY_2021_1_OR_NEWER + treeViewController.GetFieldValue("m_CachedSelection").GetFieldValue>("m_List") +#else + treeViewController?.GetMemberValue("state").GetMemberValue>("selectedIDs") +#endif + .Select(id => treeViewController.InvokeMethod("FindItem", id)) + .Where(r => r?.GetType().Name == "FolderTreeItem") + .Select(r => r.GetPropertyValue("Guid")) + .Where(r => r != null); + + + var editMultiSelection = selectedGuids.Count() > 1 && selectedGuids.Contains(guid); + + var guidsToEdit = (editMultiSelection ? selectedGuids.Where(r => AssetDatabase.IsValidFolder(r.ToPath())) : new[] { guid }).ToList(); + + + if (VFoldersPaletteWindow.instance && VFoldersPaletteWindow.instance.guids.SequenceEqual(guidsToEdit)) { VFoldersPaletteWindow.instance.Close(); return; } + + var openNearRect = rowRect; + var position = EditorGUIUtility.GUIToScreenPoint(new Vector2(curEvent.mousePosition.x + 20, openNearRect.y - 13)); + + if (!VFoldersPaletteWindow.instance) + VFoldersPaletteWindow.CreateInstance(position); + + VFoldersPaletteWindow.instance.Init(guidsToEdit); + VFoldersPaletteWindow.instance.Focus(); + + VFoldersPaletteWindow.instance.targetPosition = position; + + paletteOpenedOnRow = true; + paletteOpenedOnCell = false; + + if (editMultiSelection) + Selection.objects = null; + } + + mouseDown(); + mouseUp(); + + } + + + + setObjects(); + setState(); + + drawing(); + + altDrag(); + altClick(); + + } + + List hierarchyLines_verticalGaps = new(); + bool hierarchyLines_isFirstRowDrawn; + int hierarchyLines_prevRowDepth; + + Vector2 mouseDownPos; + + bool paletteOpenedOnRow; + + + + + public void CellGUI(Rect cellRect, string guid, int instanceId) + { + var isFolder = AssetDatabase.IsValidFolder(guid.ToPath()); + + void setLastVisibleSelectedForAltClick() + { + if (!isFolder) return; + if (!curEvent.isRepaint) return; + if (!Selection.objects.Contains(AssetDatabase.LoadAssetAtPath(guid.ToPath()))) return; + + lastVisibleSelectedCellRect = cellRect; + + } + + void drawing() + { + if (!curEvent.isRepaint) { namesDrawnForGuids.Clear(); return; } + + var folderInfo = isFolder ? GetFolderInfo(guid) : null; + var showingSecondNameLine = false; + + void hideIcon() + { + if (!isFolder) return; + if (!folderInfo.hasColor) return; + + var maskRect = cellRect.SetHeight(cellRect.width).Resize(4); + + // if (isVersionControlEnabled) + // maskRect = maskRect.SetHeightFromBottom(maskRect.height * 2 / 3); + + + maskRect.Draw(EditorGUIUtility.isProSkin ? Greyscale(.2f) : Greyscale(.75f)); + + } + void icon() + { + if (!isFolder) return; + if (!folderInfo.hasColor && !folderInfo.hasIcon) return; + + DrawBigFolderIcon(cellRect, folderInfo); + + } + void versionControlOverlay() + { + if (!isFolder) return; + if (!folderInfo.hasColor) return; + + if (!isVersionControlEnabled) return; + + typeof(Editor).Assembly.GetType("UnityEditorInternal.VersionControl.ProjectHooks").InvokeMethod("HandleVersionControlOverlays", guid, cellRect); + + } + void twoLineName() + { + if (namesDrawnForGuids.Contains(guid)) return; // disables two-line names on subassets + if (!VFoldersMenu.twoLineNamesEnabled) return; + + + var isSelected = listArea_dragSelectionList.Any() ? listArea_dragSelectionList.Contains(instanceId) : Selection.instanceIDs.Contains(instanceId); + + var isCellBeingRenamed = isSelected && renamingCell; + + if (isCellBeingRenamed) return; + + + + var maxLineWidth = cellRect.width + 14; + + var name = guid.ToPath().GetFilename(withExtension: false); // Resources.InstanceIDToObject(instanceId).name; + + + string firstLine = null; + string secondLine = null; + + Rect firstLineRect = default; + Rect secondLineRect = default; + + + void getCachedLines() + { + if (twoLineNamesCachedForCellWidth != cellRect.width) + twoLineNamesCache.Clear(); + + if (!twoLineNamesCache.ContainsKey(name)) return; + + + firstLine = twoLineNamesCache[name].Item1; + secondLine = twoLineNamesCache[name].Item2; + + } + void calcLines() + { + if (twoLineNamesCache.ContainsKey(name)) return; + if (name.GetLabelWidth() < maxLineWidth) + { + firstLine = name; + + twoLineNamesCache[name] = (firstLine, secondLine); + twoLineNamesCachedForCellWidth = cellRect.width; + + return; + + } + + + var separators = new[] { ' ', '_', '-', '.' }; + + var splitIndexes_separators = Enumerable.Range(1, name.Length - 2).Where(i => separators.Contains(name[i])).Select(i => i + 1); + var splitIndexes_camelcase = Enumerable.Range(2, name.Length - 2).Where(i => name[i].IsUpper() && name[i - 1].IsLower() && !separators.Contains(name[i - 1])); + var splitIndexes_all = Enumerable.Range(1, name.Length - 1); + + bool splitSucceeded = false; + + + string fit(string s, IEnumerable splitIndexes, bool splitToRight, bool addEllipsis = false) + { + foreach (var i in splitToRight ? splitIndexes : splitIndexes.Reverse()) + { + var substring = splitToRight ? s[i..] : s[..i]; + + substring = substring.Trim(); + + if (addEllipsis) + substring += "…"; + + + + if (substring.GetLabelWidth() < maxLineWidth) + return substring; + + } + + return null; + + } + + void truncate() + { + if (splitIndexes_separators.Any()) return; + if (splitIndexes_camelcase.Any()) return; + + firstLine = fit(name, splitIndexes_all, splitToRight: false, addEllipsis: true); + + splitSucceeded = true; + + } + void split_clean() + { + if (splitSucceeded) return; + + + firstLine = null; + firstLine ??= fit(name, splitIndexes_separators, splitToRight: false); + firstLine ??= fit(name, splitIndexes_camelcase, splitToRight: false); + + if (firstLine == null) return; + + + + secondLine = name.Remove(firstLine); + + splitSucceeded = secondLine.GetLabelWidth() < maxLineWidth; + + } + void split_withEllipsis() + { + if (splitSucceeded) return; + + + secondLine = null; + secondLine ??= fit(name, splitIndexes_separators, splitToRight: true); + secondLine ??= fit(name, splitIndexes_camelcase, splitToRight: true); + secondLine ??= fit(name, splitIndexes_all, splitToRight: true); + + + + firstLine = name.Remove(secondLine).Trim(); + + if (firstLine.GetLabelWidth() > maxLineWidth) + firstLine = fit(firstLine, Enumerable.Range(0, firstLine.Length), splitToRight: false, addEllipsis: true); + + } + + + truncate(); + split_clean(); + split_withEllipsis(); + + firstLine = firstLine.Trim(); + secondLine = secondLine?.Trim(); + + twoLineNamesCache[name] = (firstLine, secondLine); + twoLineNamesCachedForCellWidth = cellRect.width; + + } + void calcLineRects() + { + firstLineRect = cellRect.SetHeightFromBottom(12).AddHeight(2).SetWidthFromMid(maxLineWidth).MoveY(-1); + + if (secondLine.IsNullOrEmpty()) return; + + if (isFolder) + firstLineRect = firstLineRect.MoveY(-4); + else + firstLineRect = firstLineRect.MoveY(-1); + + + + secondLineRect = firstLineRect.MoveY(12); + + } + + void hideDefaultName() + { + if (!curEvent.isRepaint) return; + + var defaultNameRect = cellRect.SetHeightFromBottom(16).AddHeight(2).AddWidthFromMid(12); + var maskColor = isDarkTheme ? Greyscale(.2f) : Greyscale(.75f); + + defaultNameRect.Draw(maskColor); + + } + void selectedbackground() + { + if (!isSelected) return; + + + var backgroundColor = isListAreaFocused ? GUIColors.selectedBackground : Greyscale(isDarkTheme ? .3f : .68f); + + + + var longestLine = secondLine?.Length > firstLine.Length ? secondLine : firstLine; + + var backgroundRect = firstLineRect.SetWidthFromMid(longestLine.GetLabelWidth(fontSize: 10) + 2).SetHeightFromMid(14); + + if (!secondLine.IsNullOrEmpty()) + backgroundRect = backgroundRect.SetYMax(secondLineRect.SetHeightFromMid(14).yMax); + + + + backgroundRect.DrawRounded(backgroundColor, 4); + + } + void drawName() + { + SetLabelAlignmentCenter(); + SetLabelFontSize(10); + SetGUIColor(isSelected && isListAreaFocused ? Greyscale(123, 123) : Greyscale(1)); + + GUI.Label(firstLineRect, firstLine); + + if (secondLine != null) + GUI.Label(secondLineRect, secondLine); + + ResetGUIColor(); + ResetLabelStyle(); + + } + + + getCachedLines(); + calcLines(); + calcLineRects(); + + hideDefaultName(); + selectedbackground(); + drawName(); + + showingSecondNameLine = secondLine != null; + + namesDrawnForGuids.Add(guid); + + } + void hoverHighlight() + { + + var highlightColor = isDarkTheme ? Greyscale(1, .058f) : Greyscale(0, .07f); + + + var highlightRect = cellRect.Resize(-2).AddHeight(4).AddWidthFromMid(4); + + if (showingSecondNameLine) + highlightRect = highlightRect.AddHeight(8); + + + + var hoverRect = cellRect.AddWidthFromMid(gridHorizontalSpacing).AddHeight(gridVerticalSpacing); + + hoverRect.MarkInteractive(); + + + + + if (!VFoldersPaletteWindow.instance) + if (!hoverRect.IsHovered()) return; + + if (VFoldersPaletteWindow.instance) + if (!VFoldersPaletteWindow.instance.guids.Contains(guid) || !paletteOpenedOnCell) return; + + + highlightRect.DrawRounded(highlightColor, 5); + + } + + + hideIcon(); + icon(); + versionControlOverlay(); + twoLineName(); + hoverHighlight(); + + } + + void altDrag() + { + if (!isFolder) return; + if (!curEvent.holdingAlt) return; + + void mouseDown() + { + if (!curEvent.isMouseDown) return; + if (!cellRect.IsHovered()) return; + + mouseDownPos = curEvent.mousePosition; + + } + void mouseDrag() + { + if (!curEvent.isMouseDrag) return; + if ((curEvent.mousePosition - mouseDownPos).magnitude < 5) return; + if (!cellRect.Contains(mouseDownPos)) return; + if (!cellRect.Contains(curEvent.mousePosition - curEvent.mouseDelta)) return; + if (DragAndDrop.objectReferences.Any()) return; + + DragAndDrop.PrepareStartDrag(); + DragAndDrop.objectReferences = new[] { AssetDatabase.LoadAssetAtPath(guid.ToPath()) }; + DragAndDrop.StartDrag(guid.ToPath().GetFilename()); + + } + + mouseDown(); + mouseDrag(); + + } + void altClick() + { + if (!isFolder) return; + if (!cellRect.IsHovered()) return; + if (!curEvent.holdingAlt) return; + + void mouseDown() + { + if (!curEvent.isMouseDown) return; + + curEvent.Use(); + + } + void mouseUp() + { + if (!curEvent.isMouseUp) return; + + var selectedFoldersGuids = Selection.objects.Where(r => r is DefaultAsset).Select(r => r.GetPath().ToGuid()); + + var editMultiSelection = selectedFoldersGuids.Count() > 1 && selectedFoldersGuids.Contains(guid); + + var guidsToEdit = (editMultiSelection ? selectedFoldersGuids : new[] { guid }).ToList(); + + + if (VFoldersPaletteWindow.instance && VFoldersPaletteWindow.instance.guids.SequenceEqual(guidsToEdit)) { VFoldersPaletteWindow.instance.Close(); return; } + + var openNearRect = editMultiSelection ? lastVisibleSelectedCellRect : cellRect; + var position = EditorGUIUtility.GUIToScreenPoint(new Vector2(openNearRect.xMax + 10, openNearRect.y - 5)); + + if (!VFoldersPaletteWindow.instance) + VFoldersPaletteWindow.CreateInstance(position); + + VFoldersPaletteWindow.instance.Init(guidsToEdit); + VFoldersPaletteWindow.instance.Focus(); + + VFoldersPaletteWindow.instance.targetPosition = position; + + paletteOpenedOnCell = true; + paletteOpenedOnRow = false; + + if (editMultiSelection) + Selection.objects = null; + } + + mouseDown(); + mouseUp(); + + } + + + setLastVisibleSelectedForAltClick(); + + drawing(); + + altDrag(); + altClick(); + + } + + Rect lastVisibleSelectedCellRect; + + HashSet namesDrawnForGuids = new(); + + Dictionary twoLineNamesCache = new(); + + float twoLineNamesCachedForCellWidth; + + bool paletteOpenedOnCell; + + + + + + + + + + + + public void UpdateState_Layout() + { + isOneColumn = window.GetFieldValue("m_ViewMode") == 0; + + + listArea = isTwoColumns ? window.GetFieldValue("m_ListArea") : null; + + treeViewController = window.GetFieldValue(isTwoColumns ? "m_FolderTree" : "m_AssetTree"); + treeViewControllerData = treeViewController?.GetPropertyValue("data"); + + rows = treeViewControllerData?.InvokeMethod>("GetRows"); + + + var treeViewState = treeViewController?.GetPropertyValue("state"); + + expandedIds = treeViewState?.expandedIDs ?? new List(); + + + + var treeViewAnimator = treeViewController?.GetMemberValue("m_ExpansionAnimator"); + + treeViewAnimatesExpansion = treeViewAnimator?.GetMemberValue("isAnimating") ?? false; + + } + public void UpdateState_Repaint() + { + var treeViewControlID = window.GetFieldValue("m_TreeViewKeyboardControlID"); + var listAreaControlID = window.GetFieldValue("m_ListKeyboardControlID"); + + isTreeFocused = EditorWindow.focusedWindow == window && (GUIUtility.keyboardControl == treeViewControlID || treeViewControlID == 0 || treeViewControlID == -1); + isListAreaFocused = EditorWindow.focusedWindow == window && (GUIUtility.keyboardControl == listAreaControlID || listAreaControlID == 0 || listAreaControlID == -1); + + renamingRow = EditorGUIUtility.editingTextField && treeViewController?.GetMemberValue("state")?.GetMemberValue("renameOverlay")?.InvokeMethod("IsRenaming") == true; + renamingCell = EditorGUIUtility.editingTextField && listArea?.InvokeMethod("GetRenameOverlay")?.InvokeMethod("IsRenaming") == true; + + + + var grid = listArea?.GetMemberValue("m_LocalAssets")?.GetMemberValue("m_Grid"); + + gridHorizontalSpacing = grid?.GetMemberValue("horizontalSpacing") ?? 0; + gridVerticalSpacing = grid?.GetMemberValue("verticalSpacing") ?? 0; + + + + listArea_dragSelectionList = listArea?.GetMemberValue("m_LocalAssets")?.GetMemberValue>("m_DragSelection") ?? new(); + treeView_dragSelectionList = treeViewController?.GetFieldValue("m_DragSelection")?.GetFieldValue>("m_List") ?? new(); + treeView_normalSelectionList = isTwoColumns ? treeViewController?.GetFieldValue("m_CachedSelection")?.GetFieldValue>("m_List") ?? new() : null; + + + + // isVersionControlEnabled = typeof(Editor).Assembly.GetType("UnityEditor.AssetsTreeViewGUI").GetMemberValue("s_VCEnabled"); + + + // only treeView_normalSelectionList must be updated in repaint, the rest can be moved to UpdateState_Layout + // but they are all grouped here since they are only used for drawing + + } + + bool isOneColumn; + bool isTwoColumns => !isOneColumn; + + bool treeViewAnimatesExpansion; + + public object listArea; + + public object treeViewController; + public object treeViewControllerData; + + public IList rows; + + public List expandedIds = new(); + + bool isTreeFocused; + bool isListAreaFocused; + + bool renamingRow; + bool renamingCell; + + float gridVerticalSpacing; + float gridHorizontalSpacing; + + List listArea_dragSelectionList = new(); + List treeView_dragSelectionList = new(); + List treeView_normalSelectionList = new(); + + bool isVersionControlEnabled; + + + + + + + + + + + + + public void UpdateFoldersFirst() + { + if (!VFoldersMenu.foldersFirstEnabled) return; + if (Application.platform != RuntimePlatform.OSXEditor) return; + + void oneColumn() + { + if (isTwoColumns) return; + if (foldersFirst_initedForOneColumn) return; + + + var m_AssetTree = window.GetFieldValue("m_AssetTree"); + + if (m_AssetTree == null) return; + + m_AssetTree.GetPropertyValue("data").SetPropertyValue("foldersFirst", true); + m_AssetTree.InvokeMethod("ReloadData"); + + + foldersFirst_initedForOneColumn = true; + foldersFirst_initedForTwoColumns = false; + + } + void twoColumns() + { + if (!isTwoColumns) return; + if (foldersFirst_initedForTwoColumns) return; + + + var m_ListArea = window.GetFieldValue("m_ListArea"); + + if (m_ListArea == null) return; + + m_ListArea.SetPropertyValue("foldersFirst", true); + window.InvokeMethod("InitListArea"); + + + foldersFirst_initedForOneColumn = false; + foldersFirst_initedForTwoColumns = true; + + } + + oneColumn(); + twoColumns(); + + } + + bool foldersFirst_initedForOneColumn; + bool foldersFirst_initedForTwoColumns; + + + + + + + + + + + + + public VFoldersGUI(EditorWindow window) => this.window = window; + + public EditorWindow window; + + public VFoldersController controller => VFolders.controllers_byWindow[window]; + + } +} +#endif diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersGUI.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersGUI.cs.meta new file mode 100644 index 0000000..f66fff0 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0743ead56bea942af9c21d27c5c45b62 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersHistory.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersHistory.cs new file mode 100644 index 0000000..2f928fc --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersHistory.cs @@ -0,0 +1,403 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; +using static VFolders.VFolders; +using static VFolders.VFoldersData; +using static VFolders.VFoldersCache; + + + +namespace VFolders +{ + [System.Serializable] + public class VFoldersHistory + { + + public void CheckTreeStateChange() + { + if (!VFoldersMenu.navigationBarEnabled) return; + if (!isOneColumn) return; + + if (lastTreeState == null) { lastTreeState = new() { scrollPos = currentScrollPos, expandedIds = expandedIds.ToList() }; return; } + + + var curTreeState = new TreeState(); + + var targetScrollPosChanged = false; + var targetExpandedIdsChanged = false; + + + + void set_curState_scrollPos() + { + if (controller.animatingScroll) + curTreeState.scrollPos = controller.targetScrollPos; + else + curTreeState.scrollPos = currentScrollPos; + + } + void set_curState_expandedIds() + { + var expandedIdsHashset = expandedIds.ToHashSet(); + + + + if (treeViewAnimatesExpansion) + if (animatingItemTragetExpanded_fromTreeViewExpandAnimator == true) + expandedIdsHashset.UnionWith(new HashSet() { animatingItemId_fromTreeViewExpandAnimator }); + else + expandedIdsHashset.ExceptWith(new HashSet() { animatingItemId_fromTreeViewExpandAnimator }); + + + + if (controller.animatingExpansion) + { + foreach (var r in controller.expandQueue_toAnimate) + if (r.expand) + expandedIdsHashset.UnionWith(new HashSet() { r.id }); + else + expandedIdsHashset.ExceptWith(new HashSet() { r.id }); + + expandedIdsHashset.ExceptWith(controller.expandQueue_toCollapseAfterAnimation.ToHashSet()); + } + + + curTreeState.expandedIds = expandedIdsHashset.ToList(); + + } + + void checkScrollPosChange() + { + if (framesSinceLastExpansionAnimation < 2) return; + if (framesSinceLastScrollAnimation < 2) return; + + if (curTreeState.scrollPos == lastTreeState.scrollPos) return; + + + + if (EditorApplication.timeSinceStartup - lastScrollTime > 2 || lastTargetStateChangeWasExpandedIds) + targetScrollPosChanged = true; + + lastScrollTime = EditorApplication.timeSinceStartup; + + } + void checkExpandedIdsChange() + { + if (lastTreeState.expandedIds.ToHashSet().SetEquals(curTreeState.expandedIds.ToHashSet())) return; + + targetExpandedIdsChanged = true; + + // lastTargetState.expandedIds.LogAll("lastTargetState"); + // curTargetState.expandedIds.LogAll("curTargetState"); + + } + + void registerStateChange() + { + if (!targetScrollPosChanged && !targetExpandedIdsChanged) return; + + + prevTreeStates.Add(lastTreeState); + nextTreeStates.Clear(); + + lastTargetStateChangeWasExpandedIds = targetExpandedIdsChanged; + + + if (prevTreeStates.Count > 50) + prevTreeStates.RemoveAt(0); + + + // if (targetScrollPosChanged && targetExpandedIdsChanged) + // "expand and scroll".Log(); + // else if (targetScrollPosChanged) + // "scroll".Log(); + // else if (targetExpandedIdsChanged) + // "expand".Log(); + + } + + void updateCountersSinceAnimation() + { + if (controller.animatingExpansion || treeViewAnimatesExpansion) + framesSinceLastExpansionAnimation = 0; + else + framesSinceLastExpansionAnimation++; + + if (controller.animatingScroll || treeViewAnimatesScroll) + framesSinceLastScrollAnimation = 0; + else + framesSinceLastScrollAnimation++; + + } + + + + set_curState_scrollPos(); + set_curState_expandedIds(); + + checkScrollPosChange(); + checkExpandedIdsChange(); + + registerStateChange(); + + updateCountersSinceAnimation(); + + + lastTreeState = curTreeState; + + } + + int framesSinceLastExpansionAnimation; + int framesSinceLastScrollAnimation; + + public double lastScrollTime = 0; + + bool lastTargetStateChangeWasExpandedIds; + + [System.NonSerialized] TreeState lastTreeState = null; + + + + public void CheckFolderPathChange() + { + if (!VFoldersMenu.navigationBarEnabled) return; + if (isOneColumn) return; + + var curFolderPath = window.GetMemberValue("m_SearchFilter").GetMemberValue("folders").FirstOrDefault(); + + if (curFolderPath.IsNullOrEmpty()) return; + + if (lastFolderPath.IsNullOrEmpty()) lastFolderPath = curFolderPath; + if (curFolderPath == lastFolderPath) return; + + + prevFolderPaths.Add(lastFolderPath); + nextFolderPaths.Clear(); + + if (prevFolderPaths.Count > 50) + prevFolderPaths.RemoveAt(0); + + + lastFolderPath = curFolderPath; + + } + + string lastFolderPath = ""; + + + + + + + + public void UpdateState() + { + if (!VFoldersMenu.navigationBarEnabled) return; + + + isOneColumn = window.GetFieldValue("m_ViewMode") == 0; + + var treeViewController = window.GetFieldValue(isOneColumn ? "m_AssetTree" : "m_FolderTree"); + var treeViewControllerData = treeViewController?.GetPropertyValue("data"); + + + + var treeViewControllerState = treeViewController?.GetPropertyValue("state"); + + currentScrollPos = treeViewControllerState?.scrollPos.y ?? 0; + + expandedIds = treeViewControllerState?.expandedIDs ?? new List(); + + + + var treeViewAnimator = treeViewController?.GetMemberValue("m_ExpansionAnimator"); + var treeViewAnimatorSetup = treeViewAnimator?.GetMemberValue("m_Setup"); + + treeViewAnimatesScroll = treeViewController?.GetMemberValue("m_FramingAnimFloat").isAnimating ?? false; + + treeViewAnimatesExpansion = treeViewAnimator?.GetMemberValue("isAnimating") ?? false; + animatingItemTragetExpanded_fromTreeViewExpandAnimator = treeViewAnimatorSetup?.GetMemberValue("expanding") ?? false; + animatingItemId_fromTreeViewExpandAnimator = treeViewAnimatorSetup?.GetMemberValue("item").GetMemberValue("id") ?? 0; + + } + + public bool isOneColumn; + public bool isTwoColumns => !isOneColumn; + + public bool isSearchActive; + + public float currentScrollPos; + + public List expandedIds = new(); + + public bool treeViewAnimatesScroll; + + public bool treeViewAnimatesExpansion; + public bool animatingItemTragetExpanded_fromTreeViewExpandAnimator; + public int animatingItemId_fromTreeViewExpandAnimator; + + + + + + + + + + + + + + + public void MoveBack_OneColumn(bool withAnimation = true) + { + var prevState = prevTreeStates.Last(); + + prevTreeStates.Remove(prevState); + nextTreeStates.Add(lastTreeState); + lastTreeState = prevState; + + + if (withAnimation) + { + controller.StartScrollAnimation(prevState.scrollPos); + controller.StartExpandAnimation(prevState.expandedIds); + } + else + { + controller.SetScrollPos(prevState.scrollPos); + controller.SetExpandedIds(prevState.expandedIds); + } + + } + public void MoveForward_OneColumn(bool withAnimation = true) + { + var nextState = nextTreeStates.Last(); + + nextTreeStates.Remove(nextState); + prevTreeStates.Add(lastTreeState); + lastTreeState = nextState; + + + if (withAnimation) + { + controller.StartScrollAnimation(nextState.scrollPos); + controller.StartExpandAnimation(nextState.expandedIds); + } + else + { + controller.SetScrollPos(nextState.scrollPos); + controller.SetExpandedIds(nextState.expandedIds); + } + + } + + public List prevTreeStates = new(); + public List nextTreeStates = new(); + + + + public void MoveBack_TwoColumns(bool withAnimation = true) + { + var prevPath = prevFolderPaths.Last(); + + prevFolderPaths.RemoveLast(); + nextFolderPaths.Add(lastFolderPath); + lastFolderPath = prevPath; + + + if (withAnimation) + { + controller.RevealFolder(prevPath, expand: false, highlight: false, snapToTopMargin: false); + controller.OpenFolder(prevPath); + } + else + { + window.InvokeMethod("ShowFolderContents", AssetDatabase.LoadAssetAtPath(prevPath).GetInstanceID(), true); + } + + } + public void MoveForward_TwoColumns(bool withAnimation = true) + { + var nextPath = nextFolderPaths.Last(); + + nextFolderPaths.RemoveLast(); + prevFolderPaths.Add(lastFolderPath); + lastFolderPath = nextPath; + + + if (withAnimation) + { + controller.RevealFolder(nextPath, expand: false, highlight: false, snapToTopMargin: false); + controller.OpenFolder(nextPath); + } + else + { + window.InvokeMethod("ShowFolderContents", AssetDatabase.LoadAssetAtPath(nextPath).GetInstanceID(), true); + } + + } + + public List prevFolderPaths = new(); + public List nextFolderPaths = new(); + + + + + + + + + + + + + + [System.Serializable] + public class TreeState + { + public List expandedIds = new(); + + public float scrollPos; + + } + + + + + + + + + public VFoldersHistory(EditorWindow window) => this.window = window; + + EditorWindow window; + + VFoldersGUI gui => VFolders.guis_byWindow[window]; + VFoldersController controller => VFolders.controllers_byWindow[window]; + + } + + + public class VFoldersHistorySingleton : ScriptableSingleton + { + public SerializableDictionary histories_byWindow = new(); + } + + +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersHistory.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersHistory.cs.meta new file mode 100644 index 0000000..a0fc8da --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersHistory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2d3e0e15bb2714894bdfb696f7509428 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersIconEditor.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersIconEditor.cs new file mode 100644 index 0000000..028ed42 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersIconEditor.cs @@ -0,0 +1,5 @@ + +// this file was present in a previus version and is supposed to be deleted now +// but asset store update delivery system doesn't allow deleting files +// so instead this file is now emptied +// feel free to delete it if you want diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersIconEditor.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersIconEditor.cs.meta new file mode 100644 index 0000000..759e212 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersIconEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fc2d205dd3ec1446d843e566b05c2eda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersLibs.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersLibs.cs new file mode 100644 index 0000000..7a44a3b --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersLibs.cs @@ -0,0 +1,2667 @@ + +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using System.Reflection; +using System.Linq; +using UnityEngine; +using UnityEngine.Rendering; +using UnityEngine.Experimental.Rendering; +using UnityEditor; +using Type = System.Type; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; + + + +namespace VFolders.Libs +{ + public static class VUtils + { + + #region Reflection + + + public static object GetFieldValue(this object o, string fieldName) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(fieldName) is FieldInfo fieldInfo) + return fieldInfo.GetValue(target); + + + throw new System.Exception($"Field '{fieldName}' not found in type '{type.Name}' and its parent types"); + + } + public static object GetPropertyValue(this object o, string propertyName) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetPropertyInfo(propertyName) is PropertyInfo propertyInfo) + return propertyInfo.GetValue(target); + + + throw new System.Exception($"Property '{propertyName}' not found in type '{type.Name}' and its parent types"); + + } + public static object GetMemberValue(this object o, string memberName) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(memberName) is FieldInfo fieldInfo) + return fieldInfo.GetValue(target); + + if (type.GetPropertyInfo(memberName) is PropertyInfo propertyInfo) + return propertyInfo.GetValue(target); + + + throw new System.Exception($"Member '{memberName}' not found in type '{type.Name}' and its parent types"); + + } + + public static void SetFieldValue(this object o, string fieldName, object value) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(fieldName) is FieldInfo fieldInfo) + fieldInfo.SetValue(target, value); + + + else throw new System.Exception($"Field '{fieldName}' not found in type '{type.Name}' and its parent types"); + + } + public static void SetPropertyValue(this object o, string propertyName, object value) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetPropertyInfo(propertyName) is PropertyInfo propertyInfo) + propertyInfo.SetValue(target, value); + + + else throw new System.Exception($"Property '{propertyName}' not found in type '{type.Name}' and its parent types"); + + } + public static void SetMemberValue(this object o, string memberName, object value) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(memberName) is FieldInfo fieldInfo) + fieldInfo.SetValue(target, value); + + else if (type.GetPropertyInfo(memberName) is PropertyInfo propertyInfo) + propertyInfo.SetValue(target, value); + + + else throw new System.Exception($"Member '{memberName}' not found in type '{type.Name}' and its parent types"); + + } + + public static object InvokeMethod(this object o, string methodName, params object[] parameters) // todo handle null params (can't get their type) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetMethodInfo(methodName, parameters.Select(r => r.GetType()).ToArray()) is MethodInfo methodInfo) + return methodInfo.Invoke(target, parameters); + + + throw new System.Exception($"Method '{methodName}' not found in type '{type.Name}', its parent types and interfaces"); + + } + + + public static T GetFieldValue(this object o, string fieldName) => (T)o.GetFieldValue(fieldName); + public static T GetPropertyValue(this object o, string propertyName) => (T)o.GetPropertyValue(propertyName); + public static T GetMemberValue(this object o, string memberName) => (T)o.GetMemberValue(memberName); + public static T InvokeMethod(this object o, string methodName, params object[] parameters) => (T)o.InvokeMethod(methodName, parameters); + + + + + public static FieldInfo GetFieldInfo(this Type type, string fieldName) + { + if (fieldInfoCache.TryGetValue(type, out var fieldInfosByNames)) + if (fieldInfosByNames.TryGetValue(fieldName, out var fieldInfo)) + return fieldInfo; + + + if (!fieldInfoCache.ContainsKey(type)) + fieldInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetField(fieldName, maxBindingFlags) is FieldInfo fieldInfo) + return fieldInfoCache[type][fieldName] = fieldInfo; + + + return fieldInfoCache[type][fieldName] = null; + + } + public static PropertyInfo GetPropertyInfo(this Type type, string propertyName) + { + if (propertyInfoCache.TryGetValue(type, out var propertyInfosByNames)) + if (propertyInfosByNames.TryGetValue(propertyName, out var propertyInfo)) + return propertyInfo; + + + if (!propertyInfoCache.ContainsKey(type)) + propertyInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetProperty(propertyName, maxBindingFlags) is PropertyInfo propertyInfo) + return propertyInfoCache[type][propertyName] = propertyInfo; + + + return propertyInfoCache[type][propertyName] = null; + + } + public static MethodInfo GetMethodInfo(this Type type, string methodName, params Type[] argumentTypes) + { + var methodHash = methodName.GetHashCode() ^ argumentTypes.Aggregate(0, (hash, r) => hash ^= r.GetHashCode()); + + + if (methodInfoCache.TryGetValue(type, out var methodInfosByHashes)) + if (methodInfosByHashes.TryGetValue(methodHash, out var methodInfo)) + return methodInfo; + + + + if (!methodInfoCache.ContainsKey(type)) + methodInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetMethod(methodName, maxBindingFlags, null, argumentTypes, null) is MethodInfo methodInfo) + return methodInfoCache[type][methodHash] = methodInfo; + + foreach (var interfaceType in type.GetInterfaces()) + if (interfaceType.GetMethod(methodName, maxBindingFlags, null, argumentTypes, null) is MethodInfo methodInfo) + return methodInfoCache[type][methodHash] = methodInfo; + + + + return methodInfoCache[type][methodHash] = null; + + } + + static Dictionary> fieldInfoCache = new(); + static Dictionary> propertyInfoCache = new(); + static Dictionary> methodInfoCache = new(); + + + + + public const BindingFlags maxBindingFlags = (BindingFlags)62; + + + + + + + + + #endregion + + #region Linq + + + public static T NextTo(this IEnumerable e, T to) => e.SkipWhile(r => !r.Equals(to)).Skip(1).FirstOrDefault(); + public static T PreviousTo(this IEnumerable e, T to) => e.Reverse().SkipWhile(r => !r.Equals(to)).Skip(1).FirstOrDefault(); + public static T NextToOtFirst(this IEnumerable e, T to) => e.NextTo(to) ?? e.First(); + public static T PreviousToOrLast(this IEnumerable e, T to) => e.PreviousTo(to) ?? e.Last(); + + public static Dictionary MergeDictionaries(IEnumerable> dicts) + { + if (dicts.Count() == 0) return null; + if (dicts.Count() == 1) return dicts.First(); + + var mergedDict = new Dictionary(dicts.First()); + + foreach (var dict in dicts.Skip(1)) + foreach (var r in dict) + if (!mergedDict.ContainsKey(r.Key)) + mergedDict.Add(r.Key, r.Value); + + return mergedDict; + } + + public static IEnumerable InsertFirst(this IEnumerable ie, T t) => new[] { t }.Concat(ie); + + public static int IndexOfFirst(this List list, System.Func f) => list.FirstOrDefault(f) is T t ? list.IndexOf(t) : -1; + public static int IndexOfLast(this List list, System.Func f) => list.LastOrDefault(f) is T t ? list.IndexOf(t) : -1; + + public static void SortBy(this List list, System.Func keySelector) where T2 : System.IComparable => list.Sort((q, w) => keySelector(q).CompareTo(keySelector(w))); + + public static void RemoveValue(this IDictionary dictionary, TValue value) + { + if (dictionary.FirstOrDefault(r => r.Value.Equals(value)) is var kvp) + dictionary.Remove(kvp); + } + + public static void ForEach(this IEnumerable sequence, System.Action action) { foreach (T item in sequence) action(item); } + + + + public static T AddAt(this List l, T r, int i) + { + if (i < 0) i = 0; + if (i >= l.Count) + l.Add(r); + else + l.Insert(i, r); + return r; + } + public static T RemoveLast(this List l) + { + if (!l.Any()) return default; + + var r = l.Last(); + + l.RemoveAt(l.Count - 1); + + return r; + } + + public static void Add(this List list, params T[] items) + { + foreach (var r in items) + list.Add(r); + } + + + + + + + #endregion + + #region Math + + + public static class MathUtil // MathUtils name is taken by UnityEditor.MathUtils + { + + public static float TriangleArea(Vector2 A, Vector2 B, Vector2 C) => Vector3.Cross(A - B, A - C).z.Abs() / 2; + + public static Vector2 LineIntersection(Vector2 A, Vector2 B, Vector2 C, Vector2 D) + { + var a1 = B.y - A.y; + var b1 = A.x - B.x; + var c1 = a1 * A.x + b1 * A.y; + + var a2 = D.y - C.y; + var b2 = C.x - D.x; + var c2 = a2 * C.x + b2 * C.y; + + var d = a1 * b2 - a2 * b1; + + var x = (b2 * c1 - b1 * c2) / d; + var y = (a1 * c2 - a2 * c1) / d; + + return new Vector2(x, y); + + } + + + + + public static float Lerp(float f1, float f2, float t) => Mathf.LerpUnclamped(f1, f2, t); + public static float Lerp(ref float f1, float f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + public static Vector2 Lerp(Vector2 f1, Vector2 f2, float t) => Vector2.LerpUnclamped(f1, f2, t); + public static Vector2 Lerp(ref Vector2 f1, Vector2 f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + public static Vector3 Lerp(Vector3 f1, Vector3 f2, float t) => Vector3.LerpUnclamped(f1, f2, t); + public static Vector3 Lerp(ref Vector3 f1, Vector3 f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + public static Color Lerp(Color f1, Color f2, float t) => Color.LerpUnclamped(f1, f2, t); + public static Color Lerp(ref Color f1, Color f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + + public static float Lerp(float current, float target, float speed, float deltaTime) => Mathf.Lerp(current, target, GetLerpT(speed, deltaTime)); + public static float Lerp(ref float current, float target, float speed, float deltaTime) + { + return current = Lerp(current, target, speed, deltaTime); + } + + public static Vector2 Lerp(Vector2 current, Vector2 target, float speed, float deltaTime) => Vector2.Lerp(current, target, GetLerpT(speed, deltaTime)); + public static Vector2 Lerp(ref Vector2 current, Vector2 target, float speed, float deltaTime) + { + return current = Lerp(current, target, speed, deltaTime); + } + + public static Vector3 Lerp(Vector3 current, Vector3 target, float speed, float deltaTime) => Vector3.Lerp(current, target, GetLerpT(speed, deltaTime)); + public static Vector3 Lerp(ref Vector3 current, Vector3 target, float speed, float deltaTime) + { + return current = Lerp(current, target, speed, deltaTime); + } + + public static float SmoothDamp(float current, float target, float speed, ref float derivative, float deltaTime, float maxSpeed) => Mathf.SmoothDamp(current, target, ref derivative, .5f / speed, maxSpeed, deltaTime); + public static float SmoothDamp(float current, float target, float speed, ref float derivative, float deltaTime) + { + return Mathf.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + } + public static float SmoothDamp(float current, float target, float speed, ref float derivative) + { + return SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative, float deltaTime, float maxSpeed) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime, maxSpeed); + } + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative, float deltaTime) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + } + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative) + { + return current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) => Vector2.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative) + { + return SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + } + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative) + { + return current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) => Vector3.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative) + { + return SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + } + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative) + { + return current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + + + public static float GetLerpT(float lerpSpeed, float deltaTime) => 1 - Mathf.Exp(-lerpSpeed * 2f * deltaTime); + public static float GetLerpT(float lerpSpeed) + { + return GetLerpT(lerpSpeed, Time.deltaTime); + } + + + + } + + + public static float DistanceTo(this float f1, float f2) => Mathf.Abs(f1 - f2); + public static float DistanceTo(this Vector2 f1, Vector2 f2) => (f1 - f2).magnitude; + public static float DistanceTo(this Vector3 f1, Vector3 f2) => (f1 - f2).magnitude; + + public static float Sign(this float f) => f == 0 ? 0 : Mathf.Sign(f); + + public static int Abs(this int f) => Mathf.Abs(f); + public static float Abs(this float f) => Mathf.Abs(f); + + public static int Clamp(this int f, int f0, int f1) => Mathf.Clamp(f, f0, f1); + public static float Clamp(this float f, float f0, float f1) => Mathf.Clamp(f, f0, f1); + + + public static float Clamp01(this float f) => Mathf.Clamp(f, 0, 1); + public static Vector2 Clamp01(this Vector2 f) => new(f.x.Clamp01(), f.y.Clamp01()); + public static Vector3 Clamp01(this Vector3 f) => new(f.x.Clamp01(), f.y.Clamp01(), f.z.Clamp01()); + + + public static int Pow(this int f, int pow) => (int)Mathf.Pow(f, pow); + public static float Pow(this float f, float pow) => Mathf.Pow(f, pow); + + public static float Round(this float f) => Mathf.Round(f); + public static float Ceil(this float f) => Mathf.Ceil(f); + public static float Floor(this float f) => Mathf.Floor(f); + + public static int RoundToInt(this float f) => Mathf.RoundToInt(f); + public static int CeilToInt(this float f) => Mathf.CeilToInt(f); + public static int FloorToInt(this float f) => Mathf.FloorToInt(f); + + public static int ToInt(this float f) => (int)f; + public static float ToFloat(this int f) => (float)f; + public static float ToFloat(this double f) => (float)f; + + + + public static float Sqrt(this float f) => Mathf.Sqrt(f); + + public static int Max(this int f, int ff) => Mathf.Max(f, ff); + public static int Min(this int f, int ff) => Mathf.Min(f, ff); + public static float Max(this float f, float ff) => Mathf.Max(f, ff); + public static float Min(this float f, float ff) => Mathf.Min(f, ff); + + public static float ClampMin(this float f, float limitMin) => Mathf.Max(f, limitMin); + public static float ClampMax(this float f, float limitMax) => Mathf.Min(f, limitMax); + + + public static float Loop(this float f, float boundMin, float boundMax) + { + while (f < boundMin) f += boundMax - boundMin; + while (f > boundMax) f -= boundMax - boundMin; + return f; + } + public static float Loop(this float f, float boundMax) => f.Loop(0, boundMax); + + public static float PingPong(this float f, float boundMin, float boundMax) => boundMin + Mathf.PingPong(f - boundMin, boundMax - boundMin); + public static float PingPong(this float f, float boundMax) => f.PingPong(0, boundMax); + + + public static float ProjectOn(this Vector2 v, Vector2 on) => Vector3.Project(v, on).magnitude; + public static float ProjectOn(this Vector3 v, Vector3 on) => Vector3.Project(v, on).magnitude; + + public static float AngleTo(this Vector2 v, Vector2 to) => Vector2.Angle(v, to); + + public static Vector2 Rotate(this Vector2 v, float deg) => Quaternion.AngleAxis(deg, Vector3.forward) * v; + + public static float Smoothstep(this float f) { f = f.Clamp01(); return f * f * (3 - 2 * f); } + + public static float InverseLerp(this Vector2 v, Vector2 a, Vector2 b) + { + var ab = b - a; + var av = v - a; + return Vector2.Dot(av, ab) / Vector2.Dot(ab, ab); + } + + + public static bool IsOdd(this int i) => i % 2 == 1; + public static bool IsEven(this int i) => i % 2 == 0; + + public static bool IsInRange(this int i, int a, int b) => i >= a && i <= b; + public static bool IsInRange(this float i, float a, float b) => i >= a && i <= b; + + public static bool IsInRangeOf(this int i, IList list) => i.IsInRange(0, list.Count - 1); + public static bool IsInRangeOf(this int i, T[] array) => i.IsInRange(0, array.Length - 1); + + public static bool Approx(this float f1, float f2) => Mathf.Approximately(f1, f2); + + + + [System.Serializable] + public class GaussianKernel + { + public static float[,] GenerateArray(int size, float sharpness = .5f) + { + float[,] kr = new float[size, size]; + + if (size == 1) { kr[0, 0] = 1; return kr; } + + + var sigma = 1f - Mathf.Pow(sharpness, .1f) * .99999f; + var radius = (size / 2f).FloorToInt(); + + + var a = -2f * radius * radius / Mathf.Log(sigma); + var sum = 0f; + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + var rX = size % 2 == 1 ? (x - radius) : (x - radius) + .5f; + var rY = size % 2 == 1 ? (y - radius) : (y - radius) + .5f; + var dist = Mathf.Sqrt(rX * rX + rY * rY); + kr[x, y] = Mathf.Exp(-dist * dist / a); + sum += kr[x, y]; + } + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + kr[x, y] /= sum; + + return kr; + } + + + + public GaussianKernel(bool isEvenSize = false, int radius = 7, float sharpness = .5f) + { + this.isEvenSize = isEvenSize; + this.radius = radius; + this.sharpness = sharpness; + } + + public bool isEvenSize = false; + public int radius = 7; + public float sharpness = .5f; + + public int size => radius * 2 + (isEvenSize ? 0 : 1); + public float sigma => 1 - Mathf.Pow(sharpness, .1f) * .99999f; + + public float[,] Array2d() // todo test and use GenerateArray + { + float[,] kr = new float[size, size]; + + if (size == 1) { kr[0, 0] = 1; return kr; } + + var a = -2f * radius * radius / Mathf.Log(sigma); + var sum = 0f; + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + var rX = size % 2 == 1 ? (x - radius) : (x - radius) + .5f; + var rY = size % 2 == 1 ? (y - radius) : (y - radius) + .5f; + var dist = Mathf.Sqrt(rX * rX + rY * rY); + kr[x, y] = Mathf.Exp(-dist * dist / a); + sum += kr[x, y]; + } + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + kr[x, y] /= sum; + + return kr; + } + public float[] ArrayFlat() + { + var gk = Array2d(); + float[] flat = new float[size * size]; + + for (int i = 0; i < size; i++) + for (int j = 0; j < size; j++) + flat[(i * size + j)] = gk[i, j]; + + return flat; + } + + } + + + + + + + + #endregion + + #region Lerping + + + public static float LerpT(float lerpSpeed, float deltaTime) => 1 - Mathf.Exp(-lerpSpeed * 2f * deltaTime); + public static float LerpT(float lerpSpeed) => LerpT(lerpSpeed, Time.deltaTime); + + public static float Lerp(float f1, float f2, float t) => Mathf.LerpUnclamped(f1, f2, t); + public static float Lerp(ref float f1, float f2, float t) => f1 = Lerp(f1, f2, t); + + public static Vector2 Lerp(Vector2 f1, Vector2 f2, float t) => Vector2.LerpUnclamped(f1, f2, t); + public static Vector2 Lerp(ref Vector2 f1, Vector2 f2, float t) => f1 = Lerp(f1, f2, t); + + public static Vector3 Lerp(Vector3 f1, Vector3 f2, float t) => Vector3.LerpUnclamped(f1, f2, t); + public static Vector3 Lerp(ref Vector3 f1, Vector3 f2, float t) => f1 = Lerp(f1, f2, t); + + public static Color Lerp(Color f1, Color f2, float t) => Color.LerpUnclamped(f1, f2, t); + public static Color Lerp(ref Color f1, Color f2, float t) => f1 = Lerp(f1, f2, t); + + + public static float Lerp(float current, float target, float speed, float deltaTime) => Mathf.Lerp(current, target, LerpT(speed, deltaTime)); + public static float Lerp(ref float current, float target, float speed, float deltaTime) => current = Lerp(current, target, speed, deltaTime); + + public static Vector2 Lerp(Vector2 current, Vector2 target, float speed, float deltaTime) => Vector2.Lerp(current, target, LerpT(speed, deltaTime)); + public static Vector2 Lerp(ref Vector2 current, Vector2 target, float speed, float deltaTime) => current = Lerp(current, target, speed, deltaTime); + + public static Vector3 Lerp(Vector3 current, Vector3 target, float speed, float deltaTime) => Vector3.Lerp(current, target, LerpT(speed, deltaTime)); + public static Vector3 Lerp(ref Vector3 current, Vector3 target, float speed, float deltaTime) => current = Lerp(current, target, speed, deltaTime); + + public static float SmoothDamp(float current, float target, float speed, ref float derivative, float deltaTime, float maxSpeed) => Mathf.SmoothDamp(current, target, ref derivative, .5f / speed, maxSpeed, deltaTime); + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative, float deltaTime, float maxSpeed) => current = SmoothDamp(current, target, speed, ref derivative, deltaTime, maxSpeed); + public static float SmoothDamp(float current, float target, float speed, ref float derivative, float deltaTime) => Mathf.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative, float deltaTime) => current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + public static float SmoothDamp(float current, float target, float speed, ref float derivative) => SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative) => current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) => Vector2.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) => current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative) => SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative) => current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) => Vector3.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) => current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative) => SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative) => current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + + + + + + + #endregion + + #region Colors + + + public class ColorUtils + { + public static Color HSLToRGB(float h, float s, float l) + { + float hue2Rgb(float v1, float v2, float vH) + { + if (vH < 0f) + vH += 1f; + + if (vH > 1f) + vH -= 1f; + + if (6f * vH < 1f) + return v1 + (v2 - v1) * 6f * vH; + + if (2f * vH < 1f) + return v2; + + if (3f * vH < 2f) + return v1 + (v2 - v1) * (2f / 3f - vH) * 6f; + + return v1; + } + + if (s.Approx(0)) return new Color(l, l, l); + + float k1; + + if (l < .5f) + k1 = l * (1f + s); + else + k1 = l + s - s * l; + + + var k2 = 2f * l - k1; + + float r, g, b; + r = hue2Rgb(k2, k1, h + 1f / 3); + g = hue2Rgb(k2, k1, h); + b = hue2Rgb(k2, k1, h - 1f / 3); + + return new Color(r, g, b); + } + public static Color LCHtoRGB(float l, float c, float h) + { + l *= 100; + c *= 100; + h *= 360; + + double xw = 0.948110; + double yw = 1.00000; + double zw = 1.07304; + + float a = c * Mathf.Cos(Mathf.Deg2Rad * h); + float b = c * Mathf.Sin(Mathf.Deg2Rad * h); + + float fy = (l + 16) / 116; + float fx = fy + (a / 500); + float fz = fy - (b / 200); + + float x = (float)System.Math.Round(xw * ((System.Math.Pow(fx, 3) > 0.008856) ? System.Math.Pow(fx, 3) : ((fx - 16 / 116) / 7.787)), 5); + float y = (float)System.Math.Round(yw * ((System.Math.Pow(fy, 3) > 0.008856) ? System.Math.Pow(fy, 3) : ((fy - 16 / 116) / 7.787)), 5); + float z = (float)System.Math.Round(zw * ((System.Math.Pow(fz, 3) > 0.008856) ? System.Math.Pow(fz, 3) : ((fz - 16 / 116) / 7.787)), 5); + + float r = x * 3.2406f - y * 1.5372f - z * 0.4986f; + float g = -x * 0.9689f + y * 1.8758f + z * 0.0415f; + float bValue = x * 0.0557f - y * 0.2040f + z * 1.0570f; + + r = r > 0.0031308f ? 1.055f * (float)System.Math.Pow(r, 1 / 2.4) - 0.055f : r * 12.92f; + g = g > 0.0031308f ? 1.055f * (float)System.Math.Pow(g, 1 / 2.4) - 0.055f : g * 12.92f; + bValue = bValue > 0.0031308f ? 1.055f * (float)System.Math.Pow(bValue, 1 / 2.4) - 0.055f : bValue * 12.92f; + + // r = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, r))); + // g = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, g))); + // bValue = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, bValue))); + + return new Color(r, g, bValue); + + } + + } + + + public static Color Greyscale(float brightness, float alpha = 1) => new(brightness, brightness, brightness, alpha); + + public static Color SetAlpha(this Color color, float alpha) { color.a = alpha; return color; } + public static Color MultiplyAlpha(this Color color, float k) { color.a *= k; return color; } + + + + + + #endregion + + #region Rects + + + public static Rect Resize(this Rect rect, float px) { rect.x += px; rect.y += px; rect.width -= px * 2; rect.height -= px * 2; return rect; } + + public static Rect SetPos(this Rect rect, Vector2 v) => rect.SetPos(v.x, v.y); + public static Rect SetPos(this Rect rect, float x, float y) { rect.x = x; rect.y = y; return rect; } + + public static Rect SetX(this Rect rect, float x) => rect.SetPos(x, rect.y); + public static Rect SetY(this Rect rect, float y) => rect.SetPos(rect.x, y); + public static Rect SetXMax(this Rect rect, float xMax) { rect.xMax = xMax; return rect; } + public static Rect SetYMax(this Rect rect, float yMax) { rect.yMax = yMax; return rect; } + + public static Rect SetMidPos(this Rect r, Vector2 v) => r.SetPos(v).MoveX(-r.width / 2).MoveY(-r.height / 2); + public static Rect SetMidPos(this Rect r, float x, float y) => r.SetMidPos(new Vector2(x, y)); + + public static Rect Move(this Rect rect, Vector2 v) { rect.position += v; return rect; } + public static Rect Move(this Rect rect, float x, float y) { rect.x += x; rect.y += y; return rect; } + public static Rect MoveX(this Rect rect, float px) { rect.x += px; return rect; } + public static Rect MoveY(this Rect rect, float px) { rect.y += px; return rect; } + + public static Rect SetWidth(this Rect rect, float f) { rect.width = f; return rect; } + public static Rect SetWidthFromMid(this Rect rect, float px) { rect.x += rect.width / 2; rect.width = px; rect.x -= rect.width / 2; return rect; } + public static Rect SetWidthFromRight(this Rect rect, float px) { rect.x += rect.width; rect.width = px; rect.x -= rect.width; return rect; } + + public static Rect SetHeight(this Rect rect, float f) { rect.height = f; return rect; } + public static Rect SetHeightFromMid(this Rect rect, float px) { rect.y += rect.height / 2; rect.height = px; rect.y -= rect.height / 2; return rect; } + public static Rect SetHeightFromBottom(this Rect rect, float px) { rect.y += rect.height; rect.height = px; rect.y -= rect.height; return rect; } + + public static Rect AddWidth(this Rect rect, float f) => rect.SetWidth(rect.width + f); + public static Rect AddWidthFromMid(this Rect rect, float f) => rect.SetWidthFromMid(rect.width + f); + public static Rect AddWidthFromRight(this Rect rect, float f) => rect.SetWidthFromRight(rect.width + f); + + public static Rect AddHeight(this Rect rect, float f) => rect.SetHeight(rect.height + f); + public static Rect AddHeightFromMid(this Rect rect, float f) => rect.SetHeightFromMid(rect.height + f); + public static Rect AddHeightFromBottom(this Rect rect, float f) => rect.SetHeightFromBottom(rect.height + f); + + public static Rect SetSize(this Rect rect, Vector2 v) => rect.SetWidth(v.x).SetHeight(v.y); + public static Rect SetSize(this Rect rect, float w, float h) => rect.SetWidth(w).SetHeight(h); + public static Rect SetSize(this Rect rect, float f) { rect.height = rect.width = f; return rect; } + + public static Rect SetSizeFromMid(this Rect r, Vector2 v) => r.Move(r.size / 2).SetSize(v).Move(-v / 2); + public static Rect SetSizeFromMid(this Rect r, float x, float y) => r.SetSizeFromMid(new Vector2(x, y)); + public static Rect SetSizeFromMid(this Rect r, float f) => r.SetSizeFromMid(new Vector2(f, f)); + + public static Rect AlignToPixelGrid(this Rect r) => GUIUtility.AlignRectToDevice(r); + + + + + + #endregion + + #region Textures + + + public static Texture2D CreateTexture2D(int width, int height, GraphicsFormat graphicsFormat = GraphicsFormat.R8G8B8A8_SRGB, bool useMips = false) + { + return new Texture2D(width, height, graphicsFormat, useMips ? TextureCreationFlags.MipChain : TextureCreationFlags.None); + } + + public static RenderTexture CreateRT(int width, int height, GraphicsFormat graphicsFormat = GraphicsFormat.R8G8B8A8_SRGB, bool useMips = false, bool autoGenerateMips = true, bool useDepth = false) + { + var rt = new RenderTexture(width, height, useDepth ? 24 : 0, graphicsFormat); + + rt.useMipMap = useMips; + rt.autoGenerateMips = autoGenerateMips; + + rt.enableRandomWrite = true; + + return rt; + + } + public static RenderTexture GetTemporaryRT(int width, int height, GraphicsFormat graphicsFormat = GraphicsFormat.R8G8B8A8_SRGB, bool useMips = false, bool autoGenerateMips = true, bool useDepth = false) + { + var rt = RenderTexture.GetTemporary(width, height, useDepth ? 24 : 0, graphicsFormat); + + rt.useMipMap = useMips; + rt.autoGenerateMips = autoGenerateMips; + + rt.enableRandomWrite = true; + + return rt; + + } + + public static RenderTexture CreateRT(this RenderTextureDescriptor descriptor) => new(descriptor); + public static RenderTexture CreateRT(this RenderTextureDescriptor descriptor, int resolution) + { + descriptor.width = descriptor.height = resolution; + + return descriptor.CreateRT(); + + } + public static RenderTexture CreateRT(this RenderTextureDescriptor descriptor, int width, int height) + { + descriptor.width = width; + descriptor.height = height; + + return descriptor.CreateRT(); + + } + public static RenderTexture CreateRT(this RenderTextureDescriptor descriptor, float resolution) => descriptor.GetTemporaryRT(Mathf.RoundToInt(resolution)); + public static RenderTexture CreateRT(this RenderTextureDescriptor descriptor, float width, float height) => descriptor.CreateRT(Mathf.RoundToInt(width), Mathf.RoundToInt(height)); + public static RenderTexture GetTemporaryRT(this RenderTextureDescriptor descriptor) => RenderTexture.GetTemporary(descriptor); + public static RenderTexture GetTemporaryRT(this RenderTextureDescriptor descriptor, int resolution) + { + descriptor.width = descriptor.height = resolution; + + return descriptor.GetTemporaryRT(); + + } + public static RenderTexture GetTemporaryRT(this RenderTextureDescriptor descriptor, int width, int height) + { + descriptor.width = width; + descriptor.height = height; + + return descriptor.GetTemporaryRT(); + + } + public static RenderTexture GetTemporaryRT(this RenderTextureDescriptor descriptor, float resolution) => descriptor.GetTemporaryRT(Mathf.RoundToInt(resolution)); + public static RenderTexture GetTemporaryRT(this RenderTextureDescriptor descriptor, float width, float height) => descriptor.GetTemporaryRT(Mathf.RoundToInt(width), Mathf.RoundToInt(height)); + + public static void ReleaseTemporary(this RenderTexture rt) { if (rt) RenderTexture.ReleaseTemporary(rt); } + + + + public static Texture2D ToTexture2D(this RenderTexture rt) + { + var texture2D = CreateTexture2D(rt.width, rt.height, rt.graphicsFormat, rt.useMipMap); + + texture2D.ReadPixelsFrom(rt); + texture2D.Apply(); + + return texture2D; + + } + public static RenderTexture ToRenderTexture(this Texture2D texture2d) + { + var rt = CreateRT(texture2d.width, texture2d.height, texture2d.graphicsFormat, texture2d.mipmapCount > 1); + + Graphics.CopyTexture(texture2d, rt); + + return rt; + + } + + + public static void ReadPixelsFrom(this Texture2D texture2D, RenderTexture renderTexture) + { + var prevActive = RenderTexture.active; + + RenderTexture.active = renderTexture; + + texture2D.ReadPixels(new Rect(0, 0, renderTexture.width, renderTexture.height), 0, 0); + + RenderTexture.active = prevActive; + + } + // public static void CopyTo(this RenderTexture source, Texture2D target) // todo to readpixels overload + // { + // var prevActive = RenderTexture.active; + + // RenderTexture.active = source; + + // target.ReadPixels(new Rect(0, 0, source.width, source.height), 0, 0); + // target.Apply(); + + // RenderTexture.active = prevActive; + + + // // somewhere in unity source code reading is done like this, but it throws out of bounds read exception on win: + + // // if (!SystemInfo.graphicsUVStartsAtTop || SystemInfo.graphicsDeviceType == GraphicsDeviceType.Metal) + // // texture2d.ReadPixels(new Rect(0, 0, texture2d.width, texture2d.height), 0, 0); + // // else + // // texture2d.ReadPixels(new Rect(0, texture2d.height, texture2d.width, texture2d.height), 0, 0); + + // // this was used in the legacy rt.CopyToTexture2D extension method + + // } + + public static Texture2D CreateCopy(this Texture2D texture2d) + { + var copy = CreateTexture2D(texture2d.width, texture2d.height, texture2d.graphicsFormat, texture2d.mipmapCount > 1); + + Graphics.CopyTexture(texture2d, copy); + + return copy; + + } + public static Texture2D CreateResizedCopy(this Texture2D texture2d, int w, int h) + { + var rt = GetTemporaryRT(w, h, texture2d.graphicsFormat.GetCompatibleForRendering(), false); + + Graphics.Blit(texture2d, rt); + + + var resizedCopy = CreateTexture2D(w, h, texture2d.graphicsFormat.GetCompatibleForRendering(), texture2d.mipmapCount > 1); + + resizedCopy.ReadPixelsFrom(rt); + resizedCopy.Apply(); + + if (RenderTexture.active == rt) + RenderTexture.active = null; + + rt.ReleaseTemporary(); + + + return resizedCopy; + + } + + + public static void FillWithColor(this Texture2D texture2d, Color color) + { + var pixels = new Color32[texture2d.width * texture2d.height]; + + var color32 = (Color32)color; + + for (int i = 0; i < pixels.Length; i++) + pixels[i] = color32; + + texture2d.SetPixels32(pixels); + texture2d.Apply(); + + } + public static RenderTexture FillWithColor(this RenderTexture rt, Color color) // todo builtin shader or GL.clear + { + var mat = new Material(Shader.Find("Hidden/VBlitColor")); + + mat.SetColor("_color", color); + + Graphics.Blit(null, rt, mat); + + mat.Destroy(); + + return rt; + + } + + + + public static GraphicsFormat GetCompatibleForRendering(this GraphicsFormat graphicsFormat) + { + if ((int)graphicsFormat == 88) // unsupported on dx11 but GetCompatibleFormat() doesn't think so + if (PlayerSettings.colorSpace == ColorSpace.Linear) + return GraphicsFormat.R8G8B8A8_SRGB; + else + return GraphicsFormat.R8G8B8A8_UNorm; + +#if UNITY_2023_2_OR_NEWER + return SystemInfo.GetCompatibleFormat(graphicsFormat, GraphicsFormatUsage.Render); +#else + return SystemInfo.GetCompatibleFormat(graphicsFormat, FormatUsage.Render); +#endif + + } + + + +#if UNITY_EDITOR + + public static void SavePNG(this Texture2D texture2d, string path) => File.WriteAllBytes(path, texture2d.EncodeToPNG()); + + public static void SetImportSettings(this Texture2D texture2d, int? maxSize = null, bool? useMips = null, bool? sRGB = null, bool? isReadable = null, bool? useCompression = null) + { + var importer = texture2d.GetImporter(); + + if (useCompression != null) + importer.textureCompression = useCompression.GetValueOrDefault() ? TextureImporterCompression.Compressed : TextureImporterCompression.Uncompressed; + + if (sRGB != null) + importer.sRGBTexture = sRGB.GetValueOrDefault(); + + if (maxSize != null) + importer.maxTextureSize = maxSize.GetValueOrDefault(); + + if (useMips != null) + importer.mipmapEnabled = useMips.GetValueOrDefault(); + + + // if (texture2d.format == TextureFormat.R16 || texture2d.format == TextureFormat.RG32) + if (texture2d.format == TextureFormat.R16) + { + var platformSettings = importer.GetDefaultPlatformTextureSettings(); + + platformSettings.format = TextureImporterFormat.R16; + + if (maxSize != null) + platformSettings.maxTextureSize = maxSize.GetValueOrDefault(); + + importer.SetPlatformTextureSettings(platformSettings); + + } + + } + + public static TextureImporter GetImporter(this Texture2D t) => (TextureImporter)AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(t)); + +#endif + + + + + + + + #endregion + + #region Objects + + + public static Object[] FindObjects(Type type) + { +#if UNITY_2023_1_OR_NEWER + return Object.FindObjectsByType(type, FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(type); +#endif + } + public static T[] FindObjects() where T : Object + { +#if UNITY_2023_1_OR_NEWER + return Object.FindObjectsByType(FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(); +#endif + } + + public static void Destroy(this Object r) + { + if (Application.isPlaying) + Object.Destroy(r); + else + Object.DestroyImmediate(r); + + } + + public static void DestroyImmediate(this Object o) => Object.DestroyImmediate(o); + + + + + + #endregion + + #region Text + + + public static class TextUtils + { + public static string FormatDistance(float meters) + { + int m = (int)meters; + + if (m < 1000) + return m + " m"; + else + return (m / 1000) + "." + (m / 100) % 10 + " km"; + + } + public static string FormatLong(long l) => System.String.Format("{0:n0}", l); + public static string FormatInt(int l) => FormatLong((long)l); + public static string FormatTime(long ms, bool includeMs = false) + { + System.TimeSpan t = System.TimeSpan.FromMilliseconds(ms); + var s = ""; + if (t.Hours != 0) s += " " + t.Hours + " hour" + GetCountSuffix(t.Hours); + if (t.Minutes != 0) s += " " + t.Minutes + " minute" + GetCountSuffix(t.Minutes); + if (t.Seconds != 0) s += " " + t.Seconds + " second" + GetCountSuffix(t.Seconds); + if (t.Milliseconds != 0 && includeMs) s += " " + t.Milliseconds + " millisecond" + GetCountSuffix(t.Milliseconds); + + if (s == "") + if (includeMs) s = "0 milliseconds"; + else s = "0 seconds"; + + return s.Trim(); + } + public static string FormatFileSize(long bytes, bool sizeUnknownIfNotMoreThanZero = false) + { + if (sizeUnknownIfNotMoreThanZero && bytes == 0) return "Size unknown"; + + var ss = new[] { "B", "KB", "MB", "GB", "TB" }; + var bprev = bytes; + int i = 0; + while (bytes >= 1024 && i++ < ss.Length - 1) bytes = (bprev = bytes) / 1024; + + if (bytes < 0) return "? B"; + if (i < 3) return string.Format("{0:0.#} ", bytes) + ss[i]; + return string.Format("{0:0.##} ", bytes) + ss[i]; + } + + static string GetCountSuffix(long c) => c % 10 != 1 ? "s" : ""; + + public static string GetLoremIpsum(int words = 2) + { + var s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur Excepteur sint occaecat cupidatat non proident sunt in culpa qui officia deserunt mollit anim id est laborum"; + var ws = s.Split(' ').Select(r => r.ToLower().Trim(new[] { ',', '.' })); + ws = ws.OrderBy(r => UnityEngine.Random.Range(0, 1232)).Take(words); + var ss = string.Join(" ", ws); + return char.ToUpper(ss[0]) + ss.Substring(1); + } + + } + + + public static bool IsEmpty(this string s) => s == ""; + public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s); + + public static bool IsLower(this char c) => System.Char.IsLower(c); + public static bool IsUpper(this char c) => System.Char.IsUpper(c); + public static bool IsDigit(this char c) => System.Char.IsDigit(c); + public static bool IsLetter(this char c) => System.Char.IsLetter(c); + public static bool IsWhitespace(this char c) => System.Char.IsWhiteSpace(c); + + public static char ToLower(this char c) => System.Char.ToLower(c); + public static char ToUpper(this char c) => System.Char.ToUpper(c); + + + + public static string Decamelcase(this string s) + { + return Regex.Replace(Regex.Replace(s, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"), @"(\p{Ll})(\P{Ll})", "$1 $2"); + } + public static string FormatVariableName(this string s, bool lowercaseFollowingWords = true) + { + return string.Join(" ", s.Decamelcase() + .Split(' ') + .Select(r => new[] { "", "and", "or", "with", "without", "by", "from" }.Contains(r.ToLower()) || (lowercaseFollowingWords && !s.Trim().StartsWith(r)) ? r.ToLower() + : r.Substring(0, 1).ToUpper() + r.Substring(1))).Trim(' '); + } + + public static string Remove(this string s, string toRemove) + { + if (toRemove == "") return s; + return s.Replace(toRemove, ""); + } + + + + + + + #endregion + + #region Paths + + + public static bool HasParentPath(this string path) => path.LastIndexOf('/') > 0; + public static string GetParentPath(this string path) => path.HasParentPath() ? path.Substring(0, path.LastIndexOf('/')) : ""; + + public static string ToGlobalPath(this string localPath) => Application.dataPath + "/" + localPath.Substring(0, localPath.Length - 1); + public static string ToLocalPath(this string globalPath) => "Assets" + globalPath.Remove(Application.dataPath); + + + + public static string CombinePath(this string p, string p2) => Path.Combine(p, p2); + + public static bool IsSubpathOf(this string path, string of) => path.StartsWith(of + "/") || of == ""; + + public static string GetDirectory(this string pathOrDirectory) + { + var directory = pathOrDirectory.Contains('.') ? pathOrDirectory.Substring(0, pathOrDirectory.LastIndexOf('/')) : pathOrDirectory; + + if (directory.Contains('.')) + directory = directory.Substring(0, directory.LastIndexOf('/')); + + return directory; + + } + + public static bool DirectoryExists(this string pathOrDirectory) => Directory.Exists(pathOrDirectory.GetDirectory()); + + public static string EnsureDirExists(this string pathOrDirectory) // todo to EnsureDirectoryExists + { + var directory = pathOrDirectory.GetDirectory(); + + if (directory.HasParentPath() && !Directory.Exists(directory.GetParentPath())) + EnsureDirExists(directory.GetParentPath()); + + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + return pathOrDirectory; + + } + + + + public static string ClearDir(this string dir) + { + if (!Directory.Exists(dir)) return dir; + + var diri = new DirectoryInfo(dir); + foreach (var r in diri.EnumerateFiles()) r.Delete(); + foreach (var r in diri.EnumerateDirectories()) r.Delete(true); + + return dir; + } + + + + + + +#if UNITY_EDITOR + + public static string EnsurePathIsUnique(this string path) + { + if (!path.DirectoryExists()) return path; + + var s = AssetDatabase.GenerateUniqueAssetPath(path); // returns empty if parent dir doesnt exist + + return s == "" ? path : s; + + } + + public static void EnsureDirExistsAndRevealInFinder(string dir) + { + EnsureDirExists(dir); + UnityEditor.EditorUtility.OpenWithDefaultApp(dir); + } + +#endif + + + + #endregion + + #region AssetDatabase + +#if UNITY_EDITOR + + public static AssetImporter GetImporter(this Object t) => AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(t)); + + public static string ToPath(this string guid) => AssetDatabase.GUIDToAssetPath(guid); // returns empty string if not found + public static List ToPaths(this IEnumerable guids) => guids.Select(r => r.ToPath()).ToList(); + + public static string GetFilename(this string path, bool withExtension = false) => withExtension ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path); // prev GetName + public static string GetExtension(this string path) => Path.GetExtension(path); + + + public static string ToGuid(this string pathInProject) => AssetDatabase.AssetPathToGUID(pathInProject); + public static List ToGuids(this IEnumerable pathsInProject) => pathsInProject.Select(r => r.ToGuid()).ToList(); + + public static string GetPath(this Object o) => AssetDatabase.GetAssetPath(o); + public static string GetGuid(this Object o) => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o)); + + public static string GetScriptPath(string scriptName) => AssetDatabase.FindAssets("t: script " + scriptName, null).FirstOrDefault()?.ToPath() ?? "scirpt not found"; + + + public static bool IsValidGuid(this string guid) => AssetDatabase.AssetPathToGUID(AssetDatabase.GUIDToAssetPath(guid), AssetPathToGUIDOptions.OnlyExistingAssets) != ""; + + + + // toremove + public static Object LoadGuid(this string guid) => AssetDatabase.LoadAssetAtPath(guid.ToPath(), typeof(Object)); + public static T LoadGuid(this string guid) where T : Object => AssetDatabase.LoadAssetAtPath(guid.ToPath()); + + + + + public static List FindAllAssetsOfType_guids(Type type) => AssetDatabase.FindAssets("t:" + type.Name).ToList(); + public static List FindAllAssetsOfType_guids(Type type, string path) => AssetDatabase.FindAssets("t:" + type.Name, new[] { path }).ToList(); + public static List FindAllAssetsOfType() where T : Object => FindAllAssetsOfType_guids(typeof(T)).Select(r => (T)r.LoadGuid()).ToList(); + public static List FindAllAssetsOfType(string path) where T : Object => FindAllAssetsOfType_guids(typeof(T), path).Select(r => (T)r.LoadGuid()).ToList(); + + public static T Reimport(this T t) where T : Object { AssetDatabase.ImportAsset(t.GetPath(), ImportAssetOptions.ForceUpdate); return t; } + +#endif + + + + + + #endregion + + #region Serialization + + + [System.Serializable] + public class SerializableDictionary : Dictionary, ISerializationCallbackReceiver + { + [SerializeField] List keys = new(); + [SerializeField] List values = new(); + + public void OnBeforeSerialize() + { + keys.Clear(); + values.Clear(); + + foreach (KeyValuePair kvp in this) + { + keys.Add(kvp.Key); + values.Add(kvp.Value); + } + + } + public void OnAfterDeserialize() + { + this.Clear(); + + for (int i = 0; i < keys.Count; i++) + this[keys[i]] = values[i]; + + } + + } + + + + + + #endregion + + #region GlobalID + +#if UNITY_EDITOR + + [System.Serializable] + public struct GlobalID : System.IEquatable + { + public Object GetObject() => GlobalObjectId.GlobalObjectIdentifierToObjectSlow(globalObjectId); + public int GetObjectInstanceId() => GlobalObjectId.GlobalObjectIdentifierToInstanceIDSlow(globalObjectId); + + + public string guid => globalObjectId.assetGUID.ToString(); + public ulong fileId => globalObjectId.targetObjectId; + + public bool isNull => globalObjectId.identifierType == 0; + public bool isAsset => globalObjectId.identifierType == 1; + public bool isSceneObject => globalObjectId.identifierType == 2; + + public GlobalObjectId globalObjectId => _globalObjectId.Equals(default) && globalObjectIdString != null && GlobalObjectId.TryParse(globalObjectIdString, out var r) ? _globalObjectId = r : _globalObjectId; + public GlobalObjectId _globalObjectId; + + public GlobalID(Object o) => globalObjectIdString = (_globalObjectId = GlobalObjectId.GetGlobalObjectIdSlow(o)).ToString(); + public GlobalID(string s) => globalObjectIdString = GlobalObjectId.TryParse(s, out _globalObjectId) ? s : s; + + public string globalObjectIdString; + + + + public bool Equals(GlobalID other) => this.globalObjectIdString.Equals(other.globalObjectIdString); + + public static bool operator ==(GlobalID a, GlobalID b) => a.Equals(b); + public static bool operator !=(GlobalID a, GlobalID b) => !a.Equals(b); + + public override bool Equals(object other) => other is GlobalID otherglobalID && this.Equals(otherglobalID); + public override int GetHashCode() => globalObjectIdString == null ? 0 : globalObjectIdString.GetHashCode(); + + + public override string ToString() => globalObjectIdString; + + } + + public static GlobalID GetGlobalID(this Object o) => new(o); + public static GlobalID[] GetGlobalIDs(this IEnumerable instanceIds) + { + var unityGlobalIds = new GlobalObjectId[instanceIds.Count()]; + + GlobalObjectId.GetGlobalObjectIdsSlow(instanceIds.ToArray(), unityGlobalIds); + + var globalIds = unityGlobalIds.Select(r => new GlobalID(r.ToString())); + + return globalIds.ToArray(); + + } + + public static Object[] GetObjects(this IEnumerable globalIDs) + { + var goids = globalIDs.Select(r => r.globalObjectId).ToArray(); + + var objects = new Object[goids.Length]; + + GlobalObjectId.GlobalObjectIdentifiersToObjectsSlow(goids, objects); + + return objects; + + } + public static int[] GetObjectInstanceIds(this IEnumerable globalIDs) + { + var goids = globalIDs.Select(r => r.globalObjectId).ToArray(); + + var iids = new int[goids.Length]; + + GlobalObjectId.GlobalObjectIdentifiersToInstanceIDsSlow(goids, iids); + + return iids; + + } + + +#endif + + + + + #endregion + + #region Editor + +#if UNITY_EDITOR + + + public static class EditorUtils + { + + public static void OpenFolder(string path) + { + var folder = AssetDatabase.LoadAssetAtPath(path, typeof(Object)); + + var t = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser"); + var w = (EditorWindow)t.GetField("s_LastInteractedProjectBrowser").GetValue(null); + + var m_ListAreaState = t.GetField("m_ListAreaState", maxBindingFlags).GetValue(w); + + m_ListAreaState.GetType().GetField("m_SelectedInstanceIDs").SetValue(m_ListAreaState, new List { folder.GetInstanceID() }); + + t.GetMethod("OpenSelectedFolders", maxBindingFlags).Invoke(null, null); + + } + + public static void PingObject(Object o, bool select = false, bool focusProjectWindow = true) + { + if (select) + { + Selection.activeObject = null; + Selection.activeObject = o; + } + if (focusProjectWindow) EditorUtility.FocusProjectWindow(); + EditorGUIUtility.PingObject(o); + + } + public static void PingObject(string guid, bool select = false, bool focusProjectWindow = true) => PingObject(AssetDatabase.LoadAssetAtPath(guid.ToPath())); + + public static EditorWindow OpenObjectPicker(Object obj = null, bool allowSceneObjects = false, string searchFilter = "", int controlID = 0) where T : Object + { + EditorGUIUtility.ShowObjectPicker(obj, allowSceneObjects, searchFilter, controlID); + + return Resources.FindObjectsOfTypeAll(typeof(Editor).Assembly.GetType("UnityEditor.ObjectSelector")).FirstOrDefault() as EditorWindow; + + } + public static EditorWindow OpenColorPicker(System.Action colorChangedCallback, Color color, bool showAlpha = true, bool hdr = false) + { + typeof(Editor).Assembly.GetType("UnityEditor.ColorPicker").InvokeMethod("Show", colorChangedCallback, color, showAlpha, hdr); + + return typeof(Editor).Assembly.GetType("UnityEditor.ColorPicker").GetPropertyValue("instance"); + + } + + + + public static bool CheckUnityVersion(string versionQuery) + { + if (versionQueryCache.TryGetValue(versionQuery, out var cachedResult)) return cachedResult; + + if (versionQuery.Any(r => r.IsLetter() && !versionQuery.EndsWith(" or older") && !versionQuery.EndsWith(" or newer"))) throw new System.ArgumentException("Invalid unity version query"); + + + + + var curVersion = new string(Application.unityVersion.TakeWhile(r => !r.IsLetter()).ToArray()); + + var curMajor = int.Parse(curVersion.Split('.')[0]); + var curMinor = int.Parse(curVersion.Split('.')[1]); + var curPatch = int.Parse(curVersion.Split('.')[2]); + + + + + + var givenVersion = new string(versionQuery.TakeWhile(r => !r.IsWhitespace()).ToArray()); + + var isMinorGiven = givenVersion.Count(r => r == '.') >= 1; + var isPatchGiven = givenVersion.Count(r => r == '.') >= 2; + + var givenMajor = int.Parse(givenVersion.Split('.')[0]); + var givenMinor = isMinorGiven ? int.Parse(givenVersion.Split('.')[1]) : 0; + var givenPatch = isPatchGiven ? int.Parse(givenVersion.Split('.')[2]) : 0; + + + + + + + var curVersionCanBeNewer = versionQuery.Contains("or newer"); + var curVersionCanBeOlder = versionQuery.Contains("or older"); + + + if (curMajor > givenMajor) return versionQueryCache[versionQuery] = curVersionCanBeNewer; + if (curMajor < givenMajor) return versionQueryCache[versionQuery] = curVersionCanBeOlder; + + if (!isMinorGiven) return versionQueryCache[versionQuery] = true; + + + if (curMinor > givenMinor) return versionQueryCache[versionQuery] = curVersionCanBeNewer; + if (curMinor < givenMinor) return versionQueryCache[versionQuery] = curVersionCanBeOlder; + + if (!isPatchGiven) return versionQueryCache[versionQuery] = true; + + + if (curPatch > givenPatch) return versionQueryCache[versionQuery] = curVersionCanBeNewer; + if (curPatch < givenPatch) return versionQueryCache[versionQuery] = curVersionCanBeOlder; + + return versionQueryCache[versionQuery] = true; + + + + + // query examples: + // + // "2022.3.5 or newer" + // "2022.3.5 or older" + // "2022.3 or older" + // "2022.3" + // "2022" + + } + + static Dictionary versionQueryCache = new(); + + + + public static void SetSymbolDefinedInAsmdef(string asmdefName, string symbol, bool defined) + { + var isDefined = IsSymbolDefinedInAsmdef(asmdefName, symbol); + var shouldBeDefined = defined; + + if (shouldBeDefined && !isDefined) + DefineSymbolInAsmdef(asmdefName, symbol); + + if (!shouldBeDefined && isDefined) + UndefineSymbolInAsmdef(asmdefName, symbol); + + } + public static bool IsSymbolDefinedInAsmdef(string asmdefName, string symbol) + { + var path = AssetDatabase.FindAssets("t: asmdef " + asmdefName, null).First().ToPath(); + var importer = AssetImporter.GetAtPath(path); + + var editorType = typeof(Editor).Assembly.GetType("UnityEditor.AssemblyDefinitionImporterInspector"); + var editor = Editor.CreateEditor(importer, editorType); + + var state = editor.GetFieldValue("m_ExtraDataTargets").First(); + + + var definesList = state.GetFieldValue("versionDefines"); + var isSymbolDefined = Enumerable.Range(0, definesList.Count).Any(i => definesList[i].GetFieldValue("define") == symbol); + + + Object.DestroyImmediate(editor); + + return isSymbolDefined; + + } + + static void DefineSymbolInAsmdef(string asmdefName, string symbol) + { + var path = AssetDatabase.FindAssets("t: asmdef " + asmdefName, null).First().ToPath(); + var importer = AssetImporter.GetAtPath(path); + + var editorType = typeof(Editor).Assembly.GetType("UnityEditor.AssemblyDefinitionImporterInspector"); + var editor = Editor.CreateEditor(importer, editorType); + + var state = editor.GetFieldValue("m_ExtraDataTargets").First(); + + + var definesList = state.GetFieldValue("versionDefines"); + + var defineType = definesList.GetType().GenericTypeArguments[0]; + var newDefine = System.Activator.CreateInstance(defineType); + + newDefine.SetFieldValue("name", "Unity"); + newDefine.SetFieldValue("define", symbol); + + definesList.Add(newDefine); + + + editor.InvokeMethod("Apply"); + + Object.DestroyImmediate(editor); + + } + static void UndefineSymbolInAsmdef(string asmdefName, string symbol) + { + var path = AssetDatabase.FindAssets("t: asmdef " + asmdefName, null).First().ToPath(); + var importer = AssetImporter.GetAtPath(path); + + var editorType = typeof(Editor).Assembly.GetType("UnityEditor.AssemblyDefinitionImporterInspector"); + var editor = Editor.CreateEditor(importer, editorType); + + var state = editor.GetFieldValue("m_ExtraDataTargets").First(); + + + var definesList = state.GetFieldValue("versionDefines"); + + var defineIndex = Enumerable.Range(0, definesList.Count).First(i => definesList[i].GetFieldValue("define") == symbol); + + definesList.RemoveAt(defineIndex); + + + editor.InvokeMethod("Apply"); + + Object.DestroyImmediate(editor); + + } + + + + + public static int GetCurrendUndoGroupIndex() + { + var args = new object[] { _dummyList, 0 }; + + typeof(Undo).GetMethodInfo("GetRecords", typeof(List), typeof(int).MakeByRefType()) + .Invoke(null, args); + + + return (int)args[1]; + + } + + static List _dummyList = new(); + + + + + + public static void Hide(string path) + { + if (IsHidden(path)) return; + + if (File.Exists(path)) + File.Move(path, path + "~"); + + + path += ".meta"; + if (File.Exists(path)) + File.Move(path, path + "~"); + } + public static void Unhide(string path) + { + if (!IsHidden(path)) return; + if (path.EndsWith("~")) path = path.Substring(0, path.Length - 1); + + if (File.Exists(path + "~")) + File.Move(path + "~", path); + + path += ".meta"; + if (File.Exists(path + "~")) + File.Move(path + "~", path); + } + public static bool IsHidden(string path) => path.EndsWith("~") || File.Exists(path + "~"); + + + public static void CopyDirectoryDeep(string sourcePath, string destinationPath) + { + CopyDirectoryRecursively(sourcePath, destinationPath); + + var metas = GetFilesRecursively(destinationPath, (f) => f.EndsWith(".meta")); + var guidTable = new List<(string originalGuid, string newGuid)>(); + + foreach (string meta in metas) + { + StreamReader file = new(meta); + file.ReadLine(); + string guidLine = file.ReadLine(); + file.Close(); + string originalGuid = guidLine.Substring(6, guidLine.Length - 6); + string newGuid = GUID.Generate().ToString().Replace("-", ""); + guidTable.Add((originalGuid, newGuid)); + } + + var allFiles = GetFilesRecursively(destinationPath); + + foreach (string fileToModify in allFiles) + { + string content = File.ReadAllText(fileToModify); + + foreach (var guidPair in guidTable) + { + content = content.Replace(guidPair.originalGuid, guidPair.newGuid); + } + + File.WriteAllText(fileToModify, content); + } + + AssetDatabase.Refresh(); + } + + private static void CopyDirectoryRecursively(string sourceDirName, string destDirName) + { + DirectoryInfo dir = new(sourceDirName); + + DirectoryInfo[] dirs = dir.GetDirectories(); + + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, false); + } + + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(destDirName, subdir.Name); + CopyDirectoryRecursively(subdir.FullName, temppath); + } + } + + private static List GetFilesRecursively(string path, System.Func criteria = null, List files = null) + { + if (files == null) + { + files = new List(); + } + + files.AddRange(Directory.GetFiles(path).Where(f => criteria == null || criteria(f))); + + foreach (string directory in Directory.GetDirectories(path)) + { + GetFilesRecursively(directory, criteria, files); + } + + return files; + } + + + + + + // for non-extension methods + + } + + + public static class EditorPrefsCached + { + public static int GetInt(string key, int defaultValue = 0) + { + if (ints_byKey.ContainsKey(key)) + return ints_byKey[key]; + else + return ints_byKey[key] = EditorPrefs.GetInt(key, defaultValue); + + } + public static bool GetBool(string key, bool defaultValue = false) + { + if (bools_byKey.ContainsKey(key)) + return bools_byKey[key]; + else + return bools_byKey[key] = EditorPrefs.GetBool(key, defaultValue); + + } + public static float GetFloat(string key, float defaultValue = 0) + { + if (floats_byKey.ContainsKey(key)) + return floats_byKey[key]; + else + return floats_byKey[key] = EditorPrefs.GetFloat(key, defaultValue); + + } + public static string GetString(string key, string defaultValue = "") + { + if (strings_byKey.ContainsKey(key)) + return strings_byKey[key]; + else + return strings_byKey[key] = EditorPrefs.GetString(key, defaultValue); + + } + + public static void SetInt(string key, int value) + { + ints_byKey[key] = value; + + EditorPrefs.SetInt(key, value); + + } + public static void SetBool(string key, bool value) + { + bools_byKey[key] = value; + + EditorPrefs.SetBool(key, value); + + } + public static void SetFloat(string key, float value) + { + floats_byKey[key] = value; + + EditorPrefs.SetFloat(key, value); + + } + public static void SetString(string key, string value) + { + strings_byKey[key] = value; + + EditorPrefs.SetString(key, value); + + } + + + static Dictionary ints_byKey = new(); + static Dictionary bools_byKey = new(); + static Dictionary floats_byKey = new(); + static Dictionary strings_byKey = new(); + + } + + public static class ProjectPrefs + { + public static int GetInt(string key, int defaultValue = 0) => EditorPrefsCached.GetInt(key + projectId, defaultValue); + public static bool GetBool(string key, bool defaultValue = false) => EditorPrefsCached.GetBool(key + projectId, defaultValue); + public static float GetFloat(string key, float defaultValue = 0) => EditorPrefsCached.GetFloat(key + projectId, defaultValue); + public static string GetString(string key, string defaultValue = "") => EditorPrefsCached.GetString(key + projectId, defaultValue); + + public static void SetInt(string key, int value) => EditorPrefsCached.SetInt(key + projectId, value); + public static void SetBool(string key, bool value) => EditorPrefsCached.SetBool(key + projectId, value); + public static void SetFloat(string key, float value) => EditorPrefsCached.SetFloat(key + projectId, value); + public static void SetString(string key, string value) => EditorPrefsCached.SetString(key + projectId, value); + + + + public static bool HasKey(string key) => EditorPrefs.HasKey(key + projectId); + public static void DeleteKey(string key) => EditorPrefs.DeleteKey(key + projectId); + + + + public static int projectId => PlayerSettings.productGUID.GetHashCode(); + + } + + + + public static void RecordUndo(this Object o, string operationName = "") => Undo.RecordObject(o, operationName); + public static void Dirty(this Object o) => UnityEditor.EditorUtility.SetDirty(o); + public static void Save(this Object o) => AssetDatabase.SaveAssetIfDirty(o); + + + + public static void SelectInInspector(this Object[] objects, bool frameInHierarchy = false, bool frameInProject = false) + { + void setHierarchyLocked(bool isLocked) => allHierarchies.ForEach(r => r?.GetMemberValue("m_SceneHierarchy")?.SetMemberValue("m_RectSelectInProgress", true)); + void setProjectLocked(bool isLocked) => allProjectBrowsers.ForEach(r => r?.SetMemberValue("m_InternalSelectionChange", isLocked)); + + + if (!frameInHierarchy) setHierarchyLocked(true); + if (!frameInProject) setProjectLocked(true); + + Selection.objects = objects?.ToArray(); + + if (!frameInHierarchy) EditorApplication.delayCall += () => setHierarchyLocked(false); + if (!frameInProject) EditorApplication.delayCall += () => setProjectLocked(false); + + } + public static void SelectInInspector(this Object obj, bool frameInHierarchy = false, bool frameInProject = false) => new[] { obj }.SelectInInspector(frameInHierarchy, frameInProject); + + static IEnumerable allHierarchies => _allHierarchies ??= typeof(Editor).Assembly.GetType("UnityEditor.SceneHierarchyWindow").GetFieldValue("s_SceneHierarchyWindows").Cast(); + static IEnumerable _allHierarchies; + + static IEnumerable allProjectBrowsers => _allProjectBrowsers ??= typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser").GetFieldValue("s_ProjectBrowsers").Cast(); + static IEnumerable _allProjectBrowsers; + + + + public static void MoveTo(this EditorWindow window, Vector2 position, bool ensureFitsOnScreen = true) + { + if (!ensureFitsOnScreen) { window.position = window.position.SetPos(position); return; } + + var windowRect = window.position; + var unityWindowRect = EditorGUIUtility.GetMainWindowPosition(); + + position.x = position.x.Max(unityWindowRect.position.x); + position.y = position.y.Max(unityWindowRect.position.y); + + position.x = position.x.Min(unityWindowRect.xMax - windowRect.width); + position.y = position.y.Min(unityWindowRect.yMax - windowRect.height); + + window.position = windowRect.SetPos(position); + + } + + + +#endif + + #endregion + + } + + public static class VGUI + { + + #region Drawing + + + public static Rect Draw(this Rect rect, Color color) + { + EditorGUI.DrawRect(rect, color); + + return rect; + + } + public static Rect Draw(this Rect rect) => rect.Draw(Color.black); + + public static Rect DrawOutline(this Rect rect, Color color, float thickness = 1) + { + + rect.SetWidth(thickness).Draw(color); + rect.SetWidthFromRight(thickness).Draw(color); + + rect.SetHeight(thickness).Draw(color); + rect.SetHeightFromBottom(thickness).Draw(color); + + + return rect; + + } + public static Rect DrawOutline(this Rect rect, float thickness = 1) => rect.DrawOutline(Color.black, thickness); + + + + + public static Rect DrawRounded(this Rect rect, Color color, int cornerRadius) + { + if (!curEvent.isRepaint) return rect; + + cornerRadius = cornerRadius.Min((rect.height / 2).FloorToInt()).Min((rect.width / 2).FloorToInt()); + + if (cornerRadius < 0) return rect; + + GUIStyle style; + + void getStyle() + { + if (_roundedStylesByCornerRadius.TryGetValue(cornerRadius, out style)) return; + + var pixelsPerPoint = 2; + + var res = cornerRadius * 2 * pixelsPerPoint; + var pixels = new Color[res * res]; + + var white = Greyscale(1, 1); + var clear = Greyscale(1, 0); + var halfRes = res / 2; + + for (int x = 0; x < res; x++) + for (int y = 0; y < res; y++) + { + var sqrMagnitude = (new Vector2(x - halfRes + .5f, y - halfRes + .5f)).sqrMagnitude; + pixels[x + y * res] = sqrMagnitude <= halfRes * halfRes ? white : clear; + } + + var texture = new Texture2D(res, res); + texture.SetPropertyValue("pixelsPerPoint", pixelsPerPoint); + texture.hideFlags = HideFlags.DontSave; + texture.SetPixels(pixels); + texture.Apply(); + + + + style = new GUIStyle(); + style.normal.background = texture; + style.alignment = TextAnchor.MiddleCenter; + style.border = new RectOffset(cornerRadius, cornerRadius, cornerRadius, cornerRadius); + + + _roundedStylesByCornerRadius[cornerRadius] = style; + + } + void draw() + { + SetGUIColor(color); + + style.Draw(rect, false, false, false, false); + + ResetGUIColor(); + + } + + getStyle(); + draw(); + + return rect; + + } + public static Rect DrawRounded(this Rect rect, Color color, float cornerRadius) => rect.DrawRounded(color, cornerRadius.RoundToInt()); + + static Dictionary _roundedStylesByCornerRadius = new(); + + + + + public static Rect DrawBlurred(this Rect rect, Color color, int blurRadius) + { + if (!curEvent.isRepaint) return rect; + + var pixelsPerPoint = .5f; + // var pixelsPerPoint = 1f; + + var blurRadiusScaled = (blurRadius * pixelsPerPoint).RoundToInt().Max(1).Min(123); + + var croppedRectWidth = (rect.width * pixelsPerPoint).RoundToInt().Min(blurRadiusScaled * 2); + var croppedRectHeight = (rect.height * pixelsPerPoint).RoundToInt().Min(blurRadiusScaled * 2); + + var textureWidth = croppedRectWidth + blurRadiusScaled * 2; + var textureHeight = croppedRectHeight + blurRadiusScaled * 2; + + if (textureWidth <= 0 || textureWidth > 1232) return rect; + if (textureHeight <= 0 || textureHeight > 1232) return rect; + + + GUIStyle style; + + void getStyle() + { + if (_blurredStylesByTextureSize.TryGetValue((textureWidth, textureHeight), out style)) return; + + // VDebug.LogStart(blurRadius + ""); + + var pixels = new Color[textureWidth * textureHeight]; + var kernel = GaussianKernel.GenerateArray(blurRadiusScaled * 2 + 1); + + for (int x = 0; x < textureWidth; x++) + for (int y = 0; y < textureHeight; y++) + { + var sum = 0f; + + for (int xSample = (x - blurRadiusScaled).Max(blurRadiusScaled); xSample <= (x + blurRadiusScaled).Min(textureWidth - 1 - blurRadiusScaled); xSample++) + for (int ySample = (y - blurRadiusScaled).Max(blurRadiusScaled); ySample <= (y + blurRadiusScaled).Min(textureHeight - 1 - blurRadiusScaled); ySample++) + sum += kernel[blurRadiusScaled + xSample - x, blurRadiusScaled + ySample - y]; + + pixels[x + y * textureWidth] = Greyscale(1, sum); + + } + + var texture = new Texture2D(textureWidth, textureHeight); + texture.SetPropertyValue("pixelsPerPoint", pixelsPerPoint); + texture.hideFlags = HideFlags.DontSave; + texture.SetPixels(pixels); + texture.Apply(); + + + style = new GUIStyle(); + style.normal.background = texture; + style.alignment = TextAnchor.MiddleCenter; + + var borderX = ((textureWidth / 2f - 1) / pixelsPerPoint).FloorToInt(); + var borderY = ((textureHeight / 2f - 1) / pixelsPerPoint).FloorToInt(); + style.border = new RectOffset(borderX, borderX, borderY, borderY); + + _blurredStylesByTextureSize[(textureWidth, textureHeight)] = style; + + // VDebug.LogFinish(); + + } + void draw() + { + SetGUIColor(color); + + style.Draw(rect.SetSizeFromMid(rect.width + blurRadius * 2, rect.height + blurRadius * 2), false, false, false, false); + + ResetGUIColor(); + + } + + getStyle(); + draw(); + + return rect; + + } + public static Rect DrawBlurred(this Rect rect, Color color, float blurRadius) => rect.DrawBlurred(color, blurRadius.RoundToInt()); + + static Dictionary<(int, int), GUIStyle> _blurredStylesByTextureSize = new(); + + + + + static void DrawCurtain(this Rect rect, Color color, int dir) + { + void genTextures() + { + if (_gradientTextures != null) return; + + _gradientTextures = new Texture2D[4]; + + // var pixels = Enumerable.Range(0, 256).Select(r => Greyscale(1, r / 255f)); + var pixels = Enumerable.Range(0, 256).Select(r => Greyscale(1, (r / 255f).Smoothstep())); + + var up = new Texture2D(1, 256); + up.SetPixels(pixels.Reverse().ToArray()); + up.Apply(); + up.hideFlags = HideFlags.DontSave; + up.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[0] = up; + + var down = new Texture2D(1, 256); + down.SetPixels(pixels.ToArray()); + down.Apply(); + down.hideFlags = HideFlags.DontSave; + down.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[1] = down; + + var left = new Texture2D(256, 1); + left.SetPixels(pixels.ToArray()); + left.Apply(); + left.hideFlags = HideFlags.DontSave; + left.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[2] = left; + + var right = new Texture2D(256, 1); + right.SetPixels(pixels.Reverse().ToArray()); + right.Apply(); + right.hideFlags = HideFlags.DontSave; + right.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[3] = right; + + } + void draw() + { + SetGUIColor(color); + + GUI.DrawTexture(rect, _gradientTextures[dir]); + + ResetGUIColor(); + + } + + genTextures(); + draw(); + + } + + static Texture2D[] _gradientTextures; + + public static void DrawCurtainUp(this Rect rect, Color color) => rect.DrawCurtain(color, 0); + public static void DrawCurtainDown(this Rect rect, Color color) => rect.DrawCurtain(color, 1); + public static void DrawCurtainLeft(this Rect rect, Color color) => rect.DrawCurtain(color, 2); + public static void DrawCurtainRight(this Rect rect, Color color) => rect.DrawCurtain(color, 3); + + + + + + + #endregion + + #region Events + + + public class WrappedEvent + { + public Event e; + + public bool isRepaint => e.type == EventType.Repaint; + public bool isLayout => e.type == EventType.Layout; + public bool isUsed => e.type == EventType.Used; + public bool isMouseLeaveWindow => e.type == EventType.MouseLeaveWindow; + public bool isMouseEnterWindow => e.type == EventType.MouseEnterWindow; + public bool isContextClick => e.type == EventType.ContextClick; + + public bool isKeyDown => e.type == EventType.KeyDown; + public bool isKeyUp => e.type == EventType.KeyUp; + public KeyCode keyCode => e.keyCode; + public char characted => e.character; + + public bool isExecuteCommand => e.type == EventType.ExecuteCommand; + public string commandName => e.commandName; + + public bool isMouse => e.isMouse; + public bool isMouseDown => e.type == EventType.MouseDown; + public bool isMouseUp => e.type == EventType.MouseUp; + public bool isMouseDrag => e.type == EventType.MouseDrag; + public bool isMouseMove => e.type == EventType.MouseMove; + public bool isScroll => e.type == EventType.ScrollWheel; + public int mouseButton => e.button; + public int clickCount => e.clickCount; + public Vector2 mousePosition => e.mousePosition; + public Vector2 mousePosition_screenSpace => GUIUtility.GUIToScreenPoint(e.mousePosition); + public Vector2 mouseDelta => e.delta; + + public bool isDragUpdate => e.type == EventType.DragUpdated; + public bool isDragPerform => e.type == EventType.DragPerform; + public bool isDragExit => e.type == EventType.DragExited; + + public EventModifiers modifiers => e.modifiers; + public bool holdingAnyModifierKey => modifiers != EventModifiers.None; + + public bool holdingAlt => e.alt; + public bool holdingShift => e.shift; + public bool holdingCtrl => e.control; + public bool holdingCmd => e.command; + public bool holdingCmdOrCtrl => e.command || e.control; + + public bool holdingAltOnly => e.modifiers == EventModifiers.Alt; // in some sessions FunctionKey is always pressed? + public bool holdingShiftOnly => e.modifiers == EventModifiers.Shift; // in some sessions FunctionKey is always pressed? + public bool holdingCtrlOnly => e.modifiers == EventModifiers.Control; + public bool holdingCmdOnly => e.modifiers == EventModifiers.Command; + public bool holdingCmdOrCtrlOnly => (e.modifiers == EventModifiers.Command || e.modifiers == EventModifiers.Control); + + public EventType type => e.type; + + public void Use() => e?.Use(); + + + public WrappedEvent(Event e) => this.e = e; + + public override string ToString() => e.ToString(); + + } + + public static WrappedEvent Wrap(this Event e) => new(e); + + public static WrappedEvent curEvent => _curEvent ??= typeof(Event).GetFieldValue("s_Current").Wrap(); + static WrappedEvent _curEvent; + + + + + + #endregion + + #region Shortcuts + + + public static Rect lastRect => GUILayoutUtility.GetLastRect(); + + public static bool isDarkTheme => EditorGUIUtility.isProSkin; + + public static bool IsHovered(this Rect r) => r.Contains(curEvent.mousePosition); + + public static float GetLabelWidth(this string s) => GUI.skin.label.CalcSize(new GUIContent(s)).x; + public static float GetLabelWidth(this string s, int fontSize) + { + SetLabelFontSize(fontSize); + + var r = s.GetLabelWidth(); + + ResetLabelStyle(); + + return r; + + } + public static float GetLabelWidth(this string s, bool isBold) + { + if (isBold) + SetLabelBold(); + + var r = s.GetLabelWidth(); + + if (isBold) + ResetLabelStyle(); + + return r; + + } + public static float GetLabelWidth(this string s, int fontSize, bool isBold) + { + if (isBold) + SetLabelBold(); + + SetLabelFontSize(fontSize); + + var r = s.GetLabelWidth(); + + ResetLabelStyle(); + + return r; + + } + + public static void SetGUIEnabled(bool enabled) { _prevGuiEnabled = GUI.enabled; GUI.enabled = enabled; } + public static void ResetGUIEnabled() => GUI.enabled = _prevGuiEnabled; + static bool _prevGuiEnabled = true; + + public static void SetLabelFontSize(int size) => GUI.skin.label.fontSize = size; + public static void SetLabelBold() => GUI.skin.label.fontStyle = FontStyle.Bold; + public static void SetLabelAlignmentCenter() => GUI.skin.label.alignment = TextAnchor.MiddleCenter; + public static void ResetLabelStyle() + { + GUI.skin.label.fontSize = 0; + GUI.skin.label.fontStyle = FontStyle.Normal; + GUI.skin.label.alignment = TextAnchor.MiddleLeft; + GUI.skin.label.wordWrap = false; + } + + + public static void SetGUIColor(Color c) + { + _guiColorStack.Push(GUI.color); + + GUI.color *= c; + + } + public static void ResetGUIColor() + { + GUI.color = _guiColorStack.Pop(); + } + + static Stack _guiColorStack = new(); + + + + public static float editorDeltaTime = .0166f; + + static void EditorDeltaTime_Update() + { + editorDeltaTime = (float)(EditorApplication.timeSinceStartup - _lastUpdateTime); + + _lastUpdateTime = EditorApplication.timeSinceStartup; + + } + static double _lastUpdateTime; + + [InitializeOnLoadMethod] + static void EditorDeltaTime_Subscribe() + { + EditorApplication.update -= EditorDeltaTime_Update; + EditorApplication.update += EditorDeltaTime_Update; + } + + + + + #endregion + + #region Controls + + + public static bool IconButton(Rect rect, string iconName, float iconSize = default, Color color = default, Color colorHovered = default, Color colorPressed = default) + { + var id = EditorGUIUtility.GUIToScreenRect(rect).GetHashCode();// GUIUtility.GetControlID(FocusType.Passive, rect); + var isPressed = id == _pressedIconButtonId; + + var wasActivated = false; + + void icon() + { + if (!curEvent.isRepaint) return; + + + if (color == default) + color = Color.white; + + if (colorHovered == default) + colorHovered = Color.white; + + if (colorPressed == default) + colorPressed = Color.white.SetAlpha(.6f); + + + if (rect.IsHovered()) + color = colorHovered; + + if (isPressed) + color = colorPressed; + + + if (iconSize == default) + iconSize = rect.width.Min(rect.height); + + var iconRect = rect.SetSizeFromMid(iconSize); + + + + SetGUIColor(color); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon(iconName)); + + ResetGUIColor(); + + + } + void mouseDown() + { + if (!curEvent.isMouseDown) return; + if (!rect.IsHovered()) return; + + _pressedIconButtonId = id; + + curEvent.Use(); + + } + void mouseUp() + { + if (!curEvent.isMouseUp) return; + if (!isPressed) return; + + _pressedIconButtonId = 0; + + if (rect.IsHovered()) + wasActivated = true; + + curEvent.Use(); + + } + void mouseDrag() + { + if (!curEvent.isMouseDrag) return; + if (!isPressed) return; + + curEvent.Use(); + + } + + rect.MarkInteractive(); + + icon(); + mouseDown(); + mouseUp(); + mouseDrag(); + + return wasActivated; + + } + + static int _pressedIconButtonId; + + + + + + #endregion + + #region Layout + + + public static void Space(float px = 6) => GUILayout.Space(px); + + public static Rect ExpandWidthLabelRect() { GUILayout.Label(""/* , GUILayout.Height(0) */, GUILayout.ExpandWidth(true)); return lastRect; } + public static Rect ExpandWidthLabelRect(float height) { GUILayout.Label("", GUILayout.Height(height), GUILayout.ExpandWidth(true)); return lastRect; } + + + + + public static void BeginIndent(float f) + { + GUILayout.BeginHorizontal(); + GUILayout.Space(f); + GUILayout.BeginVertical(); + + _indentLabelWidthStack.Push(EditorGUIUtility.labelWidth); + + EditorGUIUtility.labelWidth -= f; + } + + public static void EndIndent(float f = 0) + { + GUILayout.EndVertical(); + GUILayout.Space(f); + GUILayout.EndHorizontal(); + + EditorGUIUtility.labelWidth = _indentLabelWidthStack.Pop(); + } + static Stack _indentLabelWidthStack = new(); + + + + + + public static void Horizontal() { if (__hor) GUILayout.EndHorizontal(); else GUILayout.BeginHorizontal(); __hor = !__hor; } + public static void Vertical() { if (__v) GUILayout.EndVertical(); else GUILayout.BeginVertical(); __v = !__v; } + public static void Area(Rect r) { if (__a) GUILayout.EndArea(); else GUILayout.BeginArea(r); __a = !__a; } + public static void Area() { if (__a) GUILayout.EndArea(); __a = !__a; } + public static void ResetUIBools() { __a = __hor = __v = false; _prevGuiEnabled = true; } + static bool __hor, __a, __v; + + + + + + #endregion + + #region GUIColors + + + public static class GUIColors + { + public static Color windowBackground => isDarkTheme ? Greyscale(.22f) : Greyscale(.78f); // prev backgroundCol + public static Color pressedButtonBackground => isDarkTheme ? new Color(.48f, .76f, 1f, 1f) * 1.4f : new Color(.48f, .7f, 1f, 1f) * 1.2f; // prev pressedButtonCol + public static Color greyedOutTint => Greyscale(.7f); + public static Color selectedBackground => isDarkTheme ? new Color(.17f, .365f, .535f) : new Color(.2f, .375f, .555f) * 1.2f; + } + + + + + #endregion + + #region EditorIcons + + + public static partial class EditorIcons + { + public static Texture2D GetIcon(string iconNameOrPath) + { + if (icons_byName.TryGetValue(iconNameOrPath, out var cachedResult) && cachedResult != null) return cachedResult; + + Texture2D icon = null; + + void getCustom() + { + if (icon) return; + if (!customIcons.ContainsKey(iconNameOrPath)) return; + + var pngBytesString = customIcons[iconNameOrPath]; + var pngBytes = pngBytesString.Split("-").Select(r => System.Convert.ToByte(r, 16)).ToArray(); + + icon = new Texture2D(1, 1); + + icon.LoadImage(pngBytes); + + } + void getBuiltin() + { + if (icon) return; + + icon = typeof(EditorGUIUtility).InvokeMethod("LoadIcon", iconNameOrPath) as Texture2D; + + } + void getEmpty() + { + if (icon) return; + + icon = new Texture2D(1, 1); + + } + + getCustom(); + getBuiltin(); + getEmpty(); + + return icons_byName[iconNameOrPath] = icon; + + } + + static Dictionary icons_byName = new(); + + + static Dictionary customIcons = new() + { + ["Chevron Left"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-40-00-00-00-40-08-06-00-00-00-AA-69-71-DE-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-01-29-49-44-41-54-78-01-ED-9B-CD-71-83-30-14-06-3F-A5-02-4A-71-09-A4-A3-B4-94-0A-28-01-77-10-77-90-16-D2-81-A2-A7-21-A7-FC-8C-A4-60-23-9E-76-67-34-1C-2C-1F-76-2D-30-C3-08-09-00-00-60-58-82-3A-25-C6-38-A7-C3-25-8D-29-8D-6B-08-E1-AA-11-48-E2-53-1A-6B-FC-CE-62-9F-C9-33-9B-FC-5B-FC-9D-55-5E-29-90-FF-62-D6-8E-3C-A9-03-B6-A5-6D-BF-EE-A5-60-7A-C9-9C-62-0E-0F-50-29-6F-F8-B9-0E-54-2C-FB-BB-9D-02-87-D1-28-BF-C8-03-8D-F2-36-FF-FC-CB-1F-79-E4-91-47-1E-79-E4-91-47-1E-79-E4-91-F7-21-6F-C4-C1-E5-5F-62-1D-7E-E4-8D-24-F3-3E-B2-FC-74-76-F9-2E-1E-8A-9E-9A-B3-9F-02-7B-AC-80-D7-8A-B9-F6-E4-77-ED-2D-C2-BF-88-A3-DF-03-18-44-10-11-32-44-10-11-32-44-10-11-32-44-10-11-32-44-10-11-32-44-10-11-32-44-50-73-04-5F-FB-04-1B-23-CC-DA-91-43-1F-89-85-10-3E-D2-E1-39-8D-5B-C5-D7-7C-ED-13-6C-88-E0-73-BF-70-C5-E9-30-CB-2B-05-11-7C-EC-13-FC-8B-2D-C2-F2-83-FC-1A-EF-F0-37-D8-FB-0B-13-36-EC-1A-71-1B-E6-85-09-00-00-80-C7-F1-09-79-C0-DD-81-D6-B5-69-91-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Chevron Right"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-40-00-00-00-40-08-06-00-00-00-AA-69-71-DE-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-01-1A-49-44-41-54-78-01-ED-9B-D1-0D-82-30-10-86-AF-4E-C0-28-8E-80-1B-39-82-1B-39-02-6C-00-1B-B8-82-1B-9C-6D-D0-27-05-69-4B-48-B9-7E-5F-D2-F0-40-5F-FE-2F-B4-47-9A-AB-08-00-00-40-B5-38-D9-08-55-6D-FD-E3-EC-47-E3-47-EF-9C-EB-A5-06-7C-F0-C6-8F-4E-BF-B9-87-77-62-9D-77-D0-39-06-D3-12-C2-67-AF-FF-29-5A-C2-49-F2-68-57-CC-09-FB-42-57-AA-84-5C-01-CF-95-F3-8A-95-90-2B-60-8C-98-5B-F4-97-90-CC-4C-05-38-EC-9E-10-8D-4E-65-70-88-73-80-04-24-20-01-09-48-40-02-12-90-80-04-24-20-01-09-48-40-42-04-37-B1-44-82-84-87-58-23-41-C2-A6-CB-20-F7-48-0C-72-A8-7A-09-68-DA-26-78-15-0B-24-86-1F-C4-02-5A-F3-3F-00-E1-09-4F-78-C2-13-9E-F0-84-27-3C-E1-09-4F-78-3B-E7-7B-BA-DC-27-68-3E-FC-9A-3E-C1-A2-C3-EF-D1-27-F8-21-74-94-5D-9C-73-6B-5B-EB-76-61-AF-3E-C1-22-C3-67-A3-06-5A-65-B3-D1-E5-3E-41-DB-E1-03-3A-95-C1-5F-95-A0-3B-42-F8-AD-2F-4C-84-11-D6-F9-58-CD-85-09-00-00-80-E3-F2-02-5D-3B-DF-D0-96-78-5C-6E-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Search_"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-02-00-49-44-41-54-78-01-ED-56-3D-4F-02-41-10-1D-3E-22-C6-C4-CA-D0-9A-58-6B-87-8D-12-6B-0B-1B-7F-07-85-89-95-F4-34-D6-F2-07-2C-FD-23-D0-41-A2-8D-FF-00-8A-0B-0D-7A-70-7C-1C-EB-7B-B8-47-16-C4-70-7B-77-1C-0D-2F-B9-DC-DE-DD-CE-CE-DB-99-D9-37-27-B2-C7-8E-91-B1-99-EC-38-CE-71-A1-50-38-E0-78-34-1A-8D-8B-C5-E2-97-6C-1B-4A-A9-43-38-AB-8E-C7-E3-CF-D9-6C-E6-29-0D-DF-F7-5D-BE-C3-B7-1A-E7-C8-36-E0-BA-EE-1D-1C-39-6A-03-40-CC-E5-5C-49-12-9E-E7-55-B0-B0-AF-1D-F8-93-C9-A4-81-77-0F-B8-97-79-E9-71-DB-20-E1-83-44-45-92-00-77-13-38-47-04-BA-83-C1-E0-FA-BF-B9-20-71-85-39-3D-83-44-BC-48-30-9F-0C-A9-5E-B0-D7-E9-74-8E-36-D9-70-0E-89-6A-C2-4E-AC-9A-D0-45-35-07-43-1D-D6-0E-51-2A-07-51-63-D1-4A-54-B0-B2-B5-F3-B6-58-02-36-4D-DA-72-8D-B0-36-59-F3-81-E7-3C-9B-CD-9E-72-8C-75-5E-C5-12-B0-79-E3-3D-9F-CF-9F-71-AD-30-36-4B-04-28-32-B9-5C-6E-9E-F3-4C-26-D3-12-4B-4C-A7-D3-96-B6-2D-04-82-65-45-60-17-58-22-40-79-65-0D-71-8C-7B-49-2C-81-D0-97-B4-AD-CB-B5-24-0A-8C-22-6C-8A-25-70-04-DB-B6-45-F8-07-3C-42-86-FA-59-1D-43-E3-F8-D6-24-2A-28-22-81-FE-53-5C-C2-0A-11-45-2B-E8-0B-2A-6E-73-32-A5-98-0B-87-90-E2-6E-62-52-6C-90-58-34-A3-40-98-90-DB-D5-66-D4-80-F3-45-C3-62-03-A3-ED-70-38-BC-C1-AB-0B-89-0B-1D-09-57-6D-00-53-16-EC-1C-24-6E-F1-3C-82-DD-37-08-5F-4A-5C-A8-DF-1F-92-1A-2B-1B-42-E3-1A-ED-D7-D3-3F-24-D5-20-E7-DC-39-9D-1B-73-26-F8-7E-2F-49-81-F2-DA-EF-F7-4F-78-AD-93-5A-38-3B-E7-CE-D5-F2-CF-CA-14-A9-8A-DE-9C-6C-C1-B0-73-E7-AB-69-42-6A-5E-24-2D-30-EC-DC-F9-1A-12-8F-92-16-50-0B-4F-6B-8A-F5-5D-D2-04-76-5C-37-09-20-3D-75-49-1B-28-C0-67-5C-1F-74-0E-0E-79-D9-63-8F-15-FC-00-17-02-EB-AB-1A-4B-B3-E7-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Cross"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-C5-49-44-41-54-78-01-ED-96-D1-0D-83-30-0C-44-9D-4E-D0-51-BA-02-13-B5-23-A4-1B-A4-13-31-42-3B-4A-37-70-8D-6A-04-42-E0-D8-88-E0-1F-3F-29-8A-50-1C-DF-05-48-62-80-20-08-9C-49-D2-20-22-5E-A9-BB-53-1B-FA-67-4A-E9-0B-0A-66-F3-06-5E-DA-79-6B-89-32-4E-BC-39-71-55-9C-63-47-B2-14-7F-01-3D-37-6A-BD-64-82-C7-7A-8E-1D-A9-9A-06-29-E1-62-35-9B-6F-C2-12-7B-B8-89-66-E2-1A-81-E6-E2-0A-13-ED-C5-2B-26-CE-11-57-98-D8-25-6E-D9-86-FE-B8-7E-02-D7-9F-10-3D-B7-21-7A-1E-44-96-C4-4D-4C-D0-E4-62-49-B8-61-22-4B-1A-B5-6D-38-BF-C7-3F-D4-3A-E9-6E-E7-B1-8E-63-55-68-0A-92-07-3F-16-63-41-92-E1-BF-80-B2-BB-20-09-82-E0-0C-7E-54-36-6A-69-F6-3F-13-EF-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Plus"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-A7-49-44-41-54-78-01-ED-D5-01-09-84-30-14-06-E0-7F-C7-05-B8-06-77-0D-CE-08-46-31-C2-1A-68-04-4D-A0-51-8C-A0-0D-B4-81-0D-E6-13-14-C6-10-C5-4D-11-E1-FF-60-F8-78-0C-F7-B3-E1-04-88-02-18-63-B4-8C-04-77-98-17-5F-64-F0-F4-82-BF-C8-AA-BF-F0-14-12-E0-14-0C-C0-00-0C-A0-D6-9A-72-B1-A4-F2-F8-61-5B-6C-CD-E9-64-D4-3B-F3-1B-A5-54-81-3D-D3-D5-6A-AE-A3-DD-F5-D6-8E-E0-83-EB-0C-6E-E3-ED-36-64-9B-72-49-3A-95-7F-6C-8B-71-EC-08-7A-79-77-85-B3-48-C8-CA-DA-DA-12-9E-F8-19-32-00-03-3C-3A-40-67-D5-2D-EE-30-FF-37-34-88-02-8C-19-03-9B-84-46-97-A8-ED-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Plus Thicker"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-A1-49-44-41-54-78-01-ED-96-D1-09-84-30-10-44-27-C7-15-70-D7-81-25-D9-C1-9D-95-A8-1D-D9-89-25-C4-0E-4C-07-71-85-80-12-22-91-B8-E8-CF-3C-98-8F-2C-21-79-10-D8-0D-40-48-21-DE-FB-5A-32-4B-AC-E4-87-BB-91-4B-47-BF-61-51-C8-0B-E5-7C-A0-C0-15-01-15-28-40-01-0A-98-B8-10-BA-5A-87-3C-55-B4-9E-32-FB-9D-A4-37-C6-0C-39-01-9B-38-5C-0B-27-02-DF-7D-21-F5-04-2A-1D-EE-48-20-2E-BC-13-9B-1A-49-7B-42-A4-8A-D6-13-F2-F4-D0-22-4C-C1-47-87-91-0A-14-A0-00-05-B4-04-1C-EE-46-9A-CF-3F-34-A3-F5-6B-5E-83-90-42-16-B4-42-4C-CD-3F-8F-0E-C4-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Collapse"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-CE-49-44-41-54-78-01-ED-95-01-0D-C2-30-10-45-7F-A7-60-12-2A-01-09-93-80-04-1C-80-03-EA-60-16-70-30-1C-20-01-09-93-C0-1C-1C-BF-61-24-CD-18-0C-9A-DE-1A-92-BE-A4-49-D7-25-F7-7F-72-FF-5A-A0-90-19-33-3D-10-91-1D-F4-18-8C-31-E7-B7-7F-29-7E-10-7D-5C-A8-59-4D-3C-D4-D0-67-08-3F-E6-5A-B0-D5-34-C2-16-9C-50-48-05-DB-B5-F7-C1-45-0E-28-BC-19-53-7D-F3-7B-AC-09-05-2D-57-1F-8C-96-DF-5B-AC-05-C5-AE-33-F3-ED-CF-F4-C7-98-22-ED-87-4B-A6-85-26-14-38-CA-32-3A-A1-64-E1-46-BE-A7-41-4A-E4-35-74-4B-F8-C9-B0-48-01-0B-D5-3F-8A-3F-E9-25-45-28-59-A4-93-78-3A-68-C1-E2-2E-10-72-88-A4-42-66-8A-81-62-A0-18-C8-6E-20-1A-79-BC-0F-97-71-59-14-FE-95-3B-1B-9A-F9-A3-61-43-3F-A9-00-00-00-00-49-45-4E-44-AE-42-60-82", + }; + + } + + + + #endregion + + #region Other + + + public static void MarkInteractive(this Rect rect) + { + if (!curEvent.isRepaint) return; + + var unclippedRect = (Rect)_mi_GUIClip_UnclipToWindow.Invoke(null, new object[] { rect }); + + var curGuiView = _pi_GUIView_current.GetValue(null); + + _mi_GUIView_MarkHotRegion.Invoke(curGuiView, new object[] { unclippedRect }); + + } + + static PropertyInfo _pi_GUIView_current = typeof(Editor).Assembly.GetType("UnityEditor.GUIView").GetProperty("current", maxBindingFlags); + static MethodInfo _mi_GUIView_MarkHotRegion = typeof(Editor).Assembly.GetType("UnityEditor.GUIView").GetMethod("MarkHotRegion", maxBindingFlags); + static MethodInfo _mi_GUIClip_UnclipToWindow = typeof(GUI).Assembly.GetType("UnityEngine.GUIClip").GetMethod("UnclipToWindow", maxBindingFlags, null, new[] { typeof(Rect) }, null); + + + + + public static float GetCurrentInspectorWidth() => typeof(EditorGUIUtility).GetPropertyValue("contextWidth"); + + public static void CheckScrollbarVisibility(ref bool isScrollbarVisible) + { + GUILayout.Label("", GUILayout.Height(0), GUILayout.ExpandWidth(true)); + + if (Event.current.type == EventType.Repaint) + isScrollbarVisible = GetCurrentInspectorWidth() - 33 > lastRect.width; + + } + + + + + + + #endregion + + } + +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersLibs.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersLibs.cs.meta new file mode 100644 index 0000000..0c41ce9 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersLibs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2528d4639fe84d44bfdfde62c65dc86 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenu.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenu.cs new file mode 100644 index 0000000..2f48e12 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenu.cs @@ -0,0 +1,128 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VFolders +{ + class VFoldersMenu + { + + public static bool navigationBarEnabled { get => EditorPrefsCached.GetBool("vFolders-navigationBarEnabled", false); set => EditorPrefsCached.SetBool("vFolders-navigationBarEnabled", value); } + public static bool twoLineNamesEnabled { get => EditorPrefsCached.GetBool("vFolders-twoLineNamesEnabled", false); set => EditorPrefsCached.SetBool("vFolders-twoLineNamesEnabled", value); } + + public static bool autoIconsEnabled { get => EditorPrefsCached.GetBool("vFolders-autoIconsEnabled", false); set => EditorPrefsCached.SetBool("vFolders-autoIconsEnabled", value); } + public static bool hierarchyLinesEnabled { get => EditorPrefsCached.GetBool("vFolders-hierarchyLinesEnabled", false); set => EditorPrefsCached.SetBool("vFolders-hierarchyLinesEnabled", value); } + public static bool zebraStripingEnabled { get => EditorPrefsCached.GetBool("vFolders-zebraStripingEnabled", false); set => EditorPrefsCached.SetBool("vFolders-zebraStripingEnabled", value); } + public static bool contentMinimapEnabled { get => EditorPrefsCached.GetBool("vFolders-contentMinimapEnabled", false); set => EditorPrefsCached.SetBool("vFolders-contentMinimapEnabled", value); } + public static bool backgroundColorsEnabled { get => EditorPrefsCached.GetBool("vFolders-backgroundColorsEnabled", false); set => EditorPrefsCached.SetBool("vFolders-backgroundColorsEnabled", value); } + public static bool minimalModeEnabled { get => EditorPrefsCached.GetBool("vFolders-minimalModeEnabled", false); set => EditorPrefsCached.SetBool("vFolders-minimalModeEnabled", value); } + public static bool foldersFirstEnabled { get => EditorPrefsCached.GetBool("vFolders-foldersFirstEnabled", false); set => EditorPrefsCached.SetBool("vFolders-foldersFirstEnabled", value); } + public static bool toggleExpandedEnabled { get => EditorPrefsCached.GetBool("vFolders-toggleExpandedEnabled", true); set => EditorPrefsCached.SetBool("vFolders-toggleExpandedEnabled", value); } + public static bool collapseEverythingElseEnabled { get => EditorPrefsCached.GetBool("vFolders-collapseEverythingElseEnabled", true); set => EditorPrefsCached.SetBool("vFolders-collapseEverythingElseEnabled", value); } + public static bool collapseEverythingEnabled { get => EditorPrefsCached.GetBool("vFolders-collapseEverythingEnabled", true); set => EditorPrefsCached.SetBool("vFolders-collapseEverythingEnabled", value); } + + public static bool pluginDisabled { get => EditorPrefsCached.GetBool("vFolders-pluginDisabled", false); set => EditorPrefsCached.SetBool("vFolders-pluginDisabled", value); } + + + + + const string dir = "Tools/vFolders/"; + + const string navigationBar = dir + "Navigation bar"; + const string autoIcons = dir + "Automatic icons"; + const string twoLineNames = dir + "Two-line names"; + const string hierarchyLines = dir + "Hierarchy lines"; + const string backgroundColors = dir + "Background colors"; + const string minimalMode = dir + "Minimal mode"; + const string zebraStriping = dir + "Zebra striping"; + const string contentMinimap = dir + "Content minimap"; + const string foldersFirst = dir + "Sort folders first"; + + const string toggleExpanded = dir + "E to expand \u2215 collapse folder"; + const string collapseEverythingElse = dir + "Shift-E to isolate folder"; + const string collapseEverything = dir + "Ctrl-Shift-E to collapse all folders"; + + const string disablePlugin = dir + "Disable vFolders"; + + + + + + + [MenuItem(dir + "Features", false, 1)] static void daasddsas() { } + [MenuItem(dir + "Features", true, 1)] static bool dadsdasas123() => false; + + [MenuItem(navigationBar, false, 2)] static void dadsaadsdsadasdsadadsas() { navigationBarEnabled = !navigationBarEnabled; EditorApplication.RepaintProjectWindow(); } + [MenuItem(navigationBar, true, 2)] static bool dadsaddasdsasadadsdasadsas() { Menu.SetChecked(navigationBar, navigationBarEnabled); return !pluginDisabled; } + + [MenuItem(twoLineNames, false, 3)] static void dadsadaddssadass() { twoLineNamesEnabled = !twoLineNamesEnabled; EditorApplication.RepaintProjectWindow(); } + [MenuItem(twoLineNames, true, 3)] static bool dadsaddasdsaasddsas() { Menu.SetChecked(twoLineNames, twoLineNamesEnabled); return !pluginDisabled; } + + [MenuItem(autoIcons, false, 4)] static void dadsadadsas() { autoIconsEnabled = !autoIconsEnabled; VFolders.folderInfoCache.Clear(); EditorApplication.RepaintProjectWindow(); } + [MenuItem(autoIcons, true, 4)] static bool dadsaddasadsas() { Menu.SetChecked(autoIcons, autoIconsEnabled); return !pluginDisabled; } + + [MenuItem(hierarchyLines, false, 5)] static void dadsadadsadass() { hierarchyLinesEnabled = !hierarchyLinesEnabled; EditorApplication.RepaintProjectWindow(); } + [MenuItem(hierarchyLines, true, 5)] static bool dadsaddasaasddsas() { Menu.SetChecked(hierarchyLines, hierarchyLinesEnabled); return !pluginDisabled; } + + [MenuItem(zebraStriping, false, 6)] static void dadsadaddsasadsadass() { zebraStripingEnabled = !zebraStripingEnabled; EditorApplication.RepaintProjectWindow(); } + [MenuItem(zebraStriping, true, 6)] static bool dadsaddadassadsaasddsas() { Menu.SetChecked(zebraStriping, zebraStripingEnabled); return !pluginDisabled; } + + [MenuItem(contentMinimap, false, 7)] static void dadsadadasdsadass() { contentMinimapEnabled = !contentMinimapEnabled; EditorApplication.RepaintProjectWindow(); } + [MenuItem(contentMinimap, true, 7)] static bool dadsadddasasaasddsas() { Menu.SetChecked(contentMinimap, contentMinimapEnabled); return !pluginDisabled; } + + [MenuItem(backgroundColors, false, 8)] static void dadsadadsadsadass() { backgroundColorsEnabled = !backgroundColorsEnabled; EditorApplication.RepaintProjectWindow(); } + [MenuItem(backgroundColors, true, 8)] static bool dadsaddasadsaasddsas() { Menu.SetChecked(backgroundColors, backgroundColorsEnabled); return !pluginDisabled; } + + [MenuItem(minimalMode, false, 9)] static void dadsadadsaddsasadass() { minimalModeEnabled = !minimalModeEnabled; EditorApplication.RepaintProjectWindow(); } + [MenuItem(minimalMode, true, 9)] static bool dadsaddasadsadsaasddsas() { Menu.SetChecked(minimalMode, minimalModeEnabled); return !pluginDisabled; } +#if UNITY_EDITOR_OSX + [MenuItem(foldersFirst, false, 10)] static void dadsdsfaadsdadsas() { foldersFirstEnabled = !foldersFirstEnabled; EditorApplication.RepaintProjectWindow(); if (!foldersFirstEnabled) UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation(); } + [MenuItem(foldersFirst, true, 10)] static bool dadsasdfdadsdasadsas() { Menu.SetChecked(foldersFirst, foldersFirstEnabled); return !pluginDisabled; } +#endif + + + + [MenuItem(dir + "Shortcuts", false, 101)] static void dadsas() { } + [MenuItem(dir + "Shortcuts", true, 101)] static bool dadsas123() => false; + + [MenuItem(toggleExpanded, false, 102)] static void dadsadsadasdsadadsas() => toggleExpandedEnabled = !toggleExpandedEnabled; + [MenuItem(toggleExpanded, true, 102)] static bool dadsaddsasadadsdasadsas() { Menu.SetChecked(toggleExpanded, toggleExpandedEnabled); return !pluginDisabled; } + + [MenuItem(collapseEverythingElse, false, 103)] static void dadsadsasdadasdsadadsas() => collapseEverythingElseEnabled = !collapseEverythingElseEnabled; + [MenuItem(collapseEverythingElse, true, 103)] static bool dadsaddsdasasadadsdasadsas() { Menu.SetChecked(collapseEverythingElse, collapseEverythingElseEnabled); return !pluginDisabled; } + + [MenuItem(collapseEverything, false, 104)] static void dadsadsdasadasdsadadsas() => collapseEverythingEnabled = !collapseEverythingEnabled; + [MenuItem(collapseEverything, true, 104)] static bool dadsaddssdaasadadsdasadsas() { Menu.SetChecked(collapseEverything, collapseEverythingEnabled); return !pluginDisabled; } + + + + + [MenuItem(dir + "More", false, 1001)] static void daasadsddsas() { } + [MenuItem(dir + "More", true, 1001)] static bool dadsadsdasas123() => false; + + [MenuItem(dir + "Open manual", false, 1002)] + static void dadadssadsas() => Application.OpenURL("https://kubacho-lab.gitbook.io/vfolders-2"); + + [MenuItem(dir + "Join our Discord", false, 1003)] + static void dadasdsas() => Application.OpenURL("https://discord.gg/pUektnZeJT"); + + + + + + + [MenuItem(disablePlugin, false, 10001)] static void dadsadsdasadasdasdsadadsas() { pluginDisabled = !pluginDisabled; UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation(); } + [MenuItem(disablePlugin, true, 10001)] static bool dadsaddssdaasadsadadsdasadsas() { Menu.SetChecked(disablePlugin, pluginDisabled); return true; } + + + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenu.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenu.cs.meta new file mode 100644 index 0000000..19b54b3 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f118e09f35fa241fda19f1f3b1e7c5a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenuItems.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenuItems.cs new file mode 100644 index 0000000..028ed42 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenuItems.cs @@ -0,0 +1,5 @@ + +// this file was present in a previus version and is supposed to be deleted now +// but asset store update delivery system doesn't allow deleting files +// so instead this file is now emptied +// feel free to delete it if you want diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenuItems.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenuItems.cs.meta new file mode 100644 index 0000000..4bdc039 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersMenuItems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec6d8bc24e3ec4cb6b055630a6425686 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersNavbar.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersNavbar.cs new file mode 100644 index 0000000..47068ff --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersNavbar.cs @@ -0,0 +1,1411 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; +using static VFolders.VFoldersData; +using static VFolders.VFolders; + + + +namespace VFolders +{ + public class VFoldersNavbar + { + + public void OnGUI(Rect navbarRect) + { + void updateState() + { + if (!curEvent.isLayout) return; + + + openedFolderPath = window.GetMemberValue("m_SearchFilter")?.GetMemberValue("folders")?.FirstOrDefault() ?? "Assets"; + + isOneColumn = window.GetMemberValue("m_ViewMode") == 0; + + isSearchActive = window.GetMemberValue("m_FocusSearchField") + || !window.GetMemberValue("m_SearchFieldText").IsNullOrEmpty() + || GUI.GetNameOfFocusedControl() == "navbar search field"; + + } + + void background() + { + var backgroundColor = Greyscale(isDarkTheme ? .235f : .8f); + var lineColor = Greyscale(isDarkTheme ? .13f : .58f); + + navbarRect.Draw(backgroundColor); + + navbarRect.SetHeightFromBottom(1).MoveY(1).Draw(lineColor); + + } + void hiddenMenu() + { + if (!curEvent.holdingAlt) return; + if (!curEvent.isMouseUp) return; + if (curEvent.mouseButton != 1) return; + if (!navbarRect.IsHovered()) return; + + + void selectData() + { + Selection.activeObject = data; + } + void selectPalette() + { + Selection.activeObject = palette; + } + void clearCache() + { + VFoldersCache.instance.iconTextures_byKey.Clear(); + VFoldersCache.instance.iconTextureDatas_byKey.Clear(); + VFoldersCache.instance.folderStates_byGuid.Clear(); + + VFolders.folderInfoCache.Clear(); + + bookmarkWidthsCache.Clear(); + + } + + + + GenericMenu menu = new(); + + menu.AddDisabledItem(new GUIContent("vFolders hidden menu")); + + if (isOneColumn) + { + var backForwardButtonsEnabled = EditorPrefsCached.GetBool("vFolders-showBackForwardButtonsInOneColumn", defaultValue: false); + + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Show + button"), false, backForwardButtonsEnabled ? () => EditorPrefsCached.SetBool("vFolders-showBackForwardButtonsInOneColumn", false) : null); + menu.AddItem(new GUIContent("Show < > buttons"), false, backForwardButtonsEnabled ? null : () => EditorPrefsCached.SetBool("vFolders-showBackForwardButtonsInOneColumn", true)); + } + + + if (isTwoColumns) + { + var forceCompactMode = EditorPrefsCached.GetBool("vFolders-forceCompactMode", defaultValue: false); + + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Enable compact bookmarks"), forceCompactMode, () => EditorPrefsCached.SetBool("vFolders-forceCompactMode", !forceCompactMode)); + } + + + + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Select data"), false, selectData); + menu.AddItem(new GUIContent("Select palette"), false, selectPalette); + + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Clear cache"), false, clearCache); + + menu.ShowAsContext(); + + } + + void backButton() + { + if (isOneColumn && !EditorPrefsCached.GetBool("vFolders-showBackForwardButtonsInOneColumn", defaultValue: false)) return; + + + var buttonRect = navbarRect.SetWidth(30).MoveX(4); + + if (Application.unityVersion.StartsWith("6000")) + buttonRect = buttonRect.MoveY(-.49f); + + + var iconName = "Chevron Left"; + var iconSize = 14; + var colorNormal = Greyscale(isDarkTheme ? .75f : .2f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + var colorDisabled = Greyscale(isDarkTheme ? .53f : .55f); + + + var disabled = isOneColumn ? !history.prevTreeStates.Any() : !history.prevFolderPaths.Any(); + + if (disabled) { IconButton(buttonRect, iconName, iconSize, colorDisabled, colorDisabled, colorDisabled); return; } + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + if (isOneColumn) + history.MoveBack_OneColumn(); + else + history.MoveBack_TwoColumns(); + + } + void forwardButton() + { + if (isOneColumn && !EditorPrefsCached.GetBool("vFolders-showBackForwardButtonsInOneColumn", defaultValue: false)) return; + + + var buttonRect = navbarRect.SetWidth(30).MoveX(30).MoveX(1).AddWidthFromMid(-6); + + if (Application.unityVersion.StartsWith("6000")) + buttonRect = buttonRect.MoveY(-.49f); + + + var iconName = "Chevron Right"; + var iconSize = 14; + var colorNormal = Greyscale(isDarkTheme ? .75f : .2f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + var colorDisabled = Greyscale(isDarkTheme ? .53f : .55f); + + + var disabled = isOneColumn ? !history.nextTreeStates.Any() : !history.nextFolderPaths.Any(); + + if (disabled) { IconButton(buttonRect, iconName, iconSize, colorDisabled, colorDisabled, colorDisabled); return; } + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + if (isOneColumn) + history.MoveForward_OneColumn(); + else + history.MoveForward_TwoColumns(); + + } + void plusButton_oneColumn() + { + if (!isOneColumn) return; + if (EditorPrefsCached.GetBool("vFolders-showBackForwardButtonsInOneColumn", defaultValue: false)) return; + + + var buttonRect = navbarRect.SetWidth(28).MoveX(4.5f); + + if (Application.unityVersion.StartsWith("6000")) + buttonRect = buttonRect.MoveY(-.49f); + + + var iconName = "Plus Thicker"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .7f : .44f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .42f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .6f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + EditorUtility.DisplayPopupMenu(buttonRect.MoveX(24), "Assets/Create", null); + + } + + + void searchButton() + { + if (searchAnimationT == 1) return; + + + var buttonRect = navbarRect.SetWidthFromRight(28).MoveX(-5); + + var iconName = "Search_"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .75f : .2f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + window.SetMemberValue("m_FocusSearchField", true); + + } + void plusButton_twoColumns() + { + if (isOneColumn) return; + if (searchAnimationT == 1) return; + + + var buttonRect = navbarRect.SetWidthFromRight(28).MoveX(-33); + + var iconName = "Plus"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .735f : .44f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .42f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .6f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + if (curEvent.holdingAlt) + { + history.prevTreeStates.Clear(); + history.nextTreeStates.Clear(); + + history.prevFolderPaths.Clear(); + history.nextFolderPaths.Clear(); + + history.lastScrollTime = 0; + + + window.Repaint(); + + return; + + + // for debug + + } + + EditorUtility.DisplayPopupMenu(buttonRect.MoveX(24), "Assets/Create", null); + + } + void collapseAllButton_oneColumn() + { + if (!isOneColumn) return; + if (searchAnimationT == 1) return; + + + var buttonRect = navbarRect.SetWidthFromRight(28).MoveX(-33); + + var iconName = "Collapse"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .71f : .44f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .42f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .6f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + controller.CollapseAll(); + + } + void bookmarks() + { + if (searchAnimationT == 1) return; + if (isSearchActive && !curEvent.isRepaint) return; + + void createData() + { + if (data) return; + if (!navbarRect.IsHovered()) return; + if (!DragAndDrop.objectReferences.Any()) return; + + data = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(data, GetScriptPath("VFolders").GetParentPath().CombinePath("vFolders Data.asset")); + + } + void handleUndoRedo() + { + if (!data) return; + if (curEvent.commandName != "UndoRedoPerformed") return; + + + if (repaintNeededAfterUndoRedo) + window.Repaint(); + + repaintNeededAfterUndoRedo = false; + + + bookmarkWidthsCache.Clear(); + bookmarkCenterXCache.Clear(); + + } + void divider() + { + if (!data) return; + if (!data.bookmarks.Any()) return; + + + var dividerRect = navbarRect.SetWidthFromRight(1).SetHeightFromMid(16).MoveX(-65).MoveX(isCompactMode ? 1.5f : .5f); + + var dividerColor = Greyscale(isDarkTheme ? .33f : .64f); + + + dividerRect.Draw(dividerColor); + + } + void gui() + { + if (!data) return; + + this.navbarRect = navbarRect; + this.bookmarksRect = navbarRect.AddWidth(-69).AddWidthFromRight(-60).MoveX(2).MoveX(isCompactMode ? -3 : 0); + + BookmarksGUI(); + + } + + createData(); + handleUndoRedo(); + divider(); + gui(); + + } + + void searchField() + { + if (searchAnimationT == 0) return; + + var searchFieldRect = navbarRect.SetHeightFromMid(20).AddWidth(-33).SetWidthFromRight(240f.Min(window.position.width - (isOneColumn ? 195 : 223))).Move(-1, 2); + + + GUILayout.BeginArea(searchFieldRect); + GUILayout.BeginHorizontal(); + + + GUI.SetNextControlName("navbar search field"); + + Space(2); + window.InvokeMethod("SearchField"); + + + GUILayout.EndHorizontal(); + GUILayout.EndArea(); + + } + void searchOptions() + { + if (searchAnimationT == 0) return; + + var searchFieldRect = navbarRect.SetHeightFromMid(20).AddWidth(-33).SetWidthFromRight(240f.Min(window.position.width - (isOneColumn ? 195 : 223))).Move(-1, 2); + var searchOptionsRect = navbarRect.SetHeightFromMid(20).SetXMax(searchFieldRect.x).SetWidthFromRight(123); + + + + + GUILayout.BeginArea(searchOptionsRect); + GUILayout.BeginHorizontal(); + + + + + GUILayout.FlexibleSpace(); + + Space(3); + window.InvokeMethod("TypeDropDown"); + + var masksStartX = lastRect.x; + + + window.InvokeMethod("AssetLabelsDropDown"); + + if (!isOneColumn) + window.InvokeMethod("ButtonSaveFilter"); + + + window.InvokeMethod("ToggleHiddenPackagesVisibility"); + + Space(4); + + + + + + var maskRect = searchOptionsRect.SetWidth(1).SetX(masksStartX - 1).MoveY(-3); + var maskColor = Greyscale(isDarkTheme ? .235f : .8f); + + for (int i = 0; i < (isOneColumn ? 3 : 4); i++) + { + maskRect.Draw(maskColor); + maskRect = maskRect.MoveX(26); + } + + + + + GUILayout.EndHorizontal(); + GUILayout.EndArea(); + + + } + void closeSearchButton() + { + if (searchAnimationT == 0) return; + + + var buttonRect = navbarRect.SetWidthFromRight(30).MoveX(-4); + + var iconName = "Cross"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .72f : .2f); + var colorHovered = Greyscale(isDarkTheme ? .9f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + window.SetMemberValue("m_SearchFieldText", ""); + window.GetMemberValue("m_SearchFilter").SetMemberValue("m_NameFilter", ""); + + window.InvokeMethod("UpdateSearchDelayed"); + + GUIUtility.keyboardControl = 0; + + } + void closeSearchOnEsc() + { + if (!isSearchActive) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + window.SetMemberValue("m_SearchFieldText", ""); + window.GetMemberValue("m_SearchFilter").SetMemberValue("m_NameFilter", ""); + + window.InvokeMethod("UpdateSearchDelayed"); + + GUIUtility.keyboardControl = 0; + + } + + + void searchAnimation() + { + if (!curEvent.isLayout) return; + + + var lerpSpeed = 8f; + + if (isSearchActive) + MathUtil.SmoothDamp(ref searchAnimationT, 1, lerpSpeed, ref searchAnimationDerivative, editorDeltaTime); + else + MathUtil.SmoothDamp(ref searchAnimationT, 0, lerpSpeed, ref searchAnimationDerivative, editorDeltaTime); + + + if (isSearchActive && searchAnimationT > .99f) + searchAnimationT = 1; + + if (!isSearchActive && searchAnimationT < .01f) + searchAnimationT = 0; + + + animatingSearch = searchAnimationT != 0 && searchAnimationT != 1; + + } + + void buttonsAndBookmarks() + { + SetGUIColor(Greyscale(1, (1 - searchAnimationT).Pow(2))); + GUI.BeginGroup(window.position.SetPos(0, 0).MoveX(-searchAnimationDistance * searchAnimationT)); + + searchButton(); + plusButton_twoColumns(); + collapseAllButton_oneColumn(); + bookmarks(); + + GUI.EndGroup(); + ResetGUIColor(); + + } + void search() + { + SetGUIColor(Greyscale(1, searchAnimationT.Pow(2))); + GUI.BeginGroup(window.position.SetPos(0, 0).MoveX(searchAnimationDistance * (1 - searchAnimationT))); + + searchField(); + searchOptions(); + closeSearchButton(); + closeSearchOnEsc(); + + GUI.EndGroup(); + ResetGUIColor(); + + } + + + + updateState(); + + background(); + hiddenMenu(); + + backButton(); + forwardButton(); + plusButton_oneColumn(); + + searchAnimation(); + buttonsAndBookmarks(); + search(); + + + + if (draggingBookmark || animatingDroppedBookmark || animatingGaps || animatingTooltip || animatingSearch) + window.Repaint(); + + } + + bool animatingSearch; + + float searchAnimationDistance = 90; + float searchAnimationT; + float searchAnimationDerivative; + + string openedFolderPath; + + bool isOneColumn; + bool isTwoColumns => !isOneColumn; + bool isSearchActive; + + bool isCompactMode + { + get + { + return isOneColumn || EditorPrefsCached.GetBool("vFolders-forceCompactMode", defaultValue: false); + } + } + + Rect navbarRect; + Rect bookmarksRect; + + + + + + + + + + + + + public void BookmarksGUI() + { + void bookmark(Vector2 centerPosition, Bookmark bookmark) + { + if (bookmark == null) return; + if (!curEvent.isRepaint && !curEvent.isMouseUp) return; + + + var bookmarkRect = Rect.zero.SetSize(GetBookmarkWidth(bookmark), bookmarksRect.height).SetMidPos(centerPosition); + + var IsHovered = bookmarkRect.IsHovered(); + var isSelected = openedFolderPath == bookmark.guid.ToPath() && !isCompactMode; + var isPressed = bookmark == pressedBookmark; + var isDragged = draggingBookmark && draggedBookmark == bookmark; + + + void shadow() + { + if (!draggingBookmark) return; + if (draggedBookmark != bookmark) return; + + bookmarkRect.SetSizeFromMid(GetBookmarkWidth(bookmark) - 4, bookmarkRect.height - 4).DrawBlurred(Greyscale(0, .3f), 15); + + } + void background() + { + if (isCompactMode) return; + if (!isSelected && !IsHovered && !isDragged) return; + + if (IsHovered && draggingBookmark && draggedBookmark != bookmark) return; + + + var backgroundRect = bookmarkRect.SetSizeFromMid(bookmarkRect.width - 7, bookmarkRect.height - 8).AddWidthFromRight(1); + + + var backgroundColor = isSelected ? Greyscale(isDarkTheme ? .35f : .7f) + : Greyscale(isDarkTheme ? .31f : .75f); + var outlineColor = isSelected ? Greyscale(isDarkTheme ? .21f : .65f) + : Greyscale(isDarkTheme ? .21f : .65f, .5f); + + + backgroundRect.Resize(-1).DrawRounded(outlineColor, 6); + backgroundRect.DrawRounded(backgroundColor, 5); + + } + void backgroundCompact() + { + if (!isCompactMode) return; + + if (!IsHovered) return; + if (draggingBookmark && draggedBookmark != bookmark) return; + + + var backgroundColor = Greyscale(isDarkTheme ? .35f : .7f); + + var backgroundRect = bookmarkRect.SetSizeFromMid(bookmarkRect.width - 2, bookmarkRect.width - 4); + + backgroundRect.DrawRounded(backgroundColor, 4); + + } + void icon() + { + var folderInfo = VFolders.GetFolderInfo(bookmark.guid); + + Texture iconTexture = folderInfo.hasIcon || folderInfo.hasColor ? VFolders.GetSmallFolderIcon(folderInfo) + : EditorIcons.GetIcon("Folder Icon"); + + var iconRect = isCompactMode ? bookmarkRect.SetSizeFromMid(iconSize) : bookmarkRect.SetWidth(iconSize).SetHeightFromMid(iconSize).MoveX(bookmarkSpacing / 2); + + iconRect = iconRect.SetWidthFromMid(iconRect.height * iconTexture.width / iconTexture.height); + + + + var opacity = folderInfo.hasColor ? .96f : .92f; + + if (isSelected) + opacity = 1; + + if (isPressed && !isDragged) + opacity = .73f; + + if (bookmark.isDeleted) + opacity = .4f; + + + + SetGUIColor(Greyscale(1, opacity)); + + GUI.DrawTexture(iconRect, iconTexture); + + ResetGUIColor(); + + } + void name() + { + if (isCompactMode) return; + if (!curEvent.isRepaint) return; + + + var nameRect = bookmarkRect.MoveX(bookmarkSpacing / 2 + iconSize + iconSpacing); + + var makeExtraBright = isSelected && isDarkTheme; + + + if (!makeExtraBright) + { + var opacity = .95f; + + if (isSelected) + opacity = 1; + + if (isPressed && !isDragged) + opacity = .82f; + + if (bookmark.isDeleted) + opacity = .4f; + + + + SetGUIColor(Greyscale(1, opacity)); + + GUI.Label(nameRect, bookmark.name); + + ResetGUIColor(); + + } + + if (makeExtraBright) + { + var opacity = .85f; + + nameRect = nameRect.SetHeightFromMid(16).AddHeight(4); + + + SetGUIColor(Greyscale(1, opacity)); + + GUI.skin.GetStyle("WhiteLabel").Draw(nameRect, bookmark.name, false, false, false, false); + + ResetGUIColor(); + + } + + } + void tooltip() + { + if (!isCompactMode) return; + + if (bookmark != (draggingBookmark ? (draggedBookmark) : (lastHoveredBookmark))) return; + if (tooltipOpacity == 0) return; + + var fontSize = 11; // ,maybe 12 + var tooltipText = bookmark.isDeleted ? "Deleted" : bookmark.name; + + Rect tooltipRect; + + void set_tooltipRect() + { + var width = tooltipText.GetLabelWidth(fontSize) + 6; + var height = 16 + (fontSize - 12) * 2; + + var yOffset = 28; + var rightMargin = -1; + + + tooltipRect = Rect.zero.SetMidPos(centerPosition.x, centerPosition.y + yOffset).SetSizeFromMid(width, height); + + + var maxXMax = navbarRect.xMax - rightMargin; + + if (tooltipRect.xMax > maxXMax) + tooltipRect = tooltipRect.MoveX(maxXMax - tooltipRect.xMax); + + } + void shadow() + { + var shadowAmount = .33f; + var shadowRadius = 10; + + tooltipRect.DrawBlurred(Greyscale(0, shadowAmount).MultiplyAlpha(tooltipOpacity), shadowRadius); + + } + void background() + { + var cornerRadius = 5; + + var backgroundColor = Greyscale(isDarkTheme ? .13f : .9f); + var outerEdgeColor = Greyscale(isDarkTheme ? .25f : .6f); + var innerEdgeColor = Greyscale(isDarkTheme ? .0f : .95f); + + tooltipRect.Resize(-1).DrawRounded(outerEdgeColor.SetAlpha(tooltipOpacity.Pow(2)), cornerRadius + 1); + tooltipRect.Resize(0).DrawRounded(innerEdgeColor.SetAlpha(tooltipOpacity.Pow(2)), cornerRadius + 0); + tooltipRect.Resize(1).DrawRounded(backgroundColor.SetAlpha(tooltipOpacity), cornerRadius - 1); + + } + void text() + { + var textRect = tooltipRect.MoveY(-.5f); + + var textColor = Greyscale(1f); + + SetLabelAlignmentCenter(); + SetLabelFontSize(fontSize); + SetGUIColor(textColor.SetAlpha(tooltipOpacity)); + + GUI.Label(textRect, tooltipText); + + ResetLabelStyle(); + ResetGUIColor(); + + } + + set_tooltipRect(); + shadow(); + background(); + text(); + + } + void click() + { + if (!IsHovered) return; + if (!curEvent.isMouseUp) return; + + + curEvent.Use(); + + if (draggingBookmark) return; + if ((curEvent.mousePosition - mouseDownPosiion).magnitude > 2) return; + if (bookmark.isDeleted) return; + + + if (isOneColumn) + controller.RevealFolder(bookmark.guid.ToPath(), expand: true, highlight: true, snapToTopMargin: true); + + if (isTwoColumns) + { + controller.RevealFolder(bookmark.guid.ToPath(), expand: false, highlight: false, snapToTopMargin: false); + controller.OpenFolder(bookmark.guid.ToPath()); + } + + + lastClickedBookmark = bookmark; + + hideTooltip = true; + + } + + + bookmarkRect.MarkInteractive(); + + shadow(); + background(); + backgroundCompact(); + icon(); + name(); + tooltip(); + click(); + + } + + void normalBookmark(int i) + { + if (data.bookmarks[i] == droppedBookmark && animatingDroppedBookmark) return; + + var centerX = GetBookmarkCenterX(i); + var centerY = bookmarksRect.height / 2; + + + var minX = centerX - GetBookmarkWidth(data.bookmarks[i]) / 2; + + if (minX < bookmarksRect.x) return; + + lastBookmarkX = minX; + + + bookmark(new Vector2(centerX, centerY), data.bookmarks[i]); + + } + void normalBookmarks() + { + for (int i = 0; i < data.bookmarks.Count; i++) + normalBookmark(i); + + } + void draggedBookmark_() + { + if (!draggingBookmark) return; + + var centerX = curEvent.mousePosition.x + draggedBookmarkHoldOffset.x; + var centerY = bookmarksRect.IsHovered() ? bookmarksRect.height / 2 : curEvent.mousePosition.y; + + bookmark(new Vector2(centerX, centerY), draggedBookmark); + + } + void droppedBookmark_() + { + if (!animatingDroppedBookmark) return; + + var centerX = droppedBookmarkX; + var centerY = bookmarksRect.height / 2; + + bookmark(new Vector2(centerX, centerY), droppedBookmark); + + } + + + ClearCacheIfNeeded(); + + BookmarksMouseState(); + BookmarksDragging(); + BookmarksAnimations(); + + normalBookmarks(); + draggedBookmark_(); + droppedBookmark_(); + + } + + float iconSize = 16; + float iconSpacing = 1; + float bookmarkSpacing = 16; + + float bookmarkWidth_compactMode = 24; + + bool repaintNeededAfterUndoRedo; + + public float lastBookmarkX; + + + + + int GetBookmarkIndex(float mouseX) + { + var curBookmarkWidthSum = 0f; + + for (int i = 0; i < data.bookmarks.Count; i++) + { + curBookmarkWidthSum += GetBookmarkWidth(data.bookmarks[i]); + + if (bookmarksRect.xMax - curBookmarkWidthSum < mouseX + .5f) + return i; + } + + return data.bookmarks.Count; + + } + + float GetBookmarkWidth(Bookmark bookmark) + { + if (!animatingBookmarks) + if (bookmarkWidthsCache.TryGetValue(bookmark, out var cachedWidth)) return cachedWidth; + + + + var width = isCompactMode ? bookmarkWidth_compactMode + : bookmark.name.GetLabelWidth() + iconSize + iconSpacing + bookmarkSpacing; + + + if (!animatingBookmarks) + bookmarkWidthsCache[bookmark] = width; + else + bookmarkWidthsCache.Clear(); + + + return width; + + } + float GetBookmarkCenterX(int i, bool includeGaps = true) + { + if (!animatingBookmarks) + if (bookmarkCenterXCache.TryGetValue(i, out var cachedCenterX)) return cachedCenterX; + + + + var centerX = bookmarksRect.xMax + - GetBookmarkWidth(data.bookmarks[i.Clamp(0, data.bookmarks.Count - 1)]) / 2 + - data.bookmarks.Take(i).Sum(r => GetBookmarkWidth(r)) + - (includeGaps ? gaps.Take(i + 1).Sum() : 0); + + + if (!animatingBookmarks) + bookmarkCenterXCache[i] = centerX; + else + bookmarkCenterXCache.Clear(); + + + return centerX; + + } + + Dictionary bookmarkWidthsCache = new(); + Dictionary bookmarkCenterXCache = new(); + + + + + void ClearCacheIfNeeded() + { + var modeChanged = wasCompactMode != isCompactMode; + var windowResized = prevWindowWidth != window.position.width; + var undoPerformed = curEvent.commandName == "UndoRedoPerformed"; + var renameHappened = curEvent.commandName == "NewKeyboardFocus"; + + wasCompactMode = isCompactMode; + prevWindowWidth = window.position.width; + + + + if (!modeChanged && !windowResized && !undoPerformed && !renameHappened) return; + + bookmarkWidthsCache.Clear(); + bookmarkCenterXCache.Clear(); + + } + + bool wasCompactMode; + float prevWindowWidth; + + + + + + + + + + + + + void BookmarksMouseState() + { + void down() + { + if (!curEvent.isMouseDown) return; + if (!bookmarksRect.IsHovered()) return; + + mousePressed = true; + + mouseDownPosiion = curEvent.mousePosition; + + var pressedBookmarkIndex = GetBookmarkIndex(mouseDownPosiion.x); + + if (pressedBookmarkIndex.IsInRangeOf(data.bookmarks)) + pressedBookmark = data.bookmarks[pressedBookmarkIndex]; + + doubleclickUnhandled = curEvent.clickCount == 2; + + curEvent.Use(); + + } + void up() + { + if (!curEvent.isMouseUp) return; + + mousePressed = false; + pressedBookmark = null; + + } + void hover() + { + var hoveredBookmarkIndex = GetBookmarkIndex(curEvent.mousePosition.x); + + mouseHoversBookmark = bookmarksRect.IsHovered() && hoveredBookmarkIndex.IsInRangeOf(data.bookmarks); + + if (mouseHoversBookmark) + lastHoveredBookmark = data.bookmarks[hoveredBookmarkIndex]; + + + } + + down(); + up(); + hover(); + + } + + bool mouseHoversBookmark; + bool mousePressed; + bool doubleclickUnhandled; + + Vector2 mouseDownPosiion; + + Bookmark pressedBookmark; + Bookmark lastHoveredBookmark; + + + + + + + void BookmarksDragging() + { + void initFromOutside() + { + if (draggingBookmark) return; + if (!bookmarksRect.IsHovered()) return; + if (!curEvent.isDragUpdate) return; + if (DragAndDrop.objectReferences.FirstOrDefault() is not Object draggedObject) return; + if (draggedObject is not DefaultAsset) return; + + animatingDroppedBookmark = false; + + draggingBookmark = true; + draggingBookmarkFromInside = false; + + draggedBookmark = new Bookmark(draggedObject); + draggedBookmarkHoldOffset = Vector2.zero; + + } + void initFromInside() + { + if (draggingBookmark) return; + if (!mousePressed) return; + if ((curEvent.mousePosition - mouseDownPosiion).magnitude <= 2) return; + if (pressedBookmark == null) return; + + var i = GetBookmarkIndex(mouseDownPosiion.x); + + if (i >= data.bookmarks.Count) return; + if (i < 0) return; + + + animatingDroppedBookmark = false; + + draggingBookmark = true; + draggingBookmarkFromInside = true; + + draggedBookmark = data.bookmarks[i]; + draggedBookmarkHoldOffset = new Vector2(GetBookmarkCenterX(i) - mouseDownPosiion.x, bookmarksRect.center.y - mouseDownPosiion.y); + + gaps[i] = GetBookmarkWidth(draggedBookmark); + + + data.RecordUndo(); + + data.bookmarks.Remove(draggedBookmark); + + } + + void acceptFromOutside() + { + if (!draggingBookmark) return; + if (!curEvent.isDragPerform) return; + if (!bookmarksRect.IsHovered()) return; + + DragAndDrop.AcceptDrag(); + curEvent.Use(); + + data.RecordUndo(); + + accept(); + + data.Dirty(); + + } + void acceptFromInside() + { + if (!draggingBookmark) return; + if (!curEvent.isMouseUp) return; + if (!bookmarksRect.IsHovered()) return; + + curEvent.Use(); + EditorGUIUtility.hotControl = 0; + + DragAndDrop.PrepareStartDrag(); // fixes phantom dragged component indicator after reordering bookmarks + + data.RecordUndo(); + data.Dirty(); + + accept(); + + } + void accept() + { + draggingBookmark = false; + draggingBookmarkFromInside = false; + mousePressed = false; + + data.bookmarks.AddAt(draggedBookmark, insertDraggedBookmarkAtIndex); + + gaps[insertDraggedBookmarkAtIndex] -= GetBookmarkWidth(draggedBookmark); + gaps.AddAt(0, insertDraggedBookmarkAtIndex); + + droppedBookmark = draggedBookmark; + + droppedBookmarkX = curEvent.mousePosition.x + draggedBookmarkHoldOffset.x; + droppedBookmarkXDerivative = 0; + animatingDroppedBookmark = true; + + draggedBookmark = null; + + EditorGUIUtility.hotControl = 0; + + repaintNeededAfterUndoRedo = true; + + } + + void cancelFromOutside() + { + if (!draggingBookmark) return; + if (draggingBookmarkFromInside) return; + if (bookmarksRect.IsHovered()) return; + + draggingBookmark = false; + mousePressed = false; + + } + void cancelFromInsideAndDelete() + { + if (!draggingBookmark) return; + if (!curEvent.isMouseUp) return; + if (bookmarksRect.IsHovered()) return; + + draggingBookmark = false; + + DragAndDrop.PrepareStartDrag(); // fixes phantom dragged component indicator after reordering bookmarks + + data.Dirty(); + + repaintNeededAfterUndoRedo = true; + + } + + void update() + { + if (!draggingBookmark) return; + + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + + if (draggingBookmarkFromInside) // otherwise it breaks vTabs dragndrop + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + + + insertDraggedBookmarkAtIndex = GetBookmarkIndex(curEvent.mousePosition.x + draggedBookmarkHoldOffset.x); + + } + + void dropIntoBookmark() + { + if (draggingBookmark) return; + if (!bookmarksRect.IsHovered()) return; + if (!DragAndDrop.objectReferences.Any()) return; + if (!DragAndDrop.objectReferences.Any(r => r is not DefaultAsset)) return; + if (!DragAndDrop.objectReferences.All(r => AssetDatabase.Contains(r))) return; + + if (!mouseHoversBookmark) return; + if (lastHoveredBookmark.isDeleted) return; + + + if (curEvent.isDragUpdate) + DragAndDrop.visualMode = DragAndDropVisualMode.Move; + + + if (!curEvent.isDragPerform) return; + + DragAndDrop.AcceptDrag(); + + curEvent.Use(); + + + var oldPaths = DragAndDrop.objectReferences.Select(r => r.GetPath()).ToList(); + + var newPaths = oldPaths.Select(r => lastHoveredBookmark.guid.ToPath().CombinePath(r.GetFilename(withExtension: true))) + .Select(r => AssetDatabase.GenerateUniqueAssetPath(r)).ToList(); + + + typeof(Undo).GetMethod("RegisterAssetsMoveUndo", maxBindingFlags).Invoke(null, new object[] { oldPaths.ToArray() }); + + for (int i = 0; i < newPaths.Count; i++) + AssetDatabase.MoveAsset(oldPaths[i], newPaths[i]); + + } + + + initFromOutside(); + initFromInside(); + + acceptFromOutside(); + acceptFromInside(); + + cancelFromOutside(); + cancelFromInsideAndDelete(); + + update(); + + dropIntoBookmark(); + + } + + bool draggingBookmark; + bool draggingBookmarkFromInside; + + int insertDraggedBookmarkAtIndex; + + Vector2 draggedBookmarkHoldOffset; + + Bookmark draggedBookmark; + Bookmark droppedBookmark; + + + + + + + void BookmarksAnimations() + { + if (!curEvent.isLayout) return; + + void gaps_() + { + var makeSpaceForDraggedBookmark = draggingBookmark && bookmarksRect.IsHovered(); + + var lerpSpeed = 12; + + for (int i = 0; i < gaps.Count; i++) + if (makeSpaceForDraggedBookmark && i == insertDraggedBookmarkAtIndex) + gaps[i] = MathUtil.Lerp(gaps[i], GetBookmarkWidth(draggedBookmark), lerpSpeed, editorDeltaTime); + else + gaps[i] = MathUtil.Lerp(gaps[i], 0, lerpSpeed, editorDeltaTime); + + + + for (int i = 0; i < gaps.Count; i++) + if (gaps[i].Approx(0)) + gaps[i] = 0; + + + + animatingGaps = gaps.Any(r => r > .1f); + + + } + void droppedBookmark_() + { + if (!animatingDroppedBookmark) return; + + var lerpSpeed = 8; + + var targX = GetBookmarkCenterX(data.bookmarks.IndexOf(droppedBookmark), includeGaps: false); + + MathUtil.SmoothDamp(ref droppedBookmarkX, targX, lerpSpeed, ref droppedBookmarkXDerivative, editorDeltaTime); + + if ((droppedBookmarkX - targX).Abs() < .5f) + animatingDroppedBookmark = false; + + } + void tooltip() + { + if (!mouseHoversBookmark || lastHoveredBookmark != lastClickedBookmark) + hideTooltip = false; + + + var lerpSpeed = UnityEditorInternal.InternalEditorUtility.isApplicationActive ? 15 : 12321; + + if (mouseHoversBookmark && !draggingBookmark && !hideTooltip) + MathUtil.SmoothDamp(ref tooltipOpacity, 1, lerpSpeed, ref tooltipOpacityDerivative, editorDeltaTime); + else + MathUtil.SmoothDamp(ref tooltipOpacity, 0, lerpSpeed, ref tooltipOpacityDerivative, editorDeltaTime); + + + if (tooltipOpacity > .99f) + tooltipOpacity = 1; + + if (tooltipOpacity < .01f) + tooltipOpacity = 0; + + + animatingTooltip = tooltipOpacity != 0 && tooltipOpacity != 1; + + } + + gaps_(); + droppedBookmark_(); + tooltip(); + + } + + float droppedBookmarkX; + float droppedBookmarkXDerivative; + + float tooltipOpacity; + float tooltipOpacityDerivative; + + bool animatingDroppedBookmark; + bool animatingGaps; + bool animatingTooltip; + bool animatingBookmarks => animatingDroppedBookmark || animatingGaps; + + bool hideTooltip; + + List gaps + { + get + { + while (_gaps.Count < data.bookmarks.Count + 1) _gaps.Add(0); + while (_gaps.Count > data.bookmarks.Count + 1) _gaps.RemoveLast(); + + return _gaps; + + } + } + List _gaps = new(); + + Bookmark lastClickedBookmark; + + + + + + + + + + + + + + public VFoldersNavbar(EditorWindow window) + { + this.window = window; + + if (!VFoldersHistorySingleton.instance.histories_byWindow.TryGetValue(window, out history)) + VFoldersHistorySingleton.instance.histories_byWindow[window] = history = new VFoldersHistory(window); + + } + + public EditorWindow window; + public VFoldersHistory history; + + public VFoldersController controller => VFolders.controllers_byWindow[window]; + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersNavbar.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersNavbar.cs.meta new file mode 100644 index 0000000..25cfaa7 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersNavbar.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 30acd0784e948403f8f15047ab22692c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPalette.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPalette.cs new file mode 100644 index 0000000..ab6eb4b --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPalette.cs @@ -0,0 +1,268 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditorInternal; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VFolders +{ + public class VFoldersPalette : ScriptableObject + { + public List colors = new(); + + public bool colorsEnabled; + + public float colorSaturation = 1; + public float colorBrightness = 1; + public bool colorGradientsEnabled = true; + + public void ResetColors() + { + colors.Clear(); + + for (int i = 0; i < colorsCount; i++) + colors.Add(GetDefaultColor(i)); + + colorsEnabled = true; + + this.Dirty(); + + } + + + public static Color GetDefaultColor(int colorIndex) + { + Color color = default; + + void grey() + { + if (colorIndex >= greyColorsCount) return; + +#if UNITY_2022_1_OR_NEWER + color = Greyscale(EditorGUIUtility.isProSkin ? .16f : .9f); +#else + color = Greyscale(EditorGUIUtility.isProSkin ? .315f : .9f); +#endif + + } + void rainbowDarkTheme() + { + if (colorIndex < greyColorsCount) return; + if (!isDarkTheme) return; + + + var t = (colorIndex - greyColorsCount.ToFloat()) / rainbowColorsCount; + + if (colorIndex == 0) + t += .01f; + + if (colorIndex == 1) + t -= .02f; + + if (colorIndex == 2) + t -= .015f; + + if (colorIndex == 3) + t -= .01f; + + if (colorIndex == 4) + t += .02f; + + if (colorIndex == 5) + t += .01f; + + + if (colorIndex == 8) + t -= .01f; + + + // color = HSLToRGB(t, .61f, .57f); + color = ColorUtils.HSLToRGB(t, .61f, .57f); + + + if (colorIndex == 0) + color *= 1.16f; + + if (colorIndex == 1) + color *= 1.17f; + + if (colorIndex == 2) + color *= 1.03f; + + if (colorIndex == 6) + color *= 1.2f; + + if (colorIndex == 7) + color *= 1.55f; + + if (colorIndex == 8) + color *= 1.2f; + + if (colorIndex == 9) + color *= 1.08f; + + + color.a = .1f; + + } + void rainbowLightTheme() + { + if (colorIndex < greyColorsCount) return; + if (isDarkTheme) return; + + color = ColorUtils.HSLToRGB((colorIndex - greyColorsCount.ToFloat()) / rainbowColorsCount, .99f, .75f); + + if (colorIndex == 0) + color *= 1.1f; + + if (colorIndex == 1) + color *= 1.05f; + + color.a = .1f; + + } + + grey(); + rainbowDarkTheme(); + rainbowLightTheme(); + + return color; + + } + + public static int greyColorsCount = 0; + public static int rainbowColorsCount = 10; + public static int colorsCount => greyColorsCount + rainbowColorsCount; + + + + + public List iconRows = new(); + + [System.Serializable] + public class IconRow + { + public List builtinIcons = new(); // names + public List customIcons = new(); // guids + + public bool enabled = true; + + public bool isCustom => !builtinIcons.Any() || customIcons.Any(); + public bool isEmpty => !builtinIcons.Any() && !customIcons.Any(); + public int iconCount => builtinIcons.Count + customIcons.Count; + + public IconRow(string[] builtinIcons) => this.builtinIcons = builtinIcons.ToList(); + public IconRow() { } + + } + + public void ResetIcons() + { + iconRows.Clear(); + + iconRows.Add(new IconRow(new[] + { + "SceneAsset Icon", + "Prefab Icon", + "PrefabModel Icon", + "Material Icon", + "Texture Icon", + "Mesh Icon", + "cs Script Icon", + "Shader Icon", + "ComputeShader Icon", + "ScriptableObject Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "Light Icon", + "LightProbes Icon", + "LightmapParameters Icon", + "LightingDataAsset Icon", + "Cubemap Icon" + + })); + iconRows.Add(new IconRow(new[] + { + #if UNITY_6000_0_OR_NEWER + "PhysicsMaterial Icon", + #else + "PhysicMaterial Icon", + #endif + "BoxCollider Icon", + "TerrainCollider Icon", + "MeshCollider Icon", + "WheelCollider Icon", + "Rigidbody Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "AudioClip Icon", + "AudioMixerController Icon", + "AudioMixerGroup Icon", + "AudioEchoFilter Icon", + "AudioSource Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "TextAsset Icon", + "AssemblyDefinitionAsset Icon", + "TerrainData Icon", + "Terrain Icon", + "AnimatorController Icon", + "AnimationClip Icon", + "Font Icon", + "RawImage Icon", + "Settings Icon", + + })); + + this.Dirty(); + + } + + + + + [ContextMenu("Export palette")] + public void Export() + { + var packagePath = EditorUtility.SaveFilePanel("Export vFolders Palette", "", this.GetPath().GetFilename(withExtension: false), "unitypackage"); + + var iconPaths = iconRows.SelectMany(r => r.customIcons).Select(r => r.ToPath()).Where(r => !r.IsNullOrEmpty()); + + AssetDatabase.ExportPackage(iconPaths.Append(this.GetPath()).ToArray(), packagePath); + + EditorUtility.RevealInFinder(packagePath); + + } + + + + + void Reset() + { + ResetColors(); + ResetIcons(); + + VFolders.folderInfoCache.Clear(); + + } + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPalette.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPalette.cs.meta new file mode 100644 index 0000000..318740f --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPalette.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 618f730fcd14941fab38169a7dde8c64 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteEditor.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteEditor.cs new file mode 100644 index 0000000..081c9aa --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteEditor.cs @@ -0,0 +1,1375 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEditorInternal; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; +using static VFolders.VFoldersPalette; + + +namespace VFolders +{ + [CustomEditor(typeof(VFoldersPalette))] + class VFoldersPaletteEditor : Editor + { + + public override void OnInspectorGUI() + { + void colors() + { + var rowRect = ExpandWidthLabelRect(cellSize).SetX(rowsOffsetX).SetWidth(rowWidth + 16); + + void backgroundHovered() + { + if (!rowRect.IsHovered()) return; + if (pickingColor) return; + if (draggingRow) return; + + rowRect.Draw(hoveredRowBackground); + + } + void toggle() + { + var toggleRect = rowRect.SetWidth(16).MoveX(5); + + var prevEnabled = palette.colorsEnabled; + var newEnabled = EditorGUI.Toggle(toggleRect, palette.colorsEnabled); + + if (prevEnabled != newEnabled) + palette.RecordUndo(); + + palette.colorsEnabled = newEnabled; + + if (prevEnabled != newEnabled) + palette.Dirty(); + + } + void crossIcon() + { + var crossIconRect = rowRect.SetX(rowsOffsetX + iconsOffsetX + iconSpacing / 2).SetWidth(iconSize).SetHeightFromMid(iconSize); + + SetGUIColor(palette.colorsEnabled ? Color.white : disabledRowTint); + + GUI.DrawTexture(crossIconRect, EditorIcons.GetIcon("CrossIcon")); + + ResetGUIColor(); + + } + void color(int i) + { + var cellRect = rowRect.MoveX(iconsOffsetX + (i + 1) * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + void backgroundPicking() + { + if (!pickingColor) return; + if (i != pickingColorAtIndex) return; + + cellRect.DrawRounded(pickingBackground, 2); + + } + void backgroundHovered_andStartPickingColor() + { + if (pickingColor) return; + if (!cellRect.IsHovered()) return; + + + SetGUIColor(Color.clear); + + var clicked = GUI.Button(cellRect.Resize(1), ""); + + ResetGUIColor(); + + + + + var isPressed = GUIUtility.hotControl == typeof(EditorGUIUtility).GetFieldValue("s_LastControlID"); + + cellRect.DrawRounded(Greyscale(isPressed ? .39f : .43f), 2); + + + + + if (!clicked) return; + + colorPickerWindow = EditorUtils.OpenColorPicker((c) => { palette.RecordUndo(); palette.Dirty(); palette.colors[i] = c; }, palette.colors[i], true, false); + + colorPickerWindow.MoveTo(EditorGUIUtility.GUIToScreenPoint(cellRect.Move(-3, 50).position)); + + pickingColor = true; + pickingColorAtIndex = i; + + } + void colorOutline() + { + var outlineColor = i < VFoldersPalette.greyColorsCount ? Greyscale(.0f, .4f) : Greyscale(.15f, .2f); + + if (!palette.colorsEnabled) + outlineColor *= disabledRowTint; + + + cellRect.Resize(3).DrawRounded(outlineColor, 4); + + } + void color() + { + var brightness = palette.colorBrightness; + var saturation = palette.colorSaturation; + var drawGradients = palette.colorGradientsEnabled; + + if (!palette.colorGradientsEnabled) + brightness *= isDarkTheme ? .75f : .97f; + + if (i < VFoldersPalette.greyColorsCount) + { + saturation = brightness = 1; + drawGradients = false; + } + + + var colorRaw = palette.colors[i]; + + var color = MathUtil.Lerp(Greyscale(.2f), colorRaw, brightness); + + Color.RGBToHSV(color, out float h, out float s, out float v); + color = Color.HSVToRGB(h, s * saturation, v); + + color = MathUtil.Lerp(color, colorRaw, .5f).SetAlpha(1); + + if (!palette.colorsEnabled) + color *= disabledRowTint; + + + + + cellRect.Resize(4).DrawRounded(color, 3); + + if (drawGradients) + cellRect.Resize(4).AddWidthFromRight(-2).DrawCurtainLeft(GUIColors.windowBackground.SetAlpha(.45f)); + + } + void updatePickingColor() + { + if (!pickingColor) return; + + EditorApplication.RepaintProjectWindow(); + + } + void stopPickingColor() + { + if (!pickingColor) return; + if (colorPickerWindow) return; + + pickingColor = false; + + } + + + cellRect.MarkInteractive(); + + + backgroundPicking(); + backgroundHovered_andStartPickingColor(); + + colorOutline(); + color(); + + updatePickingColor(); + stopPickingColor(); + + } + void adjustColorsButton() + { + var cellRect = rowRect.MoveX(iconsOffsetX + (palette.colors.Count + 1) * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize).MoveX(-1f); + + + var iconSize = 16; + var iconName = "Preset.Context"; + var iconColor = Greyscale(.75f, palette.colorsEnabled ? (isDarkTheme ? 1 : .8f) : .5f); + + if (!IconButton(cellRect, iconName, iconSize, iconColor)) return; + + + if (adjustColorsWindow) { adjustColorsWindow.Close(); return; } + + var windowX = 107f.Min(this.GetMemberValue("propertyViewer").position.width - 310); + var windowY = cellRect.y + 25; + var windowWidth = 270; + var windowHeight = VFoldersMenu.backgroundColorsEnabled ? 92 : 73; + + adjustColorsWindow = ScriptableObject.CreateInstance(); + adjustColorsWindow.palette = palette; + adjustColorsWindow.paletteEditor = this; + + adjustColorsWindow.ShowPopup(); + adjustColorsWindow.Focus(); + + adjustColorsWindow.position = EditorGUIUtility.GUIToScreenRect(new Rect(windowX, windowY, windowWidth, windowHeight)); + + } + + + backgroundHovered(); + toggle(); + crossIcon(); + + for (int i = 0; i < palette.colors.Count; i++) + color(i); + + adjustColorsButton(); + + Space(rowSpacing - 2); + + } + void icons() + { + void row(Rect rowRect, IconRow row) + { + var isLastRow = row == palette.iconRows.Last(); + var isDraggedRow = row == draggedRow; + var spaceForCrossIcon = 0f; + + + void updatePickingIcon() + { + if (!pickingIcon) return; + if (pickingIconAtRow != row) return; + if (pickingIconAtIndex >= row.customIcons.Count) return; // somehow happens if RecordUndo is used + + palette.RecordUndo(); + palette.Dirty(); + + row.customIcons[pickingIconAtIndex] = addIconWindow.hoveredIconName; + + } + void stopPickingIcon() + { + if (!pickingIcon) return; + if (pickingIconAtRow != row) return; + if (addIconWindow) return; + + if (pickingIconAtIndex < row.customIcons.Count) + if (row.customIcons[pickingIconAtIndex] == null) + row.customIcons.RemoveAt(pickingIconAtIndex); + + pickingIcon = false; + + } + void dragndrop() + { + if (!rowRect.IsHovered()) return; + if (!row.isCustom) return; + + if (curEvent.isDragUpdate && DragAndDrop.objectReferences.First() is Texture2D) + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + + if (!curEvent.isDragPerform) return; + if (!(DragAndDrop.objectReferences.Any(r => r is Texture2D))) return; + + DragAndDrop.AcceptDrag(); + + palette.RecordUndo(); + palette.Dirty(); + + foreach (var icon in DragAndDrop.objectReferences.Where(r => r is Texture2D)) + row.customIcons.Add(icon.GetPath().ToGuid()); + + } + + void calcSpaceForCrossIcon() + { + if (row == curFirstEnabledRow) + spaceForCrossIcon = crossIconAnimationT * cellSize; + + if (row == crossIconAnimationSourceRow) + spaceForCrossIcon = (1 - crossIconAnimationT) * cellSize; + + } + + void backgroundHovered() + { + if (!rowRect.IsHovered()) return; + if (pickingColor) return; + if (pickingIcon) return; + if (draggingRow) return; + if (DragAndDrop.objectReferences.Any() && !row.isCustom) return; + + + rowRect.Draw(hoveredRowBackground); + + } + void backgroundDragged() + { + if (!isDraggedRow) return; + + rowRect.DrawBlurred(Greyscale(0, .3f), 12); + rowRect.Draw(draggedRowBackground); + + } + void toggle() + { + var prevEnabled = row.enabled; + var newEnabled = EditorGUI.Toggle(rowRect.SetWidth(16).MoveX(5), row.enabled); + + if (prevEnabled != newEnabled) + palette.RecordUndo(); + + row.enabled = newEnabled; + + if (prevEnabled != newEnabled) + palette.Dirty(); + + } + void addIconButton() + { + if (!row.isCustom) return; + if (pickingIcon && pickingIconAtRow == row) return; + + + var cellRect = rowRect.MoveX(iconsOffsetX + row.customIcons.Count * cellSize + spaceForCrossIcon).SetWidth(cellSize).SetHeightFromMid(cellSize); + + var iconSize = 16; + var iconName = "Toolbar Plus"; + var iconColor = Greyscale(.73f, row.enabled ? (isDarkTheme ? 1 : .65f) : .5f); + + if (!IconButton(cellRect, iconName, iconSize, iconColor)) return; + + + + palette.RecordUndo(); + + row.customIcons.Add(null); + + + + var windowX = 15; + var windowY = cellRect.y + 23; + var windowWidth = (this.GetMemberValue("propertyViewer").position.width - 26).Min(679); + var windowHeight = windowWidth * 1.2f; + + windowWidth = (windowWidth / AddIconWindow.cellSize).FloorToInt() * AddIconWindow.cellSize - 3; + + + addIconWindow = ScriptableObject.CreateInstance(); + addIconWindow.palette = palette; + addIconWindow.paletteEditor = this; + + addIconWindow.ShowPopup(); + addIconWindow.Focus(); + + addIconWindow.position = EditorGUIUtility.GUIToScreenRect(new Rect(windowX, windowY, windowWidth, windowHeight)); + + addIconWindow.Init(); + + + pickingIcon = true; + pickingIconAtIndex = row.customIcons.Count - 1; + pickingIconAtRow = row; + + + } + void icon(int i) + { + var cellRect = rowRect.MoveX(iconsOffsetX + spaceForCrossIcon + i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + + void backgroundPicking() + { + if (!pickingIcon) return; + if (pickingIconAtRow != row) return; + if (pickingIconAtIndex != i) return; + + cellRect.DrawRounded(pickingBackground, 2); + + } + void backgroundHovered_andEditIconButton() + { + if (!row.isCustom) return; + if (pickingIcon) return; + if (!cellRect.IsHovered()) return; + if (DragAndDrop.objectReferences.Any()) return; + + + SetGUIColor(Color.clear); + + var clicked = GUI.Button(cellRect.Resize(1), ""); + + ResetGUIColor(); + + + + + var isPressed = GUIUtility.hotControl == typeof(EditorGUIUtility).GetFieldValue("s_LastControlID"); + + cellRect.DrawRounded(Greyscale(isPressed ? .39f : .45f), 2); + + + + + if (!clicked) return; + + GenericMenu menu = new(); + + menu.AddItem(new GUIContent("Move left"), false, i == 0 ? null : () => + { + palette.RecordUndo(); + palette.Dirty(); + + var icon = row.customIcons[i]; + + row.customIcons.RemoveAt(i); + row.customIcons.AddAt(icon, i - 1); + + }); + menu.AddItem(new GUIContent("Move right"), false, i == row.customIcons.Count - 1 ? null : () => + { + palette.RecordUndo(); + palette.Dirty(); + + var icon = row.customIcons[i]; + + row.customIcons.RemoveAt(i); + row.customIcons.AddAt(icon, i + 1); + + }); + + menu.AddSeparator(""); + + menu.AddItem(new GUIContent("Remove icon"), false, () => { palette.RecordUndo(); row.customIcons.RemoveAt(i); palette.Dirty(); }); + + + menu.ShowAsContext(); + + } + + void drawIcon() + { + var iconNameOrGuid = row.isCustom ? row.customIcons[i] : row.builtinIcons[i]; + + if (iconNameOrGuid == null) return; + + var iconNameOrPath = iconNameOrGuid.Length == 32 ? iconNameOrGuid.ToPath() : iconNameOrGuid; + var icon = EditorIcons.GetIcon(iconNameOrPath) ?? Texture2D.blackTexture; + + + var cellRect = rowRect.MoveX(iconsOffsetX + spaceForCrossIcon + i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + var iconRect = cellRect.SetSizeFromMid(iconSize); + + if (icon.width < icon.height) iconRect = iconRect.SetWidthFromMid(iconRect.height * icon.width / icon.height); + if (icon.height < icon.width) iconRect = iconRect.SetHeightFromMid(iconRect.width * icon.height / icon.width); + + + + SetGUIColor(row.enabled ? Color.white : disabledRowTint); + + GUI.DrawTexture(iconRect, icon); + + ResetGUIColor(); + + } + + + cellRect.MarkInteractive(); + + backgroundPicking(); + backgroundHovered_andEditIconButton(); + + drawIcon(); + + } + + + rowRect.MarkInteractive(); + + updatePickingIcon(); + stopPickingIcon(); + dragndrop(); + + calcSpaceForCrossIcon(); + backgroundHovered(); + backgroundDragged(); + toggle(); + addIconButton(); + + for (int i = 0; i < row.iconCount; i++) + icon(i); + + } + + void updateRowsCount() + { + palette.iconRows.RemoveAll(r => r.isEmpty && r != palette.iconRows.Last()); + + if (!palette.iconRows.Last().isEmpty) + palette.iconRows.Add(new IconRow()); + + } + void updateRowGapsCount() + { + while (rowGaps.Count < palette.iconRows.Count) + rowGaps.Add(0); + + while (rowGaps.Count > palette.iconRows.Count) + rowGaps.RemoveLast(); + + } + + void normalRow(int i) + { + Space(rowGaps[i] * (cellSize + rowSpacing)); + + if (i == 0 && lastRect.y != 0) + firstRowY = lastRect.y; + + Space(cellSize + rowSpacing); + + var rowRect = Rect.zero.SetPos(rowsOffsetX, lastRect.y).SetSize(rowWidth, cellSize); + + if (curEvent.isRepaint) + if (rowRect.IsHovered()) + hoveredRow = palette.iconRows[i]; + + + row(rowRect, palette.iconRows[i]); + + } + void draggedRow_() + { + if (!draggingRow) return; + + draggedRowY = (curEvent.mousePosition.y + draggedRowHoldOffset).Clamp(firstRowY, firstRowY + (palette.iconRows.Count - 1) * (cellSize + rowSpacing)); + + var rowRect = Rect.zero.SetPos(rowsOffsetX, draggedRowY).SetSize(rowWidth, cellSize); + + row(rowRect, draggedRow); + + } + void crossIcon() + { + if (!palette.iconRows.Any(r => r.enabled)) return; + + var rect = Rect.zero.SetPos(rowsOffsetX + iconsOffsetX, crossIconY).SetSize(cellSize, cellSize).Resize(iconSpacing / 2); + + GUI.DrawTexture(rect, EditorIcons.GetIcon("CrossIcon")); + + } + + + updateRowsCount(); + updateRowGapsCount(); + + + if (curEvent.isRepaint) + hoveredRow = null; + + for (int i = 0; i < palette.iconRows.Count; i++) + normalRow(i); + + crossIcon(); + + draggedRow_(); + + + } + void tutor() + { + SetGUIEnabled(false); + + + Space(4); + GUILayout.Label("Add icons with drag-and-drop or by clicking '+'"); + + Space(4); + GUILayout.Label("Click added icon to move or remove it"); + + Space(4); + GUILayout.Label("Drag rows to reorder them"); + + + ResetGUIEnabled(); + + } + + + Space(15); + colors(); + + Space(15); + icons(); + + Space(22); + tutor(); + + UpdateAnimations(); + + UpdateDragging(); + + palette.Dirty(); + + if (draggingRow || animatingCrossIcon) + Repaint(); + + } + + float iconSize => 14; + float iconSpacing => 6; + float cellSize => iconSize + iconSpacing; + float rowSpacing = 1; + float rowsOffsetX => 14; + float iconsOffsetX => 27; + + Color hoveredRowBackground => Greyscale(isDarkTheme ? 1 : 0, .05f); + Color draggedRowBackground => Greyscale(isDarkTheme ? .3f : .9f); + Color pickingBackground => Greyscale(1, .17f); + Color disabledRowTint => Greyscale(1, .45f); + + float rowWidth => cellSize * Mathf.Max(palette.colors.Count, palette.iconRows.Max(r => r.iconCount + 1)) + 55; + + bool pickingColor; + int pickingColorAtIndex; + EditorWindow colorPickerWindow; + + bool pickingIcon; + int pickingIconAtIndex; + IconRow pickingIconAtRow; + AddIconWindow addIconWindow; + + IconRow hoveredRow; + + float firstRowY = 51; + + static AdjustColorsWindow adjustColorsWindow; + + + + + + void UpdateAnimations() + { + void lerpRowGaps() + { + if (!curEvent.isLayout) return; + + var lerpSpeed = draggingRow ? 12 : 12321; + + for (int i = 0; i < rowGaps.Count; i++) + rowGaps[i] = MathUtil.Lerp(rowGaps[i], draggingRow && i == insertDraggedRowAtIndex ? 1 : 0, lerpSpeed, editorDeltaTime); + + for (int i = 0; i < rowGaps.Count; i++) + if (rowGaps[i].Approx(0)) + rowGaps[i] = 0; + else if (rowGaps[i].Approx(1)) + rowGaps[i] = 1; + + + } + + void lerpCrossIconAnimationT() + { + if (!curEvent.isLayout) return; + + var lerpSpeed = 12; + + MathUtil.Lerp(ref crossIconAnimationT, 1, lerpSpeed, editorDeltaTime); + + } + void startCrossIconAnimation() + { + if (prevFirstEnabledRow == null) { prevFirstEnabledRow = curFirstEnabledRow; return; } + if (prevFirstEnabledRow == curFirstEnabledRow) return; + + crossIconAnimationT = 0; + crossIconAnimationSourceRow = prevFirstEnabledRow; + + prevFirstEnabledRow = curFirstEnabledRow; + + } + void stopCrossIconAnimation() + { + if (!crossIconAnimationT.Approx(1)) return; + + crossIconAnimationT = 1; + crossIconAnimationSourceRow = null; + + } + void calcCrossIconY() + { + var indexOfFirstEnabled = palette.iconRows.IndexOfFirst(r => r.enabled); + var yOfFirstEnabled = firstRowY + indexOfFirstEnabled * (cellSize + rowSpacing); + for (int i = 0; i < indexOfFirstEnabled + 1; i++) + yOfFirstEnabled += rowGaps[i] * (cellSize + rowSpacing); + + + var indexOfSourceRow = palette.iconRows.IndexOf(crossIconAnimationSourceRow); + var yOfSourceRow = firstRowY + indexOfSourceRow * (cellSize + rowSpacing); + for (int i = 0; i < indexOfSourceRow + 1; i++) + yOfSourceRow += rowGaps[i] * (cellSize + rowSpacing); + + if (crossIconAnimationSourceRow == draggedRow) + yOfSourceRow = draggedRowY; + + + crossIconY = MathUtil.Lerp(yOfSourceRow, yOfFirstEnabled, crossIconAnimationT); + + if (indexOfFirstEnabled == indexOfSourceRow) + crossIconAnimationT = 1; + + } + + + lerpRowGaps(); + + lerpCrossIconAnimationT(); + startCrossIconAnimation(); + stopCrossIconAnimation(); + calcCrossIconY(); + + } + + List rowGaps = new(); + + float crossIconY = 51; + float crossIconAnimationT = 1; + IconRow crossIconAnimationSourceRow; + bool animatingCrossIcon => crossIconAnimationT != 1; + + [System.NonSerialized] IconRow prevFirstEnabledRow; + IconRow curFirstEnabledRow => palette.iconRows.FirstOrDefault(r => r.enabled); + + + + + + + void UpdateDragging() + { + void startDragging() + { + if (draggingRow) return; + if (!curEvent.isMouseDrag) return; + if (hoveredRow == null) return; + if (hoveredRow == palette.iconRows.Last()) return; + + palette.RecordUndo(); + + draggingRow = true; + draggedRow = hoveredRow; + draggingRowFromIndex = palette.iconRows.IndexOf(hoveredRow); + draggedRowHoldOffset = firstRowY + draggingRowFromIndex * (cellSize + rowSpacing) - curEvent.mousePosition.y; + + palette.iconRows.Remove(hoveredRow); + rowGaps[draggingRowFromIndex] = 1; + + } + void updateDragging() + { + if (!draggingRow) return; + + insertDraggedRowAtIndex = ((curEvent.mousePosition.y - firstRowY) / (cellSize + rowSpacing)).FloorToInt().Clamp(0, palette.iconRows.Count - 1); + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + } + void stopDragging() + { + if (!draggingRow) return; + if (!curEvent.isMouseUp) return; + + palette.RecordUndo(); + palette.Dirty(); + + palette.iconRows.AddAt(draggedRow, insertDraggedRowAtIndex); + + rowGaps[insertDraggedRowAtIndex] = 0; + + draggingRow = false; + draggedRow = null; + + EditorGUIUtility.hotControl = 0; + + } + + + startDragging(); + updateDragging(); + stopDragging(); + + } + + IconRow draggedRow; + bool draggingRow; + int draggingRowFromIndex; + float draggedRowHoldOffset; + float draggedRowY; + int insertDraggedRowAtIndex; + + + + + + + VFoldersPalette palette => target as VFoldersPalette; + + } + + + class AddIconWindow : EditorWindow + { + + void OnGUI() + { + void header() + { + var headerRect = Rect.zero.SetHeight(20).SetWidth(position.width); + var closeButtonRect = headerRect.SetWidthFromRight(16).SetHeightFromMid(16).Move(-3, -.5f); + + void background() + { + headerRect.Draw(EditorGUIUtility.isProSkin ? Greyscale(.18f) : Greyscale(.7f)); + } + void title() + { + SetGUIColor(Greyscale(.8f)); + SetLabelAlignmentCenter(); + + GUI.Label(headerRect.MoveY(-1), "Add icon"); + + ResetLabelStyle(); + ResetGUIColor(); + + } + void closeButton() + { + var colorNormal = isDarkTheme ? Greyscale(.55f) : Greyscale(.35f); + var colorHovered = isDarkTheme ? Greyscale(.9f) : colorNormal; + + var iconSize = 14; + + if (IconButton(closeButtonRect, "CrossIcon", iconSize, colorNormal, colorHovered)) + Close(); + + } + void escHint() + { + if (!closeButtonRect.IsHovered()) return; + + var textRect = headerRect.SetWidthFromRight(42).MoveY(-.5f).MoveX(1); + var fontSize = 11; + var color = Greyscale(.65f); + + + SetLabelFontSize(fontSize); + SetGUIColor(color); + + GUI.Label(textRect, "Esc"); + + ResetGUIColor(); + ResetLabelStyle(); + + } + + background(); + title(); + closeButton(); + escHint(); + + Space(headerRect.height); + + } + void search() + { + var backgroundRect = ExpandWidthLabelRect(height: 21).SetWidthFromMid(position.width); + var backgroundColor = isDarkTheme ? Greyscale(.25f) : Greyscale(.8f); + + backgroundRect.Draw(backgroundColor); + + + var lineRect = backgroundRect.SetHeightFromBottom(1).MoveY(.5f); + var lineColor = isDarkTheme ? Greyscale(.15f) : Greyscale(.7f); + + lineRect.Draw(lineColor); + + + var searchRect = backgroundRect.Resize(2); + + EditorGUI.BeginChangeCheck(); + + searchString = searchField.OnGUI(searchRect, searchString); + + if (EditorGUI.EndChangeCheck()) + { + GenerateRows(); + FilterIconsBySearch(); + GenerateRows(); + } + + } + void icons() + { + void row(int i) + { + var rowRect = position.SetPos(0, 0).SetHeight(cellSize).MoveY(i * rowHeight); + var iconNames = rows[i]; + + void icon(int i) + { + var iconName = iconNames[i]; + var icon = EditorIcons.GetIcon(iconName); + + var cellRect = rowRect.SetWidth(cellSize).MoveX(i * cellSize + paddingLeft); + var iconRect = cellRect.SetSizeFromMid(iconSize); + + if (icon.width < icon.height) iconRect = iconRect.SetWidthFromMid(iconRect.height * icon.width / icon.height); + if (icon.height < icon.width) iconRect = iconRect.SetHeightFromMid(iconRect.width * icon.height / icon.width); + + + + var hoverRect = cellRect.AddHeightFromMid(rowSpacing); + + hoverRect.MarkInteractive(); + + if (hoverRect.IsHovered()) + cellRect.Draw(Greyscale(isDarkTheme ? .42f : .69f)); + + + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon(iconName)); + + + + if (curEvent.isRepaint) + if (hoverRect.IsHovered()) + hoveredIconName = iconName; + + if (hoverRect.IsHovered() && curEvent.isMouseDown) + Close(); + + } + + for (int ii = 0; ii < iconNames.Count; ii++) + icon(ii); + } + + + if (curEvent.isRepaint) + hoveredIconName = null; + + + scrollPos = GUILayout.BeginScrollView(new Vector2(0, scrollPos)).y; + + GUILayout.Space(rows.Count * rowHeight + 23); + + + var i0 = (scrollPos / rowHeight).FloorToInt(); + var i1 = (i0 + ((position.height - 30) / rowHeight).CeilToInt()).Min(rows.Count); + + for (int ii = i0; ii < i1; ii++) + row(ii); + + + GUILayout.EndScrollView(); + + } + void hoveredIconLabel() + { + if (hoveredIconName == null) return; + + + var nameRect = position.SetPos(0, 0).SetHeightFromBottom(18).SetWidth(hoveredIconName.GetLabelWidth() + 6).Move(1, -1); + + var shadowRect = nameRect.AddWidthFromRight(10).AddHeight(10); + + + shadowRect.DrawBlurred(GUIColors.windowBackground, 12); + shadowRect.DrawBlurred(GUIColors.windowBackground.SetAlpha(.4f), 8); + + + SetLabelAlignmentCenter(); + + GUI.Label(nameRect, hoveredIconName); + + ResetLabelStyle(); + + } + void closeOnEsc() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + hoveredIconName = null; + + Close(); + + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + + header(); + search(); + icons(); + hoveredIconLabel(); + closeOnEsc(); + outline(); + + paletteEditor.Repaint(); + + if (EditorWindow.focusedWindow != this) + Close(); + + } + + public static float iconSize => 16; + public static float iconSpacing => 6; + public static float cellSize => iconSize + iconSpacing; + public static float rowSpacing = 1; + + public static float rowHeight => cellSize + rowSpacing; + + public static float paddingLeft => 3; + public static float paddingRight => 3; + + public string hoveredIconName; + + static string searchString = ""; + static float scrollPos; + + + + + + void LoadAllIcons() + { + if (allIconNames != null) return; + + + (float h, float s, float v, float a) GetAverageColor(Texture2D texture) + { + + var readableTexture = new Texture2D(texture.width, texture.height, texture.format, texture.mipmapCount > 1); + + Graphics.CopyTexture(texture, readableTexture); + + var pixels = readableTexture.GetPixels32(); + + readableTexture.DestroyImmediate(); + + + float hSum = 0; + float sSum = 0; + float vSum = 0; + + int nonTransparentPxCount = pixels.Length; + int coloredPxCount = pixels.Length; + + for (var i = 0; i < pixels.Length; i++) + { + if (pixels[i].a <= .1f) { nonTransparentPxCount--; coloredPxCount--; continue; } + + Color.RGBToHSV(pixels[i], out float h, out float s, out float v); + + if (s > .1f) + hSum += h; + else + coloredPxCount--; + + sSum += s; + + vSum += v; + + } + + var hAvg = hSum / coloredPxCount; + var sAvg = sSum / nonTransparentPxCount; + var vAvg = vSum / nonTransparentPxCount; + var aAvg = nonTransparentPxCount / pixels.Length.ToFloat(); + + if (coloredPxCount == 0) + hAvg = -1; + + + return (hAvg, sAvg, vAvg, aAvg); + + } + + + + var editorAssetBundle = typeof(EditorGUIUtility).InvokeMethod("GetEditorAssetBundle"); + + allIconNames = ( + + from path in editorAssetBundle.GetAllAssetNames() + + + let icon = editorAssetBundle.LoadAsset(path) + + where icon + + + where path.StartsWith("icons/") + where !path.Contains("avatarinspector") + + where !icon.name.ToLower().StartsWith("d_") + + where !icon.name.ToLower().EndsWith(".small") + where !icon.name.ToLower().EndsWith("_sml") + + where !icon.name.Contains("@") + where !icon.name.Contains("TreeEditor") + where !icon.name.Contains("scene-template") + where !icon.name.Contains("StateMachineEditor.Background") + where !icon.name.Contains("SpeedTree") + where !icon.name.Contains("TextMesh") + where !icon.name.Contains("Profiler.Instrumentation") + where !icon.name.Contains("Profiler.Record") + where !icon.name.Contains("SocialNetworks") + where !icon.name.Contains("Groove") + + + + let avgColor = GetAverageColor(icon) + + where avgColor.a > .1f + + + + orderby avgColor.h * -3f + + avgColor.s * .09f + + avgColor.v * 0f, icon.name + + + + select icon.name + + ).ToHashSet() + .ToList(); + + } + + static List allIconNames; + + + + void FilterIconsBySearch() + { + filteredIcons = ( + + from iconName in allIconNames + + where iconName.ToLower().Contains(searchString.ToLower()) + + orderby iconName.ToLower().IndexOf(searchString.ToLower(), System.StringComparison.Ordinal) + + select iconName + + ).ToList(); + } + + static List filteredIcons; + + + + void GenerateRows() + { + + var iconsPerRow = ((position.width - paddingLeft - paddingRight) / cellSize).FloorToInt(); + + rows = new(); + + var curRow = new List(); + + foreach (var icon in filteredIcons) + { + curRow.Add(icon); + + if (curRow.Count == iconsPerRow) + { + rows.Add(curRow); + curRow = new(); + } + } + + if (curRow.Any()) + rows.Add(curRow); + + } + + static List> rows; + + + + + + + public void Init() + { + LoadAllIcons(); + FilterIconsBySearch(); + GenerateRows(); + + searchField = new(); + + } + + SearchField searchField; + + + + + + public VFoldersPalette palette; + public VFoldersPaletteEditor paletteEditor; + + } + + class AdjustColorsWindow : EditorWindow + { + void OnGUI() + { + void header() + { + var headerRect = Rect.zero.SetHeight(20).SetWidth(position.width); + var closeButtonRect = headerRect.SetWidthFromRight(16).SetHeightFromMid(16).Move(-3, -.5f); + + void background() + { + headerRect.Draw(EditorGUIUtility.isProSkin ? Greyscale(.18f) : Greyscale(.7f)); + } + void title() + { + SetGUIColor(Greyscale(.8f)); + SetLabelAlignmentCenter(); + + GUI.Label(headerRect.MoveY(-1), "Adjust colors"); + + ResetLabelStyle(); + ResetGUIColor(); + + } + void closeButton() + { + var colorNormal = isDarkTheme ? Greyscale(.55f) : Greyscale(.35f); + var colorHovered = isDarkTheme ? Greyscale(.9f) : colorNormal; + + var iconSize = 14; + + if (IconButton(closeButtonRect, "CrossIcon", iconSize, colorNormal, colorHovered)) + Close(); + + } + void escHint() + { + if (!closeButtonRect.IsHovered()) return; + + var textRect = headerRect.SetWidthFromRight(42).MoveY(-.5f).MoveX(1); + var fontSize = 11; + var color = Greyscale(.65f); + + + SetLabelFontSize(fontSize); + SetGUIColor(color); + + GUI.Label(textRect, "Esc"); + + ResetGUIColor(); + ResetLabelStyle(); + + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + + background(); + title(); + closeButton(); + escHint(); + outline(); + + Space(headerRect.height); + + } + void body() + { + EditorGUIUtility.labelWidth = 85; + EditorGUIUtility.keyboardControl = 0; + + palette.RecordUndo(); + + + EditorGUI.BeginChangeCheck(); + + palette.colorBrightness = (EditorGUILayout.Slider("Brightness", palette.colorBrightness, 0, 2) / .1f).RoundToInt() * .1f; + palette.colorSaturation = (EditorGUILayout.Slider("Saturation", palette.colorSaturation, 0, 2) / .1f).RoundToInt() * .1f; + + if (VFoldersMenu.backgroundColorsEnabled) + palette.colorGradientsEnabled = EditorGUILayout.Toggle("Gradients", palette.colorGradientsEnabled); + + if (EditorGUI.EndChangeCheck()) + { + paletteEditor.Repaint(); + EditorApplication.RepaintProjectWindow(); + + palette.Dirty(); + + VFolders.folderInfoCache.Clear(); + + } + + + EditorGUIUtility.labelWidth = 0; + + } + void closeOnEsc() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + Close(); + + } + + + header(); + + Space(7); + BeginIndent(10); + + body(); + + EndIndent(5); + + + + closeOnEsc(); + + if (EditorWindow.focusedWindow != this) + Close(); + + Repaint(); // for undo + + } + + public VFoldersPalette palette; + public VFoldersPaletteEditor paletteEditor; + + } + +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteEditor.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteEditor.cs.meta new file mode 100644 index 0000000..2a387df --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 085d323b143d5403fb4beda728e65139 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteWindow.cs b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteWindow.cs new file mode 100644 index 0000000..5247f2d --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteWindow.cs @@ -0,0 +1,783 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VFolders.VFolders; +using static VFolders.VFoldersData; +using static VFolders.VFoldersPalette; +using static VFolders.VFoldersCache; +using static VFolders.Libs.VUtils; +using static VFolders.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VFolders +{ + public class VFoldersPaletteWindow : EditorWindow + { + + void OnGUI() + { + if (!palette) { Close(); return; } + + int hoveredColorIndex = -1; + string hoveredIconNameOrGuid = null; + + void background() + { + position.SetPos(0, 0).Draw(windowBackground); + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + void colors() + { + if (!palette.colorsEnabled) return; + + var rowRect = this.position.SetPos(paddingX, paddingY).SetHeight(cellSize); + + + void color(int i) + { + var cellRect = rowRect.MoveX(i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + void backgroundSelected() + { + if (!colorIndexes_initial.Contains(i)) return; + + cellRect.Resize(1).DrawRounded(selectedBackground, 2); + + } + void backgroundHovered() + { + if (!cellRect.IsHovered()) return; + + cellRect.Resize(1).DrawRounded(this.hoveredBackground, 2); + + } + void crossIcon() + { + if (i != 0) return; + + GUI.DrawTexture(cellRect.SetSizeFromMid(iconSize), EditorIcons.GetIcon("CrossIcon")); + + } + void colorOutline() + { + if (i == 0) return; + + var outlineColor = i <= VFoldersPalette.greyColorsCount ? Greyscale(.0f, .4f) : Greyscale(.15f, .2f); + + cellRect.Resize(3).DrawRounded(outlineColor, 4); + + } + void color() + { + if (i == 0) return; + + var brightness = palette.colorBrightness; + var saturation = palette.colorSaturation; + var drawGradients = palette.colorGradientsEnabled; + + if (!palette.colorGradientsEnabled) + brightness *= isDarkTheme ? .75f : .97f; + + if (i <= VFoldersPalette.greyColorsCount) + { + saturation = brightness = 1; + drawGradients = false; + } + + + var colorRaw = palette ? palette.colors[i - 1] : VFoldersPalette.GetDefaultColor(i - 1); + + var color = MathUtil.Lerp(Greyscale(.2f), colorRaw, brightness); + + Color.RGBToHSV(color, out float h, out float s, out float v); + color = Color.HSVToRGB(h, s * saturation, v); + + color = MathUtil.Lerp(color, colorRaw, .5f).SetAlpha(1); + + + + + cellRect.Resize(4).DrawRounded(color, 3); + + if (drawGradients) + cellRect.Resize(4).AddWidthFromRight(-2).DrawCurtainLeft(GUIColors.windowBackground.SetAlpha(.45f)); + + } + void recursiveIndicator() + { + if (!curEvent.isRepaint) return; + + + var isRecursive = folderDatas.First().colorIndex == i && folderDatas.First().isColorRecursive; + + if (!isRecursive) return; + + + + var iconRect = cellRect.SetSizeFromMid(16).Move(-6, -7); + var shadowRect = iconRect.Resize(3).Move(2, 1).AddWidthFromRight(3); + var shadowRadius = 4; + + shadowRect.DrawBlurred(GUIColors.windowBackground, shadowRadius); + + + SetGUIColor(Color.white * 2); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon("UnityEditor.SceneHierarchyWindow@2x")); + + ResetGUIColor(); + + + } + + void setHovered() + { + if (!cellRect.IsHovered()) return; + + hoveredColorIndex = i; + + } + void closeOnClick() + { + if (!cellRect.IsHovered()) return; + if (!curEvent.isMouseUp) return; + + curEvent.Use(); + + Close(); + + } + + + + cellRect.MarkInteractive(); + + backgroundSelected(); + backgroundHovered(); + crossIcon(); + colorOutline(); + color(); + recursiveIndicator(); + + setHovered(); + closeOnClick(); + + } + + + for (int i = 0; i < palette.colors.Count + 1; i++) + color(i); + + } + void icons() + { + void row(int i, IconRow iconRow) + { + var rowRect = this.position.SetPos(paddingX, paddingY).SetHeight(cellSize).MoveY(palette.colorsEnabled ? cellSize + spaceAfterColors : 0).MoveY(i * (cellSize + rowSpacing)); + + var isFirstEnabledRow = palette.iconRows.First(r => r.enabled) == iconRow; + + + void icon(int i) + { + var cellRect = rowRect.MoveX(i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + var isCrossIcon = isFirstEnabledRow && i == 0; + var actualIconIndex = isFirstEnabledRow ? i - 1 : i; + var isCustomIcon = !isCrossIcon && actualIconIndex >= iconRow.builtinIcons.Count; + var iconNameOrGuid = isCrossIcon ? "" : isCustomIcon ? iconRow.customIcons[actualIconIndex - iconRow.builtinIcons.Count] : iconRow.builtinIcons[actualIconIndex]; + + + void backgroundSelected() + { + if (!iconNamesOrGuids_initial.Contains(iconNameOrGuid)) return; + + cellRect.Resize(1).DrawRounded(selectedBackground, 2); + + } + void backgroundHovered() + { + if (!cellRect.IsHovered()) return; + + cellRect.Resize(1).DrawRounded(this.hoveredBackground, 2); + + } + void crossIcon() + { + if (!isCrossIcon) return; + + GUI.DrawTexture(cellRect.SetSizeFromMid(iconSize), EditorIcons.GetIcon("CrossIcon")); + + } + void normalIcon() + { + if (isCrossIcon) return; + + var iconNameOrPath = iconNameOrGuid?.Length == 32 ? iconNameOrGuid.ToPath() : iconNameOrGuid; + var icon = EditorIcons.GetIcon(iconNameOrPath) ?? Texture2D.blackTexture; + + var iconRect = cellRect.SetSizeFromMid(iconSize); + + if (icon.width < icon.height) iconRect = iconRect.SetWidthFromMid(iconRect.height * icon.width / icon.height); + if (icon.height < icon.width) iconRect = iconRect.SetHeightFromMid(iconRect.width * icon.height / icon.width); + + + GUI.DrawTexture(iconRect, icon); + + } + void recursiveIndicator() + { + if (!curEvent.isRepaint) return; + + + var isRecursive = folderDatas.First().iconNameOrGuid == iconNameOrGuid && folderDatas.First().isIconRecursive; + + if (!isRecursive) return; + + + + var iconRect = cellRect.SetSizeFromMid(16).Move(-6, -7); + var shadowRect = iconRect.Resize(3).Move(2, 1).AddWidthFromRight(3); + var shadowRadius = 4; + + shadowRect.DrawBlurred(GUIColors.windowBackground, shadowRadius); + + + + SetGUIColor(Color.white * 2); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon("UnityEditor.SceneHierarchyWindow@2x")); + + ResetGUIColor(); + + + } + + void setHovered() + { + if (!cellRect.IsHovered()) return; + + hoveredIconNameOrGuid = iconNameOrGuid; + + } + void closeOnClick() + { + if (!cellRect.IsHovered()) return; + if (!curEvent.isMouseUp) return; + + curEvent.Use(); + + Close(); + + } + + + + cellRect.MarkInteractive(); + + backgroundSelected(); + backgroundHovered(); + crossIcon(); + normalIcon(); + recursiveIndicator(); + + setHovered(); + closeOnClick(); + + } + + + for (int j = 0; j < iconRow.iconCount + (isFirstEnabledRow ? 1 : 0); j++) + icon(j); + + } + + + var i = 0; + + foreach (var iconRow in palette.iconRows) + { + if (!iconRow.enabled) continue; + if (iconRow.isEmpty) continue; + + row(i, iconRow); + + i++; + } + + } + void editPaletteButton() + { + var buttonRect = position.SetPos(0, 0).SetWidthFromRight(16).SetHeightFromBottom(16).Move(-14, -14); + var buttonColor = isDarkTheme ? Greyscale(.6f) : Greyscale(1, .6f); + + if (!IconButton(buttonRect, "Toolbar Plus", 16, buttonColor)) return; + + + palette.SelectInInspector(frameInProject: false); + + this.Close(); + + } + + void setColorsAndIcons() + { + if (!curEvent.isLayout) return; + + + if (palette.iconRows.Any(r => r.enabled)) + if (hoveredIconNameOrGuid != null) + SetIcon(hoveredIconNameOrGuid, isRecursive: curEvent.holdingAlt); + else + SetInitialIcons(); + + + if (palette.colorsEnabled) + if (hoveredColorIndex != -1) + SetColor(hoveredColorIndex, isRecursive: curEvent.holdingAlt); + else + SetInitialColors(); + + } + void updatePosition() + { + if (!curEvent.isLayout) return; + + void calcDeltaTime() + { + deltaTime = (float)(EditorApplication.timeSinceStartup - lastLayoutTime); + + if (deltaTime > .05f) + deltaTime = .0166f; + + lastLayoutTime = EditorApplication.timeSinceStartup; + + } + void resetCurPos() + { + if (currentPosition != default) return; + + currentPosition = position.position; // position.position is always int, which can't be used for lerping + + } + void lerpCurPos() + { + var speed = 9; + + MathUtil.SmoothDamp(ref currentPosition, targetPosition, speed, ref positionDeriv, deltaTime); + // MathfUtils.Lerp(ref currentPosition, targetPosition, speed, deltaTime); + + } + void setCurPos() + { + position = position.SetPos(currentPosition); + } + + calcDeltaTime(); + resetCurPos(); + lerpCurPos(); + setCurPos(); + + if (!currentPosition.magnitude.Approx(targetPosition.magnitude)) + Repaint(); + + } + void closeOnEscape() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + SetInitialColors(); + SetInitialIcons(); + + Close(); + + } + + + RecordUndoOnDatas(); + + background(); + outline(); + colors(); + icons(); + editPaletteButton(); + + setColorsAndIcons(); + updatePosition(); + closeOnEscape(); + + + + VFolders.folderInfoCache.Clear(); + + EditorApplication.RepaintProjectWindow(); + + EditorApplication.delayCall += EditorApplication.RepaintProjectWindow; // to show icons that will be generated in update + + } + + static float iconSize => 14; + static float iconSpacing => 6; + static float cellSize => iconSize + iconSpacing; + static float spaceAfterColors => 13; + public float rowSpacing = 1; + static float paddingX => 12; + static float paddingY => 12; + + Color windowBackground => isDarkTheme ? Greyscale(.23f) : Greyscale(.75f); + Color selectedBackground => isDarkTheme ? new Color(.3f, .5f, .7f, .8f) : new Color(.3f, .5f, .7f, .6f) * 1.25f; + Color hoveredBackground => isDarkTheme ? Greyscale(1, .3f) : Greyscale(0, .1f); + + public Vector2 targetPosition; + public Vector2 currentPosition; + Vector2 positionDeriv; + float deltaTime; + double lastLayoutTime; + + + + + + + void SetIcon(string iconNameOrGuid, bool isRecursive) + { + foreach (var r in folderDatas) + { + r.isIconRecursive = isRecursive; // setting it firstbecause iconNameOrGuid setter relies on isIconRecursive + r.iconNameOrGuid = iconNameOrGuid; + } + } + void SetColor(int colorIndex, bool isRecursive) + { + foreach (var r in folderDatas) + { + r.isColorRecursive = isRecursive; + r.colorIndex = colorIndex; + } + } + + void SetInitialIcons() + { + for (int i = 0; i < folderDatas.Count; i++) + { + folderDatas[i].isIconRecursive = isIconRecursives_initial[i]; + folderDatas[i].iconNameOrGuid = iconNamesOrGuids_initial[i]; + } + } + void SetInitialColors() + { + for (int i = 0; i < folderDatas.Count; i++) + { + folderDatas[i].isColorRecursive = isColorRecursives_initial[i]; + folderDatas[i].colorIndex = colorIndexes_initial[i]; + } + } + + void RemoveEmptyFolderDatas() + { + if (VFoldersData.storeDataInMetaFiles) return; // empties removed from meta files in SaveData() + + var toRemove = folderDatas.Select(r => r.folderData).Where(r => r.iconNameOrGuid == "" && r.colorIndex == 0); + + foreach (var r in toRemove) + data.folderDatas_byGuid.RemoveValue(r); + + if (toRemove.Any()) + Undo.CollapseUndoOperations(Undo.GetCurrentGroup() - 1); + + } + + void RecordUndoOnDatas() + { + if (!VFoldersData.storeDataInMetaFiles) + if (data) + data.RecordUndo(); + + if (VFoldersData.storeDataInMetaFiles) + foreach (var r in guids) + AssetImporter.GetAtPath(r.ToPath()).RecordUndo(); + + } + void MarkDatasDirty() + { + if (!VFoldersData.storeDataInMetaFiles) + if (data) + data.Dirty(); + + if (VFoldersData.storeDataInMetaFiles) + VFolders.folderDatasFromMetaFiles_byGuid.Clear(); + + } + void SaveData() + { + if (!VFoldersData.storeDataInMetaFiles) return; + // if (!VFoldersData.storeDataInMetaFiles) { data.Save(); return; } + + for (int i = 0; i < guids.Count; i++) + if (folderDatas[i].iconNameOrGuid == "" && folderDatas[i].colorIndex == 0) + AssetImporter.GetAtPath(guids[i].ToPath()).userData = ""; + else + AssetImporter.GetAtPath(guids[i].ToPath()).userData = JsonUtility.ToJson(folderDatas[i].folderData); + + for (int i = 0; i < guids.Count; i++) + AssetImporter.GetAtPath(guids[i].ToPath()).SaveAndReimport(); + + } + + + + + + + + + + void OnLostFocus() + { + if (curEvent.holdingAlt && EditorWindow.focusedWindow?.GetType().Name == "ProjectBrowser") + CloseNextFrameIfNotRefocused(); + else + Close(); + + } + + void CloseNextFrameIfNotRefocused() + { + EditorApplication.delayCall += () => { if (EditorWindow.focusedWindow != this) Close(); }; + } + + + + + static void RepaintOnAlt() // Update + { + if (curEvent.holdingAlt != wasHoldingAlt) + if (EditorWindow.mouseOverWindow is VFoldersPaletteWindow paletteWindow) + paletteWindow.Repaint(); + + wasHoldingAlt = curEvent.holdingAlt; + + } + + static bool wasHoldingAlt; + + + + + + + + + + public void Init(List guids) + { + void createData() + { + if (VFolders.data) return; + + VFolders.data = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(VFolders.data, GetScriptPath("VFolders").GetParentPath().CombinePath("vFolders Data.asset")); + + } + void createPalette() + { + if (VFolders.palette) return; + + VFolders.palette = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(VFolders.palette, GetScriptPath("VFolders").GetParentPath().CombinePath("vFolders Palette.asset")); + + } + void setSize() + { + if (!palette.colorsEnabled && !palette.iconRows.Any(r => r.enabled && !r.isEmpty)) // somehow happened on first palette window opening in 2022.3.50 + palette.InvokeMethod("Reset"); + + + + var rowCellCounts = new List(); + + if (palette.colorsEnabled) + rowCellCounts.Add(palette.colors.Count + 1); + + foreach (var r in palette.iconRows.Where(r => r.enabled && !r.isEmpty)) + rowCellCounts.Add(r.iconCount + (r == palette.iconRows.First(r => r.enabled) ? 1 : 0)); + + var width = paddingX + + rowCellCounts.Max() * cellSize + + (rowCellCounts.Last() == rowCellCounts.Max() ? cellSize : 0) + + paddingX; + + + + var iconRowCount = palette.iconRows.Count(r => r.enabled && !r.isEmpty); + + var height = paddingY + + (palette.colorsEnabled ? cellSize : 0) + + (palette.colorsEnabled && palette.iconRows.Any(r => r.enabled && !r.isEmpty) ? spaceAfterColors : 0) + + cellSize * iconRowCount + + rowSpacing * (iconRowCount - 1) + + paddingY; + + + position = position.SetSize(width, height).SetPos(targetPosition); + + } + void getFolderDatas() + { + folderDatas.Clear(); + + foreach (var guid in guids) + folderDatas.Add(new FolderDataWrapper(guid)); + + } + void getInitialState() + { + iconNamesOrGuids_initial = folderDatas.Select(r => r.iconNameOrGuid).ToList(); + colorIndexes_initial = folderDatas.Select(r => r.colorIndex).ToList(); + + isIconRecursives_initial = folderDatas.Select(r => r.isIconRecursive).ToList(); + isColorRecursives_initial = folderDatas.Select(r => r.isColorRecursive).ToList(); + + } + + + this.guids = guids; + + RecordUndoOnDatas(); + + createData(); + createPalette(); + setSize(); + getFolderDatas(); + getInitialState(); + + Undo.undoRedoPerformed -= EditorApplication.RepaintProjectWindow; + Undo.undoRedoPerformed += EditorApplication.RepaintProjectWindow; + + EditorApplication.update -= RepaintOnAlt; + EditorApplication.update += RepaintOnAlt; + + } + + void OnDestroy() + { + RemoveEmptyFolderDatas(); + MarkDatasDirty(); + SaveData(); + + EditorApplication.update -= RepaintOnAlt; + + } + + public List guids = new(); + public List folderDatas = new(); + + public List iconNamesOrGuids_initial = new(); + public List colorIndexes_initial = new(); + + public List isIconRecursives_initial = new(); + public List isColorRecursives_initial = new(); + + static VFoldersPalette palette => VFolders.palette; + static VFoldersData data => VFolders.data; + + + + + + + + public static void CreateInstance(Vector2 position) + { + instance = ScriptableObject.CreateInstance(); + + instance.ShowPopup(); + + instance.position = instance.position.SetPos(position).SetSize(200, 300); + instance.targetPosition = position; + + } + + public static VFoldersPaletteWindow instance; + + + + + + + public class FolderDataWrapper + { + public string iconNameOrGuid + { + get + { + if (folderData != null && folderData.iconNameOrGuid != "") + if (folderData.iconNameOrGuid == "none") return ""; + else return folderData.iconNameOrGuid ?? ""; + + else if (VFoldersMenu.autoIconsEnabled && folderState.autoIconName != "") + return folderState.autoIconName; + + else return ""; + + } + set + { + if (VFoldersMenu.autoIconsEnabled && folderState.autoIconName != "") + if (value == folderState.autoIconName && !folderData.isIconRecursive) + folderData.iconNameOrGuid = ""; + else if (value == "") + folderData.iconNameOrGuid = "none"; + else + folderData.iconNameOrGuid = value; + + + else folderData.iconNameOrGuid = value; + + } + + } + public bool isIconRecursive { get => folderData.isIconRecursive; set => folderData.isIconRecursive = value; } + + public int colorIndex { get => folderData.colorIndex; set => folderData.colorIndex = value; } + public bool isColorRecursive { get => folderData.isColorRecursive; set => folderData.isColorRecursive = value; } + + + public FolderDataWrapper(string guid) + { + folderData = VFolders.GetFolderData(guid, createDataIfDoesntExist: true); + folderState = VFolders.GetFolderState(guid); + } + + public FolderData folderData; + public FolderState folderState; + + + // used as an interlayer between folderData and palette window to account for automatic icons + // it's the only structural difference between vFolders' PaletteWindow and vHierarchy's + + } + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteWindow.cs.meta b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteWindow.cs.meta new file mode 100644 index 0000000..6f8ecd4 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vFolders/VFoldersPaletteWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 68011e63381a84f3bacee6e5ee9e0b82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy.meta new file mode 100644 index 0000000..0d51a57 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd2b9e231b0ec4a9f97e258072ae2b33 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/Read me.pdf b/Assets/OtherAssets/DEV/PAID/vHierarchy/Read me.pdf new file mode 100644 index 0000000..754aee7 Binary files /dev/null and b/Assets/OtherAssets/DEV/PAID/vHierarchy/Read me.pdf differ diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/Read me.pdf.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/Read me.pdf.meta new file mode 100644 index 0000000..0227813 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/Read me.pdf.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 520b2a02f70c04615b08281be020f305 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.asmdef b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.asmdef new file mode 100644 index 0000000..22702c6 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.asmdef @@ -0,0 +1,16 @@ +{ + "name": "VHierarchy", + "rootNamespace": "", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.asmdef.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.asmdef.meta new file mode 100644 index 0000000..d859f2d --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2c3f48364a5004fd3a152fbdf5fea703 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.cs new file mode 100644 index 0000000..bfa82f8 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.cs @@ -0,0 +1,1808 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; +using static VHierarchy.VHierarchyData; +using static VHierarchy.VHierarchyCache; + + + +namespace VHierarchy +{ + public static class VHierarchy + { + + static void WrappedGUI(EditorWindow window) + { + var navbarHeight = 26; + + void navbarGui() + { + if (!navbars_byWindow.ContainsKey(window)) + navbars_byWindow[window] = new VHierarchyNavbar(window); + + var navbarRect = window.position.SetPos(0, 0).SetHeight(navbarHeight); + + + navbars_byWindow[window].OnGUI(navbarRect); + + } + void defaultGuiWithOffset() + { + var defaultTopBarHeight = 20; + var topOffset = navbarHeight - defaultTopBarHeight; + + var m_Pos_original = window.GetFieldValue("m_Pos"); + + + + + GUI.BeginGroup(m_Pos_original.SetPos(0, 0).AddHeightFromBottom(-topOffset)); + + window.SetFieldValue("m_Pos", m_Pos_original.AddHeightFromBottom(-topOffset)); + + + try + { + if (curEvent.isMouseDown && m_Pos_original.IsHovered()) + t_SceneHierarchyWindow.SetMemberValue("s_LastInteractedHierarchy", window); + + window.InvokeMethod("DoSceneHierarchy"); + window.InvokeMethod("ExecuteCommands"); + + // same as SceneHierarchyWindow.OnGUI() but without DoToolbarLayout(): + + } + catch (System.Exception exception) + { + if (exception.InnerException is ExitGUIException) + throw exception.InnerException; + else + throw exception; + + // GUIUtility.ExitGUI() works by throwing ExitGUIException, which just exits imgui loop and doesn't appear in console + // but if ExitGUI is called from a reflected method (DoSceneHierarchy in this case), the exception becomes TargetInvokationException + // which gets logged to console (only if debugger is attached, for some reason) + // so here in such cases we rethrow the original ExitGUIException + + } + + + window.SetFieldValue("m_Pos", m_Pos_original); + + GUI.EndGroup(); + + } + void shadow() + { + if (!curEvent.isRepaint) return; + + var shadowLength = 30; + var shadowPos = 21; + var shadowGreyscale = isDarkTheme ? .1f : .28f; + var shadowAlpha = isDarkTheme ? .35f : .15f; + + var minScrollPos = 10; + var maxScrollPos = 20; + + if (StageUtility.GetCurrentStage() is PrefabStage) + shadowPos += 30; + else if (EditorSceneManager.loadedRootSceneCount > 1) + shadowPos += 16; + + + + var scrollPos = window.GetMemberValue("m_SceneHierarchy").GetMemberValue("m_TreeViewState").scrollPos.y; + + if (scrollPos <= minScrollPos) return; + + var opacity = ((scrollPos - minScrollPos) / (maxScrollPos - minScrollPos)).Clamp01(); + + + var rectWidth = window.position.width;// - 12; + + var rect = window.position.SetPos(0, 0).MoveY(shadowPos).SetHeight(shadowLength).SetWidth(rectWidth); + + + + var clipAtY = navbarHeight + 1; + + if (EditorSceneManager.loadedRootSceneCount > 1) + clipAtY += 16; + + + GUI.BeginClip(window.position.SetPos(0, clipAtY)); + + rect.MoveY(-clipAtY).DrawCurtainDown(Greyscale(shadowGreyscale, shadowAlpha * opacity)); + + GUI.EndClip(); + + } + + + + var doNavbarFirst = navbars_byWindow.ContainsKey(window) && navbars_byWindow[window].isSearchActive; + + if (doNavbarFirst) + navbarGui(); + + defaultGuiWithOffset(); + shadow(); + + if (!doNavbarFirst) + navbarGui(); + + } + + static Dictionary navbars_byWindow = new(); + + + + static void UpdateGUIWrapping(EditorWindow window) + { + if (!window.hasFocus) return; + + var curOnGUIMethod = window.GetMemberValue("m_Parent").GetMemberValue("m_OnGUI").Method; + + var isWrapped = curOnGUIMethod == mi_WrappedGUI; + var shouldBeWrapped = VHierarchyMenu.navigationBarEnabled; + + void wrap() + { + var hostView = window.GetMemberValue("m_Parent"); + + var newDelegate = typeof(VHierarchy).GetMethod(nameof(WrappedGUI), maxBindingFlags).CreateDelegate(t_EditorWindowDelegate, window); + + hostView.SetMemberValue("m_OnGUI", newDelegate); + + window.Repaint(); + + } + void unwrap() + { + var hostView = window.GetMemberValue("m_Parent"); + + var originalDelegate = hostView.InvokeMethod("CreateDelegate", "OnGUI"); + + hostView.SetMemberValue("m_OnGUI", originalDelegate); + + window.Repaint(); + + } + + + if (shouldBeWrapped && !isWrapped) + wrap(); + + if (!shouldBeWrapped && isWrapped) + unwrap(); + + } + static void UpdateGUIWrappingForAllHierarchies() => allHierarchies.ForEach(r => UpdateGUIWrapping(r)); + + static void OnDomainReloaded() => toCallInGUI += UpdateGUIWrappingForAllHierarchies; + + static void OnWindowUnmaximized() => UpdateGUIWrappingForAllHierarchies(); + + static void OnHierarchyFocused() => UpdateGUIWrapping(EditorWindow.focusedWindow); + + static void OnDelayCall() => UpdateGUIWrappingForAllHierarchies(); + + + + + + static void CheckIfFocusedWindowChanged() + { + if (prevFocusedWindow != EditorWindow.focusedWindow) + if (EditorWindow.focusedWindow?.GetType() == t_SceneHierarchyWindow) + OnHierarchyFocused(); + + prevFocusedWindow = EditorWindow.focusedWindow; + + } + + static EditorWindow prevFocusedWindow; + + + + static void CheckIfWindowWasUnmaximized() + { + var isMaximized = EditorWindow.focusedWindow?.maximized == true; + + if (!isMaximized && wasMaximized) + OnWindowUnmaximized(); + + wasMaximized = isMaximized; + + } + + static bool wasMaximized; + + + + static void OnSomeGUI() + { + toCallInGUI?.Invoke(); + toCallInGUI = null; + + CheckIfFocusedWindowChanged(); + + } + + static void ProjectWindowItemOnGUI(string _, Rect __) => OnSomeGUI(); + static void HierarchyWindowItemOnGUI(int _, Rect __) => OnSomeGUI(); + + static System.Action toCallInGUI; + + + + static void DelayCallLoop() + { + OnDelayCall(); + + EditorApplication.delayCall -= DelayCallLoop; + EditorApplication.delayCall += DelayCallLoop; + + } + + + + + + + + + + + + + + + static void RowGUI(int instanceId, Rect rowRect) + { + EditorWindow window; + + void findWindow() + { + if (allHierarchies.Count() == 1) { window = allHierarchies.First(); return; } + + + var pointInsideWindow = EditorGUIUtility.GUIToScreenPoint(rowRect.center); + + window = allHierarchies.FirstOrDefault(r => r.position.AddHeight(30).Contains(pointInsideWindow) && r.hasFocus); + + } + void updateWindow() + { + if (!window) return; // happens on half-visible rows during expand animation + + if (curEvent.isLayout && !lastEventWasLayout) + UpdateWindow(window); + + lastEventWasLayout = curEvent.isLayout; + + } + void catchScrollInputForController() + { + if (!window) return; + if (!controllers_byWindow.ContainsKey(window)) return; + + if (curEvent.isScroll) + controllers_byWindow[window].animatingScroll = false; + + } + void callGUI() + { + if (!window) return; + if (!guis_byWindow.ContainsKey(window)) return; + + + + var gui = guis_byWindow[window]; + + if (EditorUtility.InstanceIDToObject(instanceId) is GameObject go) + gui.RowGUI_GameObject(rowRect, go); + else + for (int i = 0; i < EditorSceneManager.sceneCount; i++) + if (EditorSceneManager.GetSceneAt(i).GetHashCode() == instanceId) + gui.RowGUI_Scene(rowRect, EditorSceneManager.GetSceneAt(i)); + + } + + findWindow(); + updateWindow(); + catchScrollInputForController(); + callGUI(); + + } + + static bool lastEventWasLayout; + + + + static void UpdateWindow(EditorWindow window) + { + if (!guis_byWindow.TryGetValue(window, out var gui)) + gui = guis_byWindow[window] = new(window); + + if (!controllers_byWindow.TryGetValue(window, out var controller)) + controller = controllers_byWindow[window] = new(window); + + + gui.UpdateState(); + + controller.UpdateState(); + controller.UpdateExpandQueue(); + controller.UpdateScrollAnimation(); + controller.UpdateHighlightAnimation(); + + } + + public static Dictionary guis_byWindow = new(); + public static Dictionary controllers_byWindow = new(); + + + + + + + + + + + + + + + public static Texture GetComponentIcon(Component component) + { + if (!component) return null; + + if (!componentIcons_byType.ContainsKey(component.GetType())) + componentIcons_byType[component.GetType()] = EditorGUIUtility.ObjectContent(component, component.GetType()).image; + + return componentIcons_byType[component.GetType()]; + + } + + static Dictionary componentIcons_byType = new(); + + + + static Texture2D GetIcon_forVTabs(GameObject gameObject) + { + var goData = GetGameObjectData(gameObject, false); + + if (goData == null) return null; + + var iconNameOrPath = goData.iconNameOrGuid.Length == 32 ? goData.iconNameOrGuid.ToPath() : goData.iconNameOrGuid; + + if (!iconNameOrPath.IsNullOrEmpty()) + return EditorIcons.GetIcon(iconNameOrPath); + + return null; + + } + + static string GetIconName_forVFavorites(GameObject gameObject) + { + var goData = GetGameObjectData(gameObject, false); + + if (goData == null) return ""; + + var iconNameOrPath = goData.iconNameOrGuid.Length == 32 ? goData.iconNameOrGuid.ToPath() : goData.iconNameOrGuid; + + return iconNameOrPath; + + } + static string GetIconName_forVInspector(GameObject gameObject) + { + return GetIconName_forVFavorites(gameObject); + } + + public static void SetIcon(GameObject gameObject, string iconName, bool recursive = false) + { + goDataCache.Clear(); + + var goData = GetGameObjectData(gameObject, createDataIfDoesntExist: true); + + goData.iconNameOrGuid = iconName ?? ""; + goData.isIconRecursive = recursive; + + + goInfoCache.Clear(); + + EditorApplication.RepaintHierarchyWindow(); + + } + public static void SetColor(GameObject gameObject, int colorIndex, bool recursive = false) + { + goDataCache.Clear(); + + var goData = GetGameObjectData(gameObject, createDataIfDoesntExist: true); + + goData.colorIndex = colorIndex; + goData.isColorRecursive = recursive; + + + goInfoCache.Clear(); + + EditorApplication.RepaintHierarchyWindow(); + + } + + + + + + + + + + + + + + + static void Shortcuts() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode == KeyCode.None) return; + if (EditorWindow.mouseOverWindow is not EditorWindow hoveredWindow) return; + if (hoveredWindow?.GetType() != t_SceneHierarchyWindow) return; + + + void toggleExpanded() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.E) return; + if (curEvent.holdingAnyModifierKey) return; + if (!VHierarchyMenu.toggleExpandedEnabled) return; + + if (Tools.viewTool == ViewTool.FPS) return; + if (hoveredGo == null && hoveredScene == default) return; + + + curEvent.Use(); + + + if (transformToolNeedsReset = Application.unityVersion.Contains("2022")) + previousTransformTool = Tools.current; + + if (hoveredScene == default) + if (hoveredGo.transform.childCount == 0) return; + + + if (hoveredScene != default) + controllers_byWindow[hoveredWindow].ToggleExpanded(hoveredScene.handle); + else + controllers_byWindow[hoveredWindow].ToggleExpanded(hoveredGo.GetInstanceID()); + + } + void collapseAll() + { + if (curEvent.modifiers != (EventModifiers.Shift | EventModifiers.Command) && curEvent.modifiers != (EventModifiers.Shift | EventModifiers.Control)) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.E) return; + if (!VHierarchyMenu.collapseEverythingEnabled) return; + + curEvent.Use(); + + + controllers_byWindow[hoveredWindow].CollapseAll(); + + } + void isolate() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.E) return; + if (curEvent.modifiers != EventModifiers.Shift) return; + if (!VHierarchyMenu.isolateEnabled) return; + + if (hoveredGo == null && hoveredScene == default) return; + + curEvent.Use(); + + if (hoveredGo && hoveredGo.transform.childCount == 0) return; + if (!hoveredGo && hoveredScene.rootCount == 0) return; + + + if (hoveredScene != default) + controllers_byWindow[hoveredWindow].Isolate(hoveredScene.handle); + else + controllers_byWindow[hoveredWindow].Isolate(hoveredGo.GetInstanceID()); + + } + void toggleActive() + { + if (!hoveredGo) return; + if (curEvent.holdingAnyModifierKey) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.A) return; + if (Tools.viewTool == ViewTool.FPS) return; + if (!VHierarchyMenu.toggleActiveEnabled) return; + + curEvent.Use(); + + + var gos = Selection.gameObjects.Contains(hoveredGo) ? Selection.gameObjects : new[] { hoveredGo }; + var active = !gos.Any(r => r.activeSelf); + + foreach (var r in gos) + { + r.RecordUndo(); + r.SetActive(active); + } + + } + void delete() + { + if (!hoveredGo) return; + if (curEvent.holdingAnyModifierKey) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.X) return; + if (!VHierarchyMenu.deleteEnabled) return; + + var gos = Selection.gameObjects.Contains(hoveredGo) ? Selection.gameObjects : new[] { hoveredGo }; + + foreach (var r in gos) + Undo.DestroyObjectImmediate(r); + + curEvent.Use(); + } + void focus() + { + if (!curEvent.isKeyDown) return; + if (curEvent.modifiers != EventModifiers.None) return; + if (curEvent.keyCode != KeyCode.F) return; + if (SceneView.sceneViews.Count == 0) return; + if (!hoveredGo) return; + if (!VHierarchyMenu.focusEnabled) return; + + var sv = SceneView.lastActiveSceneView; + + if (!sv || !sv.hasFocus) + sv = SceneView.sceneViews.ToArray().FirstOrDefault(r => (r as SceneView).hasFocus) as SceneView; + + if (!sv) + (sv = SceneView.lastActiveSceneView ?? SceneView.sceneViews[0] as SceneView).Focus(); + + sv.Frame(hoveredGo.GetBounds(), false); + + } + void setDefaultParent() + { + if (!curEvent.isKeyDown) return; + if (curEvent.modifiers != EventModifiers.None) return; + if (curEvent.keyCode != KeyCode.D) return; + if (!hoveredGo) return; + if (!VHierarchyMenu.setDefaultParentEnabled) return; + + + var isDefaultParentHovered = hoveredGo == typeof(SceneView).InvokeMethod("GetDefaultParentObjectIfSet")?.gameObject; + + if (isDefaultParentHovered) + EditorUtility.ClearDefaultParentObject(); + else + EditorUtility.SetDefaultParentObject(hoveredGo); + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + } + void resetDefaultParent() + { + if (!curEvent.isKeyDown) return; + if (curEvent.modifiers != EventModifiers.Shift) return; + if (curEvent.keyCode != KeyCode.D) return; + if (!VHierarchyMenu.setDefaultParentEnabled) return; + + + EditorUtility.ClearDefaultParentObject(); + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + } + + + toggleExpanded(); + toggleActive(); + delete(); + collapseAll(); + isolate(); + focus(); + setDefaultParent(); + resetDefaultParent(); + + } + + public static GameObject hoveredGo; + public static Scene hoveredScene; + + + + + + + + + + + + + + + + public static GameObjectInfo GetGameObjectInfo(GameObject go) + { + if (goInfoCache.TryGetValue(go, out var cachedGoInfo)) return cachedGoInfo; + + + var goInfo = new GameObjectInfo(); + + var goData = goInfo.goData = GetGameObjectData(go, createDataIfDoesntExist: false); + + + var recursiveIconNameOrGuid = ""; + var recursiveColorIndex = 0; + + var ruledIconNameOrGuid = ""; + var ruledColorIndex = 0; + + void checkRules() + { + if (rules == null) + rules = TypeCache.GetMethodsWithAttribute() + .Where(r => r.IsStatic + && r.GetParameters().Count() == 1 + && r.GetParameters().First().ParameterType == typeof(ObjectInfo)).ToList(); + + if (!rules.Any()) return; + + + + var objectInfo = new ObjectInfo(go); + + foreach (var rule in rules) + rule.Invoke(null, new[] { objectInfo }); + + + ruledIconNameOrGuid = objectInfo.icon; + ruledColorIndex = objectInfo.color; + + + } + void checkRecursion(Transform transform, int depth) + { + if (!transform.parent) return; + + var parentGoData = GetGameObjectData(transform.parent.gameObject, createDataIfDoesntExist: false); + + if (parentGoData != null) + { + + if (parentGoData.isIconRecursive && parentGoData.iconNameOrGuid != "") + if (recursiveIconNameOrGuid == "") + recursiveIconNameOrGuid = parentGoData.iconNameOrGuid; + + if (parentGoData.isColorRecursive && parentGoData.colorIndex != 0) + if (recursiveColorIndex == 0) + recursiveColorIndex = parentGoData.colorIndex; + + + if (parentGoData.isColorRecursive && parentGoData.colorIndex != 0) + goInfo.maxColorRecursionDepth = depth + 1; + + } + + + + checkRecursion(transform.parent, depth + 1); + + } + void setIcon() + { + var iconNameOrGuid = ""; + + if (goData != null && goData.iconNameOrGuid != "") + iconNameOrGuid = goData.iconNameOrGuid; + + else if (recursiveIconNameOrGuid != "") + iconNameOrGuid = recursiveIconNameOrGuid; + + else if (ruledIconNameOrGuid != "") + iconNameOrGuid = ruledIconNameOrGuid; + + + + if (iconNameOrGuid == "") { goInfo.hasIcon = false; return; } + + goInfo.hasIcon = true; + goInfo.hasIconByRecursion = recursiveIconNameOrGuid != ""; + + goInfo.iconNameOrPath = iconNameOrGuid.Length == 32 ? iconNameOrGuid.ToPath() + : iconNameOrGuid; + + } + void setColor() + { + var colorIndex = 0; + + if (goData != null && goData.colorIndex > 0) + colorIndex = goData.colorIndex; + + else if (recursiveColorIndex != 0) + colorIndex = recursiveColorIndex; + + else if (ruledColorIndex != 0) + colorIndex = ruledColorIndex; + + + + if (colorIndex == 0) { goInfo.hasColor = false; return; } + + goInfo.hasColor = true; + goInfo.hasColorByRecursion = recursiveColorIndex != 0; + + + + + var brightness = palette?.colorBrightness ?? 1; + var saturation = palette?.colorSaturation ?? 1; + + if (colorIndex <= VHierarchyPalette.greyColorsCount) + saturation = brightness = 1; + + + var rawColor = palette ? palette.colors[colorIndex - 1] : VHierarchyPalette.GetDefaultColor(colorIndex - 1); + + var brightenedColor = MathUtil.Lerp(Greyscale(.2f), rawColor, brightness); + + Color.RGBToHSV(brightenedColor, out float h, out float s, out float v); + var saturatedColor = Color.HSVToRGB(h, s * saturation, v); + + + goInfo.color = saturatedColor; + + goInfo.isGreyColor = colorIndex <= VHierarchyPalette.greyColorsCount; + + } + + checkRules(); + checkRecursion(go.transform, 0); + setIcon(); + setColor(); + + + return goInfoCache[go] = goInfo; + + } + + public static Dictionary goInfoCache = new(); + + public static List rules = null; + + public class GameObjectInfo + { + public string iconNameOrPath = ""; + public bool hasIcon; + public bool hasIconByRecursion; + + public Color color; + public bool hasColor; + public bool hasColorByRecursion; + public int maxColorRecursionDepth; + public bool isGreyColor; + + + public GameObjectData goData; + + } + + + + public static GameObjectData GetGameObjectData(GameObject go, bool createDataIfDoesntExist) + { + if (!data) return null; + if (goDataCache.TryGetValue(go, out var cachedResult)) return cachedResult; + + GameObjectData goData = null; + SceneData sceneData = null; + + void sceneObject() + { + if (StageUtility.GetCurrentStage() is PrefabStage) return; + + + SceneIdMap sceneIdMap = null; + + var currentSceneGuid = go.scene.path.ToGuid(); + var originalSceneGuid = cache.originalSceneGuids_byInstanceId.GetValueOrDefault(go.GetInstanceID()) ?? currentSceneGuid; + + + void getSceneDataFromComponents() + { + if (!VHierarchyData.teamModeEnabled) return; + + if (!dataComponents_byScene.ContainsKey(go.scene)) + dataComponents_byScene[go.scene] = Resources.FindObjectsOfTypeAll().FirstOrDefault(r => r.gameObject?.scene == go.scene); + + if (dataComponents_byScene[go.scene]) + sceneData = dataComponents_byScene[go.scene].sceneData; + + } + void getSceneDataFromScriptableObject() + { + if (sceneData != null) return; + + data.sceneDatas_byGuid.TryGetValue(originalSceneGuid, out sceneData); + + } + void createSceneData() + { + if (sceneData != null) return; + if (!createDataIfDoesntExist) return; + + sceneData = new SceneData(); + + data.sceneDatas_byGuid[originalSceneGuid] = sceneData; + + } + + void getSceneIdMap() + { + if (sceneData == null) return; + + cache.sceneIdMaps_bySceneGuid.TryGetValue(originalSceneGuid, out sceneIdMap); + + } + void createSceneIdMap() + { + if (sceneIdMap != null) return; + if (sceneData == null) return; + if (currentSceneGuid != originalSceneGuid) return; + + sceneIdMap = new SceneIdMap(); + + cache.sceneIdMaps_bySceneGuid[currentSceneGuid] = sceneIdMap; + + } + void updateSceneIdMapAndOriginalSceneGuids() + { + if (sceneIdMap == null) return; + if (currentSceneGuid != originalSceneGuid) return; + if (!go.scene.isLoaded) return; // can happen when setting icons via api + + + var curInstanceIdsHash = go.scene.GetRootGameObjects().FirstOrDefault()?.GetInstanceID() ?? 0; + var curGlobalIdsHash = sceneData.goDatas_byGlobalId.Keys.Aggregate(0, (hash, r) => hash ^= r.GetHashCode()); + + if (sceneIdMap.instanceIdsHash == curInstanceIdsHash && sceneIdMap.globalIdsHash == curGlobalIdsHash) return; + + + var globalIds = sceneData.goDatas_byGlobalId.Keys.ToList(); + var instanceIds = globalIds.Select(r => Application.isPlaying ? r.UnpackForPrefab() : r) + .GetObjectInstanceIds(); + void clearIdMap() + { + if (Application.isPlaying) return; // not clearing in playmode fixes data loss on first root object when it's moved to DontDestroyOnLoad (sice it causes map update) + + sceneIdMap.globalIds_byInstanceId = new SerializableDictionary(); + + } + void clearSceneGuids() + { + if (Application.isPlaying) return; // not clearing in playmode fixes data loss on first root object when it's moved to DontDestroyOnLoad (sice it causes map update) + + foreach (var instanceId in sceneIdMap.globalIds_byInstanceId.Keys) + cache.originalSceneGuids_byInstanceId.Remove(instanceId); + + } + void fillIdMap() + { + for (int i = 0; i < instanceIds.Length; i++) + if (instanceIds[i] != 0) + sceneIdMap.globalIds_byInstanceId[instanceIds[i]] = globalIds[i]; + + } + void fillSceneGuids() + { + for (int i = 0; i < instanceIds.Length; i++) + cache.originalSceneGuids_byInstanceId[instanceIds[i]] = currentSceneGuid; + + } + + + clearIdMap(); + clearSceneGuids(); + fillIdMap(); + fillSceneGuids(); + + sceneIdMap.instanceIdsHash = curInstanceIdsHash; + sceneIdMap.globalIdsHash = curGlobalIdsHash; + + } + + void getGoData() + { + if (sceneData == null) return; + if (sceneIdMap == null) return; + if (!sceneIdMap.globalIds_byInstanceId.TryGetValue(go.GetInstanceID(), out var globalId)) return; + + sceneData.goDatas_byGlobalId.TryGetValue(globalId, out goData); + + } + void moveGoDataToCurrentSceneGuid() + { + if (goData == null) return; + if (currentSceneGuid == originalSceneGuid) return; + if (Application.isPlaying) return; + + var originalSceneData = sceneData; + var currentSceneData = dataComponents_byScene.GetValueOrDefault(go.scene)?.sceneData ?? data.sceneDatas_byGuid.GetValueOrDefault(currentSceneGuid); + + if (originalSceneData == null) return; + if (currentSceneData == null) return; + + var globalId = go.GetGlobalID(); + + originalSceneData.goDatas_byGlobalId.Remove(originalSceneData.goDatas_byGlobalId.First(r => r.Value == goData).Key); + currentSceneData.goDatas_byGlobalId[go.GetGlobalID()] = goData; + + } + void createGoData() + { + if (goData != null) return; + if (!createDataIfDoesntExist) return; + + goData = new GameObjectData(); + + sceneData.goDatas_byGlobalId[go.GetGlobalID()] = goData; + + } + + + getSceneDataFromComponents(); + getSceneDataFromScriptableObject(); + createSceneData(); + + getSceneIdMap(); + createSceneIdMap(); + updateSceneIdMapAndOriginalSceneGuids(); + + getGoData(); + moveGoDataToCurrentSceneGuid(); + createGoData(); + + } + void prefabObject() + { + if (StageUtility.GetCurrentStage() is not PrefabStage prefabStage) return; + + + var prefabGuid = prefabStage.assetPath.ToGuid(); + + GlobalID sourceGlobalId; + + void calcGlobalId() + { + + var rawGlobalId = go.GetGlobalID(); + + +#if UNITY_2023_2_OR_NEWER + + var so = new SerializedObject(go); + + so.SetPropertyValue("inspectorMode", UnityEditor.InspectorMode.Debug); + + var rawFileId = so.FindProperty("m_LocalIdentfierInFile").longValue; + + if (rawFileId == 0) // happens for prefab variants in unity 6 + rawFileId = (long)t_Unsupported.InvokeMethod("GetOrGenerateFileIDHint", go); + +#else + var rawFileId = rawGlobalId.fileId; +#endif + + // fixes fileId for prefab variants + // also works for getting prefab's unpacked fileId + var fileId = ((long)rawFileId ^ (long)rawGlobalId.globalObjectId.targetPrefabId) & 0x7fffffffffffffff; + + + sourceGlobalId = new GlobalID($"GlobalObjectId_V1-1-{prefabGuid}-{fileId}-0"); + + } + + + void getSceneDataFromScriptableObject() + { + data.sceneDatas_byGuid.TryGetValue(prefabGuid, out sceneData); + } + void createSceneData() + { + if (sceneData != null) return; + if (!createDataIfDoesntExist) return; + + sceneData = new SceneData(); + + data.sceneDatas_byGuid[prefabGuid] = sceneData; + + } + + void getGoData() + { + if (sceneData == null) return; + + sceneData.goDatas_byGlobalId.TryGetValue(sourceGlobalId, out goData); + + } + void createGoData() + { + if (goData != null) return; + if (!createDataIfDoesntExist) return; + + goData = new GameObjectData(); + + sceneData.goDatas_byGlobalId[sourceGlobalId] = goData; + + } + + + calcGlobalId(); + + getSceneDataFromScriptableObject(); + createSceneData(); + + getGoData(); + createGoData(); + + } + void prefabInstance_editMode() + { + if (!PrefabUtility.IsPartOfPrefabInstance(go)) return; + if (goData != null) return; + + void tryGetForSourceGo(GameObject sourceGo) + { + var sourceGoGlobalId = sourceGo.GetGlobalID(); + var sourcePrefabGuid = sourceGoGlobalId.guid; + + cache.prefabInstanceGlobalIds_byInstanceIds[go.GetInstanceID()] = sourceGoGlobalId; + + + data.sceneDatas_byGuid.TryGetValue(sourcePrefabGuid, out sceneData); + + sceneData?.goDatas_byGlobalId.TryGetValue(sourceGoGlobalId, out goData); + + + if (goData == null) + try + { + if (PrefabUtility.GetCorrespondingObjectFromSource(sourceGo) is GameObject previousSourceGo) + tryGetForSourceGo(previousSourceGo); + + // wrapped in try-catch because GetCorrespondingObjectFromSource throws exceptions on broken prefabs + } + catch { } + + } + + tryGetForSourceGo(PrefabUtility.GetCorrespondingObjectFromSource(go)); + + } + void prefabInstance_playmode() + { + if (!Application.isPlaying) return; + if (goData != null) return; + + + if (!cache.prefabInstanceGlobalIds_byInstanceIds.TryGetValue(go.GetInstanceID(), out var globalId)) return; + + var prefabGuid = globalId.guid; + + + data.sceneDatas_byGuid.TryGetValue(prefabGuid, out sceneData); + + sceneData?.goDatas_byGlobalId.TryGetValue(globalId, out goData); + + } + + sceneObject(); + prefabObject(); + prefabInstance_editMode(); + prefabInstance_playmode(); + + if (goData != null) + goData.sceneData = sceneData; + + return goDataCache[go] = goData; + + } + + public static Dictionary goDataCache = new(); + + public static Dictionary dataComponents_byScene = new(); + + static VHierarchyCache cache => VHierarchyCache.instance; + + + + public static void OnHierarchyChanged() { goInfoCache.Clear(); } + public static void OnDataSerialization() { goInfoCache.Clear(); goDataCache.Clear(); } + + + + + + + + + + + + + + + + + + + static void LoadSceneBookmarkObjects() // update + { + if (!data) return; + + + var scenesToLoadFor = unloadedSceneBookmarks_sceneGuids.Select(r => EditorSceneManager.GetSceneByPath(r.ToPath())) + .Where(r => r.isLoaded); + if (!scenesToLoadFor.Any()) return; + + + + foreach (var scene in scenesToLoadFor) + { + var bookmarksFromThisScene = data.bookmarks.Where(r => r.globalId.guid == scene.path.ToGuid()).ToList(); + + var objectsForTheseBookmarks = bookmarksFromThisScene.Select(r => !Application.isPlaying ? r.globalId + : r.globalId.UnpackForPrefab()).GetObjects(); + + for (int i = 0; i < bookmarksFromThisScene.Count; i++) + if (objectsForTheseBookmarks[i]) + bookmarksFromThisScene[i]._go = objectsForTheseBookmarks[i] as GameObject; + else + bookmarksFromThisScene[i].failedToLoadSceneObject = true; + + } + + unloadedSceneBookmarks_sceneGuids.Clear(); + + + foreach (var window in allHierarchies) + window.Repaint(); + + } + + public static HashSet unloadedSceneBookmarks_sceneGuids = new(); + + + + + static void StashBookmarkObjects() // on playmode enter before awake + { + stashedBookmarkObjects_byBookmark.Clear(); + + foreach (var bookmark in data.bookmarks) + stashedBookmarkObjects_byBookmark[bookmark] = bookmark._go; + + } + static void UnstashBookmarkObjects() // on playmode exit + { + foreach (var bookmark in data.bookmarks) + if (stashedBookmarkObjects_byBookmark.TryGetValue(bookmark, out var stashedObject)) + if (stashedObject != null) + bookmark._go = stashedObject; + + } + + static Dictionary stashedBookmarkObjects_byBookmark = new(); + + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void OnPlaymodeEnter_beforeAwake() + { + if (!data) return; + + StashBookmarkObjects(); + + } + static void OnPlaymodeExit(PlayModeStateChange state) + { + if (state != PlayModeStateChange.EnteredEditMode) return; + if (!data) return; + + + UnstashBookmarkObjects(); + + // scene objects can get recreated in playmode if the scene was reloaded + // in this case their respective bookmarks will be updated in OnSceneLoaded_inPlaymode to reference the recreated versions + // so we ensure that after playmode bookmarks reference the same objects as they did before playmode + + + + + foreach (var bookmark in data.bookmarks) + if (bookmark.globalId.guid == "00000000000000000000000000000000") + if (bookmark._go is GameObject gameObject) + { + bookmark.globalId = new GlobalID(bookmark.globalId.ToString().Replace("00000000000000000000000000000000", gameObject.scene.path.ToGuid())); + data.Dirty(); + } + + // objects from DontDestroyOnLoad that were bookmarked in playmode have globalIds with blank scene guids + // we fix this after playmode, when scene guids become available + + } + + + + + static void RepaintOnAlt() // Update + { + var lastEvent = typeof(Event).GetFieldValue("s_Current"); + + if (lastEvent.alt != wasAlt) + if (EditorWindow.mouseOverWindow is EditorWindow hoveredWindow) + if (hoveredWindow.GetType() == t_SceneHierarchyWindow || hoveredWindow is VHierarchySceneSelectorWindow) + hoveredWindow.Repaint(); + + wasAlt = lastEvent.alt; + + } + + static bool wasAlt; + + + + + static void SetPreviousTransformTool() + { + if (!transformToolNeedsReset) return; + + Tools.current = previousTransformTool; + + transformToolNeedsReset = false; + + // E shortcut changes transform tool in 2022 + // here we undo this + + } + + static bool transformToolNeedsReset; + static Tool previousTransformTool; + + + + + static void DuplicateSceneData(string originalSceneGuid, string duplicatedSceneGuid) + { + var originalSceneData = data.sceneDatas_byGuid[originalSceneGuid]; + var duplicatedSceneData = data.sceneDatas_byGuid[duplicatedSceneGuid] = new SceneData(); + + foreach (var kvp in originalSceneData.goDatas_byGlobalId) + { + var duplicatedGlobalId = new GlobalID(kvp.Key.ToString().Replace(originalSceneGuid, duplicatedSceneGuid)); + var duplicatedGoData = new GameObjectData() { colorIndex = kvp.Value.colorIndex, iconNameOrGuid = kvp.Value.iconNameOrGuid }; + + duplicatedSceneData.goDatas_byGlobalId[duplicatedGlobalId] = duplicatedGoData; + + } + + data.Dirty(); + + } + + static void OnSceneImported(string importedScenePath) + { + if (curEvent.commandName != "Duplicate" && curEvent.commandName != "Paste") return; + + + var copiedAssets_paths = new List(); + + var assetClipboard = typeof(Editor).Assembly.GetType("UnityEditor.AssetClipboardUtility").GetMemberValue("assetClipboard").InvokeMethod("GetEnumerator"); + + while (assetClipboard.MoveNext()) + copiedAssets_paths.Add(assetClipboard.Current.GetMemberValue("guid").ToString().ToPath()); + + + + var originalScenePath = copiedAssets_paths.FirstOrDefault(r => File.Exists(r) && new FileInfo(r).Length + == new FileInfo(importedScenePath).Length); + var originalSceneGuid = originalScenePath.ToGuid(); + var duplicatedSceneGuid = importedScenePath.ToGuid(); + + if (!data.sceneDatas_byGuid.ContainsKey(originalSceneGuid)) return; + if (data.sceneDatas_byGuid.ContainsKey(duplicatedSceneGuid)) return; + + DuplicateSceneData(originalSceneGuid, duplicatedSceneGuid); + + } + + class SceneImportDetector : AssetPostprocessor + { + // scene data duplication won't work on earlier versions anyway +#if UNITY_2021_2_OR_NEWER + static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload) + { + if (!data) return; + + foreach (var r in importedAssets) + if (r.EndsWith(".unity")) + OnSceneImported(r); + + } +#endif + } + + + + + [UnityEditor.Callbacks.PostProcessBuild] + public static void ClearCacheAfterBuild(BuildTarget _, string __) => VHierarchyCache.Clear(); + + static void ClearCacheOnProjectLoaded() => VHierarchyCache.Clear(); + + + + + + + + + + + + + + + [InitializeOnLoadMethod] + static void Init() + { + if (VHierarchyMenu.pluginDisabled) return; + + void subscribe() + { + + // gui + + EditorApplication.hierarchyWindowItemOnGUI -= RowGUI; + EditorApplication.hierarchyWindowItemOnGUI = RowGUI + EditorApplication.hierarchyWindowItemOnGUI; + + + + + // wrapping updaters + + EditorApplication.projectWindowItemOnGUI -= ProjectWindowItemOnGUI; + EditorApplication.projectWindowItemOnGUI += ProjectWindowItemOnGUI; + + EditorApplication.hierarchyWindowItemOnGUI -= HierarchyWindowItemOnGUI; + EditorApplication.hierarchyWindowItemOnGUI += HierarchyWindowItemOnGUI; + + EditorApplication.delayCall -= DelayCallLoop; + EditorApplication.delayCall += DelayCallLoop; + + EditorApplication.update -= CheckIfFocusedWindowChanged; + EditorApplication.update += CheckIfFocusedWindowChanged; + + + + // shortcuts + + var globalEventHandler = typeof(EditorApplication).GetFieldValue("globalEventHandler"); + typeof(EditorApplication).SetFieldValue("globalEventHandler", Shortcuts + (globalEventHandler - Shortcuts)); + + + + + // loading bookmarked objects + + EditorApplication.update -= LoadSceneBookmarkObjects; + EditorApplication.update += LoadSceneBookmarkObjects; + + + + // other + + EditorApplication.update -= RepaintOnAlt; + EditorApplication.update += RepaintOnAlt; + + EditorApplication.update -= SetPreviousTransformTool; + EditorApplication.update += SetPreviousTransformTool; + + EditorApplication.hierarchyChanged -= OnHierarchyChanged; + EditorApplication.hierarchyChanged += OnHierarchyChanged; + + var projectWasLoaded = typeof(EditorApplication).GetFieldValue("projectWasLoaded"); + typeof(EditorApplication).SetFieldValue("projectWasLoaded", (projectWasLoaded - ClearCacheOnProjectLoaded) + ClearCacheOnProjectLoaded); + + } + void loadData() + { + data = AssetDatabase.LoadAssetAtPath(ProjectPrefs.GetString("vHierarchy-lastKnownDataPath")); + + + if (data) return; + + data = AssetDatabase.FindAssets("t:VHierarchyData").Select(guid => AssetDatabase.LoadAssetAtPath(guid.ToPath())).FirstOrDefault(); + + + if (!data) return; + + ProjectPrefs.SetString("vHierarchy-lastKnownDataPath", data.GetPath()); + + } + void loadPalette() + { + palette = AssetDatabase.LoadAssetAtPath(ProjectPrefs.GetString("vHierarchy-lastKnownPalettePath")); + + + if (palette) return; + + palette = AssetDatabase.FindAssets("t:VHierarchyPalette").Select(guid => AssetDatabase.LoadAssetAtPath(guid.ToPath())).FirstOrDefault(); + + + if (!palette) return; + + ProjectPrefs.SetString("vHierarchy-lastKnownPalettePath", palette.GetPath()); + + } + void loadDataAndPaletteDelayed() + { + if (!data) + EditorApplication.delayCall += () => EditorApplication.delayCall += loadData; + + if (!palette) + EditorApplication.delayCall += () => EditorApplication.delayCall += loadPalette; + + // AssetDatabase isn't up to date at this point (it gets updated after InitializeOnLoadMethod) + // and if current AssetDatabase state doesn't contain the data - it won't be loaded during Init() + // so here we schedule an additional, delayed attempt to load the data + // this addresses reports of data loss when trying to load it on a new machine + + } + void migrateDataFromV1() + { + if (!data) return; + if (ProjectPrefs.GetBool("vHierarchy-dataMigrationFromV1Attempted", false)) return; + + ProjectPrefs.SetBool("vHierarchy-dataMigrationFromV1Attempted", true); + + var lines = System.IO.File.ReadAllLines(data.GetPath()); + + if (lines.Length < 15 || !lines[14].Contains("sceneDatasByGuid")) return; + + var sceneGuids = new List(); + var globalIdLists = new List>(); + var goDatasByInstanceIdCounts = new List(); + var sceneDatas = new List(); + + + void parseSceneGuids() + { + for (int i = 16; i < lines.Length; i++) + { + if (lines[i].Contains("values:")) break; + + var startIndex = lines[i].IndexOf("- ") + 2; + + if (startIndex < lines[i].Length) + sceneGuids.Add(lines[i].Substring(startIndex)); + else + sceneGuids.Add(""); + + } + + } + void parseGlobalIdLists_andCountGoDatasByInstanceId() + { + var parsingGlobalIdList = false; + var parsingGlobalIdListAtIndex = -1; + + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i]; + + void startParsing() + { + if (!line.Contains("goDatasByGlobalId")) return; + + parsingGlobalIdList = true; + parsingGlobalIdListAtIndex++; + + globalIdLists.Add(new List()); + + } + void parse() + { + if (!parsingGlobalIdList) return; + if (!line.Contains("- GlobalObjectId")) return; + + var startIndex = line.IndexOf("- ") + 2; + + if (startIndex < line.Length) + globalIdLists[parsingGlobalIdListAtIndex].Add(line.Substring(startIndex)); + else + globalIdLists[parsingGlobalIdListAtIndex].Add(""); + + } + void stopParsing_andCountDatasByInstanceId() + { + if (!line.Contains("goDatasByInstanceId")) return; + + + parsingGlobalIdList = false; + + + var goDatasByInstanceId_keysLine = lines[i + 1]; + var goDatasByInstanceId_count = (goDatasByInstanceId_keysLine.Length - 14) / 8; + + goDatasByInstanceIdCounts.Add(goDatasByInstanceId_count); + + + } + + startParsing(); + parse(); + stopParsing_andCountDatasByInstanceId(); + + } + + } + void parseSceneDatas() + { + var firstLineIndexOfFirstSceneData = 17 + sceneGuids.Count; + + + + void parseSceneData(int sceneDataIndex) + { + var sceneData = new SceneData(); + + var globalIds = globalIdLists[sceneDataIndex]; + var firstLineIndex = getFirstLineIndex(sceneDataIndex); + + + + void parseGoData(int iGoData) + { + var goData = new GameObjectData(); + + + var colorLine = lines[getColorLineIndex(iGoData)]; + + if (colorLine.Length > 18) + goData.colorIndex = int.Parse(colorLine.Substring(18)); + + + var iconLine = lines[getIconLineIndex(iGoData)]; + + if (iconLine.Length > 16) + goData.iconNameOrGuid = iconLine.Substring(16); + + + + var globalIdString = globalIdLists[sceneDataIndex][iGoData]; + + var globalId = new GlobalID(globalIdString); + + + + sceneData.goDatas_byGlobalId[globalId] = goData; + // sceneData.goDatas_byGlobalId.Add(globalId, goData); + + } + + int getColorLineIndex(int goDataIndex) + { + var index = firstLineIndex; // - goDatasByGlobalId: + + index += 1; // keys: + index += globalIds.Count; + + index += 1; // values: + index += 1; // zeroth godata + index += goDataIndex * 2; + + return index; + + } + int getIconLineIndex(int goDataIndex) => getColorLineIndex(goDataIndex) + 1; + + + + for (int i = 0; i < globalIds.Count; i++) + parseGoData(i); + + sceneDatas.Add(sceneData); + + } + + int getSceneDataLength(int sceneDataIndex) + { + int length = 0; + + length += 1; // - goDatasByGlobalId: + + length += 1; // - keys: + length += globalIdLists[sceneDataIndex].Count; + + length += 1; // - values: + length += globalIdLists[sceneDataIndex].Count * 2; + + + + length += 1; // - goDatasByInstanceId: + + length += 1; // - keys: 123123123 + + length += 1; // - values: + length += goDatasByInstanceIdCounts[sceneDataIndex] * 2; + + + return length; + + } + int getFirstLineIndex(int sceneDataIndex) + { + var index = firstLineIndexOfFirstSceneData; + + for (int i = 0; i < sceneDataIndex; i++) + index += getSceneDataLength(i); + + return index; + + } + + + + for (int i = 0; i < sceneGuids.Count; i++) + parseSceneData(i); + + } + + void remapColorIndexes() + { + foreach (var sceneData in sceneDatas) + foreach (var goData in sceneData.goDatas_byGlobalId.Values) + if (goData.colorIndex == 7) + goData.colorIndex = 1; + else if (goData.colorIndex == 8) + goData.colorIndex = 2; + else if (goData.colorIndex >= 2) + goData.colorIndex += 2; + + } + void setSceneDatasToData() + { + for (int i = 0; i < sceneDatas.Count; i++) + data.sceneDatas_byGuid[sceneGuids[i]] = sceneDatas[i]; + + data.Dirty(); + data.Save(); + + } + + + try + { + parseSceneGuids(); + parseGlobalIdLists_andCountGoDatasByInstanceId(); + parseSceneDatas(); + + remapColorIndexes(); + setSceneDatasToData(); + + } + catch { } + + } + void removeDeletedBookmarks() + { + if (!data) return; + + + var toRemove = data.bookmarks.Where(r => r.isDeleted); + + if (!toRemove.Any()) return; + + + foreach (var r in toRemove.ToList()) + data.bookmarks.Remove(r); + + data.Dirty(); + + + // delayed to give bookmarks a chance to load in update + + } + + + subscribe(); + loadData(); + loadPalette(); + loadDataAndPaletteDelayed(); + migrateDataFromV1(); + + EditorApplication.delayCall += () => removeDeletedBookmarks(); + + OnDomainReloaded(); + + } + + public static VHierarchyData data; + public static VHierarchyPalette palette; + + + + + + static IEnumerable allHierarchies => _allHierarchies ??= t_SceneHierarchyWindow.GetFieldValue("s_SceneHierarchyWindows").Cast(); + static IEnumerable _allHierarchies; + + static Type t_SceneHierarchyWindow = typeof(Editor).Assembly.GetType("UnityEditor.SceneHierarchyWindow"); + static Type t_HostView = typeof(Editor).Assembly.GetType("UnityEditor.HostView"); + static Type t_EditorWindowDelegate = t_HostView.GetNestedType("EditorWindowDelegate", maxBindingFlags); + static Type t_Unsupported = typeof(Editor).Assembly.GetType("UnityEditor.Unsupported"); + + static Type t_VTabs = Type.GetType("VTabs.VTabs") ?? Type.GetType("VTabs.VTabs, VTabs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + static Type t_VFavorites = Type.GetType("VFavorites.VFavorites") ?? Type.GetType("VFavorites.VFavorites, VFavorites, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + + static MethodInfo mi_WrappedGUI = typeof(VHierarchy).GetMethod(nameof(WrappedGUI), maxBindingFlags); + + + + + + public const string version = "2.1.1"; + + } + + #region Rules + + public class RuleAttribute : System.Attribute { } + + public class ObjectInfo + { + public int color = 0; + public string icon = ""; + + + public ObjectInfo(GameObject gameObject) => this.gameObject = gameObject; + + public GameObject gameObject; + + + } + + + + #endregion + +} +#endif diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.cs.meta new file mode 100644 index 0000000..1831a9a --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchy.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67e949be20d3641adbc9494ed5bd764e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyCache.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyCache.cs new file mode 100644 index 0000000..2e27db0 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyCache.cs @@ -0,0 +1,61 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VHierarchy.VHierarchyData; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VHierarchy +{ + [FilePath("Library/vHierarchy Cache.asset", FilePathAttribute.Location.ProjectFolder)] + public class VHierarchyCache : ScriptableSingleton + { + // used for finding SceneData and SceneIdMap for objects that were moved out of their original scene + public SerializableDictionary originalSceneGuids_byInstanceId = new(); + + // used as cache for converting GlobalID to InstanceID and as a way to find GameObjectData for prefabs in playmode (when prefabs produce invalid GlobalIDs) + public SerializableDictionary sceneIdMaps_bySceneGuid = new(); + + // used for fetching icons set inside prefab instances in playmode (when prefabs produce invalid GlobalIDs) + public SerializableDictionary prefabInstanceGlobalIds_byInstanceIds = new SerializableDictionary(); + + + + [System.Serializable] + public class SceneIdMap + { + public SerializableDictionary globalIds_byInstanceId = new(); + + public int instanceIdsHash; + public int globalIdsHash; + + } + + + + + + + + public static void Clear() + { + instance.originalSceneGuids_byInstanceId.Clear(); + instance.sceneIdMaps_bySceneGuid.Clear(); + + } + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyCache.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyCache.cs.meta new file mode 100644 index 0000000..b6d9512 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyCache.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3fd3b966dd497472d86df0d7c9271088 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentEditor.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentEditor.cs new file mode 100644 index 0000000..8fb6b18 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentEditor.cs @@ -0,0 +1,6 @@ + + +// this file was present in a previus version and is supposed to be deleted now +// but asset store update delivery system doesn't allow deleting files +// so instead this file is now emptied +// feel free to delete it if you want diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentEditor.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentEditor.cs.meta new file mode 100644 index 0000000..18dd4d0 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d51d8117d96b64eaa9a83667bf4297d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentWindow.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentWindow.cs new file mode 100644 index 0000000..b84d74a --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentWindow.cs @@ -0,0 +1,599 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VHierarchy.VHierarchyData; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VHierarchy +{ + public class VHierarchyComponentWindow : EditorWindow + { + + void OnGUI() + { + if (!component) component = EditorUtility.InstanceIDToObject(componentIid) as Component; + if (!component) { Close(); return; } + + if (!editor) Init(component); + + + void background() + { + position.SetPos(0, 0).Draw(GUIColors.windowBackground); + } + void header() + { + var headerRect = ExpandWidthLabelRect(18).Resize(-1).AddWidthFromMid(6); + var pinButtonRect = headerRect.SetWidthFromRight(17).SetHeightFromMid(17).Move(-21, .5f); + var closeButtonRect = headerRect.SetWidthFromRight(16).SetHeightFromMid(16).Move(-3, .5f); + + var backgroundColor = isDarkTheme ? Greyscale(.25f) : GUIColors.windowBackground; + + void startDragging() + { + if (isResizingVertically) return; + if (isResizingHorizontally) return; + if (isDragged) return; + if (!curEvent.isMouseDrag) return; + if (!headerRect.IsHovered()) return; + + + isDragged = true; + + dragStartMousePos = EditorGUIUtility.GUIToScreenPoint(curEvent.mousePosition); + dragStartWindowPos = position.position; + + + isPinned = true; + + if (floatingInstance == this) + floatingInstance = null; + + EditorApplication.RepaintHierarchyWindow(); + + + } + void updateDragging() + { + if (!isDragged) return; + + + var draggedPosition = dragStartWindowPos + EditorGUIUtility.GUIToScreenPoint(curEvent.mousePosition) - dragStartMousePos; + + if (!curEvent.isRepaint) + position = position.SetPos(draggedPosition); + + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + } + void stopDragging() + { + if (!isDragged) return; + if (!curEvent.isMouseUp) return; + + + isDragged = false; + + EditorGUIUtility.hotControl = 0; + + } + + void background() + { + headerRect.Draw(backgroundColor); + + headerRect.SetHeightFromBottom(1).Draw(isDarkTheme ? Greyscale(.2f) : Greyscale(.7f)); + + } + void icon() + { + var iconRect = headerRect.SetWidth(20).MoveX(14).MoveY(-1); + + GUI.Label(iconRect, VHierarchy.GetComponentIcon(component)); + + } + void toggle() + { + var toggleRect = headerRect.MoveX(36).SetSize(20, 20); + + + var pi_enabled = component.GetType().GetProperty("enabled") ?? + component.GetType().BaseType?.GetProperty("enabled") ?? + component.GetType().BaseType?.BaseType?.GetProperty("enabled") ?? + component.GetType().BaseType?.BaseType?.BaseType?.GetProperty("enabled"); + + + if (pi_enabled == null) return; + + var enabled = (bool)pi_enabled.GetValue(component); + + + if (GUI.Toggle(toggleRect, enabled, "") == enabled) return; + + component.RecordUndo(); + pi_enabled.SetValue(component, !enabled); + + } + void name() + { + var nameRect = headerRect.MoveX(54).MoveY(-1); + + var s = new GUIContent(EditorGUIUtility.ObjectContent(component, component.GetType())).text; + s = s.Substring(s.LastIndexOf('(') + 1); + s = s.Substring(0, s.Length - 1); + + if (isPinned) + s += " of " + component.gameObject.name; + + + SetLabelBold(); + + GUI.Label(nameRect, s); + + ResetLabelStyle(); + + } + void nameCurtain() + { + var flatColorRect = headerRect.SetX(pinButtonRect.x + 3).SetXMax(headerRect.xMax); + var gradientRect = headerRect.SetXMax(flatColorRect.x).SetWidthFromRight(30); + + flatColorRect.Draw(backgroundColor); + gradientRect.DrawCurtainLeft(backgroundColor); + + } + void pinButton() + { + if (!isPinned && closeButtonRect.IsHovered()) return; + + + var normalColor = isDarkTheme ? Greyscale(.65f) : Greyscale(.8f); + var hoveredColor = isDarkTheme ? Greyscale(.9f) : normalColor; + var activeColor = Color.white; + + + + SetGUIColor(isPinned ? activeColor : pinButtonRect.IsHovered() ? hoveredColor : normalColor); + + GUI.Label(pinButtonRect, EditorGUIUtility.IconContent("pinned")); + + ResetGUIColor(); + + + SetGUIColor(Color.clear); + + var clicked = GUI.Button(pinButtonRect, ""); + + ResetGUIColor(); + + + if (!clicked) return; + + isPinned = !isPinned; + + if (isPinned && floatingInstance == this) + floatingInstance = null; + + if (!isPinned && !floatingInstance) + floatingInstance = this; + + EditorApplication.RepaintHierarchyWindow(); + + + } + void closeButton() + { + + SetGUIColor(Color.clear); + + if (GUI.Button(closeButtonRect, "")) + Close(); + + ResetGUIColor(); + + + var normalColor = isDarkTheme ? Greyscale(.65f) : Greyscale(.35f); + var hoveredColor = isDarkTheme ? Greyscale(.9f) : normalColor; + + + SetGUIColor(closeButtonRect.IsHovered() ? hoveredColor : normalColor); + + GUI.Label(closeButtonRect, EditorGUIUtility.IconContent("CrossIcon")); + + ResetGUIColor(); + + + if (isPinned) return; + + var escRect = closeButtonRect.Move(-22, -1).SetWidth(70); + + SetGUIEnabled(false); + + if (closeButtonRect.IsHovered()) + GUI.Label(escRect, "Esc"); + + ResetGUIEnabled(); + + } + void rightClick() + { + if (!curEvent.isMouseDown) return; + if (curEvent.mouseButton != 1) return; + if (!headerRect.IsHovered()) return; + + typeof(EditorUtility).InvokeMethod("DisplayObjectContextMenu", Rect.zero.SetPos(curEvent.mousePosition), component, 0); + + } + + startDragging(); + updateDragging(); + stopDragging(); + + background(); + icon(); + toggle(); + name(); + nameCurtain(); + pinButton(); + closeButton(); + rightClick(); + + } + void body() + { + EditorGUIUtility.labelWidth = (this.position.width * .4f).Max(120); + + + scrollPosition = EditorGUILayout.BeginScrollView(Vector2.up * scrollPosition).y; + BeginIndent(17); + + + editor?.OnInspectorGUI(); + + updateHeight(); + + + EndIndent(1); + EditorGUILayout.EndScrollView(); + + + EditorGUIUtility.labelWidth = 0; + + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + + void updateHeight() + { + + ExpandWidthLabelRect(height: -5); + + if (!curEvent.isRepaint) return; + if (isResizingVertically) return; + + + targetHeight = lastRect.y + 30; + + position = position.SetHeight(targetHeight.Min(maxHeight)); + + + prevHeight = position.height; + + } + void updatePosition() + { + if (!curEvent.isLayout) return; + + void calcDeltaTime() + { + deltaTime = (float)(EditorApplication.timeSinceStartup - lastLayoutTime); + + if (deltaTime > .05f) + deltaTime = .0166f; + + lastLayoutTime = EditorApplication.timeSinceStartup; + + } + void resetCurPos() + { + if (currentPosition != default && !isPinned) return; + + currentPosition = position.position; // position.position is always int, which can't be used for lerping + + } + void lerpCurPos() + { + if (isPinned) return; + + var speed = 9; + + MathUtil.SmoothDamp(ref currentPosition, targetPosition, speed, ref positionDeriv, deltaTime); + // MathfUtils.Lerp(ref currentPosition, targetPosition, speed, deltaTime); + + } + void setCurPos() + { + if (isPinned) return; + + position = position.SetPos(currentPosition); + + } + + calcDeltaTime(); + resetCurPos(); + lerpCurPos(); + setCurPos(); + + } + void closeOnEscape() + { + if (isPinned) return; + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + Close(); + } + + void horizontalResize() + { + var showingScrollbar = targetHeight > maxHeight; + + var resizeArea = this.position.SetPos(0, 0).SetWidthFromRight(showingScrollbar ? 3 : 5).AddHeightFromBottom(-20); + + void startResize() + { + if (isDragged) return; + if (isResizingHorizontally) return; + if (!curEvent.isMouseDown && !curEvent.isMouseDrag) return; + if (!resizeArea.IsHovered()) return; + + isResizingHorizontally = true; + + resizeStartMousePos = curEvent.mousePosition_screenSpace; + resizeStartWindowSize = this.position.size; + + } + void updateResize() + { + if (!isResizingHorizontally) return; + + + var resizedWidth = resizeStartWindowSize.x + curEvent.mousePosition_screenSpace.x - resizeStartMousePos.x; + + var width = resizedWidth.Max(300); + + if (!curEvent.isRepaint) + position = position.SetWidth(width); + + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + // GUI.focused + + } + void stopResize() + { + if (!isResizingHorizontally) return; + if (!curEvent.isMouseUp) return; + + isResizingHorizontally = false; + + EditorGUIUtility.hotControl = 0; + + } + + + EditorGUIUtility.AddCursorRect(resizeArea, MouseCursor.ResizeHorizontal); + + startResize(); + updateResize(); + stopResize(); + + } + void verticalResize() + { + var resizeArea = this.position.SetPos(0, 0).SetHeightFromBottom(5); + + void startResize() + { + if (isDragged) return; + if (isResizingVertically) return; + if (!curEvent.isMouseDown && !curEvent.isMouseDrag) return; + if (!resizeArea.IsHovered()) return; + + isResizingVertically = true; + + resizeStartMousePos = curEvent.mousePosition_screenSpace; + resizeStartWindowSize = this.position.size; + + } + void updateResize() + { + if (!isResizingVertically) return; + + + var resizedHeight = resizeStartWindowSize.y + curEvent.mousePosition_screenSpace.y - resizeStartMousePos.y; + + var height = resizedHeight.Min(targetHeight).Max(50); + + if (!curEvent.isRepaint) + position = position.SetHeight(height); + + maxHeight = height; + + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + // GUI.focused + + } + void stopResize() + { + if (!isResizingVertically) return; + if (!curEvent.isMouseUp) return; + + isResizingVertically = false; + + EditorGUIUtility.hotControl = 0; + + } + + + EditorGUIUtility.AddCursorRect(resizeArea, MouseCursor.ResizeVertical); + + startResize(); + updateResize(); + stopResize(); + + } + + + background(); + + horizontalResize(); + verticalResize(); + + + header(); + + Space(3); + body(); + outline(); + + Space(7); + + + updatePosition(); + closeOnEscape(); + + + if (!isPinned) + Repaint(); + + EditorApplication.delayCall -= Repaint; + EditorApplication.delayCall += Repaint; + + } + + public Vector2 targetPosition; + public Vector2 currentPosition; + Vector2 positionDeriv; + float deltaTime; + double lastLayoutTime; + + bool isDragged; + Vector2 dragStartMousePos; + Vector2 dragStartWindowPos; + + public bool isResizingHorizontally; + public bool isResizingVertically; + public Vector2 resizeStartMousePos; + public Vector2 resizeStartWindowSize; + + public float scrollPosition; + + public float targetHeight; + public float maxHeight; + public float prevHeight; + + + + + + void OnLostFocus() + { + if (isPinned) return; + + if (curEvent.holdingAlt && EditorWindow.focusedWindow.GetType().Name == "SceneHierarchyWindow") + CloseNextFrameIfNotRefocused(); + else + Close(); + + } + + void CloseNextFrameIfNotRefocused() + { + EditorApplication.delayCall += () => { if (EditorWindow.focusedWindow != this) Close(); }; + } + + public bool isPinned; + + + + + + public void Init(Component component) + { + if (editor) + editor.DestroyImmediate(); + + this.component = component; + this.componentIid = component.GetInstanceID(); + this.editor = Editor.CreateEditor(component); + + } + + void OnDestroy() + { + editor?.DestroyImmediate(); + + editor = null; + component = null; + + EditorPrefs.SetFloat("vHierarchy-componentWindowWidth", position.width); + + } + + public Component component; + public Editor editor; + + public int componentIid; + + + + + + public static void CreateFloatingInstance(Vector2 position) + { + floatingInstance = ScriptableObject.CreateInstance(); + + floatingInstance.ShowPopup(); + + + floatingInstance.maxHeight = EditorGUIUtility.GetMainWindowPosition().height * .7f; + + + var savedWidth = EditorPrefs.GetFloat("vHierarchy-componentWindowWidth", minWidth); + + var width = savedWidth.Max(minWidth); + + floatingInstance.position = Rect.zero.SetPos(position).SetWidth(width).SetHeight(200); + floatingInstance.prevHeight = floatingInstance.position.height; + + floatingInstance.targetPosition = position; + + } + + public static VHierarchyComponentWindow floatingInstance; + + public static float minWidth => 300; + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentWindow.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentWindow.cs.meta new file mode 100644 index 0000000..741ab1d --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyComponentWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4b48d49a631ab443990f28938cbdedb8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyController.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyController.cs new file mode 100644 index 0000000..ffc54b9 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyController.cs @@ -0,0 +1,511 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; +using static VHierarchy.VHierarchy; +using static VHierarchy.VHierarchyData; +using static VHierarchy.VHierarchyCache; + + + +namespace VHierarchy +{ + public class VHierarchyController + { + + public void UpdateExpandQueue() + { + if (treeViewAnimatesExpansion) return; + + if (!expandQueue_toAnimate.Any()) + { + if (!expandQueue_toCollapseAfterAnimation.Any()) return; + + foreach (var r in expandQueue_toCollapseAfterAnimation) + SetExpanded_withoutAnimation(r, false); + + expandQueue_toCollapseAfterAnimation.Clear(); + + return; + + } + + + var id = expandQueue_toAnimate.First().id; + var expand = expandQueue_toAnimate.First().expand; + + + + var itemIndex = treeViewControllerData.InvokeMethod("GetRow", id); + var items = treeViewControllerData.GetMemberValue>("m_Rows"); + + var stuckCollapsing = itemIndex != -1 && items[itemIndex].id != id; // happens when collapsing long hierarchies due to a bug in TreeViewController + + if (stuckCollapsing) { window.SendEvent(new Event() { type = EventType.KeyDown, keyCode = KeyCode.None }); return; } + + + + + if (expandedIds.Contains(id) != expand) + SetExpanded_withAnimation(id, expand); + + expandQueue_toAnimate.RemoveAt(0); + + + window.Repaint(); + + + // must be called from gui because reflected methods rely on Event.current + + } + + public List expandQueue_toAnimate = new(); + public List expandQueue_toCollapseAfterAnimation = new(); + + public struct ExpandQueueEntry { public int id; public bool expand; } + + public bool animatingExpansion => expandQueue_toAnimate.Any() || expandQueue_toCollapseAfterAnimation.Any(); + + + + + + + public void UpdateScrollAnimation() + { + if (!animatingScroll) return; + + + var lerpSpeed = 10; + + var lerpedScrollPos = MathUtil.SmoothDamp(currentScrollPos, targetScrollPos, lerpSpeed, ref scrollPosDerivative, editorDeltaTime); + + SetScrollPos(lerpedScrollPos); + + window.Repaint(); + + + + if (lerpedScrollPos.DistanceTo(targetScrollPos) > .4f) return; + + SetScrollPos(targetScrollPos); + + animatingScroll = false; + + + } + + public float targetScrollPos; + public float scrollPosDerivative; + + public bool animatingScroll; + + + + + + + + public void UpdateHighlightAnimation() + { + if (!animatingHighlight) return; + + + var lerpSpeed = 1.3f; + + MathUtil.SmoothDamp(ref highlightAmount, 0, lerpSpeed, ref highlightDerivative, editorDeltaTime); + + window.Repaint(); + + + + if (highlightAmount > .05f) return; + + highlightAmount = 0; + + animatingHighlight = false; + + + } + + public float highlightAmount; + public float highlightDerivative; + + public bool animatingHighlight; + + public GameObject objectToHighlight; + + + + + + + + public void UpdateState() + { + var sceneHierarchy = window?.GetFieldValue("m_SceneHierarchy"); + + treeViewController = sceneHierarchy.GetFieldValue("m_TreeView"); + treeViewControllerData = treeViewController.GetMemberValue("data"); + + + + var treeViewControllerState = treeViewController?.GetPropertyValue("state"); + + currentScrollPos = treeViewControllerState?.scrollPos.y ?? 0; + + expandedIds = treeViewControllerState?.expandedIDs ?? new List(); + + + + var treeViewAnimator = treeViewController?.GetMemberValue("m_ExpansionAnimator"); + var treeViewAnimatorSetup = treeViewAnimator?.GetMemberValue("m_Setup"); + + treeViewAnimatesScroll = treeViewController?.GetMemberValue("m_FramingAnimFloat").isAnimating ?? false; + + treeViewAnimatesExpansion = treeViewAnimator?.GetMemberValue("isAnimating") ?? false; + + } + + object treeViewController; + object treeViewControllerData; + + public float currentScrollPos; + + public List expandedIds = new(); + + public bool treeViewAnimatesScroll; + public bool treeViewAnimatesExpansion; + + public int GetRowIndex(int instanceId) + { + return treeViewControllerData.InvokeMethod("GetRow", instanceId); + } + + + + + + + + + + + + + + + + + public void ToggleExpanded(int id) + { + SetExpanded_withAnimation(id, !expandedIds.Contains(id)); + + window.Repaint(); + + } + + public void CollapseAll() + { + var expandedRoots = new List(); + var expandedChildren = new List(); + + foreach (var iid in expandedIds) + if (EditorUtility.InstanceIDToObject(iid) is GameObject expandedGo) + if (expandedGo.transform.parent) + expandedChildren.Add(expandedGo); + else + expandedRoots.Add(expandedGo); + + + + expandQueue_toCollapseAfterAnimation = expandedChildren.Select(r => r.GetInstanceID()).ToList(); + + expandQueue_toAnimate = expandedRoots.Select(r => new ExpandQueueEntry { id = r.GetInstanceID(), expand = false }) + .OrderBy(r => GetRowIndex(r.id)).ToList(); + + StartScrollAnimation(targetScrollPos: 0); + + + window.Repaint(); + + } + + public void Isolate(int targetId) + { + + List getParents(int id) + { + var parentIds = new List(); + + if (EditorUtility.InstanceIDToObject(id) is not GameObject go) return parentIds; + + + while (go.transform.parent) + parentIds.Add((go = go.transform.parent.gameObject).GetInstanceID()); + + parentIds.Add(go.scene.handle); + + + return parentIds; + + } + + var targetItemParents = getParents(targetId); + + + + var itemsToCollapse = expandedIds.ToList(); + + itemsToCollapse.Remove(targetId); + itemsToCollapse.RemoveAll(r => targetItemParents.Contains(r)); + itemsToCollapse.RemoveAll(r => itemsToCollapse.Intersect(getParents(r)).Any()); + + if (EditorUtility.InstanceIDToObject(targetId) is GameObject) + itemsToCollapse.RemoveAll(r => EditorUtility.InstanceIDToObject(r) is not GameObject); // won't collapse scenes + + + + + expandQueue_toAnimate = itemsToCollapse.Select(id => new ExpandQueueEntry { id = id, expand = false }) + .Append(new ExpandQueueEntry { id = targetId, expand = true }) + .OrderBy(r => GetRowIndex(r.id)).ToList(); + + + window.Repaint(); + + } + + + + + + public void StartExpandAnimation(List targetExpandedIds) + { + + var toExpand = targetExpandedIds.Except(expandedIds).ToHashSet(); + var toCollapse = expandedIds.Except(targetExpandedIds).ToHashSet(); + + + + + // hanlde destroyed objects + + var sceneIds = Enumerable.Range(0, EditorSceneManager.sceneCount).Select(i => EditorSceneManager.GetSceneAt(i).handle).ToHashSet(); + + var toExpand_destroyed = toExpand.Where(id => !sceneIds.Contains(id) && Resources.InstanceIDToObject(id) as GameObject == null).ToHashSet(); + var toCollapse_destroyed = toCollapse.Where(id => !sceneIds.Contains(id) && Resources.InstanceIDToObject(id) as GameObject == null).ToHashSet(); + + + foreach (var id in toExpand_destroyed) + expandedIds.Add(id); + + foreach (var id in toCollapse_destroyed) + expandedIds.Remove(id); + + + toExpand.ExceptWith(toExpand_destroyed); + toCollapse.ExceptWith(toCollapse_destroyed); + + + + + + + // hanlde non-animated expansions/collapses + + bool hasParentToCollapse(int id) + { + var go = Resources.InstanceIDToObject(id) as GameObject; + + if (!go) return false; + if (!go.transform.parent) return false; + + + var parentId = go.transform.parent.gameObject.GetInstanceID(); + + return toCollapse.Contains(parentId) + || hasParentToCollapse(parentId); + + } + bool areAllParentsExpanded(int id) + { + var go = Resources.InstanceIDToObject(id) as GameObject; + + if (!go) return true; + if (!go.transform.parent) return true; + + + var parentId = go.transform.parent.gameObject.GetInstanceID(); + + return expandedIds.Contains(parentId) + && areAllParentsExpanded(parentId); + + } + + var toExpand_beforeAnimation = toExpand.Where(id => !areAllParentsExpanded(id)).ToHashSet(); + var toCollapse_afterAnimation = toCollapse.Where(id => hasParentToCollapse(id)).ToHashSet(); + + + foreach (var id in toExpand_beforeAnimation) + SetExpanded_withoutAnimation(id, true); + + foreach (var id in toCollapse_afterAnimation) + expandQueue_toCollapseAfterAnimation.Add(id); + + + toExpand.ExceptWith(toExpand_beforeAnimation); + toCollapse.ExceptWith(toCollapse_afterAnimation); + + + + + + + // setup animation + + expandQueue_toAnimate = toCollapse.Select(id => new ExpandQueueEntry { id = id, expand = false }) + .Concat(toExpand.Select(id => new ExpandQueueEntry { id = id, expand = true })) + .OrderBy(r => GetRowIndex(r.id)).ToList(); + + } + + public void SetExpandedIds(List targetExpandedIds) + { + treeViewControllerData.InvokeMethod("SetExpandedIDs", targetExpandedIds.ToArray()); + } + public void SetExpanded_withAnimation(int instanceId, bool expanded) + { + treeViewController.InvokeMethod("ChangeFoldingForSingleItem", instanceId, expanded); + } + public void SetExpanded_withoutAnimation(int instanceId, bool expanded) + { + treeViewControllerData.InvokeMethod("SetExpanded", instanceId, expanded); + } + + + + public void StartScrollAnimation(float targetScrollPos) + { + if (targetScrollPos.DistanceTo(currentScrollPos) < .05f) return; + + this.targetScrollPos = targetScrollPos; + + animatingScroll = true; + + } + + public void SetScrollPos(float targetScrollPos) + { + window.GetMemberValue("m_SceneHierarchy").GetMemberValue("m_TreeViewState").scrollPos = Vector2.up * targetScrollPos; + } + + + + public void RevealObject(GameObject go, bool expand, bool highlight, bool snapToTopMargin) + { + + var idsToExpand = new List(); + + if (expand && go.transform.childCount > 0) + idsToExpand.Add(go.GetInstanceID()); + + var cur = go.transform; + while (cur = cur.parent) + idsToExpand.Add(cur.gameObject.GetInstanceID()); + + idsToExpand.Add(go.scene.handle); + + idsToExpand.RemoveAll(r => expandedIds.Contains(r)); + + + + + foreach (var id in idsToExpand.SkipLast(1)) + SetExpanded_withoutAnimation(id, true); + + if (idsToExpand.Any()) + SetExpanded_withAnimation(idsToExpand.Last(), true); + + + + + var rowCount = treeViewControllerData.GetMemberValue("m_Rows").Count; + var maxScrollPos = rowCount * 16 - window.position.height + 26.9f; + + var rowIndex = treeViewControllerData.InvokeMethod("GetRow", go.GetInstanceID()); + var rowPos = rowIndex * 16f + 8; + + var scrollAreaHeight = window.GetMemberValue("treeViewRect").height; + + + + + var margin = 48; + + var targetScrollPos = 0f; + + if (expand) + targetScrollPos = (rowPos - margin).Min(maxScrollPos) + .Max(0); + else + targetScrollPos = currentScrollPos.Min(rowPos - margin) + .Max(rowPos - scrollAreaHeight + margin) + .Min(maxScrollPos) + .Max(0); + if (targetScrollPos < 25) + targetScrollPos = 0; + + + + StartScrollAnimation(targetScrollPos); + + + + + if (!highlight) return; + + highlightAmount = 2.2f; + + animatingHighlight = true; + + objectToHighlight = go; + + + } + + + + + + + + + + + + + + + public VHierarchyController(EditorWindow window) => this.window = window; + + public EditorWindow window; + + public VHierarchyGUI gui => VHierarchy.guis_byWindow[window]; + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyController.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyController.cs.meta new file mode 100644 index 0000000..3983a7d --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyController.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3513d7b44a8b94c8d800e841067bf035 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyData.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyData.cs new file mode 100644 index 0000000..75be7b6 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyData.cs @@ -0,0 +1,227 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VHierarchy.VHierarchyCache; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VHierarchy +{ + public class VHierarchyData : ScriptableObject, ISerializationCallbackReceiver + { + + public SerializableDictionary sceneDatas_byGuid = new(); + + + [System.Serializable] + public class SceneData + { + public SerializableDictionary goDatas_byGlobalId = new(); + } + + + [System.Serializable] + public class GameObjectData + { + public int colorIndex; + public string iconNameOrGuid = ""; + + public bool isIconRecursive; + public bool isColorRecursive; + + + [System.NonSerialized] + public SceneData sceneData; // set in GetGameObjectData + + } + + public void OnBeforeSerialize() => VHierarchy.OnDataSerialization(); + public void OnAfterDeserialize() { } + + + + + + public List bookmarks = new(); + + [System.Serializable] + public class Bookmark + { + + public GameObject go + { + get + { + if (_go == null) + if (!failedToLoadSceneObject) // to prevent continuous GlobalID.GetObjects() calls if object is deleted + VHierarchy.unloadedSceneBookmarks_sceneGuids.Add(globalId.guid); + + return _go; + + } + } + public GameObject _go; + + [System.NonSerialized] + public bool failedToLoadSceneObject; + + + + public bool isLoadable => go != null; + + public bool isDeleted + { + get + { + if (isLoadable) + return false; + + if (!AssetDatabase.LoadAssetAtPath(globalId.guid.ToPath())) + return true; + + for (int i = 0; i < EditorSceneManager.sceneCount; i++) + if (EditorSceneManager.GetSceneAt(i).path == globalId.guid.ToPath()) + return true; + + return false; + + } + } + + public string assetPath => globalId.guid.ToPath(); + + public string name => go ? go.name : "Can't load object"; + + + + public Bookmark(GameObject o) + { + globalId = o.GetGlobalID(); + + _go = o; + + } + + public GlobalID globalId; + + } + + + + + public List bookmarkedScenePaths = new(); + + + + + + + + [CustomEditor(typeof(VHierarchyData))] + class Editor : UnityEditor.Editor + { + public override void OnInspectorGUI() + { + var style = new GUIStyle(EditorStyles.label) { wordWrap = true }; + + void normal() + { + if (teamModeEnabled) return; + + SetGUIEnabled(false); + BeginIndent(0); + + Space(10); + EditorGUILayout.LabelField("This file stores data about which icons and colors are assigned to objects, along with bookmarks from navigation bar and scene selector.", style); + + Space(6); + GUILayout.Label("If there are multiple people working on the project, you might want to store icon/color data in scenes to avoid merge conflicts. To do that, click the ⋮ button at the top right corner and enable Team Mode.", style); + + EndIndent(10); + ResetGUIEnabled(); + } + void teamMode() + { + if (!teamModeEnabled) return; + + SetGUIEnabled(false); + BeginIndent(0); + + Space(10); + EditorGUILayout.LabelField("Now that Team Mode is enabled, create an empty script that inherits from VHierarchy.VHierarchyDataComponent and add it to any object in a scene.", style); + + Space(6); + GUILayout.Label("If such a script is present in a scene - all icon/color data for that scene will be serialized in that script, otherwise icon/color data for the scene will end up in this file.", style); + + EndIndent(10); + ResetGUIEnabled(); + } + + normal(); + teamMode(); + + } + } + + public static bool teamModeEnabled { get => EditorPrefsCached.GetBool("vHierarchy-teamModeEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-teamModeEnabled", value); } + + + + [ContextMenu("Enable Team Mode", isValidateFunction: false, priority: 1)] + public void EnableTeamMode() + { + var option = EditorUtility.DisplayDialogComplex("Licensing notice", + "To use vHierarchy 2 within a team, licenses must be purchased for each individual user as per the Asset Store EULA.\n\n Sharing one license across the team is illegal and considered piracy.", + "Acknowledge", + "Cancel", + "Purchase more seats"); + if (option == 2) + Application.OpenURL("https://prf.hn/click/camref:1100lGLBn/pubref:teammode/destination:https://assetstore.unity.com/packages/tools/utilities/vhierarchy-2-253397"); + // Application.OpenURL("https://assetstore.unity.com/packages/slug/253397"); + + + + if (option != 0) return; + + teamModeEnabled = true; + + VHierarchy.goInfoCache.Clear(); + VHierarchy.goDataCache.Clear(); + + EditorApplication.RepaintHierarchyWindow(); + + } + + [ContextMenu("Disable Team Mode", isValidateFunction: false, priority: 2)] + public void DisableTeamMode() + { + teamModeEnabled = false; + + VHierarchy.goInfoCache.Clear(); + VHierarchy.goDataCache.Clear(); + + EditorApplication.RepaintHierarchyWindow(); + + } + + [ContextMenu("Enable Team Mode", isValidateFunction: true, priority: 1)] bool asd() => !teamModeEnabled; + [ContextMenu("Disable Team Mode", isValidateFunction: true, priority: 2)] bool ads() => teamModeEnabled; + + + + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyData.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyData.cs.meta new file mode 100644 index 0000000..cd5eee5 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3a9752b0c8e144801967e6897679604b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyDataComponent.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyDataComponent.cs new file mode 100644 index 0000000..744c302 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyDataComponent.cs @@ -0,0 +1,142 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.Experimental.SceneManagement; +using Type = System.Type; +using static VHierarchy.VHierarchyData; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VHierarchy +{ + [ExecuteInEditMode] + public abstract class VHierarchyDataComponent : MonoBehaviour, ISerializationCallbackReceiver + { + public void Awake() + { + void register() + { + VHierarchy.dataComponents_byScene[gameObject.scene] = this; + } + void handleSceneDuplication() + { + if (sceneData == null) return; + if (!sceneData.goDatas_byGlobalId.Any()) return; + + + var curSceneGuid = gameObject.scene.path.ToGuid(); + var dataSceneGuid = sceneData.goDatas_byGlobalId.Keys.First().guid; + + if (curSceneGuid == dataSceneGuid) return; + + + var newDic = new SerializableDictionary(); + + foreach (var kvp in sceneData.goDatas_byGlobalId) + newDic[new GlobalID(kvp.Key.ToString().Replace(dataSceneGuid, curSceneGuid))] = kvp.Value; + + + sceneData.goDatas_byGlobalId = newDic; + + + EditorSceneManager.MarkSceneDirty(gameObject.scene); + EditorSceneManager.SaveScene(gameObject.scene); + + } + + register(); + handleSceneDuplication(); + + } + + + public SceneData sceneData; + + + public void OnBeforeSerialize() => VHierarchy.goDataCache.Clear(); + public void OnAfterDeserialize() => VHierarchy.goDataCache.Clear(); + + + + [CustomEditor(typeof(VHierarchyDataComponent), true)] + class Editor : UnityEditor.Editor + { + public override void OnInspectorGUI() + { + var style = new GUIStyle(EditorStyles.label) { wordWrap = true }; + + + void teamModeOn() + { + if (!VHierarchyData.teamModeEnabled) return; + + SetGUIEnabled(false); + BeginIndent(0); + + Space(4); + EditorGUILayout.LabelField("This component stores vHierarchy's data about which icons and colors are assigned to objects in this scene", style); + + // Space(6); + // EditorGUILayout.LabelField("You can disable Team Mode to store icon/color data in vHierarchy Data scriptable object, as it is done by default", style); + + + Space(2); + + EndIndent(10); + ResetGUIEnabled(); + + + + + // Space(10); + + // if (!GUILayout.Button("Disable Team Mode", GUILayout.Height(27))) return; + + // VHierarchy.data?.DisableTeamMode(); + + } + void teamModeOff() + { + if (VHierarchyData.teamModeEnabled) return; + + SetGUIEnabled(false); + BeginIndent(0); + + Space(4); + EditorGUILayout.LabelField("Enable Team Mode to store icon/color data for this scene in this component", style); + + Space(2); + + EndIndent(10); + ResetGUIEnabled(); + + + + Space(4); + + if (!GUILayout.Button("Enable Team Mode", GUILayout.Height(27))) return; + + VHierarchy.data?.EnableTeamMode(); + + } + + teamModeOn(); + teamModeOff(); + + + } + } + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyDataComponent.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyDataComponent.cs.meta new file mode 100644 index 0000000..be05b15 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyDataComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e20c7ea1a24b4a899ba82e98ad2b375 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyGUI.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyGUI.cs new file mode 100644 index 0000000..76c7114 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyGUI.cs @@ -0,0 +1,1024 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; +using static VHierarchy.VHierarchy; +using static VHierarchy.VHierarchyData; +using static VHierarchy.VHierarchyCache; + + + +namespace VHierarchy +{ + public class VHierarchyGUI + { + + public void RowGUI_GameObject(Rect rowRect, GameObject go) + { + var fullRowRect = rowRect.SetX(32).SetXMax(rowRect.xMax + 16); + + var isRowHovered = fullRowRect.AddWidthFromRight(32).IsHovered(); + + + var isRowSelected = false; + var isRowBeingRenamed = false; + var isDefaultParent = go == defaultParent; + + void setState() + { + void set_isRowSelected() + { + if (!curEvent.isRepaint) return; + + + var dragging = dragSelectionList != null + && dragSelectionList.Any(); + + isRowSelected = dragging ? (dragSelectionList.Contains(go.GetInstanceID())) : Selection.Contains(go); + + } + void set_lastVisibleSelectedRowRect() + { + if (!Selection.gameObjects.Contains(go)) return; + + lastVisibleSelectedRowRect = rowRect; + + } + void set_mousePressed() + { + if (curEvent.isMouseDown && isRowHovered) + mousePressed = true; + + if (curEvent.isMouseUp || curEvent.isMouseLeaveWindow || curEvent.isDragPerform) + mousePressed = false; + + } + void set_hoveredGo() + { + if (curEvent.isLayout) + VHierarchy.hoveredGo = null; + + if (curEvent.isRepaint && isRowHovered) + VHierarchy.hoveredGo = go; + + } + + set_isRowSelected(); + set_lastVisibleSelectedRowRect(); + set_mousePressed(); + set_hoveredGo(); + + isRowBeingRenamed = renamingRow && isRowSelected; + + } + + + void drawing() + { + if (!curEvent.isRepaint) { hierarchyLines_isFirstRowDrawn = false; return; } + + var goInfo = GetGameObjectInfo(go); + + var drawBackgroundColor = goInfo.hasColor; + var drawCustomIcon = goInfo.hasIcon; + var drawDefaultIcon = !drawCustomIcon && (isRowBeingRenamed || (!VHierarchyMenu.minimalModeEnabled || (PrefabUtility.IsAddedGameObjectOverride(go) && PrefabUtility.IsPartOfPrefabInstance(go)))); + + var makeTriangleBrighter = drawBackgroundColor && !goInfo.isGreyColor && isDarkTheme; + var makeNameBrighter = drawBackgroundColor && !goInfo.isGreyColor && isDarkTheme && !isDefaultParent; + var makeNameBold = isDefaultParent && isDarkTheme; + + Color defaultBackground; + + + void set_defaultBackground() + { + var selectedFocused = GUIColors.selectedBackground; + var selectedUnfocused = isDarkTheme ? Greyscale(.3f) : Greyscale(.68f); + var hovered = isDarkTheme ? Greyscale(.265f) : Greyscale(.7f); + var normal = GUIColors.windowBackground; + + if (isRowSelected && !isRowBeingRenamed) + defaultBackground = isTreeFocused ? selectedFocused : selectedUnfocused; + + else if (isRowHovered) + defaultBackground = hovered; + + else + defaultBackground = normal; + + } + void hideDefaultIcon() + { + if (drawDefaultIcon) return; + + rowRect.SetWidth(16).Draw(defaultBackground); + + } + void hideName() + { + if (!drawBackgroundColor && (drawCustomIcon || drawDefaultIcon) && !makeNameBold) return; + + var nameRect = rowRect.MoveX(16).SetWidth(go.name.GetLabelWidth(isBold: isDefaultParent)); +#if UNITY_2023_2_OR_NEWER + if (!go.activeInHierarchy && PrefabUtility.IsPartOfPrefabInstance(go)) + nameRect.width *= 1.1f; +#endif + + nameRect.Draw(defaultBackground); + + } + + void backgroundColor() + { + if (!drawBackgroundColor) return; + + + + var color = goInfo.color; + + if (isRowHovered) + color *= isDarkTheme ? 1.1f : .92f; + + if (isRowSelected) + color *= isDarkTheme ? 1.2f : .8f; + + if (palette?.colorGradientsEnabled == false) + color = MathUtil.Lerp(color, Greyscale(.2f), isDarkTheme ? .25f : .03f); + + if (goInfo.hasColorByRecursion) + color = MathUtil.Lerp(color, Greyscale(isDarkTheme ? .25f : .8f), .5f); + + + + + + var colorRect = rowRect.AddWidthFromRight(28).AddWidth(16); + + if (goInfo.hasColorByRecursion) + colorRect = colorRect.AddWidthFromRight(goInfo.maxColorRecursionDepth * 14); + + if (!isRowSelected && !goInfo.hasColorByRecursion) + colorRect = colorRect.AddHeightFromMid(EditorGUIUtility.pixelsPerPoint >= 2 ? -.5f : -1); + + if (goInfo.hasColorByRecursion) + colorRect = colorRect.MoveY(EditorGUIUtility.pixelsPerPoint >= 2 ? -.25f : -.5f); + + if (palette?.colorGradientsEnabled == false || goInfo.isGreyColor) { colorRect.Draw(color); return; } + + + + var hasLeftGradient = colorRect.x > 32; + + if (hasLeftGradient) + colorRect = colorRect.AddWidthFromRight(3); + + if (PrefabUtility.HasPrefabInstanceAnyOverrides(go, false) && PrefabUtility.IsOutermostPrefabInstanceRoot(go) && !hasLeftGradient) + colorRect = colorRect.AddWidthFromRight(EditorGUIUtility.pixelsPerPoint >= 2 ? -2.5f : -3); + + + + var leftGradientWith = hasLeftGradient ? 22 : 0; + var rightGradientWidth = (fullRowRect.width * .77f).Min(colorRect.width - leftGradientWith); + + var leftGradientRect = colorRect.SetWidth(leftGradientWith); + var rightGradientRect = colorRect.SetWidthFromRight(rightGradientWidth); + + var flatColorRect = colorRect.SetX(leftGradientRect.xMax).SetXMax(rightGradientRect.x); + + + + + + + leftGradientRect.AddWidth(1).DrawCurtainLeft(color); + + flatColorRect.AddWidth(1).Draw(color); + + rightGradientRect.Draw(color.MultiplyAlpha(.1f)); + rightGradientRect.DrawCurtainRight(color); + + + } + void triangle() + { + if (!drawBackgroundColor) return; + if (go.transform.childCount == 0) return; + + var triangleRect = rowRect.MoveX(-15.5f).SetWidth(16).Resize(1.5f); + + GUI.DrawTexture(triangleRect, EditorIcons.GetIcon(controller.expandedIds.Contains(go.GetInstanceID()) ? "IN_foldout_on" : "IN_foldout")); + + + if (!makeTriangleBrighter) return; + + GUI.DrawTexture(triangleRect, EditorIcons.GetIcon(controller.expandedIds.Contains(go.GetInstanceID()) ? "IN_foldout_on" : "IN_foldout")); + + } + void name() + { + if (!drawBackgroundColor && (drawCustomIcon || drawDefaultIcon) && !makeNameBold) return; + if (isRowBeingRenamed) return; + + + var nameRect = rowRect.MoveX(18).AddHeight(1); + + if (VHierarchyMenu.minimalModeEnabled && !drawCustomIcon && !drawDefaultIcon) + nameRect = nameRect.MoveX(-17); + + if (drawBackgroundColor && !goInfo.isGreyColor) + nameRect = nameRect.MoveY(.5f); + + if (!go.activeInHierarchy) // correcting unity's style padding inconsistencies + if (PrefabUtility.IsPartOfAnyPrefab(go)) + nameRect = nameRect.MoveY(-1); + else + nameRect = nameRect.Move(-1, -1.5f); + + if (makeNameBrighter && go.activeInHierarchy && !makeNameBold) + nameRect = nameRect.MoveX(-2).MoveY(-.5f); + + + + var styleName = PrefabUtility.IsPartOfAnyPrefab(go) ? + (go.activeInHierarchy ? "PR PrefabLabel" : "PR DisabledPrefabLabel") : + (go.activeInHierarchy ? "TV Line" : "PR DisabledLabel"); + + if (makeNameBrighter && go.activeInHierarchy) + styleName = "WhiteLabel"; + + if (makeNameBold) + styleName = "TV LineBold"; + + + + if (makeNameBrighter) + SetGUIColor(Greyscale(!go.activeInHierarchy ? 1.4f : isRowSelected ? 1 : goInfo.hasColorByRecursion ? .9f : .95f)); + + GUI.skin.GetStyle(styleName).Draw(nameRect, go.name, false, false, isRowSelected || makeNameBold, isTreeFocused || makeNameBold); + + if (makeNameBrighter) + ResetGUIColor(); + + } + void defaultIcon() + { + if (!drawBackgroundColor) return; + if (!drawDefaultIcon) return; + + var iconRect = rowRect.SetWidth(16); + var icon = PrefabUtility.GetIconForGameObject(go); + + if (!isDarkTheme && isRowSelected && isTreeFocused && icon.name == "GameObject Icon") + icon = EditorIcons.GetIcon("GameObject On Icon"); + + + SetGUIColor(go.activeInHierarchy ? Color.white : Greyscale(1, .4f)); + + GUI.DrawTexture(iconRect, icon); + + if (PrefabUtility.IsAddedGameObjectOverride(go)) + GUI.DrawTexture(iconRect, EditorIcons.GetIcon("PrefabOverlayAdded Icon")); + + ResetGUIColor(); + + } + void customIcon() + { + if (!drawCustomIcon) return; + + var icon = EditorIcons.GetIcon(goInfo.iconNameOrPath) ?? Texture2D.blackTexture; + + var iconRect = rowRect.SetWidth(16); + + if (icon.width < icon.height) iconRect = iconRect.SetWidthFromMid(iconRect.height * icon.width / icon.height); + if (icon.height < icon.width) iconRect = iconRect.SetHeightFromMid(iconRect.width * icon.height / icon.width); + + + SetGUIColor(go.activeInHierarchy ? Color.white : Greyscale(1, .4f)); + + GUI.DrawTexture(iconRect, icon); + + ResetGUIColor(); + + } + void hierarchyLines() + { + if (!VHierarchyMenu.hierarchyLinesEnabled) return; + + + var lineThickness = 1f; + var lineColor = isDarkTheme ? Greyscale(1, .165f) : Greyscale(0, .23f); + + var depth = ((rowRect.x - 60) / 14).RoundToInt().Max(0); + + bool isLastChild(Transform transform) => transform.parent?.GetChild(transform.parent.childCount - 1) == transform; + bool hasChilren(Transform transform) => transform.childCount > 0; + + void calcVerticalGaps_beforeFirstRowDrawn() + { + if (hierarchyLines_isFirstRowDrawn) return; + + hierarchyLines_verticalGaps.Clear(); + + var curTransform = go.transform.parent; + var curDepth = depth - 1; + + while (curTransform != null && curTransform.parent != null) + { + if (isLastChild(curTransform)) + hierarchyLines_verticalGaps.Add(curDepth - 1); + + curTransform = curTransform.parent; + curDepth--; + } + + } + void updateVerticalGaps_beforeNextRowDrawn() + { + if (isLastChild(go.transform)) + hierarchyLines_verticalGaps.Add(depth - 1); + + if (depth < hierarchyLines_prevRowDepth) + hierarchyLines_verticalGaps.RemoveAll(r => r >= depth); + + } + + void drawVerticals() + { + for (int i = 0; i < depth; i++) + if (!hierarchyLines_verticalGaps.Contains(i)) + rowRect.SetX(53 + i * 14 - lineThickness / 2) + .SetWidth(lineThickness) + .SetHeight(isLastChild(go.transform) && i == depth - 1 ? 8 + lineThickness / 2 : 16) + .Draw(lineColor); + + } + void drawHorizontals() + { + if (depth == 0) return; + + rowRect.MoveX(-21) + .SetHeightFromMid(lineThickness) + .SetWidth(hasChilren(go.transform) ? 7 : 17) + .AddWidthFromRight(-lineThickness / 2f) + .Draw(lineColor); + + } + + + + calcVerticalGaps_beforeFirstRowDrawn(); + + drawVerticals(); + drawHorizontals(); + + updateVerticalGaps_beforeNextRowDrawn(); + + hierarchyLines_prevRowDepth = depth; + hierarchyLines_isFirstRowDrawn = true; + + } + void zebraStriping() + { + if (!VHierarchyMenu.zebraStripingEnabled) return; + if (isRowSelected) return; + if (goInfo.goData?.colorIndex == 1) return; + + + var contrast = isDarkTheme ? .033f : .05f; + + + var t = rowRect.y.PingPong(16f) / 16f; + + if (isRowHovered || isRowSelected) + t = 1; + + if (t.Approx(0)) return; + + + + fullRowRect.Draw(Greyscale(isDarkTheme ? 1 : 0, contrast * t)); + + + } + void highlight() + { + if (!controller.animatingHighlight) return; + if (go != controller.objectToHighlight) return; + + + var highlightBrightness = .16f; + + + var highlightAmount = controller.highlightAmount.Clamp01(); + + highlightAmount = highlightAmount * highlightAmount * (3 - 2 * highlightAmount); + + + fullRowRect.AddWidthFromRight(123).Draw(Greyscale(1, highlightBrightness * highlightAmount)); + + } + + + set_defaultBackground(); + hideDefaultIcon(); + hideName(); + + backgroundColor(); + hierarchyLines(); + + triangle(); + name(); + defaultIcon(); + customIcon(); + zebraStriping(); + highlight(); + + } + + void componentMinimap() + { + if (!VHierarchyMenu.componentMinimapEnabled) return; + + void componentButton(Rect buttonRect, Component component) + { + void componentIcon() + { + if (!curEvent.isRepaint) return; + + + var normalOpacity = isDarkTheme ? .47f : .7f; + var activeOpacity = 1; + var pressedOpacity = isDarkTheme ? .65f : .9f; + + var isActive = (buttonRect.IsHovered() && curEvent.holdingAlt) || VHierarchyComponentWindow.floatingInstance?.component == component; + var isPressed = buttonRect.IsHovered() && mousePressed; + + var icon = GetComponentIcon(component); + + + if (!icon) return; + + SetGUIColor(Greyscale(1, isActive ? (isPressed ? pressedOpacity : activeOpacity) : normalOpacity)); + + GUI.DrawTexture(buttonRect.SetSizeFromMid(12, 12), icon); + + ResetGUIColor(); + + } + + void mouseDown() + { + if (!curEvent.holdingAlt) return; + if (!curEvent.isMouseDown) return; + if (!buttonRect.IsHovered()) return; + + curEvent.Use(); + + mouseDownPos = curEvent.mousePosition; + + } + void mouseUp() + { + if (!curEvent.holdingAlt) return; + if (!curEvent.isMouseUp) return; + if (!buttonRect.IsHovered()) return; + + curEvent.Use(); + + if (VHierarchyComponentWindow.floatingInstance?.component == component) { VHierarchyComponentWindow.floatingInstance.Close(); return; } + + + var position = EditorGUIUtility.GUIToScreenPoint(new Vector2(rowRect.xMax + 25, rowRect.y)); + + if (!VHierarchyComponentWindow.floatingInstance) + VHierarchyComponentWindow.CreateFloatingInstance(position); + + VHierarchyComponentWindow.floatingInstance.Init(component); + VHierarchyComponentWindow.floatingInstance.Focus(); + + VHierarchyComponentWindow.floatingInstance.targetPosition = position; + + } + + + if (curEvent.holdingAlt) + buttonRect.MarkInteractive(); + + componentIcon(); + + mouseDown(); + mouseUp(); + + } + + void transformComponent() + { + if (!isRowHovered) return; + if (!curEvent.holdingAlt) return; + if (!go.GetComponent()) return; + + componentButton(fullRowRect.SetWidth(13).MoveX(1.5f), go.GetComponent()); + + } + void otherComponetns() + { + var buttonWidth = 13; + var minButtonX = rowRect.x + go.name.GetLabelWidth() + buttonWidth + 2; + var buttonRect = fullRowRect.SetWidthFromRight(buttonWidth).MoveX(-1.5f); + + if (PrefabUtility.IsAnyPrefabInstanceRoot(go) && !PrefabUtility.IsPartOfModelPrefab(go)) + buttonRect = buttonRect.MoveX(-13); + + foreach (var component in go.GetComponents()) + { + if (component is Transform) continue; + if (buttonRect.x < minButtonX) continue; + + componentButton(buttonRect, component); + + buttonRect = buttonRect.MoveX(-buttonWidth); + + } + + + } + + transformComponent(); + otherComponetns(); + + } + void activationToggle() + { + if (!VHierarchyMenu.activationToggleEnabled) return; + if (!isRowHovered) return; + + var toggleRect = fullRowRect.SetWidth(16).MoveX(1); + + + SetGUIColor(Greyscale(1, .9f)); + + var newActiveSelf = EditorGUI.Toggle(toggleRect, go.activeSelf); + + ResetGUIColor(); + + + if (newActiveSelf == go.activeSelf) return; + + var gos = Selection.gameObjects.Contains(go) ? Selection.gameObjects : new[] { go }; + var newActive = gos != null && !gos.Any(r => r && r.activeSelf); + + foreach (var r in gos) + r.RecordUndo(); + + foreach (var r in gos) + r.SetActive(newActiveSelf); + + GUI.FocusControl(null); + + } + void defaultParentIndicator() + { + if (!isDefaultParent) return; + + + + var drawCustomIcon = GetGameObjectInfo(go).hasIcon; + var drawDefaultIcon = !drawCustomIcon && (isRowBeingRenamed || (!VHierarchyMenu.minimalModeEnabled || (PrefabUtility.IsAddedGameObjectOverride(go) && PrefabUtility.IsPartOfPrefabInstance(go)))); + + var indicatorRect = rowRect.MoveX(go.name.GetLabelWidth(isBold: true) + 16.5f); + + if (!drawCustomIcon && !drawDefaultIcon) + indicatorRect = indicatorRect.MoveX(-16); + + + + SetGUIColor(Greyscale(1, .6f)); + SetLabelFontSize(10); + + GUI.Label(indicatorRect, "Default parent"); + + ResetLabelStyle(); + ResetGUIColor(); + + + + + + if (!fullRowRect.IsHovered()) return; + + + var buttonRect = indicatorRect.MoveX(68.5f).SetWidth(16).MoveY(.49f); + + var iconName = "CrossIcon"; + var iconSize = 10; + var colorNormal = Greyscale(isDarkTheme ? .75f : .2f, .55f); + var colorHovered = Greyscale(isDarkTheme ? 123f : .2f, 123f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + var colorDisabled = Greyscale(isDarkTheme ? .53f : .55f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + EditorUtility.ClearDefaultParentObject(); + + + } + + void altDrag() + { + if (!curEvent.holdingAlt) return; + + void mouseDown() + { + if (!curEvent.isMouseDown) return; + if (!rowRect.IsHovered()) return; + + mouseDownPos = curEvent.mousePosition; + + } + void mouseDrag() + { + if (!curEvent.isMouseDrag) return; + if ((curEvent.mousePosition - mouseDownPos).magnitude < 5) return; + if (!rowRect.Contains(mouseDownPos)) return; + if (!rowRect.Contains(curEvent.mousePosition - curEvent.mouseDelta)) return; + if (DragAndDrop.objectReferences.Any()) return; + + DragAndDrop.PrepareStartDrag(); + DragAndDrop.objectReferences = new[] { go }; + DragAndDrop.StartDrag(go.name); + + } + + mouseDown(); + mouseDrag(); + + // altdrag has to be set up manually before altClick because altClick will use() mouseDown event to prevent selection change + } + void altClick() + { + if (!fullRowRect.IsHovered()) return; + if (!curEvent.holdingAlt) return; + if (Application.isPlaying) return; + + void mouseDown() + { + if (!curEvent.isMouseDown) return; + + curEvent.Use(); + + } + void mouseUp() + { + if (!curEvent.isMouseUp) return; + + var editMultiSelection = Selection.gameObjects.Length > 1 && Selection.gameObjects.Contains(go); + + var gosToEdit = (editMultiSelection ? Selection.gameObjects : new[] { go }).ToList(); + + + if (VHierarchyPaletteWindow.instance && VHierarchyPaletteWindow.instance.gameObjects.SequenceEqual(gosToEdit)) { VHierarchyPaletteWindow.instance.Close(); return; } + + var openNearRect = editMultiSelection ? lastVisibleSelectedRowRect : rowRect; + var position = EditorGUIUtility.GUIToScreenPoint(new Vector2(curEvent.mousePosition.x + 20, openNearRect.y - 13)); + // var position = EditorGUIUtility.GUIToScreenPoint(new Vector2(openNearRect.x - 14, openNearRect.y + 18)); + + if (!VHierarchyPaletteWindow.instance) + VHierarchyPaletteWindow.CreateInstance(position); + + VHierarchyPaletteWindow.instance.Init(gosToEdit); + VHierarchyPaletteWindow.instance.Focus(); + + VHierarchyPaletteWindow.instance.targetPosition = position; + + if (editMultiSelection) + Selection.objects = null; + + } + + mouseDown(); + mouseUp(); + + } + + + + setState(); + + drawing(); + + componentMinimap(); + activationToggle(); + defaultParentIndicator(); + + altDrag(); + altClick(); + + } + + List hierarchyLines_verticalGaps = new(); + bool hierarchyLines_isFirstRowDrawn; + int hierarchyLines_prevRowDepth; + + bool mousePressed; + Vector2 mouseDownPos; + + Rect lastVisibleSelectedRowRect; + + + + + public void RowGUI_Scene(Rect rowRect, Scene scene) + { + var fullRowRect = rowRect.SetX(32).SetXMax(rowRect.xMax + 16); + + var isRowHovered = fullRowRect.AddWidthFromRight(32).IsHovered(); + var isActiveScene = EditorSceneManager.GetActiveScene() == scene; + var isStickyHeader = rowRect.y != 0 && EditorGUIUtility.GUIToScreenPoint(rowRect.position).y - window.position.y == 45; + + + void set_hoveredScene() + { + if (curEvent.isLayout) + VHierarchy.hoveredScene = default; + + if (curEvent.isRepaint && isRowHovered) + VHierarchy.hoveredScene = scene; + + } + void sceneSelector() + { + if (!VHierarchyMenu.sceneSelectorEnabled) return; + if (!scene.isLoaded) return; + + + var nameWidth = (scene.name == "" ? "Untitled" : scene.name).GetLabelWidth(isBold: isActiveScene) + (scene.isDirty ? 5 : 0) + (isActiveScene ? -.5f : -1f); + var selectorRect = rowRect.MoveX(18).SetWidth(nameWidth + 16); + + var id = EditorGUIUtility.GUIToScreenRect(selectorRect).GetHashCode(); + var isPressed = id == pressedSceneSelectorId; + + var highlightName = selectorRect.IsHovered() || (VHierarchySceneSelectorWindow.instance && VHierarchySceneSelectorWindow.instance.sceneToReplace == scene); + + + + void dummyRow() + { + if (!highlightName) return; + if (!isDarkTheme) return; + + void background() + { + var backgroundColor = Application.unityVersion.Contains("2021") ? Greyscale(isDarkTheme ? .32f : .9f) + : Greyscale(isDarkTheme ? .16f : .9f); + + rowRect.AddWidthFromMid(123).AddHeight(-1).Draw(backgroundColor); + + } + void tripleDotButton() + { + GUI.DrawTexture(rowRect.SetWidthFromRight(16).MoveX(12), EditorIcons.GetIcon("More")); + } + void sceneIcon() + { + GUI.DrawTexture(rowRect.SetWidth(16), EditorIcons.GetIcon("SceneAsset Icon")); + } + void foldoutIcon() + { + if (scene.rootCount == 0) return; + + var isSceneExpanded = controller.expandedIds.Contains(scene.handle); + + GUI.DrawTexture(rowRect.SetWidth(16).MoveX(-15.5f).SetSizeFromMid(13), EditorIcons.GetIcon(isSceneExpanded ? "IN_foldout_on" : "IN_foldout")); + } + + background(); + tripleDotButton(); + sceneIcon(); + foldoutIcon(); + + } + + void dropdownIcon() + { + var iconRect = rowRect.MoveY(-.5f).MoveX(nameWidth + 14).SetWidth(16); + + + var iconBrightness = highlightName ? 1 : .78f; + + if (!isActiveScene) + iconBrightness *= .82f; + + if (isPressed) + iconBrightness *= .83f; + + if (!isDarkTheme) + iconBrightness = .35f; + + + + SetGUIColor(Greyscale(iconBrightness)); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon("Dropdown")); + + ResetGUIColor(); + + } + void highlightedName() + { + if (!curEvent.isRepaint) return; + if (!highlightName) return; + + var nameRect = rowRect.MoveX(18).SetWidth(nameWidth + 32); + + + + + var nameStyle = isActiveScene ? "TV LineBold" : "TV Line"; + + var nameText = scene.name == "" ? "Untitled" : scene.name; + + if (scene.isDirty) + nameText += "*"; + + + var nameBrightness = 1f; + + if (isPressed) + nameBrightness *= .83f; + + if (!isDarkTheme) + nameBrightness = .0f; + + + + SetGUIColor(Greyscale(nameBrightness)); + + GUI.skin.GetStyle(nameStyle).Draw(nameRect, nameText, false, false, true, true); + + ResetGUIColor(); + + } + void buttonLogic() + { + void mouseDown() + { + var couldBeMouseDown = isStickyHeader && isRowHovered && curEvent.isUsed && !isPressed; // gets used on sticky headers by default row gui + + if (!curEvent.isMouseDown && !couldBeMouseDown) return; + if (!selectorRect.IsHovered()) return; + + + pressedSceneSelectorId = id; + + mouseDownOnSelectorPos = curEvent.mousePosition; + + curEvent.Use(); + + } + void mouseUp() + { + var couldBeMouseUp = isStickyHeader && isRowHovered && curEvent.isUsed && isPressed; // gets used on sticky headers by default row gui + + if (!curEvent.isMouseUp && !couldBeMouseUp) return; + if (!isPressed) return; + + + pressedSceneSelectorId = 0; + + curEvent.Use(); + + + if (!selectorRect.IsHovered()) return; + + if (VHierarchySceneSelectorWindow.instance) + VHierarchySceneSelectorWindow.instance.Close(); + else + VHierarchySceneSelectorWindow.Open(EditorGUIUtility.GUIToScreenPoint(rowRect.position), scene); + + } + void mouseDrag() + { + if (!curEvent.isMouseDrag) return; + if (!isPressed) return; + + if (curEvent.mousePosition.DistanceTo(mouseDownOnSelectorPos) < 3) { curEvent.Use(); return; } + + pressedSceneSelectorId = 0; + + + var sceneHierarchy = window?.GetFieldValue("m_SceneHierarchy"); + + var treeViewController = sceneHierarchy.GetFieldValue("m_TreeView"); + var treeViewControllerData = treeViewController.GetMemberValue("data"); + + var item = treeViewControllerData.InvokeMethod("FindItem", scene.handle); + + treeViewController.GetMemberValue("dragging").InvokeMethod("StartDrag", item, new List()); + + } + + + selectorRect.MarkInteractive(); + + mouseDown(); + mouseUp(); + mouseDrag(); + + } + + + + dummyRow(); + + dropdownIcon(); + highlightedName(); + buttonLogic(); + + } + + set_hoveredScene(); + sceneSelector(); + + } + + int pressedSceneSelectorId; + + Vector2 mouseDownOnSelectorPos; + + + + + + + + + + public void UpdateState() + { + + var sceneHierarchy = window?.GetFieldValue("m_SceneHierarchy"); + + var treeViewController = sceneHierarchy.GetFieldValue("m_TreeView"); + var treeViewControllerData = treeViewController.GetMemberValue("data"); + + + isTreeFocused = EditorWindow.focusedWindow == window + && GUIUtility.keyboardControl == sceneHierarchy?.GetMemberValue("m_TreeViewKeyboardControlID"); + + renamingRow = EditorGUIUtility.editingTextField + && treeViewController?.GetMemberValue("state")?.GetMemberValue("renameOverlay")?.InvokeMethod("IsRenaming") == true; + + +#if UNITY_2021_1_OR_NEWER + dragSelectionList = treeViewController?.GetFieldValue("m_DragSelection")?.GetFieldValue>("m_List"); +#else + dragSelectionList = treeViewController?.GetFieldValue>("m_DragSelection"); +#endif + + + defaultParent = typeof(SceneView).InvokeMethod("GetDefaultParentObjectIfSet")?.gameObject; + + + } + + public bool isTreeFocused; + public bool renamingRow; + + public List dragSelectionList = new(); + + GameObject defaultParent; + + + + + + + + + + + + public VHierarchyGUI(EditorWindow window) => this.window = window; + + public EditorWindow window; + + public VHierarchyController controller => VHierarchy.controllers_byWindow[window]; + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyGUI.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyGUI.cs.meta new file mode 100644 index 0000000..ceb389a --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyGUI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 879081410e5ee4ddcb0145a4a9937193 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyIconEditor.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyIconEditor.cs new file mode 100644 index 0000000..8fb6b18 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyIconEditor.cs @@ -0,0 +1,6 @@ + + +// this file was present in a previus version and is supposed to be deleted now +// but asset store update delivery system doesn't allow deleting files +// so instead this file is now emptied +// feel free to delete it if you want diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyIconEditor.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyIconEditor.cs.meta new file mode 100644 index 0000000..90282a9 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyIconEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8710ada63f66c4909ab73d206f31e954 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLibs.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLibs.cs new file mode 100644 index 0000000..a9484bd --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLibs.cs @@ -0,0 +1,2563 @@ +#if UNITY_EDITOR +using Type = System.Type; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using UnityEngine; +using UnityEditor; +using System.Linq; +using System.Text.RegularExpressions; +using System.Diagnostics; +using System.Reflection; +using UnityEngine.Experimental.Rendering; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; + + +namespace VHierarchy.Libs +{ + public static class VUtils + { + + #region Reflection + + + public static object GetFieldValue(this object o, string fieldName) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(fieldName) is FieldInfo fieldInfo) + return fieldInfo.GetValue(target); + + + throw new System.Exception($"Field '{fieldName}' not found in type '{type.Name}' and its parent types"); + + } + public static object GetPropertyValue(this object o, string propertyName) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetPropertyInfo(propertyName) is PropertyInfo propertyInfo) + return propertyInfo.GetValue(target); + + + throw new System.Exception($"Property '{propertyName}' not found in type '{type.Name}' and its parent types"); + + } + public static object GetMemberValue(this object o, string memberName) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(memberName) is FieldInfo fieldInfo) + return fieldInfo.GetValue(target); + + if (type.GetPropertyInfo(memberName) is PropertyInfo propertyInfo) + return propertyInfo.GetValue(target); + + + throw new System.Exception($"Member '{memberName}' not found in type '{type.Name}' and its parent types"); + + } + + public static void SetFieldValue(this object o, string fieldName, object value) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(fieldName) is FieldInfo fieldInfo) + fieldInfo.SetValue(target, value); + + + else throw new System.Exception($"Field '{fieldName}' not found in type '{type.Name}' and its parent types"); + + } + public static void SetPropertyValue(this object o, string propertyName, object value) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetPropertyInfo(propertyName) is PropertyInfo propertyInfo) + propertyInfo.SetValue(target, value); + + + else throw new System.Exception($"Property '{propertyName}' not found in type '{type.Name}' and its parent types"); + + } + public static void SetMemberValue(this object o, string memberName, object value) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetFieldInfo(memberName) is FieldInfo fieldInfo) + fieldInfo.SetValue(target, value); + + else if (type.GetPropertyInfo(memberName) is PropertyInfo propertyInfo) + propertyInfo.SetValue(target, value); + + + else throw new System.Exception($"Member '{memberName}' not found in type '{type.Name}' and its parent types"); + + } + + public static object InvokeMethod(this object o, string methodName, params object[] parameters) // todo handle null params (can't get their type) + { + var type = o as Type ?? o.GetType(); + var target = o is Type ? null : o; + + + if (type.GetMethodInfo(methodName, parameters.Select(r => r.GetType()).ToArray()) is MethodInfo methodInfo) + return methodInfo.Invoke(target, parameters); + + + throw new System.Exception($"Method '{methodName}' not found in type '{type.Name}', its parent types and interfaces"); + + } + + + public static T GetFieldValue(this object o, string fieldName) => (T)o.GetFieldValue(fieldName); + public static T GetPropertyValue(this object o, string propertyName) => (T)o.GetPropertyValue(propertyName); + public static T GetMemberValue(this object o, string memberName) => (T)o.GetMemberValue(memberName); + public static T InvokeMethod(this object o, string methodName, params object[] parameters) => (T)o.InvokeMethod(methodName, parameters); + + + + + public static FieldInfo GetFieldInfo(this Type type, string fieldName) + { + if (fieldInfoCache.TryGetValue(type, out var fieldInfosByNames)) + if (fieldInfosByNames.TryGetValue(fieldName, out var fieldInfo)) + return fieldInfo; + + + if (!fieldInfoCache.ContainsKey(type)) + fieldInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetField(fieldName, maxBindingFlags) is FieldInfo fieldInfo) + return fieldInfoCache[type][fieldName] = fieldInfo; + + + return fieldInfoCache[type][fieldName] = null; + + } + public static PropertyInfo GetPropertyInfo(this Type type, string propertyName) + { + if (propertyInfoCache.TryGetValue(type, out var propertyInfosByNames)) + if (propertyInfosByNames.TryGetValue(propertyName, out var propertyInfo)) + return propertyInfo; + + + if (!propertyInfoCache.ContainsKey(type)) + propertyInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetProperty(propertyName, maxBindingFlags) is PropertyInfo propertyInfo) + return propertyInfoCache[type][propertyName] = propertyInfo; + + + return propertyInfoCache[type][propertyName] = null; + + } + public static MethodInfo GetMethodInfo(this Type type, string methodName, params Type[] argumentTypes) + { + var methodHash = methodName.GetHashCode() ^ argumentTypes.Aggregate(0, (hash, r) => hash ^= r.GetHashCode()); + + + if (methodInfoCache.TryGetValue(type, out var methodInfosByHashes)) + if (methodInfosByHashes.TryGetValue(methodHash, out var methodInfo)) + return methodInfo; + + + + if (!methodInfoCache.ContainsKey(type)) + methodInfoCache[type] = new Dictionary(); + + for (var curType = type; curType != null; curType = curType.BaseType) + if (curType.GetMethod(methodName, maxBindingFlags, null, argumentTypes, null) is MethodInfo methodInfo) + return methodInfoCache[type][methodHash] = methodInfo; + + foreach (var interfaceType in type.GetInterfaces()) + if (interfaceType.GetMethod(methodName, maxBindingFlags, null, argumentTypes, null) is MethodInfo methodInfo) + return methodInfoCache[type][methodHash] = methodInfo; + + + + return methodInfoCache[type][methodHash] = null; + + } + + static Dictionary> fieldInfoCache = new(); + static Dictionary> propertyInfoCache = new(); + static Dictionary> methodInfoCache = new(); + + + + + + + public static T GetCustomAttributeCached(this MemberInfo memberInfo) where T : System.Attribute + { + if (!attributesCache.TryGetValue(memberInfo, out var attributes_byType)) + attributes_byType = attributesCache[memberInfo] = new(); + + if (!attributes_byType.TryGetValue(typeof(T), out var attribute)) + attribute = attributes_byType[typeof(T)] = memberInfo.GetCustomAttribute(); + + return attribute as T; + + } + + static Dictionary> attributesCache = new(); + + + + + + + public static List GetSubclasses(this Type t) => t.Assembly.GetTypes().Where(type => type.IsSubclassOf(t)).ToList(); + + public static object GetDefaultValue(this FieldInfo f, params object[] constructorVars) => f.GetValue(System.Activator.CreateInstance(((MemberInfo)f).ReflectedType, constructorVars)); + public static object GetDefaultValue(this FieldInfo f) => f.GetValue(System.Activator.CreateInstance(((MemberInfo)f).ReflectedType)); + + public static IEnumerable GetFieldsWithoutBase(this Type t) => t.GetFields().Where(r => !t.BaseType.GetFields().Any(rr => rr.Name == r.Name)); + public static IEnumerable GetPropertiesWithoutBase(this Type t) => t.GetProperties().Where(r => !t.BaseType.GetProperties().Any(rr => rr.Name == r.Name)); + + + public const BindingFlags maxBindingFlags = (BindingFlags)62; + + + + + + + + + #endregion + + #region Collections + + + public static class CollectionUtils + { + public static Dictionary MergeDictionaries(IEnumerable> dicts) + { + if (dicts.Count() == 0) return null; + if (dicts.Count() == 1) return dicts.First(); + + var mergedDict = new Dictionary(dicts.First()); + + foreach (var dict in dicts.Skip(1)) + foreach (var r in dict) + if (!mergedDict.ContainsKey(r.Key)) + mergedDict.Add(r.Key, r.Value); + + return mergedDict; + } + + } + + + public static T NextTo(this IEnumerable e, T to) => e.SkipWhile(r => !r.Equals(to)).Skip(1).FirstOrDefault(); + public static T PreviousTo(this IEnumerable e, T to) => e.Reverse().SkipWhile(r => !r.Equals(to)).Skip(1).FirstOrDefault(); + public static T NextToOrFirst(this IEnumerable e, T to) => e.NextTo(to) ?? e.First(); + public static T PreviousToOrLast(this IEnumerable e, T to) => e.PreviousTo(to) ?? e.Last(); + + public static IEnumerable InsertFirst(this IEnumerable ie, T t) => new[] { t }.Concat(ie); + + public static int IndexOfFirst(this List list, System.Func f) => list.FirstOrDefault(f) is T t ? list.IndexOf(t) : -1; + public static int IndexOfLast(this List list, System.Func f) => list.LastOrDefault(f) is T t ? list.IndexOf(t) : -1; + + public static void SortBy(this List list, System.Func keySelector) where T2 : System.IComparable => list.Sort((q, w) => keySelector(q).CompareTo(keySelector(w))); + + public static void RemoveValue(this IDictionary dictionary, TValue value) + { + if (dictionary.FirstOrDefault(r => r.Value.Equals(value)) is var kvp) + dictionary.Remove(kvp); + } + + public static void ForEach(this IEnumerable sequence, System.Action action) { foreach (T item in sequence) action(item); } + + + + public static T AddAt(this List l, T r, int i) + { + if (i < 0) i = 0; + if (i >= l.Count) + l.Add(r); + else + l.Insert(i, r); + return r; + } + public static T RemoveLast(this List l) + { + if (!l.Any()) return default; + + var r = l.Last(); + + l.RemoveAt(l.Count - 1); + + return r; + } + + public static void Add(this List list, params T[] items) + { + foreach (var r in items) + list.Add(r); + } + + + + + + + #endregion + + #region Math + + + public static class MathUtil // MathUtils name is taken by UnityEditor.MathUtils + { + + public static float TriangleArea(Vector2 A, Vector2 B, Vector2 C) => Vector3.Cross(A - B, A - C).z.Abs() / 2; + + public static Vector2 LineIntersection(Vector2 A, Vector2 B, Vector2 C, Vector2 D) + { + var a1 = B.y - A.y; + var b1 = A.x - B.x; + var c1 = a1 * A.x + b1 * A.y; + + var a2 = D.y - C.y; + var b2 = C.x - D.x; + var c2 = a2 * C.x + b2 * C.y; + + var d = a1 * b2 - a2 * b1; + + var x = (b2 * c1 - b1 * c2) / d; + var y = (a1 * c2 - a2 * c1) / d; + + return new Vector2(x, y); + + } + + + + + public static float Lerp(float f1, float f2, float t) => Mathf.LerpUnclamped(f1, f2, t); + public static float Lerp(ref float f1, float f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + public static Vector2 Lerp(Vector2 f1, Vector2 f2, float t) => Vector2.LerpUnclamped(f1, f2, t); + public static Vector2 Lerp(ref Vector2 f1, Vector2 f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + public static Vector3 Lerp(Vector3 f1, Vector3 f2, float t) => Vector3.LerpUnclamped(f1, f2, t); + public static Vector3 Lerp(ref Vector3 f1, Vector3 f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + public static Color Lerp(Color f1, Color f2, float t) => Color.LerpUnclamped(f1, f2, t); + public static Color Lerp(ref Color f1, Color f2, float t) + { + return f1 = Lerp(f1, f2, t); + } + + + public static float Lerp(float current, float target, float speed, float deltaTime) => Mathf.Lerp(current, target, GetLerpT(speed, deltaTime)); + public static float Lerp(ref float current, float target, float speed, float deltaTime) + { + return current = Lerp(current, target, speed, deltaTime); + } + + public static Vector2 Lerp(Vector2 current, Vector2 target, float speed, float deltaTime) => Vector2.Lerp(current, target, GetLerpT(speed, deltaTime)); + public static Vector2 Lerp(ref Vector2 current, Vector2 target, float speed, float deltaTime) + { + return current = Lerp(current, target, speed, deltaTime); + } + + public static Vector3 Lerp(Vector3 current, Vector3 target, float speed, float deltaTime) => Vector3.Lerp(current, target, GetLerpT(speed, deltaTime)); + public static Vector3 Lerp(ref Vector3 current, Vector3 target, float speed, float deltaTime) + { + return current = Lerp(current, target, speed, deltaTime); + } + + public static float SmoothDamp(float current, float target, float speed, ref float derivative, float deltaTime, float maxSpeed) => Mathf.SmoothDamp(current, target, ref derivative, .5f / speed, maxSpeed, deltaTime); + public static float SmoothDamp(float current, float target, float speed, ref float derivative, float deltaTime) + { + return Mathf.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + } + public static float SmoothDamp(float current, float target, float speed, ref float derivative) + { + return SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative, float deltaTime, float maxSpeed) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime, maxSpeed); + } + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative, float deltaTime) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + } + public static float SmoothDamp(ref float current, float target, float speed, ref float derivative) + { + return current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) => Vector2.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector2 SmoothDamp(Vector2 current, Vector2 target, float speed, ref Vector2 derivative) + { + return SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative, float deltaTime) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + } + public static Vector2 SmoothDamp(ref Vector2 current, Vector2 target, float speed, ref Vector2 derivative) + { + return current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) => Vector3.SmoothDamp(current, target, ref derivative, .5f / speed, Mathf.Infinity, deltaTime); + public static Vector3 SmoothDamp(Vector3 current, Vector3 target, float speed, ref Vector3 derivative) + { + return SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative, float deltaTime) + { + return current = SmoothDamp(current, target, speed, ref derivative, deltaTime); + } + public static Vector3 SmoothDamp(ref Vector3 current, Vector3 target, float speed, ref Vector3 derivative) + { + return current = SmoothDamp(current, target, speed, ref derivative, Time.deltaTime); + } + + + public static float GetLerpT(float lerpSpeed, float deltaTime) => 1 - Mathf.Exp(-lerpSpeed * 2f * deltaTime); + public static float GetLerpT(float lerpSpeed) + { + return GetLerpT(lerpSpeed, Time.deltaTime); + } + + + + } + + + public static float DistanceTo(this float f1, float f2) => Mathf.Abs(f1 - f2); + public static float DistanceTo(this Vector2 f1, Vector2 f2) => (f1 - f2).magnitude; + public static float DistanceTo(this Vector3 f1, Vector3 f2) => (f1 - f2).magnitude; + + public static float Sign(this float f) => f == 0 ? 0 : Mathf.Sign(f); + + public static int Abs(this int f) => Mathf.Abs(f); + public static float Abs(this float f) => Mathf.Abs(f); + + public static int Clamp(this int f, int f0, int f1) => Mathf.Clamp(f, f0, f1); + public static float Clamp(this float f, float f0, float f1) => Mathf.Clamp(f, f0, f1); + + + public static float Clamp01(this float f) => Mathf.Clamp(f, 0, 1); + public static Vector2 Clamp01(this Vector2 f) => new(f.x.Clamp01(), f.y.Clamp01()); + public static Vector3 Clamp01(this Vector3 f) => new(f.x.Clamp01(), f.y.Clamp01(), f.z.Clamp01()); + + + public static int Pow(this int f, int pow) => (int)Mathf.Pow(f, pow); + public static float Pow(this float f, float pow) => Mathf.Pow(f, pow); + + public static float Round(this float f) => Mathf.Round(f); + public static float Ceil(this float f) => Mathf.Ceil(f); + public static float Floor(this float f) => Mathf.Floor(f); + + public static int RoundToInt(this float f) => Mathf.RoundToInt(f); + public static int CeilToInt(this float f) => Mathf.CeilToInt(f); + public static int FloorToInt(this float f) => Mathf.FloorToInt(f); + + public static int ToInt(this float f) => (int)f; + public static float ToFloat(this int f) => (float)f; + public static float ToFloat(this double f) => (float)f; + + + + public static float Sqrt(this float f) => Mathf.Sqrt(f); + + public static int Max(this int f, int ff) => Mathf.Max(f, ff); + public static int Min(this int f, int ff) => Mathf.Min(f, ff); + public static float Max(this float f, float ff) => Mathf.Max(f, ff); + public static float Min(this float f, float ff) => Mathf.Min(f, ff); + + public static float ClampMin(this float f, float limitMin) => Mathf.Max(f, limitMin); + public static float ClampMax(this float f, float limitMax) => Mathf.Min(f, limitMax); + + public static Vector3 ClampMaxMagnitude(this Vector3 v, float maxMagnitude) + { + if (v.sqrMagnitude <= maxMagnitude * maxMagnitude) + return v; + else + return v.normalized * maxMagnitude; + } + + + public static float Loop(this float f, float boundMin, float boundMax) + { + while (f < boundMin) f += boundMax - boundMin; + while (f > boundMax) f -= boundMax - boundMin; + return f; + } + public static float Loop(this float f, float boundMax) => f.Loop(0, boundMax); + + public static float PingPong(this float f, float boundMin, float boundMax) => boundMin + Mathf.PingPong(f - boundMin, boundMax - boundMin); + public static float PingPong(this float f, float boundMax) => f.PingPong(0, boundMax); + + + public static float Dot(this Vector3 v1, Vector3 v2) => Vector3.Dot(v1, v2); + + public static Vector3 Cross(this Vector3 v1, Vector3 v2) => Vector3.Cross(v1, v2); + + + public static Vector2 ProjectOn(this Vector2 v, Vector2 on) => Vector3.Project(v, on); + public static Vector3 ProjectOn(this Vector3 v, Vector3 on) => Vector3.Project(v, on); + + public static Vector3 ProjectOnPlane(this Vector3 v, Vector3 normal) => Vector3.ProjectOnPlane(v, normal); + + + public static float AngleTo(this Vector2 v, Vector2 to) => Vector2.Angle(v, to); + + public static Vector2 Rotate(this Vector2 v, float deg) => Quaternion.AngleAxis(deg, Vector3.forward) * v; + + public static float Smoothstep(this float f) { f = f.Clamp01(); return f * f * (3 - 2 * f); } + + public static float InverseLerp(this Vector2 v, Vector2 a, Vector2 b) + { + var ab = b - a; + var av = v - a; + return Vector2.Dot(av, ab) / Vector2.Dot(ab, ab); + } + + + public static bool IsOdd(this int i) => i % 2 == 1; + public static bool IsEven(this int i) => i % 2 == 0; + + public static bool IsInRange(this int i, int a, int b) => i >= a && i <= b; + public static bool IsInRange(this float i, float a, float b) => i >= a && i <= b; + + public static bool IsInRangeOf(this int i, IList list) => i.IsInRange(0, list.Count - 1); + public static bool IsInRangeOf(this int i, T[] array) => i.IsInRange(0, array.Length - 1); + + public static bool Approx(this float f1, float f2) => Mathf.Approximately(f1, f2); + + + + [System.Serializable] + public class GaussianKernel + { + public static float[,] GenerateArray(int size, float sharpness = .5f) + { + float[,] kr = new float[size, size]; + + if (size == 1) { kr[0, 0] = 1; return kr; } + + + var sigma = 1f - Mathf.Pow(sharpness, .1f) * .99999f; + var radius = (size / 2f).FloorToInt(); + + + var a = -2f * radius * radius / Mathf.Log(sigma); + var sum = 0f; + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + var rX = size % 2 == 1 ? (x - radius) : (x - radius) + .5f; + var rY = size % 2 == 1 ? (y - radius) : (y - radius) + .5f; + var dist = Mathf.Sqrt(rX * rX + rY * rY); + kr[x, y] = Mathf.Exp(-dist * dist / a); + sum += kr[x, y]; + } + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + kr[x, y] /= sum; + + return kr; + } + + + + public GaussianKernel(bool isEvenSize = false, int radius = 7, float sharpness = .5f) + { + this.isEvenSize = isEvenSize; + this.radius = radius; + this.sharpness = sharpness; + } + + public bool isEvenSize = false; + public int radius = 7; + public float sharpness = .5f; + + public int size => radius * 2 + (isEvenSize ? 0 : 1); + public float sigma => 1 - Mathf.Pow(sharpness, .1f) * .99999f; + + public float[,] Array2d() // todo test and use GenerateArray + { + float[,] kr = new float[size, size]; + + if (size == 1) { kr[0, 0] = 1; return kr; } + + var a = -2f * radius * radius / Mathf.Log(sigma); + var sum = 0f; + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + var rX = size % 2 == 1 ? (x - radius) : (x - radius) + .5f; + var rY = size % 2 == 1 ? (y - radius) : (y - radius) + .5f; + var dist = Mathf.Sqrt(rX * rX + rY * rY); + kr[x, y] = Mathf.Exp(-dist * dist / a); + sum += kr[x, y]; + } + + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + kr[x, y] /= sum; + + return kr; + } + public float[] ArrayFlat() + { + var gk = Array2d(); + float[] flat = new float[size * size]; + + for (int i = 0; i < size; i++) + for (int j = 0; j < size; j++) + flat[(i * size + j)] = gk[i, j]; + + return flat; + } + + } + + + + + + + + #endregion + + #region Rects + + + public static Rect Resize(this Rect rect, float px) { rect.x += px; rect.y += px; rect.width -= px * 2; rect.height -= px * 2; return rect; } + + public static Rect SetPos(this Rect rect, Vector2 v) => rect.SetPos(v.x, v.y); + public static Rect SetPos(this Rect rect, float x, float y) { rect.x = x; rect.y = y; return rect; } + + public static Rect SetX(this Rect rect, float x) => rect.SetPos(x, rect.y); + public static Rect SetY(this Rect rect, float y) => rect.SetPos(rect.x, y); + public static Rect SetXMax(this Rect rect, float xMax) { rect.xMax = xMax; return rect; } + public static Rect SetYMax(this Rect rect, float yMax) { rect.yMax = yMax; return rect; } + + public static Rect SetMidPos(this Rect r, Vector2 v) => r.SetPos(v).MoveX(-r.width / 2).MoveY(-r.height / 2); + public static Rect SetMidPos(this Rect r, float x, float y) => r.SetMidPos(new Vector2(x, y)); + + public static Rect Move(this Rect rect, Vector2 v) { rect.position += v; return rect; } + public static Rect Move(this Rect rect, float x, float y) { rect.x += x; rect.y += y; return rect; } + public static Rect MoveX(this Rect rect, float px) { rect.x += px; return rect; } + public static Rect MoveY(this Rect rect, float px) { rect.y += px; return rect; } + + public static Rect SetWidth(this Rect rect, float f) { rect.width = f; return rect; } + public static Rect SetWidthFromMid(this Rect rect, float px) { rect.x += rect.width / 2; rect.width = px; rect.x -= rect.width / 2; return rect; } + public static Rect SetWidthFromRight(this Rect rect, float px) { rect.x += rect.width; rect.width = px; rect.x -= rect.width; return rect; } + + public static Rect SetHeight(this Rect rect, float f) { rect.height = f; return rect; } + public static Rect SetHeightFromMid(this Rect rect, float px) { rect.y += rect.height / 2; rect.height = px; rect.y -= rect.height / 2; return rect; } + public static Rect SetHeightFromBottom(this Rect rect, float px) { rect.y += rect.height; rect.height = px; rect.y -= rect.height; return rect; } + + public static Rect AddWidth(this Rect rect, float f) => rect.SetWidth(rect.width + f); + public static Rect AddWidthFromMid(this Rect rect, float f) => rect.SetWidthFromMid(rect.width + f); + public static Rect AddWidthFromRight(this Rect rect, float f) => rect.SetWidthFromRight(rect.width + f); + + public static Rect AddHeight(this Rect rect, float f) => rect.SetHeight(rect.height + f); + public static Rect AddHeightFromMid(this Rect rect, float f) => rect.SetHeightFromMid(rect.height + f); + public static Rect AddHeightFromBottom(this Rect rect, float f) => rect.SetHeightFromBottom(rect.height + f); + + public static Rect SetSize(this Rect rect, Vector2 v) => rect.SetWidth(v.x).SetHeight(v.y); + public static Rect SetSize(this Rect rect, float w, float h) => rect.SetWidth(w).SetHeight(h); + public static Rect SetSize(this Rect rect, float f) { rect.height = rect.width = f; return rect; } + + public static Rect SetSizeFromMid(this Rect r, Vector2 v) => r.Move(r.size / 2).SetSize(v).Move(-v / 2); + public static Rect SetSizeFromMid(this Rect r, float x, float y) => r.SetSizeFromMid(new Vector2(x, y)); + public static Rect SetSizeFromMid(this Rect r, float f) => r.SetSizeFromMid(new Vector2(f, f)); + + public static Rect AlignToPixelGrid(this Rect r) => GUIUtility.AlignRectToDevice(r); + + + + + + #endregion + + #region Vectors + + + public static Vector2 AddX(this Vector2 v, float f) => new(v.x + f, v.y + 0); + public static Vector2 AddY(this Vector2 v, float f) => new(v.x + 0, v.y + f); + + public static Vector3 AddX(this Vector3 v, float f) => new(v.x + f, v.y + 0, v.z + 0); + public static Vector3 AddY(this Vector3 v, float f) => new(v.x + 0, v.y + f, v.z + 0); + public static Vector3 AddZ(this Vector3 v, float f) => new(v.x + 0, v.y + 0, v.z + f); + + public static Vector3 SetX(this Vector3 v, float f) => new(f, v.y, v.z); + public static Vector3 SetY(this Vector3 v, float f) => new(v.x, f, v.z); + public static Vector3 SetZ(this Vector3 v, float f) => new(v.x, v.y, f); + + public static Vector2 xx(this Vector3 v) { return new Vector2(v.x, v.x); } + public static Vector2 xy(this Vector3 v) { return new Vector2(v.x, v.y); } + public static Vector2 xz(this Vector3 v) { return new Vector2(v.x, v.z); } + public static Vector2 yx(this Vector3 v) { return new Vector2(v.y, v.x); } + public static Vector2 yy(this Vector3 v) { return new Vector2(v.y, v.y); } + public static Vector2 yz(this Vector3 v) { return new Vector2(v.y, v.z); } + public static Vector2 zx(this Vector3 v) { return new Vector2(v.z, v.x); } + public static Vector2 zy(this Vector3 v) { return new Vector2(v.z, v.y); } + public static Vector2 zz(this Vector3 v) { return new Vector2(v.z, v.z); } + + + + + + #endregion + + #region Colors + + + public class ColorUtils + { + public static Color HSLToRGB(float h, float s, float l) + { + float hue2Rgb(float v1, float v2, float vH) + { + if (vH < 0f) + vH += 1f; + + if (vH > 1f) + vH -= 1f; + + if (6f * vH < 1f) + return v1 + (v2 - v1) * 6f * vH; + + if (2f * vH < 1f) + return v2; + + if (3f * vH < 2f) + return v1 + (v2 - v1) * (2f / 3f - vH) * 6f; + + return v1; + } + + if (s.Approx(0)) return new Color(l, l, l); + + float k1; + + if (l < .5f) + k1 = l * (1f + s); + else + k1 = l + s - s * l; + + + var k2 = 2f * l - k1; + + float r, g, b; + r = hue2Rgb(k2, k1, h + 1f / 3); + g = hue2Rgb(k2, k1, h); + b = hue2Rgb(k2, k1, h - 1f / 3); + + return new Color(r, g, b); + } + public static Color LCHtoRGB(float l, float c, float h) + { + l *= 100; + c *= 100; + h *= 360; + + double xw = 0.948110; + double yw = 1.00000; + double zw = 1.07304; + + float a = c * Mathf.Cos(Mathf.Deg2Rad * h); + float b = c * Mathf.Sin(Mathf.Deg2Rad * h); + + float fy = (l + 16) / 116; + float fx = fy + (a / 500); + float fz = fy - (b / 200); + + float x = (float)System.Math.Round(xw * ((System.Math.Pow(fx, 3) > 0.008856) ? System.Math.Pow(fx, 3) : ((fx - 16 / 116) / 7.787)), 5); + float y = (float)System.Math.Round(yw * ((System.Math.Pow(fy, 3) > 0.008856) ? System.Math.Pow(fy, 3) : ((fy - 16 / 116) / 7.787)), 5); + float z = (float)System.Math.Round(zw * ((System.Math.Pow(fz, 3) > 0.008856) ? System.Math.Pow(fz, 3) : ((fz - 16 / 116) / 7.787)), 5); + + float r = x * 3.2406f - y * 1.5372f - z * 0.4986f; + float g = -x * 0.9689f + y * 1.8758f + z * 0.0415f; + float bValue = x * 0.0557f - y * 0.2040f + z * 1.0570f; + + r = r > 0.0031308f ? 1.055f * (float)System.Math.Pow(r, 1 / 2.4) - 0.055f : r * 12.92f; + g = g > 0.0031308f ? 1.055f * (float)System.Math.Pow(g, 1 / 2.4) - 0.055f : g * 12.92f; + bValue = bValue > 0.0031308f ? 1.055f * (float)System.Math.Pow(bValue, 1 / 2.4) - 0.055f : bValue * 12.92f; + + // r = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, r))); + // g = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, g))); + // bValue = (float)System.Math.Round(System.Math.Max(0, System.Math.Min(1, bValue))); + + return new Color(r, g, bValue); + + } + + } + + + public static Color Greyscale(float brightness, float alpha = 1) => new(brightness, brightness, brightness, alpha); + + public static Color SetAlpha(this Color color, float alpha) { color.a = alpha; return color; } + public static Color MultiplyAlpha(this Color color, float k) { color.a *= k; return color; } + + + + + + #endregion + + #region Objects + + + public static Object[] FindObjects(Type type) + { +#if UNITY_2023_1_OR_NEWER + return Object.FindObjectsByType(type, FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(type); +#endif + } + public static T[] FindObjects() where T : Object + { +#if UNITY_2023_1_OR_NEWER + return Object.FindObjectsByType(FindObjectsSortMode.None); +#else + return Object.FindObjectsOfType(); +#endif + } + + public static void Destroy(this Object r) + { + if (Application.isPlaying) + Object.Destroy(r); + else + Object.DestroyImmediate(r); + + } + + public static void DestroyImmediate(this Object o) => Object.DestroyImmediate(o); + + + + + + #endregion + + #region GameObjects + + + public static bool IsPrefab(this GameObject go) => go.scene.name == null || go.scene.name == go.name; + + public static Bounds GetBounds(this GameObject go, bool local = false) + { + Bounds bounds = default; + + foreach (var r in go.GetComponentsInChildren()) + { + var b = local ? r.gameObject.GetComponent().sharedMesh.bounds : r.bounds; + + if (bounds == default) + bounds = b; + else + bounds.Encapsulate(b); + } + + foreach (var r in go.GetComponentsInChildren()) + { + var b = local ? new Bounds(r.terrainData.size / 2, r.terrainData.size) : new Bounds(r.transform.position + r.terrainData.size / 2, r.terrainData.size); + + if (bounds == default) + bounds = b; + else + bounds.Encapsulate(new Bounds(r.transform.position + r.terrainData.size / 2, r.terrainData.size)); + + } + + foreach (var r in go.GetComponentsInChildren()) + { + var localBounds = RectTransformUtility.CalculateRelativeRectTransformBounds(r); + var worldBounds = new Bounds(r.TransformPoint(localBounds.center), r.TransformVector(localBounds.size)); + + if (bounds == default) + bounds = worldBounds; + else + bounds.Encapsulate(worldBounds); + + } + + + if (bounds == default) + bounds.center = go.transform.position; + + return bounds; + } + + + + + + #endregion + + #region Text + + + public static bool IsEmpty(this string s) => s == ""; + public static bool IsNullOrEmpty(this string s) => string.IsNullOrEmpty(s); + + public static bool IsLower(this char c) => System.Char.IsLower(c); + public static bool IsUpper(this char c) => System.Char.IsUpper(c); + public static bool IsDigit(this char c) => System.Char.IsDigit(c); + public static bool IsLetter(this char c) => System.Char.IsLetter(c); + public static bool IsWhitespace(this char c) => System.Char.IsWhiteSpace(c); + + public static char ToLower(this char c) => System.Char.ToLower(c); + public static char ToUpper(this char c) => System.Char.ToUpper(c); + + + + public static string Decamelcase(this string s) + { + return Regex.Replace(Regex.Replace(s, @"(\P{Ll})(\P{Ll}\p{Ll})", "$1 $2"), @"(\p{Ll})(\P{Ll})", "$1 $2"); + } + public static string FormatVariableName(this string s, bool lowercaseFollowingWords = true) + { + return string.Join(" ", s.Decamelcase() + .Split(' ') + .Select(r => new[] { "", "and", "or", "with", "without", "by", "from" }.Contains(r.ToLower()) || (lowercaseFollowingWords && !s.Trim().StartsWith(r)) ? r.ToLower() + : r.Substring(0, 1).ToUpper() + r.Substring(1))).Trim(' '); + } + + public static string Remove(this string s, string toRemove) + { + if (toRemove == "") return s; + return s.Replace(toRemove, ""); + } + + + + + + + #endregion + + #region Paths + + + public static bool HasParentPath(this string path) => path.LastIndexOf('/') > 0; + public static string GetParentPath(this string path) => path.HasParentPath() ? path.Substring(0, path.LastIndexOf('/')) : ""; + + public static string GetFilename(this string path, bool withExtension = false) => withExtension ? Path.GetFileName(path) : Path.GetFileNameWithoutExtension(path); + public static string GetExtension(this string path) => Path.GetExtension(path); + + + public static string ToGlobalPath(this string localPath) => Application.dataPath + "/" + localPath.Substring(0, localPath.Length - 1); + public static string ToLocalPath(this string globalPath) => "Assets" + globalPath.Replace(Application.dataPath, ""); + + + + public static string CombinePath(this string p1, string p2, bool useBackslashOnWindows = false) + { + if (useBackslashOnWindows) // false by default because all paths in unity use forward slashes, even on Windows + return Path.Combine(p1, p2); + else + return Path.Combine(p1, p2).Replace('\\', '/'); + + } + + public static bool IsSubpathOf(this string path, string of) => path.StartsWith(of + "/") || of == ""; + + public static string GetDirectory(this string pathOrDirectory) + { + var directory = pathOrDirectory.Contains('.') ? pathOrDirectory.Substring(0, pathOrDirectory.LastIndexOf('/')) : pathOrDirectory; + + if (directory.Contains('.')) + directory = directory.Substring(0, directory.LastIndexOf('/')); + + return directory; + + } + + public static bool DirectoryExists(this string pathOrDirectory) => Directory.Exists(pathOrDirectory.GetDirectory()); + + public static string EnsureDirExists(this string pathOrDirectory) // todo to EnsureDirectoryExists + { + var directory = pathOrDirectory.GetDirectory(); + + if (directory.HasParentPath() && !Directory.Exists(directory.GetParentPath())) + EnsureDirExists(directory.GetParentPath()); + + if (!Directory.Exists(directory)) + Directory.CreateDirectory(directory); + + return pathOrDirectory; + + } + + + + public static string ClearDir(this string dir) + { + if (!Directory.Exists(dir)) return dir; + + var diri = new DirectoryInfo(dir); + foreach (var r in diri.EnumerateFiles()) r.Delete(); + foreach (var r in diri.EnumerateDirectories()) r.Delete(true); + + return dir; + } + + + + + + +#if UNITY_EDITOR + + public static string EnsurePathIsUnique(this string path) + { + if (!path.DirectoryExists()) return path; + + var s = AssetDatabase.GenerateUniqueAssetPath(path); // returns empty if parent dir doesnt exist + + return s == "" ? path : s; + + } + + public static void EnsureDirExistsAndRevealInFinder(string dir) + { + EnsureDirExists(dir); + UnityEditor.EditorUtility.OpenWithDefaultApp(dir); + } + +#endif + + + + #endregion + + #region AssetDatabase + +#if UNITY_EDITOR + + public static AssetImporter GetImporter(this Object t) => AssetImporter.GetAtPath(AssetDatabase.GetAssetPath(t)); + + public static string ToPath(this string guid) => AssetDatabase.GUIDToAssetPath(guid); // returns empty string if not found + public static List ToPaths(this IEnumerable guids) => guids.Select(r => r.ToPath()).ToList(); + + + public static string ToGuid(this string pathInProject) => AssetDatabase.AssetPathToGUID(pathInProject); + public static List ToGuids(this IEnumerable pathsInProject) => pathsInProject.Select(r => r.ToGuid()).ToList(); + + public static string GetPath(this Object o) => AssetDatabase.GetAssetPath(o); + public static string GetGuid(this Object o) => AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(o)); + + public static string GetScriptPath(string scriptName) => AssetDatabase.FindAssets("t: script " + scriptName, null).FirstOrDefault()?.ToPath() ?? "scirpt not found"; // todonow to editorutils + + + +#endif + + + + + + #endregion + + #region Serialization + + + [System.Serializable] + public class SerializableDictionary : Dictionary, ISerializationCallbackReceiver + { + [SerializeField] List keys = new(); + [SerializeField] List values = new(); + + public void OnBeforeSerialize() + { + keys.Clear(); + values.Clear(); + + foreach (KeyValuePair kvp in this) + { + keys.Add(kvp.Key); + values.Add(kvp.Value); + } + + } + public void OnAfterDeserialize() + { + this.Clear(); + + for (int i = 0; i < keys.Count; i++) + this[keys[i]] = values[i]; + + } + + } + + +#if UNITY_EDITOR + + public static object GetBoxedValue(this SerializedProperty p) + { + +#if UNITY_2022_1_OR_NEWER + switch (p.propertyType) + { + case SerializedPropertyType.Integer: + switch (p.numericType) + { + case SerializedPropertyNumericType.Int8: return (sbyte)p.intValue; + case SerializedPropertyNumericType.UInt8: return (byte)p.uintValue; + case SerializedPropertyNumericType.Int16: return (short)p.intValue; + case SerializedPropertyNumericType.UInt16: return (ushort)p.uintValue; + case SerializedPropertyNumericType.Int32: return p.intValue; + case SerializedPropertyNumericType.UInt32: return p.uintValue; + case SerializedPropertyNumericType.Int64: return p.longValue; + case SerializedPropertyNumericType.UInt64: return p.ulongValue; + default: return p.intValue; + + } + + case SerializedPropertyType.Float: + if (p.numericType == SerializedPropertyNumericType.Double) + return p.doubleValue; + else + return p.floatValue; + + case SerializedPropertyType.Hash128: return p.hash128Value; + case SerializedPropertyType.Character: return (ushort)p.uintValue; + case SerializedPropertyType.Gradient: return p.gradientValue; + case SerializedPropertyType.ManagedReference: return p.managedReferenceValue; + + + } +#endif + + switch (p.propertyType) + { + case SerializedPropertyType.Integer: return p.intValue; + case SerializedPropertyType.Float: return p.floatValue; + case SerializedPropertyType.Vector2: return p.vector2Value; + case SerializedPropertyType.Vector3: return p.vector3Value; + case SerializedPropertyType.Vector4: return p.vector4Value; + case SerializedPropertyType.Vector2Int: return p.vector2IntValue; + case SerializedPropertyType.Vector3Int: return p.vector3IntValue; + case SerializedPropertyType.Quaternion: return p.quaternionValue; + case SerializedPropertyType.Rect: return p.rectValue; + case SerializedPropertyType.RectInt: return p.rectIntValue; + case SerializedPropertyType.Bounds: return p.boundsValue; + case SerializedPropertyType.BoundsInt: return p.boundsIntValue; + case SerializedPropertyType.Enum: return p.enumValueIndex; + case SerializedPropertyType.Boolean: return p.boolValue; + case SerializedPropertyType.String: return p.stringValue; + case SerializedPropertyType.Color: return p.colorValue; + case SerializedPropertyType.ArraySize: return p.intValue; + case SerializedPropertyType.Character: return (ushort)p.intValue; + case SerializedPropertyType.AnimationCurve: return p.animationCurveValue; + case SerializedPropertyType.ObjectReference: return p.objectReferenceValue; + case SerializedPropertyType.ExposedReference: return p.exposedReferenceValue; + case SerializedPropertyType.FixedBufferSize: return p.intValue; + case SerializedPropertyType.LayerMask: return (LayerMask)p.intValue; + + } + + + return _noValue; + + } + public static void SetBoxedValue(this SerializedProperty p, object value) + { + if (value == _noValue) return; + + try + { + +#if UNITY_2022_1_OR_NEWER + switch (p.propertyType) + { + case SerializedPropertyType.ArraySize: + case SerializedPropertyType.Integer: + if (p.numericType == SerializedPropertyNumericType.UInt64) + p.ulongValue = System.Convert.ToUInt64(value); + else + p.longValue = System.Convert.ToInt64(value); + return; + + case SerializedPropertyType.Float: + if (p.numericType == SerializedPropertyNumericType.Double) + p.doubleValue = System.Convert.ToDouble(value); + else + p.floatValue = System.Convert.ToSingle(value); + return; + + case SerializedPropertyType.Character: p.uintValue = System.Convert.ToUInt16(value); return; + case SerializedPropertyType.Gradient: p.gradientValue = (Gradient)value; return; + case SerializedPropertyType.Hash128: p.hash128Value = (Hash128)value; return; + + } +#endif + + switch (p.propertyType) + { + case SerializedPropertyType.ArraySize: + case SerializedPropertyType.Integer: p.intValue = System.Convert.ToInt32(value); return; + case SerializedPropertyType.Float: p.floatValue = System.Convert.ToSingle(value); return; + case SerializedPropertyType.Vector2: p.vector2Value = (Vector2)value; return; + case SerializedPropertyType.Vector3: p.vector3Value = (Vector3)value; return; + case SerializedPropertyType.Vector4: p.vector4Value = (Vector4)value; return; + case SerializedPropertyType.Vector2Int: p.vector2IntValue = (Vector2Int)value; return; + case SerializedPropertyType.Vector3Int: p.vector3IntValue = (Vector3Int)value; return; + case SerializedPropertyType.Quaternion: p.quaternionValue = (Quaternion)value; return; + case SerializedPropertyType.Rect: p.rectValue = (Rect)value; return; + case SerializedPropertyType.RectInt: p.rectIntValue = (RectInt)value; return; + case SerializedPropertyType.Bounds: p.boundsValue = (Bounds)value; return; + case SerializedPropertyType.BoundsInt: p.boundsIntValue = (BoundsInt)value; return; + case SerializedPropertyType.String: p.stringValue = (string)value; return; + case SerializedPropertyType.Boolean: p.boolValue = (bool)value; return; + case SerializedPropertyType.Enum: p.enumValueIndex = (int)value; return; + case SerializedPropertyType.Color: p.colorValue = (Color)value; return; + case SerializedPropertyType.AnimationCurve: p.animationCurveValue = (AnimationCurve)value; return; + case SerializedPropertyType.ObjectReference: p.objectReferenceValue = (UnityEngine.Object)value; return; + case SerializedPropertyType.ExposedReference: p.exposedReferenceValue = (UnityEngine.Object)value; return; + case SerializedPropertyType.ManagedReference: p.managedReferenceValue = value; return; + + case SerializedPropertyType.LayerMask: + try + { + p.intValue = ((LayerMask)value).value; return; + } + catch (System.InvalidCastException) + { + p.intValue = System.Convert.ToInt32(value); return; + } + + } + + } + catch { } + + } + + static object _noValue = new(); + +#endif + + + + + #endregion + + #region GlobalID + +#if UNITY_EDITOR + + [System.Serializable] + public struct GlobalID : System.IEquatable + { + public Object GetObject() => GlobalObjectId.GlobalObjectIdentifierToObjectSlow(globalObjectId); + public int GetObjectInstanceId() => GlobalObjectId.GlobalObjectIdentifierToInstanceIDSlow(globalObjectId); + + + public int idType => globalObjectId.identifierType; + public string guid => globalObjectId.assetGUID.ToString(); + public ulong fileId => globalObjectId.targetObjectId; + public ulong prefabId => globalObjectId.targetPrefabId; + + public bool isNull => globalObjectId.identifierType == 0; + public bool isAsset => globalObjectId.identifierType == 1; + public bool isSceneObject => globalObjectId.identifierType == 2; + + public GlobalObjectId globalObjectId => _globalObjectId.Equals(default) && globalObjectIdString != null && GlobalObjectId.TryParse(globalObjectIdString, out var r) ? _globalObjectId = r : _globalObjectId; + public GlobalObjectId _globalObjectId; + + public GlobalID(Object o) => globalObjectIdString = (_globalObjectId = GlobalObjectId.GetGlobalObjectIdSlow(o)).ToString(); + public GlobalID(string s) => globalObjectIdString = GlobalObjectId.TryParse(s, out _globalObjectId) ? s : s; + + public string globalObjectIdString; + + + + public bool Equals(GlobalID other) => this.globalObjectIdString.Equals(other.globalObjectIdString); + + public static bool operator ==(GlobalID a, GlobalID b) => a.Equals(b); + public static bool operator !=(GlobalID a, GlobalID b) => !a.Equals(b); + + public override bool Equals(object other) => other is GlobalID otherglobalID && this.Equals(otherglobalID); + public override int GetHashCode() => globalObjectIdString == null ? 0 : globalObjectIdString.GetHashCode(); + + + public override string ToString() => globalObjectIdString; + + + + + public GlobalID UnpackForPrefab() + { + var unpackedFileId = (this.fileId ^ this.prefabId) & 0x7fffffffffffffff; + + var unpackedGId = new GlobalID($"GlobalObjectId_V1-{this.idType}-{this.guid}-{unpackedFileId}-0"); + + return unpackedGId; + + } + + } + + public static GlobalID GetGlobalID(this Object o) => new(o); + public static GlobalID[] GetGlobalIDs(this IEnumerable instanceIds) + { + var unityGlobalIds = new GlobalObjectId[instanceIds.Count()]; + + GlobalObjectId.GetGlobalObjectIdsSlow(instanceIds.ToArray(), unityGlobalIds); + + var globalIds = unityGlobalIds.Select(r => new GlobalID(r.ToString())); + + return globalIds.ToArray(); + + } + + public static Object[] GetObjects(this IEnumerable globalIDs) + { + var goids = globalIDs.Select(r => r.globalObjectId).ToArray(); + + var objects = new Object[goids.Length]; + + GlobalObjectId.GlobalObjectIdentifiersToObjectsSlow(goids, objects); + + return objects; + + } + public static int[] GetObjectInstanceIds(this IEnumerable globalIDs) + { + var goids = globalIDs.Select(r => r.globalObjectId).ToArray(); + + var iids = new int[goids.Length]; + + GlobalObjectId.GlobalObjectIdentifiersToInstanceIDsSlow(goids, iids); + + return iids; + + } + + +#endif + + + + + #endregion + + #region Editor + +#if UNITY_EDITOR + + + public static class EditorUtils + { + + public static void OpenFolder(string path) + { + var folder = AssetDatabase.LoadAssetAtPath(path, typeof(Object)); + + var t = typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser"); + var w = (EditorWindow)t.GetField("s_LastInteractedProjectBrowser").GetValue(null); + + var m_ListAreaState = t.GetField("m_ListAreaState", maxBindingFlags).GetValue(w); + + m_ListAreaState.GetType().GetField("m_SelectedInstanceIDs").SetValue(m_ListAreaState, new List { folder.GetInstanceID() }); + + t.GetMethod("OpenSelectedFolders", maxBindingFlags).Invoke(null, null); + + } + + public static void PingObject(Object o, bool select = false, bool focusProjectWindow = true) + { + if (select) + { + Selection.activeObject = null; + Selection.activeObject = o; + } + if (focusProjectWindow) EditorUtility.FocusProjectWindow(); + EditorGUIUtility.PingObject(o); + + } + public static void PingObject(string guid, bool select = false, bool focusProjectWindow = true) => PingObject(AssetDatabase.LoadAssetAtPath(guid.ToPath())); + + public static EditorWindow OpenObjectPicker(Object obj = null, bool allowSceneObjects = false, string searchFilter = "", int controlID = 0) where T : Object + { + EditorGUIUtility.ShowObjectPicker(obj, allowSceneObjects, searchFilter, controlID); + + return Resources.FindObjectsOfTypeAll(typeof(Editor).Assembly.GetType("UnityEditor.ObjectSelector")).FirstOrDefault() as EditorWindow; + + } + public static EditorWindow OpenColorPicker(System.Action colorChangedCallback, Color color, bool showAlpha = true, bool hdr = false) + { + typeof(Editor).Assembly.GetType("UnityEditor.ColorPicker").InvokeMethod("Show", colorChangedCallback, color, showAlpha, hdr); + + return typeof(Editor).Assembly.GetType("UnityEditor.ColorPicker").GetPropertyValue("instance"); + + } + + + + public static bool CheckUnityVersion(string versionQuery) + { + if (versionQueryCache.TryGetValue(versionQuery, out var cachedResult)) return cachedResult; + + if (versionQuery.Any(r => r.IsLetter() && !versionQuery.EndsWith(" or older") && !versionQuery.EndsWith(" or newer"))) throw new System.ArgumentException("Invalid unity version query"); + + + + + var curVersion = new string(Application.unityVersion.TakeWhile(r => !r.IsLetter()).ToArray()); + + var curMajor = int.Parse(curVersion.Split('.')[0]); + var curMinor = int.Parse(curVersion.Split('.')[1]); + var curPatch = int.Parse(curVersion.Split('.')[2]); + + + + + + var givenVersion = new string(versionQuery.TakeWhile(r => !r.IsWhitespace()).ToArray()); + + var isMinorGiven = givenVersion.Count(r => r == '.') >= 1; + var isPatchGiven = givenVersion.Count(r => r == '.') >= 2; + + var givenMajor = int.Parse(givenVersion.Split('.')[0]); + var givenMinor = isMinorGiven ? int.Parse(givenVersion.Split('.')[1]) : 0; + var givenPatch = isPatchGiven ? int.Parse(givenVersion.Split('.')[2]) : 0; + + + + + + + var curVersionCanBeNewer = versionQuery.Contains("or newer"); + var curVersionCanBeOlder = versionQuery.Contains("or older"); + + + if (curMajor > givenMajor) return versionQueryCache[versionQuery] = curVersionCanBeNewer; + if (curMajor < givenMajor) return versionQueryCache[versionQuery] = curVersionCanBeOlder; + + if (!isMinorGiven) return versionQueryCache[versionQuery] = true; + + + if (curMinor > givenMinor) return versionQueryCache[versionQuery] = curVersionCanBeNewer; + if (curMinor < givenMinor) return versionQueryCache[versionQuery] = curVersionCanBeOlder; + + if (!isPatchGiven) return versionQueryCache[versionQuery] = true; + + + if (curPatch > givenPatch) return versionQueryCache[versionQuery] = curVersionCanBeNewer; + if (curPatch < givenPatch) return versionQueryCache[versionQuery] = curVersionCanBeOlder; + + return versionQueryCache[versionQuery] = true; + + + + + // query examples: + // + // "2022.3.5 or newer" + // "2022.3.5 or older" + // "2022.3 or older" + // "2022.3" + // "2022" + + } + + static Dictionary versionQueryCache = new(); + + + + public static void SetSymbolDefinedInAsmdef(string asmdefName, string symbol, bool defined) + { + var isDefined = IsSymbolDefinedInAsmdef(asmdefName, symbol); + var shouldBeDefined = defined; + + if (shouldBeDefined && !isDefined) + DefineSymbolInAsmdef(asmdefName, symbol); + + if (!shouldBeDefined && isDefined) + UndefineSymbolInAsmdef(asmdefName, symbol); + + } + public static bool IsSymbolDefinedInAsmdef(string asmdefName, string symbol) + { + var path = AssetDatabase.FindAssets("t: asmdef " + asmdefName, null).First().ToPath(); + var importer = AssetImporter.GetAtPath(path); + + var editorType = typeof(Editor).Assembly.GetType("UnityEditor.AssemblyDefinitionImporterInspector"); + var editor = Editor.CreateEditor(importer, editorType); + + var state = editor.GetFieldValue("m_ExtraDataTargets").First(); + + + var definesList = state.GetFieldValue("versionDefines"); + var isSymbolDefined = Enumerable.Range(0, definesList.Count).Any(i => definesList[i].GetFieldValue("define") == symbol); + + + Object.DestroyImmediate(editor); + + return isSymbolDefined; + + } + + static void DefineSymbolInAsmdef(string asmdefName, string symbol) + { + var path = AssetDatabase.FindAssets("t: asmdef " + asmdefName, null).First().ToPath(); + var importer = AssetImporter.GetAtPath(path); + + var editorType = typeof(Editor).Assembly.GetType("UnityEditor.AssemblyDefinitionImporterInspector"); + var editor = Editor.CreateEditor(importer, editorType); + + var state = editor.GetFieldValue("m_ExtraDataTargets").First(); + + + var definesList = state.GetFieldValue("versionDefines"); + + var defineType = definesList.GetType().GenericTypeArguments[0]; + var newDefine = System.Activator.CreateInstance(defineType); + + newDefine.SetFieldValue("name", "Unity"); + newDefine.SetFieldValue("define", symbol); + + definesList.Add(newDefine); + + + editor.InvokeMethod("Apply"); + + Object.DestroyImmediate(editor); + + } + static void UndefineSymbolInAsmdef(string asmdefName, string symbol) + { + var path = AssetDatabase.FindAssets("t: asmdef " + asmdefName, null).First().ToPath(); + var importer = AssetImporter.GetAtPath(path); + + var editorType = typeof(Editor).Assembly.GetType("UnityEditor.AssemblyDefinitionImporterInspector"); + var editor = Editor.CreateEditor(importer, editorType); + + var state = editor.GetFieldValue("m_ExtraDataTargets").First(); + + + var definesList = state.GetFieldValue("versionDefines"); + + var defineIndex = Enumerable.Range(0, definesList.Count).First(i => definesList[i].GetFieldValue("define") == symbol); + + definesList.RemoveAt(defineIndex); + + + editor.InvokeMethod("Apply"); + + Object.DestroyImmediate(editor); + + } + + + + + public static int GetCurrendUndoGroupIndex() + { + var args = new object[] { _dummyList, 0 }; + + typeof(Undo).GetMethodInfo("GetRecords", typeof(List), typeof(int).MakeByRefType()) + .Invoke(null, args); + + + return (int)args[1]; + + } + + static List _dummyList = new(); + + + + + + public static void Hide(string path) + { + if (IsHidden(path)) return; + + if (File.Exists(path)) + File.Move(path, path + "~"); + + + path += ".meta"; + if (File.Exists(path)) + File.Move(path, path + "~"); + } + public static void Unhide(string path) + { + if (!IsHidden(path)) return; + if (path.EndsWith("~")) path = path.Substring(0, path.Length - 1); + + if (File.Exists(path + "~")) + File.Move(path + "~", path); + + path += ".meta"; + if (File.Exists(path + "~")) + File.Move(path + "~", path); + } + public static bool IsHidden(string path) => path.EndsWith("~") || File.Exists(path + "~"); + + + public static void CopyDirectoryDeep(string sourcePath, string destinationPath) + { + CopyDirectoryRecursively(sourcePath, destinationPath); + + var metas = GetFilesRecursively(destinationPath, (f) => f.EndsWith(".meta")); + var guidTable = new List<(string originalGuid, string newGuid)>(); + + foreach (string meta in metas) + { + StreamReader file = new(meta); + file.ReadLine(); + string guidLine = file.ReadLine(); + file.Close(); + string originalGuid = guidLine.Substring(6, guidLine.Length - 6); + string newGuid = GUID.Generate().ToString().Replace("-", ""); + guidTable.Add((originalGuid, newGuid)); + } + + var allFiles = GetFilesRecursively(destinationPath); + + foreach (string fileToModify in allFiles) + { + string content = File.ReadAllText(fileToModify); + + foreach (var guidPair in guidTable) + { + content = content.Replace(guidPair.originalGuid, guidPair.newGuid); + } + + File.WriteAllText(fileToModify, content); + } + + AssetDatabase.Refresh(); + } + + private static void CopyDirectoryRecursively(string sourceDirName, string destDirName) + { + DirectoryInfo dir = new(sourceDirName); + + DirectoryInfo[] dirs = dir.GetDirectories(); + + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, false); + } + + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(destDirName, subdir.Name); + CopyDirectoryRecursively(subdir.FullName, temppath); + } + } + + private static List GetFilesRecursively(string path, System.Func criteria = null, List files = null) + { + if (files == null) + { + files = new List(); + } + + files.AddRange(Directory.GetFiles(path).Where(f => criteria == null || criteria(f))); + + foreach (string directory in Directory.GetDirectories(path)) + { + GetFilesRecursively(directory, criteria, files); + } + + return files; + } + + + + + + // for non-extension methods + + } + + + public static class EditorPrefsCached + { + public static int GetInt(string key, int defaultValue = 0) + { + if (ints_byKey.ContainsKey(key)) + return ints_byKey[key]; + else + return ints_byKey[key] = EditorPrefs.GetInt(key, defaultValue); + + } + public static bool GetBool(string key, bool defaultValue = false) + { + if (bools_byKey.ContainsKey(key)) + return bools_byKey[key]; + else + return bools_byKey[key] = EditorPrefs.GetBool(key, defaultValue); + + } + public static float GetFloat(string key, float defaultValue = 0) + { + if (floats_byKey.ContainsKey(key)) + return floats_byKey[key]; + else + return floats_byKey[key] = EditorPrefs.GetFloat(key, defaultValue); + + } + public static string GetString(string key, string defaultValue = "") + { + if (strings_byKey.ContainsKey(key)) + return strings_byKey[key]; + else + return strings_byKey[key] = EditorPrefs.GetString(key, defaultValue); + + } + + public static void SetInt(string key, int value) + { + ints_byKey[key] = value; + + EditorPrefs.SetInt(key, value); + + } + public static void SetBool(string key, bool value) + { + bools_byKey[key] = value; + + EditorPrefs.SetBool(key, value); + + } + public static void SetFloat(string key, float value) + { + floats_byKey[key] = value; + + EditorPrefs.SetFloat(key, value); + + } + public static void SetString(string key, string value) + { + strings_byKey[key] = value; + + EditorPrefs.SetString(key, value); + + } + + + static Dictionary ints_byKey = new(); + static Dictionary bools_byKey = new(); + static Dictionary floats_byKey = new(); + static Dictionary strings_byKey = new(); + + } + + public static class ProjectPrefs + { + public static int GetInt(string key, int defaultValue = 0) => EditorPrefsCached.GetInt(key + projectId, defaultValue); + public static bool GetBool(string key, bool defaultValue = false) => EditorPrefsCached.GetBool(key + projectId, defaultValue); + public static float GetFloat(string key, float defaultValue = 0) => EditorPrefsCached.GetFloat(key + projectId, defaultValue); + public static string GetString(string key, string defaultValue = "") => EditorPrefsCached.GetString(key + projectId, defaultValue); + + public static void SetInt(string key, int value) => EditorPrefsCached.SetInt(key + projectId, value); + public static void SetBool(string key, bool value) => EditorPrefsCached.SetBool(key + projectId, value); + public static void SetFloat(string key, float value) => EditorPrefsCached.SetFloat(key + projectId, value); + public static void SetString(string key, string value) => EditorPrefsCached.SetString(key + projectId, value); + + + + public static bool HasKey(string key) => EditorPrefs.HasKey(key + projectId); + public static void DeleteKey(string key) => EditorPrefs.DeleteKey(key + projectId); + + + + public static int projectId => PlayerSettings.productGUID.GetHashCode(); + + } + + + + public static void RecordUndo(this Object o, string operationName = "") => Undo.RecordObject(o, operationName); + public static void Dirty(this Object o) => UnityEditor.EditorUtility.SetDirty(o); + public static void Save(this Object o) => AssetDatabase.SaveAssetIfDirty(o); + + + + public static void SelectInInspector(this Object[] objects, bool frameInHierarchy = false, bool frameInProject = false) + { + void setHierarchyLocked(bool isLocked) => allHierarchies.ForEach(r => r?.GetMemberValue("m_SceneHierarchy")?.SetMemberValue("m_RectSelectInProgress", isLocked)); + void setProjectLocked(bool isLocked) => allProjectBrowsers.ForEach(r => r?.SetMemberValue("m_InternalSelectionChange", isLocked)); + + + if (!frameInHierarchy) setHierarchyLocked(true); + if (!frameInProject) setProjectLocked(true); + + Selection.objects = objects?.ToArray(); + + if (!frameInHierarchy) EditorApplication.delayCall += () => setHierarchyLocked(false); + if (!frameInProject) EditorApplication.delayCall += () => setProjectLocked(false); + + } + public static void SelectInInspector(this Object obj, bool frameInHierarchy = false, bool frameInProject = false) => new[] { obj }.SelectInInspector(frameInHierarchy, frameInProject); + + static IEnumerable allHierarchies => _allHierarchies ??= typeof(Editor).Assembly.GetType("UnityEditor.SceneHierarchyWindow").GetFieldValue("s_SceneHierarchyWindows").Cast(); + static IEnumerable _allHierarchies; + + static IEnumerable allProjectBrowsers => _allProjectBrowsers ??= typeof(Editor).Assembly.GetType("UnityEditor.ProjectBrowser").GetFieldValue("s_ProjectBrowsers").Cast(); + static IEnumerable _allProjectBrowsers; + + + + public static void MoveTo(this EditorWindow window, Vector2 position, bool ensureFitsOnScreen = true) + { + if (!ensureFitsOnScreen) { window.position = window.position.SetPos(position); return; } + + var windowRect = window.position; + var unityWindowRect = EditorGUIUtility.GetMainWindowPosition(); + + position.x = position.x.Max(unityWindowRect.position.x); + position.y = position.y.Max(unityWindowRect.position.y); + + position.x = position.x.Min(unityWindowRect.xMax - windowRect.width); + position.y = position.y.Min(unityWindowRect.yMax - windowRect.height); + + window.position = windowRect.SetPos(position); + + } + + + +#endif + + #endregion + + } + + public static class VGUI + { + + #region Drawing + + + public static Rect Draw(this Rect rect, Color color) + { + EditorGUI.DrawRect(rect, color); + + return rect; + + } + public static Rect Draw(this Rect rect) => rect.Draw(Color.black); + + public static Rect DrawOutline(this Rect rect, Color color, float thickness = 1) + { + + rect.SetWidth(thickness).Draw(color); + rect.SetWidthFromRight(thickness).Draw(color); + + rect.SetHeight(thickness).Draw(color); + rect.SetHeightFromBottom(thickness).Draw(color); + + + return rect; + + } + public static Rect DrawOutline(this Rect rect, float thickness = 1) => rect.DrawOutline(Color.black, thickness); + + + + + public static Rect DrawRounded(this Rect rect, Color color, int cornerRadius) + { + if (!curEvent.isRepaint) return rect; + + cornerRadius = cornerRadius.Min((rect.height / 2).FloorToInt()).Min((rect.width / 2).FloorToInt()); + + if (cornerRadius < 0) return rect; + + GUIStyle style; + + void getStyle() + { + if (_roundedStylesByCornerRadius.TryGetValue(cornerRadius, out style)) return; + + var pixelsPerPoint = 2; + + var res = cornerRadius * 2 * pixelsPerPoint; + var pixels = new Color[res * res]; + + var white = Greyscale(1, 1); + var clear = Greyscale(1, 0); + var halfRes = res / 2; + + for (int x = 0; x < res; x++) + for (int y = 0; y < res; y++) + { + var sqrMagnitude = (new Vector2(x - halfRes + .5f, y - halfRes + .5f)).sqrMagnitude; + pixels[x + y * res] = sqrMagnitude <= halfRes * halfRes ? white : clear; + } + + var texture = new Texture2D(res, res); + texture.SetPropertyValue("pixelsPerPoint", pixelsPerPoint); + texture.hideFlags = HideFlags.DontSave; + texture.SetPixels(pixels); + texture.Apply(); + + + + style = new GUIStyle(); + style.normal.background = texture; + style.alignment = TextAnchor.MiddleCenter; + style.border = new RectOffset(cornerRadius, cornerRadius, cornerRadius, cornerRadius); + + + _roundedStylesByCornerRadius[cornerRadius] = style; + + } + void draw() + { + SetGUIColor(color); + + style.Draw(rect, false, false, false, false); + + ResetGUIColor(); + + } + + getStyle(); + draw(); + + return rect; + + } + public static Rect DrawRounded(this Rect rect, Color color, float cornerRadius) => rect.DrawRounded(color, cornerRadius.RoundToInt()); + + static Dictionary _roundedStylesByCornerRadius = new(); + + + + + public static Rect DrawBlurred(this Rect rect, Color color, int blurRadius) + { + if (!curEvent.isRepaint) return rect; + + var pixelsPerPoint = .5f; + // var pixelsPerPoint = 1f; + + var blurRadiusScaled = (blurRadius * pixelsPerPoint).RoundToInt().Max(1).Min(123); + + var croppedRectWidth = (rect.width * pixelsPerPoint).RoundToInt().Min(blurRadiusScaled * 2); + var croppedRectHeight = (rect.height * pixelsPerPoint).RoundToInt().Min(blurRadiusScaled * 2); + + var textureWidth = croppedRectWidth + blurRadiusScaled * 2; + var textureHeight = croppedRectHeight + blurRadiusScaled * 2; + + if (textureWidth <= 0 || textureWidth > 1232) return rect; + if (textureHeight <= 0 || textureHeight > 1232) return rect; + + + GUIStyle style; + + void getStyle() + { + if (_blurredStylesByTextureSize.TryGetValue((textureWidth, textureHeight), out style)) return; + + // VDebug.LogStart(blurRadius + ""); + + var pixels = new Color[textureWidth * textureHeight]; + var kernel = GaussianKernel.GenerateArray(blurRadiusScaled * 2 + 1); + + for (int x = 0; x < textureWidth; x++) + for (int y = 0; y < textureHeight; y++) + { + var sum = 0f; + + for (int xSample = (x - blurRadiusScaled).Max(blurRadiusScaled); xSample <= (x + blurRadiusScaled).Min(textureWidth - 1 - blurRadiusScaled); xSample++) + for (int ySample = (y - blurRadiusScaled).Max(blurRadiusScaled); ySample <= (y + blurRadiusScaled).Min(textureHeight - 1 - blurRadiusScaled); ySample++) + sum += kernel[blurRadiusScaled + xSample - x, blurRadiusScaled + ySample - y]; + + pixels[x + y * textureWidth] = Greyscale(1, sum); + + } + + var texture = new Texture2D(textureWidth, textureHeight); + texture.SetPropertyValue("pixelsPerPoint", pixelsPerPoint); + texture.hideFlags = HideFlags.DontSave; + texture.SetPixels(pixels); + texture.Apply(); + + + style = new GUIStyle(); + style.normal.background = texture; + style.alignment = TextAnchor.MiddleCenter; + + var borderX = ((textureWidth / 2f - 1) / pixelsPerPoint).FloorToInt(); + var borderY = ((textureHeight / 2f - 1) / pixelsPerPoint).FloorToInt(); + style.border = new RectOffset(borderX, borderX, borderY, borderY); + + _blurredStylesByTextureSize[(textureWidth, textureHeight)] = style; + + // VDebug.LogFinish(); + + } + void draw() + { + SetGUIColor(color); + + style.Draw(rect.SetSizeFromMid(rect.width + blurRadius * 2, rect.height + blurRadius * 2), false, false, false, false); + + ResetGUIColor(); + + } + + getStyle(); + draw(); + + return rect; + + } + public static Rect DrawBlurred(this Rect rect, Color color, float blurRadius) => rect.DrawBlurred(color, blurRadius.RoundToInt()); + + static Dictionary<(int, int), GUIStyle> _blurredStylesByTextureSize = new(); + + + + + static void DrawCurtain(this Rect rect, Color color, int dir) + { + void genTextures() + { + if (_gradientTextures != null) return; + + _gradientTextures = new Texture2D[4]; + + // var pixels = Enumerable.Range(0, 256).Select(r => Greyscale(1, r / 255f)); + var pixels = Enumerable.Range(0, 256).Select(r => Greyscale(1, (r / 255f).Smoothstep())); + + var up = new Texture2D(1, 256); + up.SetPixels(pixels.Reverse().ToArray()); + up.Apply(); + up.hideFlags = HideFlags.DontSave; + up.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[0] = up; + + var down = new Texture2D(1, 256); + down.SetPixels(pixels.ToArray()); + down.Apply(); + down.hideFlags = HideFlags.DontSave; + down.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[1] = down; + + var left = new Texture2D(256, 1); + left.SetPixels(pixels.ToArray()); + left.Apply(); + left.hideFlags = HideFlags.DontSave; + left.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[2] = left; + + var right = new Texture2D(256, 1); + right.SetPixels(pixels.Reverse().ToArray()); + right.Apply(); + right.hideFlags = HideFlags.DontSave; + right.wrapMode = TextureWrapMode.Clamp; + _gradientTextures[3] = right; + + } + void draw() + { + SetGUIColor(color); + + GUI.DrawTexture(rect, _gradientTextures[dir]); + + ResetGUIColor(); + + } + + genTextures(); + draw(); + + } + + static Texture2D[] _gradientTextures; + + public static void DrawCurtainUp(this Rect rect, Color color) => rect.DrawCurtain(color, 0); + public static void DrawCurtainDown(this Rect rect, Color color) => rect.DrawCurtain(color, 1); + public static void DrawCurtainLeft(this Rect rect, Color color) => rect.DrawCurtain(color, 2); + public static void DrawCurtainRight(this Rect rect, Color color) => rect.DrawCurtain(color, 3); + + + + + + + #endregion + + #region Events + + + public class WrappedEvent + { + public Event e; + + public bool isRepaint => e.type == EventType.Repaint; + public bool isLayout => e.type == EventType.Layout; + public bool isUsed => e.type == EventType.Used; + public bool isMouseLeaveWindow => e.type == EventType.MouseLeaveWindow; + public bool isMouseEnterWindow => e.type == EventType.MouseEnterWindow; + public bool isContextClick => e.type == EventType.ContextClick; + public bool isIgnore => e.type == EventType.Ignore; + + public bool isKeyDown => e.type == EventType.KeyDown; + public bool isKeyUp => e.type == EventType.KeyUp; + public KeyCode keyCode => e.keyCode; + public char characted => e.character; + + public bool isExecuteCommand => e.type == EventType.ExecuteCommand; + public string commandName => e.commandName; + + public bool isMouse => e.isMouse; + public bool isMouseDown => e.type == EventType.MouseDown; + public bool isMouseUp => e.type == EventType.MouseUp; + public bool isMouseDrag => e.type == EventType.MouseDrag; + public bool isMouseMove => e.type == EventType.MouseMove; + public bool isScroll => e.type == EventType.ScrollWheel; + public int mouseButton => e.button; + public int clickCount => e.clickCount; + public Vector2 mousePosition => e.mousePosition; + public Vector2 mousePosition_screenSpace => GUIUtility.GUIToScreenPoint(e.mousePosition); + public Vector2 mouseDelta => e.delta; + + public bool isDragUpdate => e.type == EventType.DragUpdated; + public bool isDragPerform => e.type == EventType.DragPerform; + public bool isDragExit => e.type == EventType.DragExited; + + public EventModifiers modifiers => e.modifiers; + public bool holdingAnyModifierKey => modifiers != EventModifiers.None; + + public bool holdingAlt => e.alt; + public bool holdingShift => e.shift; + public bool holdingCtrl => e.control; + public bool holdingCmd => e.command; + public bool holdingCmdOrCtrl => e.command || e.control; + + public bool holdingAltOnly => e.modifiers == EventModifiers.Alt; // in some sessions FunctionKey is always pressed? + public bool holdingShiftOnly => e.modifiers == EventModifiers.Shift; // in some sessions FunctionKey is always pressed? + public bool holdingCtrlOnly => e.modifiers == EventModifiers.Control; + public bool holdingCmdOnly => e.modifiers == EventModifiers.Command; + public bool holdingCmdOrCtrlOnly => (e.modifiers == EventModifiers.Command || e.modifiers == EventModifiers.Control); + + public EventType type => e.type; + + public void Use() => e?.Use(); + + + public WrappedEvent(Event e) => this.e = e; + + public override string ToString() => e.ToString(); + + } + + public static WrappedEvent Wrap(this Event e) => new(e); + + public static WrappedEvent curEvent => _curEvent ??= typeof(Event).GetFieldValue("s_Current").Wrap(); + static WrappedEvent _curEvent; + + + + + + #endregion + + #region Shortcuts + + + public static Rect lastRect => GUILayoutUtility.GetLastRect(); + + public static bool isDarkTheme => EditorGUIUtility.isProSkin; + + public static bool IsHovered(this Rect r) => r.Contains(curEvent.mousePosition); + + public static float GetLabelWidth(this string s) => GUI.skin.label.CalcSize(new GUIContent(s)).x; + public static float GetLabelWidth(this string s, int fontSize) + { + SetLabelFontSize(fontSize); + + var r = s.GetLabelWidth(); + + ResetLabelStyle(); + + return r; + + } + public static float GetLabelWidth(this string s, bool isBold) + { + if (isBold) + SetLabelBold(); + + var r = s.GetLabelWidth(); + + if (isBold) + ResetLabelStyle(); + + return r; + + } + public static float GetLabelWidth(this string s, int fontSize, bool isBold) + { + if (isBold) + SetLabelBold(); + + SetLabelFontSize(fontSize); + + var r = s.GetLabelWidth(); + + ResetLabelStyle(); + + return r; + + } + + public static void SetGUIEnabled(bool enabled) { _prevGuiEnabled = GUI.enabled; GUI.enabled = enabled; } + public static void ResetGUIEnabled() => GUI.enabled = _prevGuiEnabled; + static bool _prevGuiEnabled = true; + + public static void SetLabelFontSize(int size) => GUI.skin.label.fontSize = size; + public static void SetLabelBold() => GUI.skin.label.fontStyle = FontStyle.Bold; + public static void SetLabelAlignmentCenter() => GUI.skin.label.alignment = TextAnchor.MiddleCenter; + public static void ResetLabelStyle() + { + GUI.skin.label.fontSize = 0; + GUI.skin.label.fontStyle = FontStyle.Normal; + GUI.skin.label.alignment = TextAnchor.MiddleLeft; + GUI.skin.label.wordWrap = false; + } + + + public static void SetGUIColor(Color c) + { + _guiColorStack.Push(GUI.color); + + GUI.color *= c; + + } + public static void ResetGUIColor() + { + GUI.color = _guiColorStack.Pop(); + } + + static Stack _guiColorStack = new(); + + + + public static float editorDeltaTime = .0166f; + + static void EditorDeltaTime_Update() + { + editorDeltaTime = (float)(EditorApplication.timeSinceStartup - _lastUpdateTime); + + _lastUpdateTime = EditorApplication.timeSinceStartup; + + } + static double _lastUpdateTime; + + [InitializeOnLoadMethod] + static void EditorDeltaTime_Subscribe() + { + EditorApplication.update -= EditorDeltaTime_Update; + EditorApplication.update += EditorDeltaTime_Update; + } + + + + + #endregion + + #region Controls + + + public static bool IconButton(Rect rect, string iconName, float iconSize = default, Color color = default, Color colorHovered = default, Color colorPressed = default) + { + var id = EditorGUIUtility.GUIToScreenRect(rect).GetHashCode();// GUIUtility.GetControlID(FocusType.Passive, rect); + var isPressed = id == _pressedIconButtonId; + + var wasActivated = false; + + void icon() + { + if (!curEvent.isRepaint) return; + + + if (color == default) + color = Color.white; + + if (colorHovered == default) + colorHovered = Color.white; + + if (colorPressed == default) + colorPressed = Color.white.SetAlpha(.6f); + + + if (rect.IsHovered()) + color = colorHovered; + + if (isPressed) + color = colorPressed; + + + if (iconSize == default) + iconSize = rect.width.Min(rect.height); + + var iconRect = rect.SetSizeFromMid(iconSize); + + + + SetGUIColor(color); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon(iconName)); + + ResetGUIColor(); + + + } + void mouseDown() + { + if (!curEvent.isMouseDown) return; + if (!rect.IsHovered()) return; + + _pressedIconButtonId = id; + + curEvent.Use(); + + } + void mouseUp() + { + if (!curEvent.isMouseUp) return; + if (!isPressed) return; + + _pressedIconButtonId = 0; + + if (rect.IsHovered()) + wasActivated = true; + + curEvent.Use(); + + } + void mouseDrag() + { + if (!curEvent.isMouseDrag) return; + if (!isPressed) return; + + curEvent.Use(); + + } + + rect.MarkInteractive(); + + icon(); + mouseDown(); + mouseUp(); + mouseDrag(); + + return wasActivated; + + } + + static int _pressedIconButtonId; + + + + + + #endregion + + #region Layout + + + public static void Space(float px = 6) => GUILayout.Space(px); + + public static Rect ExpandWidthLabelRect() { GUILayout.Label(""/* , GUILayout.Height(0) */, GUILayout.ExpandWidth(true)); return lastRect; } + public static Rect ExpandWidthLabelRect(float height) { GUILayout.Label("", GUILayout.Height(height), GUILayout.ExpandWidth(true)); return lastRect; } + + + + + public static void BeginIndent(float f) + { + GUILayout.BeginHorizontal(); + GUILayout.Space(f); + GUILayout.BeginVertical(); + + _indentLabelWidthStack.Push(EditorGUIUtility.labelWidth); + + EditorGUIUtility.labelWidth -= f; + } + + public static void EndIndent(float f = 0) + { + GUILayout.EndVertical(); + GUILayout.Space(f); + GUILayout.EndHorizontal(); + + EditorGUIUtility.labelWidth = _indentLabelWidthStack.Pop(); + } + static Stack _indentLabelWidthStack = new(); + + + + + + #endregion + + #region GUIColors + + + public static class GUIColors + { + public static Color windowBackground => isDarkTheme ? Greyscale(.22f) : Greyscale(.78f); // prev backgroundCol + public static Color pressedButtonBackground => isDarkTheme ? new Color(.48f, .76f, 1f, 1f) * 1.4f : new Color(.48f, .7f, 1f, 1f) * 1.2f; // prev pressedButtonCol + public static Color greyedOutTint => Greyscale(.7f); + public static Color selectedBackground => isDarkTheme ? new Color(.17f, .365f, .535f) : new Color(.2f, .375f, .555f) * 1.2f; + } + + + + + #endregion + + #region EditorIcons + + + public static partial class EditorIcons + { + public static Texture2D GetIcon(string iconNameOrPath, bool returnNullIfNotFound = false) + { + iconNameOrPath ??= ""; + + if (icons_byName.TryGetValue(iconNameOrPath, out var cachedResult) && cachedResult) return cachedResult; + + + Texture2D icon = null; + + void getCustom() + { + if (icon) return; + if (!customIcons.ContainsKey(iconNameOrPath)) return; + + var pngBytesString = customIcons[iconNameOrPath]; + var pngBytes = pngBytesString.Split("-").Select(r => System.Convert.ToByte(r, 16)).ToArray(); + + icon = new Texture2D(1, 1); + + icon.LoadImage(pngBytes); + + } + void getBuiltin() + { + if (icon) return; + + icon = typeof(EditorGUIUtility).InvokeMethod("LoadIcon", iconNameOrPath) as Texture2D; + + } + + getCustom(); + getBuiltin(); + + icons_byName[iconNameOrPath] = icon; + + if (icon == null && !returnNullIfNotFound) return Texture2D.grayTexture; + else return icon; + + } + + static Dictionary icons_byName = new(); + + static Dictionary customIcons = new() + { + ["Search_"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-02-00-49-44-41-54-78-01-ED-56-3D-4F-02-41-10-1D-3E-22-C6-C4-CA-D0-9A-58-6B-87-8D-12-6B-0B-1B-7F-07-85-89-95-F4-34-D6-F2-07-2C-FD-23-D0-41-A2-8D-FF-00-8A-0B-0D-7A-70-7C-1C-EB-7B-B8-47-16-C4-70-7B-77-1C-0D-2F-B9-DC-DE-DD-CE-CE-DB-99-D9-37-27-B2-C7-8E-91-B1-99-EC-38-CE-71-A1-50-38-E0-78-34-1A-8D-8B-C5-E2-97-6C-1B-4A-A9-43-38-AB-8E-C7-E3-CF-D9-6C-E6-29-0D-DF-F7-5D-BE-C3-B7-1A-E7-C8-36-E0-BA-EE-1D-1C-39-6A-03-40-CC-E5-5C-49-12-9E-E7-55-B0-B0-AF-1D-F8-93-C9-A4-81-77-0F-B8-97-79-E9-71-DB-20-E1-83-44-45-92-00-77-13-38-47-04-BA-83-C1-E0-FA-BF-B9-20-71-85-39-3D-83-44-BC-48-30-9F-0C-A9-5E-B0-D7-E9-74-8E-36-D9-70-0E-89-6A-C2-4E-AC-9A-D0-45-35-07-43-1D-D6-0E-51-2A-07-51-63-D1-4A-54-B0-B2-B5-F3-B6-58-02-36-4D-DA-72-8D-B0-36-59-F3-81-E7-3C-9B-CD-9E-72-8C-75-5E-C5-12-B0-79-E3-3D-9F-CF-9F-71-AD-30-36-4B-04-28-32-B9-5C-6E-9E-F3-4C-26-D3-12-4B-4C-A7-D3-96-B6-2D-04-82-65-45-60-17-58-22-40-79-65-0D-71-8C-7B-49-2C-81-D0-97-B4-AD-CB-B5-24-0A-8C-22-6C-8A-25-70-04-DB-B6-45-F8-07-3C-42-86-FA-59-1D-43-E3-F8-D6-24-2A-28-22-81-FE-53-5C-C2-0A-11-45-2B-E8-0B-2A-6E-73-32-A5-98-0B-87-90-E2-6E-62-52-6C-90-58-34-A3-40-98-90-DB-D5-66-D4-80-F3-45-C3-62-03-A3-ED-70-38-BC-C1-AB-0B-89-0B-1D-09-57-6D-00-53-16-EC-1C-24-6E-F1-3C-82-DD-37-08-5F-4A-5C-A8-DF-1F-92-1A-2B-1B-42-E3-1A-ED-D7-D3-3F-24-D5-20-E7-DC-39-9D-1B-73-26-F8-7E-2F-49-81-F2-DA-EF-F7-4F-78-AD-93-5A-38-3B-E7-CE-D5-F2-CF-CA-14-A9-8A-DE-9C-6C-C1-B0-73-E7-AB-69-42-6A-5E-24-2D-30-EC-DC-F9-1A-12-8F-92-16-50-0B-4F-6B-8A-F5-5D-D2-04-76-5C-37-09-20-3D-75-49-1B-28-C0-67-5C-1F-74-0E-0E-79-D9-63-8F-15-FC-00-17-02-EB-AB-1A-4B-B3-E7-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Cross"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-C5-49-44-41-54-78-01-ED-96-D1-0D-83-30-0C-44-9D-4E-D0-51-BA-02-13-B5-23-A4-1B-A4-13-31-42-3B-4A-37-70-8D-6A-04-42-E0-D8-88-E0-1F-3F-29-8A-50-1C-DF-05-48-62-80-20-08-9C-49-D2-20-22-5E-A9-BB-53-1B-FA-67-4A-E9-0B-0A-66-F3-06-5E-DA-79-6B-89-32-4E-BC-39-71-55-9C-63-47-B2-14-7F-01-3D-37-6A-BD-64-82-C7-7A-8E-1D-A9-9A-06-29-E1-62-35-9B-6F-C2-12-7B-B8-89-66-E2-1A-81-E6-E2-0A-13-ED-C5-2B-26-CE-11-57-98-D8-25-6E-D9-86-FE-B8-7E-02-D7-9F-10-3D-B7-21-7A-1E-44-96-C4-4D-4C-D0-E4-62-49-B8-61-22-4B-1A-B5-6D-38-BF-C7-3F-D4-3A-E9-6E-E7-B1-8E-63-55-68-0A-92-07-3F-16-63-41-92-E1-BF-80-B2-BB-20-09-82-E0-0C-7E-54-36-6A-69-F6-3F-13-EF-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Plus"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-A7-49-44-41-54-78-01-ED-D5-01-09-84-30-14-06-E0-7F-C7-05-B8-06-77-0D-CE-08-46-31-C2-1A-68-04-4D-A0-51-8C-A0-0D-B4-81-0D-E6-13-14-C6-10-C5-4D-11-E1-FF-60-F8-78-0C-F7-B3-E1-04-88-02-18-63-B4-8C-04-77-98-17-5F-64-F0-F4-82-BF-C8-AA-BF-F0-14-12-E0-14-0C-C0-00-0C-A0-D6-9A-72-B1-A4-F2-F8-61-5B-6C-CD-E9-64-D4-3B-F3-1B-A5-54-81-3D-D3-D5-6A-AE-A3-DD-F5-D6-8E-E0-83-EB-0C-6E-E3-ED-36-64-9B-72-49-3A-95-7F-6C-8B-71-EC-08-7A-79-77-85-B3-48-C8-CA-DA-DA-12-9E-F8-19-32-00-03-3C-3A-40-67-D5-2D-EE-30-FF-37-34-88-02-8C-19-03-9B-84-46-97-A8-ED-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Star"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-01-16-49-44-41-54-78-01-ED-94-6D-0D-C2-30-10-86-DF-11-04-20-61-12-70-C0-1C-50-07-AB-03-90-80-04-50-00-28-19-0E-90-00-0E-C0-C1-71-CD-BA-B0-B1-86-B5-BD-0E-FE-EC-49-2E-6D-2E-D7-CB-F5-BE-80-89-09-01-44-94-1B-81-80-19-64-28-16-0D-01-73-C8-28-59-9E-F8-07-36-FD-0D-39-22-91-94-40-B5-EE-1A-91-48-02-58-B7-EE-2B-FC-92-8F-F4-37-2C-10-41-6C-06-0A-87-4E-23-82-DE-14-F0-4F-0A-3E-F2-81-77-1B-87-AE-E4-B7-43-13-71-CF-B2-EC-F2-D5-C2-A4-92-E5-44-E9-39-05-95-89-8D-B7-2C-0F-92-63-7C-6C-11-03-D5-CD-76-A3-78-AE-24-5C-D5-4D-20-3B-0A-67-8F-94-B0-43-E5-99-0D-63-53-60-0C-D8-71-E5-11-40-85-31-A0-7A-3A-7C-30-4D-E7-DD-ED-21-8B-48-79-DA-2D-02-6C-83-02-58-3B-74-07-96-B3-43-5F-22-25-8E-F4-77-66-9B-EF-9A-BA-3B-23-A8-0C-3E-01-E8-96-73-E7-6C-53-7F-67-68-A4-82-9D-1D-AD-D3-C1-D9-A6-F7-CE-38-22-15-F6-D7-45-80-BD-B2-6F-E4-65-60-27-4B-8A-58-A7-B6-24-E9-FA-60-62-62-2C-5E-30-1D-6B-34-83-5B-F0-2B-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Star Hollow"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-01-5A-49-44-41-54-78-01-ED-96-FD-6D-C2-30-10-C5-2F-15-03-B0-41-33-42-47-48-37-C8-06-64-03-BA-41-D9-80-6E-00-1B-B4-9D-20-DD-20-EA-04-C9-06-65-83-EB-3B-F1-2C-8C-04-F9-B0-AD-F0-4F-7E-D2-29-16-3A-5F-EC-77-1F-41-64-61-21-02-55-CD-CD-24-82-27-89-A3-84-55-12-C1-4A-E2-D8-C0-4E-F2-08-28-BF-23-97-40-62-52-60-F2-77-72-56-A0-92-40-32-09-04-B7-AE-79-00-8B-F1-9C-65-D9-AB-CC-85-27-7F-09-2B-B8-5E-CB-5C-E0-65-15-EC-8F-EB-B5-AD-61-6F-12-C0-EA-46-F0-02-8F-7C-60-DF-16-F6-65-0B-48-7F-C2-9E-6F-2C-37-78-0E-75-44-07-FF-9F-5E-0F-DE-E8-A8-C3-94-FE-A1-47-F8-1F-27-A5-C9-24-A5-B4-6D-48-9B-B1-4E-9A-98-F4-B8-20-ED-D4-20-F0-DD-72-4F-A3-91-A3-DA-05-DC-51-C6-43-5F-40-A6-EF-40-DF-0F-49-09-5B-CE-D4-68-7B-7C-1A-FA-14-32-92-D1-93-10-D5-6B-55-DF-C1-7E-7B-DC-AC-0B-86-2B-3D-04-CA-6B-54-DE-6F-57-9F-63-AF-70-D3-0F-25-3D-0F-1F-75-2F-64-EB-B9-2E-A9-EE-1D-32-E5-01-3E-61-35-5F-B2-77-85-A6-97-99-F1-4E-3F-F3-A9-25-25-DE-CD-76-B7-7A-9B-EA-38-35-F6-C9-D3-E0-C9-AF-F7-7A-DB-9B-19-9A-3C-0D-53-7A-5B-BD-99-21-A9-E0-AD-8B-09-FE-25-F7-C4-A7-01-41-5E-34-FC-5B-30-DF-7F-84-85-85-50-FE-01-12-E7-01-A3-5F-51-F9-4C-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Collapse"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-CE-49-44-41-54-78-01-ED-95-01-0D-C2-30-10-45-7F-A7-60-12-2A-01-09-93-80-04-1C-80-03-EA-60-16-70-30-1C-20-01-09-93-C0-1C-1C-BF-61-24-CD-18-0C-9A-DE-1A-92-BE-A4-49-D7-25-F7-7F-72-FF-5A-A0-90-19-33-3D-10-91-1D-F4-18-8C-31-E7-B7-7F-29-7E-10-7D-5C-A8-59-4D-3C-D4-D0-67-08-3F-E6-5A-B0-D5-34-C2-16-9C-50-48-05-DB-B5-F7-C1-45-0E-28-BC-19-53-7D-F3-7B-AC-09-05-2D-57-1F-8C-96-DF-5B-AC-05-C5-AE-33-F3-ED-CF-F4-C7-98-22-ED-87-4B-A6-85-26-14-38-CA-32-3A-A1-64-E1-46-BE-A7-41-4A-E4-35-74-4B-F8-C9-B0-48-01-0B-D5-3F-8A-3F-E9-25-45-28-59-A4-93-78-3A-68-C1-E2-2E-10-72-88-A4-42-66-8A-81-62-A0-18-C8-6E-20-1A-79-BC-0F-97-71-59-14-FE-95-3B-1B-9A-F9-A3-61-43-3F-A9-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Plus Thicker"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-A1-49-44-41-54-78-01-ED-96-D1-09-84-30-10-44-27-C7-15-70-D7-81-25-D9-C1-9D-95-A8-1D-D9-89-25-C4-0E-4C-07-71-85-80-12-22-91-B8-E8-CF-3C-98-8F-2C-21-79-10-D8-0D-40-48-21-DE-FB-5A-32-4B-AC-E4-87-BB-91-4B-47-BF-61-51-C8-0B-E5-7C-A0-C0-15-01-15-28-40-01-0A-98-B8-10-BA-5A-87-3C-55-B4-9E-32-FB-9D-A4-37-C6-0C-39-01-9B-38-5C-0B-27-02-DF-7D-21-F5-04-2A-1D-EE-48-20-2E-BC-13-9B-1A-49-7B-42-A4-8A-D6-13-F2-F4-D0-22-4C-C1-47-87-91-0A-14-A0-00-05-B4-04-1C-EE-46-9A-CF-3F-34-A3-F5-6B-5E-83-90-42-16-B4-42-4C-CD-3F-8F-0E-C4-00-00-00-00-49-45-4E-44-AE-42-60-82", + ["Dropdown"] = "89-50-4E-47-0D-0A-1A-0A-00-00-00-0D-49-48-44-52-00-00-00-20-00-00-00-20-08-06-00-00-00-73-7A-7A-F4-00-00-00-09-70-48-59-73-00-00-0B-13-00-00-0B-13-01-00-9A-9C-18-00-00-00-01-73-52-47-42-00-AE-CE-1C-E9-00-00-00-04-67-41-4D-41-00-00-B1-8F-0B-FC-61-05-00-00-00-78-49-44-41-54-78-01-ED-D0-B1-0D-80-20-14-84-E1-D3-D2-15-98-82-41-18-C6-51-5C-85-41-98-C2-15-AC-F1-E8-2C-50-91-04-8C-F1-BE-E4-35-0A-FC-04-40-44-44-FE-6E-28-59-14-09-15-06-BA-5B-33-A2-CC-82-E7-6A-F6-9C-E3-23-F8-58-CE-A3-05-1E-1C-0A-E2-01-AD-F0-F0-89-B3-5E-C4-D3-BF-09-2D-31-60-38-5B-26-9E-BE-19-F4-C0-90-CD-5C-C0-A2-27-06-DD-21-EE-F0-06-86-E7-34-10-11-91-2F-DB-01-40-06-CB-42-92-53-E4-4B-00-00-00-00-49-45-4E-44-AE-42-60-82", + }; + } + + + + #endregion + + #region Other + + + public static void MarkInteractive(this Rect rect) + { + if (!curEvent.isRepaint) return; + + var unclippedRect = (Rect)_mi_GUIClip_UnclipToWindow.Invoke(null, new object[] { rect }); + + var curGuiView = _pi_GUIView_current.GetValue(null); + + _mi_GUIView_MarkHotRegion.Invoke(curGuiView, new object[] { unclippedRect }); + + } + + static PropertyInfo _pi_GUIView_current = typeof(Editor).Assembly.GetType("UnityEditor.GUIView").GetProperty("current", maxBindingFlags); + static MethodInfo _mi_GUIView_MarkHotRegion = typeof(Editor).Assembly.GetType("UnityEditor.GUIView").GetMethod("MarkHotRegion", maxBindingFlags); + static MethodInfo _mi_GUIClip_UnclipToWindow = typeof(GUI).Assembly.GetType("UnityEngine.GUIClip").GetMethod("UnclipToWindow", maxBindingFlags, null, new[] { typeof(Rect) }, null); + + + + + + + + + + #endregion + + } + +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLibs.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLibs.cs.meta new file mode 100644 index 0000000..68cfb99 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLibs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd278e4ae9a8649f59f016c7d739ce1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLightingWindow.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLightingWindow.cs new file mode 100644 index 0000000..8fb6b18 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLightingWindow.cs @@ -0,0 +1,6 @@ + + +// this file was present in a previus version and is supposed to be deleted now +// but asset store update delivery system doesn't allow deleting files +// so instead this file is now emptied +// feel free to delete it if you want diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLightingWindow.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLightingWindow.cs.meta new file mode 100644 index 0000000..6a1a7b3 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyLightingWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 25324ddc1ebdc4ee5b1d9902a8efafa7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenu.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenu.cs new file mode 100644 index 0000000..b04b012 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenu.cs @@ -0,0 +1,159 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VHierarchy +{ + class VHierarchyMenu + { + + public static bool navigationBarEnabled { get => EditorPrefsCached.GetBool("vHierarchy-navigationBarEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-navigationBarEnabled", value); } + public static bool sceneSelectorEnabled { get => EditorPrefsCached.GetBool("vHierarchy-sceneSelectorEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-sceneSelectorEnabled", value); } + public static bool componentMinimapEnabled { get => EditorPrefsCached.GetBool("vHierarchy-componentMinimapEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-componentMinimapEnabled", value); } + public static bool activationToggleEnabled { get => EditorPrefsCached.GetBool("vHierarchy-acctivationToggleEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-acctivationToggleEnabled", value); } + public static bool hierarchyLinesEnabled { get => EditorPrefsCached.GetBool("vHierarchy-hierarchyLinesEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-hierarchyLinesEnabled", value); } + public static bool minimalModeEnabled { get => EditorPrefsCached.GetBool("vHierarchy-minimalModeEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-minimalModeEnabled", value); } + public static bool zebraStripingEnabled { get => EditorPrefsCached.GetBool("vHierarchy-zebraStripingEnabled", false); set => EditorPrefsCached.SetBool("vHierarchy-zebraStripingEnabled", value); } + + public static bool toggleActiveEnabled { get => EditorPrefsCached.GetBool("vHierarchy-toggleActiveEnabled", true); set => EditorPrefsCached.SetBool("vHierarchy-toggleActiveEnabled", value); } + public static bool focusEnabled { get => EditorPrefsCached.GetBool("vHierarchy-focusEnabled", true); set => EditorPrefsCached.SetBool("vHierarchy-focusEnabled", value); } + public static bool deleteEnabled { get => EditorPrefsCached.GetBool("vHierarchy-deleteEnabled", true); set => EditorPrefsCached.SetBool("vHierarchy-deleteEnabled", value); } + public static bool toggleExpandedEnabled { get => EditorPrefsCached.GetBool("vHierarchy-toggleExpandedEnabled", true); set => EditorPrefsCached.SetBool("vHierarchy-toggleExpandedEnabled", value); } + public static bool isolateEnabled { get => EditorPrefsCached.GetBool("vHierarchy-collapseEverythingElseEnabled", true); set => EditorPrefsCached.SetBool("vHierarchy-collapseEverythingElseEnabled", value); } + public static bool collapseEverythingEnabled { get => EditorPrefsCached.GetBool("vHierarchy-collapseEverythingEnabled", true); set => EditorPrefsCached.SetBool("vHierarchy-collapseEverythingEnabled", value); } + public static bool setDefaultParentEnabled { get => EditorPrefsCached.GetBool("vHierarchy-setDefaultParentEnabled", true); set => EditorPrefsCached.SetBool("vHierarchy-setDefaultParentEnabled", value); } + + public static bool pluginDisabled { get => EditorPrefsCached.GetBool("vHierarchy-pluginDisabled", false); set => EditorPrefsCached.SetBool("vHierarchy-pluginDisabled", value); } + + + + + const string dir = "Tools/vHierarchy/"; + + const string navigationBar = dir + "Navigation bar"; + const string sceneSelector = dir + "Scene selector"; + const string componentMinimap = dir + "Component minimap"; + const string activationToggle = dir + "Activation toggle"; + const string hierarchyLines = dir + "Hierarchy lines"; + const string zebraStriping = dir + "Zebra striping"; + const string minimalMode = dir + "Minimal mode"; + + const string toggleActive = dir + "A to toggle active"; + const string focus = dir + "F to focus"; + const string delete = dir + "X to delete"; + const string toggleExpanded = dir + "E to expand or collapse"; + const string isolate = dir + "Shift-E to isolate"; + const string collapseEverything = dir + "Ctrl-Shift-E to collapse all"; + const string setDefaultParent = dir + "D to set default parent"; + + const string disablePlugin = dir + "Disable vHierarchy"; + + + + + + + [MenuItem(dir + "Features", false, 1)] static void daasddsas() { } + [MenuItem(dir + "Features", true, 1)] static bool dadsdasas123() => false; + + [MenuItem(navigationBar, false, 2)] static void dadsaadsdsadasdsadadsas() { navigationBarEnabled = !navigationBarEnabled; EditorApplication.RepaintHierarchyWindow(); } + [MenuItem(navigationBar, true, 2)] static bool dadsaddasdsasadadsdasadsas() { Menu.SetChecked(navigationBar, navigationBarEnabled); return !pluginDisabled; } + + [MenuItem(sceneSelector, false, 3)] static void dadsaadsdsadassddsadadsas() { sceneSelectorEnabled = !sceneSelectorEnabled; EditorApplication.RepaintHierarchyWindow(); } + [MenuItem(sceneSelector, true, 3)] static bool dadsaddasdsasadsdadsdasadsas() { Menu.SetChecked(sceneSelector, sceneSelectorEnabled); return !pluginDisabled; } + + [MenuItem(componentMinimap, false, 4)] static void daadsdsadasdadsas() { componentMinimapEnabled = !componentMinimapEnabled; EditorApplication.RepaintHierarchyWindow(); } + [MenuItem(componentMinimap, true, 4)] static bool dadsadasddasadsas() { Menu.SetChecked(componentMinimap, componentMinimapEnabled); return !pluginDisabled; } + + [MenuItem(activationToggle, false, 5)] static void daadsdsadadsasdadsas() { activationToggleEnabled = !activationToggleEnabled; EditorApplication.RepaintHierarchyWindow(); } + [MenuItem(activationToggle, true, 5)] static bool dadsadasdsaddasadsas() { Menu.SetChecked(activationToggle, activationToggleEnabled); return !pluginDisabled; } + + [MenuItem(hierarchyLines, false, 6)] static void dadsadadsadadasss() { hierarchyLinesEnabled = !hierarchyLinesEnabled; EditorApplication.RepaintHierarchyWindow(); } + [MenuItem(hierarchyLines, true, 6)] static bool dadsaddasdasaasddsas() { Menu.SetChecked(hierarchyLines, hierarchyLinesEnabled); return !pluginDisabled; } + + [MenuItem(zebraStriping, false, 7)] static void dadsadadadssadsadass() { zebraStripingEnabled = !zebraStripingEnabled; EditorApplication.RepaintHierarchyWindow(); } + [MenuItem(zebraStriping, true, 7)] static bool dadsaddadaadsssadsaasddsas() { Menu.SetChecked(zebraStriping, zebraStripingEnabled); return !pluginDisabled; } + + [MenuItem(minimalMode, false, 8)] static void dadsadadasdsdasadadasss() { minimalModeEnabled = !minimalModeEnabled; EditorApplication.RepaintHierarchyWindow(); } + [MenuItem(minimalMode, true, 8)] static bool dadsaddadsasdadsasaasddsas() { Menu.SetChecked(minimalMode, minimalModeEnabled); return !pluginDisabled; } + + + + + + + [MenuItem(dir + "Shortcuts", false, 101)] static void dadsas() { } + [MenuItem(dir + "Shortcuts", true, 101)] static bool dadsas123() => false; + + + + [MenuItem(setDefaultParent, false, 102)] static void dadsadasdsdasadasdsadadsas() => setDefaultParentEnabled = !setDefaultParentEnabled; + [MenuItem(setDefaultParent, true, 102)] static bool dadsadsdadssdaasadadsdasadsas() { Menu.SetChecked(setDefaultParent, setDefaultParentEnabled); return !pluginDisabled; } + + + [MenuItem(toggleActive, false, 103)] static void dadsadadsas() => toggleActiveEnabled = !toggleActiveEnabled; + [MenuItem(toggleActive, true, 103)] static bool dadsaddasadsas() { Menu.SetChecked(toggleActive, toggleActiveEnabled); return !pluginDisabled; } + + [MenuItem(focus, false, 104)] static void dadsadasdadsas() => focusEnabled = !focusEnabled; + [MenuItem(focus, true, 104)] static bool dadsadsaddasadsas() { Menu.SetChecked(focus, focusEnabled); return !pluginDisabled; } + + [MenuItem(delete, false, 105)] static void dadsadsadasdadsas() => deleteEnabled = !deleteEnabled; + [MenuItem(delete, true, 105)] static bool dadsaddsasaddasadsas() { Menu.SetChecked(delete, deleteEnabled); return !pluginDisabled; } + + [MenuItem(toggleExpanded, false, 106)] static void dadsadsadasdsadadsas() => toggleExpandedEnabled = !toggleExpandedEnabled; + [MenuItem(toggleExpanded, true, 106)] static bool dadsaddsasadadsdasadsas() { Menu.SetChecked(toggleExpanded, toggleExpandedEnabled); return !pluginDisabled; } + + [MenuItem(isolate, false, 107)] static void dadsadsasdadasdsadadsas() => isolateEnabled = !isolateEnabled; + [MenuItem(isolate, true, 107)] static bool dadsaddsdasasadadsdasadsas() { Menu.SetChecked(isolate, isolateEnabled); return !pluginDisabled; } + + [MenuItem(collapseEverything, false, 108)] static void dadsadsdasadasdsadadsas() => collapseEverythingEnabled = !collapseEverythingEnabled; + [MenuItem(collapseEverything, true, 108)] static bool dadsaddssdaasadadsdasadsas() { Menu.SetChecked(collapseEverything, collapseEverythingEnabled); return !pluginDisabled; } + + + + + [MenuItem(dir + "More", false, 1001)] static void daasadsddsas() { } + [MenuItem(dir + "More", true, 1001)] static bool dadsadsdasas123() => false; + + + [MenuItem(dir + "Open manual", false, 1002)] + static void dadadssadsas() => Application.OpenURL("https://kubacho-lab.gitbook.io/vhierarchy-2"); + + [MenuItem(dir + "Join our Discord", false, 1003)] + static void dadasdsas() => Application.OpenURL("https://discord.gg/pUektnZeJT"); + + + + + [MenuItem(dir + "Deals ending soon/Get vFolders 2 at 50% off", false, 1004)] + static void dadadssadasdsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/255470?aid=1100lGLBn&pubref=deal50menu"); + + [MenuItem(dir + "Deals ending soon/Get vInspector 2 at 50% off", false, 1005)] + static void dadadssasddsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/252297?aid=1100lGLBn&pubref=deal50menu"); + + [MenuItem(dir + "Deals ending soon/Get vTabs 2 at 50% off", false, 1006)] + static void dadadadsssadsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/253396?aid=1100lGLBn&pubref=deal50menu"); + + [MenuItem(dir + "Deals ending soon/Get vFavorites 2 at 50% off", false, 1007)] + static void dadadadsssadsadsas() => Application.OpenURL("https://assetstore.unity.com/packages/slug/263643?aid=1100lGLBn&pubref=deal50menu"); + + + + + + [MenuItem(disablePlugin, false, 10001)] static void dadsadsdasadasdasdsadadsas() { pluginDisabled = !pluginDisabled; UnityEditor.Compilation.CompilationPipeline.RequestScriptCompilation(); } + [MenuItem(disablePlugin, true, 10001)] static bool dadsaddssdaasadsadadsdasadsas() { Menu.SetChecked(disablePlugin, pluginDisabled); return true; } + + + + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenu.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenu.cs.meta new file mode 100644 index 0000000..764244b --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenu.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d7f0448bdeda4aad9cfdd9e7c7ee27f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenuItems.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenuItems.cs new file mode 100644 index 0000000..8fb6b18 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenuItems.cs @@ -0,0 +1,6 @@ + + +// this file was present in a previus version and is supposed to be deleted now +// but asset store update delivery system doesn't allow deleting files +// so instead this file is now emptied +// feel free to delete it if you want diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenuItems.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenuItems.cs.meta new file mode 100644 index 0000000..70b2abb --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyMenuItems.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9ddf1decb62f94768bcaec3173017c87 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyNavbar.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyNavbar.cs new file mode 100644 index 0000000..af5370a --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyNavbar.cs @@ -0,0 +1,1081 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using Type = System.Type; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; +using static VHierarchy.VHierarchyData; +using static VHierarchy.VHierarchy; + + + +namespace VHierarchy +{ + public class VHierarchyNavbar + { + + public void OnGUI(Rect navbarRect) + { + void updateState() + { + if (!curEvent.isLayout) return; + + + + var isTreeFocused = window.GetFieldValue("m_SceneHierarchy").GetMemberValue("m_TreeViewKeyboardControlID") == GUIUtility.keyboardControl; + + var isWindowFocused = window == EditorWindow.focusedWindow; + + + + if (!isTreeFocused && isSearchActive) + EditorGUI.FocusTextInControl("SearchFilter"); + + + if (isTreeFocused || !isWindowFocused) + if (window.GetMemberValue("m_SearchFilter").ToString().IsNullOrEmpty()) + isSearchActive = false; + + + // in vFolders the following is used to check if search is active: + // GUI.GetNameOfFocusedControl() == "SearchFilter"; + // but in hierarchy focused control changes erratically when multiple scene headers are visible + // so a bool state is used instead + + + + + this.defaultParent = typeof(SceneView).InvokeMethod("GetDefaultParentObjectIfSet")?.gameObject; + + } + + void background() + { + var backgroundColor = Greyscale(isDarkTheme ? .235f : .8f); + var lineColor = Greyscale(isDarkTheme ? .13f : .58f); + + navbarRect.Draw(backgroundColor); + + navbarRect.SetHeightFromBottom(1).MoveY(1).Draw(lineColor); + + } + void hiddenMenu() + { + if (!curEvent.holdingAlt) return; + if (!curEvent.isMouseUp) return; + if (curEvent.mouseButton != 1) return; + if (!navbarRect.IsHovered()) return; + + + void selectData() + { + Selection.activeObject = data; + } + void selectPalette() + { + Selection.activeObject = palette; + } + void clearCache() + { + VHierarchyCache.Clear(); + } + + + + GenericMenu menu = new(); + + menu.AddDisabledItem(new GUIContent("vHierarchy hidden menu")); + + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Select data"), false, selectData); + menu.AddItem(new GUIContent("Select palette"), false, selectPalette); + + menu.AddSeparator(""); + menu.AddItem(new GUIContent("Clear cache"), false, clearCache); + + menu.ShowAsContext(); + + } + + + void plusButton() + { + + var buttonRect = navbarRect.SetWidth(28).MoveX(4.5f); + + if (Application.unityVersion.StartsWith("6000")) + buttonRect = buttonRect.MoveY(-.49f); + + + var iconName = "Plus Thicker"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .7f : .44f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .42f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .6f); + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + + GUIUtility.hotControl = 0; + + var sceneHierarchy = window.GetMemberValue("m_SceneHierarchy"); + var m_CustomParentForNewGameObjects = window.GetMemberValue("m_SceneHierarchy").GetMemberValue("m_CustomParentForNewGameObjects"); + var targetSceneHandle = m_CustomParentForNewGameObjects != null ? m_CustomParentForNewGameObjects.gameObject.scene.handle : 0; + + + var menu = new GenericMenu(); + + sceneHierarchy.GetType().GetMethod("AddCreateGameObjectItemsToMenu", maxBindingFlags).Invoke(sceneHierarchy, new object[] { menu, null, true, true, false, targetSceneHandle, 3 }); + + typeof(UnityEditor.SceneManagement.SceneHierarchyHooks).InvokeMethod("AddCustomItemsToCreateMenu", menu); + + menu.DropDown(buttonRect); + + + } + + void searchButton() + { + if (searchAnimationT == 1) return; + + + var buttonRect = navbarRect.SetWidthFromRight(28).MoveX(-5); + + var iconName = "Search_"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .75f : .2f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + EditorGUI.FocusTextInControl("SearchFilter"); + + EditorApplication.delayCall += () => EditorGUI.FocusTextInControl("SearchFilter"); + + isSearchActive = true; + + } + void searchOnCtrlF() + { + if (searchAnimationT == 1) return; + + if (!curEvent.isKeyDown) return; + if (!curEvent.holdingCmd && !curEvent.holdingCtrl) return; + if (curEvent.keyCode != KeyCode.F) return; + + + EditorGUI.FocusTextInControl("SearchFilter"); + + EditorApplication.delayCall += () => EditorGUI.FocusTextInControl("SearchFilter"); + + isSearchActive = true; + + + curEvent.Use(); + + } + void collapseAllButton() + { + if (searchAnimationT == 1) return; + + + var buttonRect = navbarRect.SetWidthFromRight(28).MoveX(-33); + + var iconName = "Collapse"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? .71f : .44f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .42f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .6f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + controller.CollapseAll(); + + } + void bookmarks() + { + if (searchAnimationT == 1) return; + if (isSearchActive && !curEvent.isRepaint) return; + + void createData() + { + if (data) return; + if (!navbarRect.IsHovered()) return; + if (!DragAndDrop.objectReferences.Any()) return; + + data = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(data, GetScriptPath("VHierarchy").GetParentPath().CombinePath("vHierarchy Data.asset")); + + } + void divider() + { + if (!data) return; + if (!data.bookmarks.Any(r => r.go)) return; + + + var dividerRect = navbarRect.SetWidthFromRight(1).SetHeightFromMid(16).MoveX(-65).MoveX(1.5f); + + var dividerColor = Greyscale(isDarkTheme ? .33f : .64f); + + + dividerRect.Draw(dividerColor); + + } + void gui() + { + if (!data) return; + + this.navbarRect = navbarRect; + this.bookmarksRect = navbarRect.AddWidth(-69).AddWidthFromRight(-60).MoveX(2).MoveX(-3); + + BookmarksGUI(); + + } + + createData(); + divider(); + gui(); + + } + + void searchField() + { + if (searchAnimationT == 0) return; + + var searchFieldRect = navbarRect.SetHeightFromMid(20).AddWidth(-33).SetWidthFromRight(200f.Min(window.position.width - 120)).Move(-1, 2); + + + GUILayout.BeginArea(searchFieldRect); + GUILayout.BeginHorizontal(); + + Space(2); + window.InvokeMethod("SearchFieldGUI"); + + GUILayout.EndHorizontal(); + GUILayout.EndArea(); + + } + void closeSearchButton() + { + if (searchAnimationT == 0) return; + + + var buttonRect = navbarRect.SetWidthFromRight(30).MoveX(-4); + + var iconName = "CrossIcon"; + var iconSize = 15; + var colorNormal = Greyscale(isDarkTheme ? .9f : .2f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + window.InvokeMethod("ClearSearchFilter"); + + GUIUtility.keyboardControl = 0; + + isSearchActive = false; + + } + void closeSearchOnEsc() + { + if (!isSearchActive) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + window.InvokeMethod("ClearSearchFilter"); + + GUIUtility.keyboardControl = 0; + + isSearchActive = false; + + } + + void searchAnimation() + { + if (!curEvent.isLayout) return; + + + var lerpSpeed = 8f; + + if (isSearchActive) + MathUtil.SmoothDamp(ref searchAnimationT, 1, lerpSpeed, ref searchAnimationDerivative, editorDeltaTime); + else + MathUtil.SmoothDamp(ref searchAnimationT, 0, lerpSpeed, ref searchAnimationDerivative, editorDeltaTime); + + + if (isSearchActive && searchAnimationT > .99f) + searchAnimationT = 1; + + if (!isSearchActive && searchAnimationT < .01f) + searchAnimationT = 0; + + + animatingSearch = searchAnimationT != 0 && searchAnimationT != 1; + + } + + void buttonsAndBookmarks() + { + SetGUIColor(Greyscale(1, (1 - searchAnimationT).Pow(2))); + GUI.BeginGroup(window.position.SetPos(0, 0).MoveX(-searchAnimationDistance * searchAnimationT)); + + searchButton(); + searchOnCtrlF(); + collapseAllButton(); + bookmarks(); + + GUI.EndGroup(); + ResetGUIColor(); + + } + void search() + { + SetGUIColor(Greyscale(1, searchAnimationT.Pow(2))); + GUI.BeginGroup(window.position.SetPos(0, 0).MoveX(searchAnimationDistance * (1 - searchAnimationT))); + + searchField(); + closeSearchButton(); + closeSearchOnEsc(); + + GUI.EndGroup(); + ResetGUIColor(); + + } + + + + updateState(); + + background(); + hiddenMenu(); + + plusButton(); + + searchAnimation(); + buttonsAndBookmarks(); + search(); + + + + if (draggingBookmark || animatingDroppedBookmark || animatingGaps || animatingTooltip || animatingSearch) + window.Repaint(); + + } + + bool animatingSearch; + + float searchAnimationDistance = 90; + float searchAnimationT; + float searchAnimationDerivative; + + string openedFolderPath; + + public bool isSearchActive; + + bool isDefaultParentTextPressed; + + GameObject defaultParent; + + GUIStyle defaultParentTextGUIStyle; + + Rect navbarRect; + Rect bookmarksRect; + + + + + + + + + + + + + void BookmarksGUI() + { + void bookmark(Vector2 centerPosition, Bookmark bookmark) + { + if (bookmark == null) return; + if (curEvent.isLayout) return; + + + var bookmarkRect = Rect.zero.SetSize(bookmarkWidth, bookmarksRect.height).SetMidPos(centerPosition); + + + void shadow() + { + if (!draggingBookmark) return; + if (draggedBookmark != bookmark) return; + + bookmarkRect.SetSizeFromMid(bookmarkWidth - 4, bookmarkWidth - 4).DrawBlurred(Greyscale(0, .3f), 15); + + } + void background() + { + if (!bookmarkRect.IsHovered()) return; + if (draggingBookmark && draggedBookmark != bookmark) return; + + var backgroundColor = Greyscale(isDarkTheme ? .35f : .7f); + + var backgroundRect = bookmarkRect.SetSizeFromMid(bookmarkRect.width - 2, bookmarkWidth - 2); + + backgroundRect.DrawRounded(backgroundColor, 4); + + + } + void icon() + { + var opacity = 1f; + var iconTexture = default(Texture); + + void set_opacity() + { + var opacityNormal = .9f; + var opacityHovered = 1f; + var opacityPressed = .75f; + var opacityDragged = .75f; + var opacityDisabled = .4f; + + var isDisabled = !bookmark.isLoadable; + + + opacity = opacityNormal; + + if (draggingBookmark) + opacity = bookmark == draggedBookmark ? opacityDragged : opacityNormal; + + else if (bookmark == pressedBookmark) + opacity = opacityPressed; + + else if (bookmarkRect.IsHovered()) + opacity = opacityHovered; + + if (isDisabled) + opacity = opacityDisabled; + + } + void getTexture() + { + var iconName = ""; + + if (bookmark.go) + if (VHierarchy.GetGameObjectData(bookmark.go, createDataIfDoesntExist: false) is GameObjectData goData && !goData.iconNameOrGuid.IsNullOrEmpty()) + iconName = goData.iconNameOrGuid.Length == 32 ? goData.iconNameOrGuid.ToPath() : goData.iconNameOrGuid; + else + iconName = AssetPreview.GetMiniThumbnail(bookmark.go).name; + + if (iconName.IsNullOrEmpty()) + iconName = "GameObject icon"; + + + iconTexture = EditorIcons.GetIcon(iconName); + + } + void drawTexture() + { + if (!iconTexture) return; + + + SetGUIColor(Greyscale(1, opacity)); + + GUI.DrawTexture(bookmarkRect.SetSizeFromMid(iconSize), iconTexture); + + ResetGUIColor(); + + } + + + set_opacity(); + getTexture(); + drawTexture(); + + } + void tooltip() + { + if (bookmark != (draggingBookmark ? (draggedBookmark) : (lastHoveredBookmark))) return; + if (tooltipOpacity == 0) return; + + var fontSize = 11; + var tooltipText = bookmark.name; + + Rect tooltipRect; + + void set_tooltipRect() + { + var width = tooltipText.GetLabelWidth(fontSize) + 6; + var height = 16 + (fontSize - 12) * 2; + + var yOffset = 28; + var rightMargin = -1; + + + tooltipRect = Rect.zero.SetMidPos(centerPosition.x, centerPosition.y + yOffset).SetSizeFromMid(width, height); + + + var maxXMax = navbarRect.xMax - rightMargin; + + if (tooltipRect.xMax > maxXMax) + tooltipRect = tooltipRect.MoveX(maxXMax - tooltipRect.xMax); + + } + void shadow() + { + var shadowAmount = .33f; + var shadowRadius = 10; + + tooltipRect.DrawBlurred(Greyscale(0, shadowAmount).MultiplyAlpha(tooltipOpacity), shadowRadius); + + } + void background() + { + var cornerRadius = 5; + + var backgroundColor = Greyscale(isDarkTheme ? .13f : .9f); + var outerEdgeColor = Greyscale(isDarkTheme ? .25f : .6f); + var innerEdgeColor = Greyscale(isDarkTheme ? .0f : .95f); + + tooltipRect.Resize(-1).DrawRounded(outerEdgeColor.SetAlpha(tooltipOpacity.Pow(2)), cornerRadius + 1); + tooltipRect.Resize(0).DrawRounded(innerEdgeColor.SetAlpha(tooltipOpacity.Pow(2)), cornerRadius + 0); + tooltipRect.Resize(1).DrawRounded(backgroundColor.SetAlpha(tooltipOpacity), cornerRadius - 1); + + } + void text() + { + var textRect = tooltipRect.MoveY(-.5f); + + var textColor = Greyscale(1f); + + SetLabelAlignmentCenter(); + SetLabelFontSize(fontSize); + SetGUIColor(textColor.SetAlpha(tooltipOpacity)); + + GUI.Label(textRect, tooltipText); + + ResetLabelStyle(); + ResetGUIColor(); + + } + + set_tooltipRect(); + shadow(); + background(); + text(); + + } + void click() + { + if (!bookmarkRect.IsHovered()) return; + if (!curEvent.isMouseUp) return; + + curEvent.Use(); + + + if (draggingBookmark) return; + if ((curEvent.mousePosition - mouseDownPosiion).magnitude > 2) return; + if (!bookmark.isLoadable) return; + + controller.RevealObject(bookmark.go, expand: true, highlight: true, snapToTopMargin: true); + + lastClickedBookmark = bookmark; + + hideTooltip = true; + + + + if (curEvent.mouseButton == 2 && VHierarchyMenu.setDefaultParentEnabled) + EditorUtility.SetDefaultParentObject(bookmark.go); + + } + + + bookmarkRect.MarkInteractive(); + + shadow(); + background(); + icon(); + tooltip(); + click(); + + } + + void normalBookmark(int i, float centerX) + { + if (data.bookmarks[i] == droppedBookmark && animatingDroppedBookmark) return; + + var centerY = bookmarksRect.height / 2; + + + var minX = centerX - bookmarkWidth / 2; + + if (minX < bookmarksRect.x) return; + + lastBookmarkX = minX; + + + bookmark(new Vector2(centerX, centerY), data.bookmarks[i]); + + } + void normalBookmarks() + { + var curCenterX = bookmarksRect.xMax - bookmarkWidth / 2; + + for (int i = 0; i < data.bookmarks.Count; i++) + { + curCenterX -= gaps[i]; + + if (!data.bookmarks[i].go) continue; + + + normalBookmark(i, curCenterX); + + + curCenterX -= bookmarkWidth; + + } + + } + void draggedBookmark_() + { + if (!draggingBookmark) return; + + var centerX = curEvent.mousePosition.x + draggedBookmarkHoldOffset.x; + var centerY = bookmarksRect.IsHovered() ? bookmarksRect.height / 2 : curEvent.mousePosition.y; + + bookmark(new Vector2(centerX, centerY), draggedBookmark); + + } + void droppedBookmark_() + { + if (!animatingDroppedBookmark) return; + + var centerX = droppedBookmarkX; + var centerY = bookmarksRect.height / 2; + + bookmark(new Vector2(centerX, centerY), droppedBookmark); + + } + + + BookmarksMouseState(); + BookmarksDragging(); + BookmarksAnimations(); + + normalBookmarks(); + draggedBookmark_(); + droppedBookmark_(); + + } + + float bookmarkWidth => 24; + float iconSize => 16; + + float lastBookmarkX; + + + + int GetBookmarkIndex(float mouseX) + { + var curBookmarkWidthSum = 0f; + + for (int i = 0; i < data.bookmarks.Count; i++) + { + if (!data.bookmarks[i].go) continue; + + curBookmarkWidthSum += bookmarkWidth; + + if (bookmarksRect.xMax - curBookmarkWidthSum < mouseX + .5f) + return i; + } + + return data.bookmarks.IndexOfLast(r => r.go) + 1; + + } + + float GetBookmarkCenterX(int i, bool includeGaps = true) + { + return bookmarksRect.xMax + - bookmarkWidth / 2 + - data.bookmarks.Take(i).Sum(r => r.go ? bookmarkWidth : 0) + - (includeGaps ? gaps.Take(i + 1).Sum() : 0); + } + + + + + + + + void BookmarksMouseState() + { + void down() + { + if (!curEvent.isMouseDown) return; + if (!bookmarksRect.IsHovered()) return; + + mousePressed = true; + + mouseDownPosiion = curEvent.mousePosition; + + var pressedBookmarkIndex = GetBookmarkIndex(mouseDownPosiion.x); + + if (pressedBookmarkIndex.IsInRangeOf(data.bookmarks)) + pressedBookmark = data.bookmarks[pressedBookmarkIndex]; + + doubleclickUnhandled = curEvent.clickCount == 2; + + curEvent.Use(); + + } + void up() + { + if (!curEvent.isMouseUp) return; + + mousePressed = false; + pressedBookmark = null; + + } + void hover() + { + var hoveredBookmarkIndex = GetBookmarkIndex(curEvent.mousePosition.x); + + mouseHoversBookmark = bookmarksRect.IsHovered() && hoveredBookmarkIndex.IsInRangeOf(data.bookmarks); + + if (mouseHoversBookmark) + lastHoveredBookmark = data.bookmarks[hoveredBookmarkIndex]; + + + } + + down(); + up(); + hover(); + + } + + bool mouseHoversBookmark; + bool mousePressed; + bool doubleclickUnhandled; + + Vector2 mouseDownPosiion; + + Bookmark pressedBookmark; + Bookmark lastHoveredBookmark; + + + + + + + void BookmarksDragging() + { + void initFromOutside() + { + if (draggingBookmark) return; + if (!bookmarksRect.IsHovered()) return; + if (!curEvent.isDragUpdate) return; + if (DragAndDrop.objectReferences.FirstOrDefault() is not GameObject draggedGo) return; + + animatingDroppedBookmark = false; + + draggingBookmark = true; + draggingBookmarkFromInside = false; + + draggedBookmark = new Bookmark(draggedGo); + draggedBookmarkHoldOffset = Vector2.zero; + + } + void initFromInside() + { + if (draggingBookmark) return; + if (!mousePressed) return; + if ((curEvent.mousePosition - mouseDownPosiion).magnitude <= 2) return; + if (pressedBookmark == null) return; + + var i = GetBookmarkIndex(mouseDownPosiion.x); + + if (i >= data.bookmarks.Count) return; + if (i < 0) return; + + + animatingDroppedBookmark = false; + + draggingBookmark = true; + draggingBookmarkFromInside = true; + + draggedBookmark = data.bookmarks[i]; + draggedBookmarkHoldOffset = new Vector2(GetBookmarkCenterX(i) - mouseDownPosiion.x, bookmarksRect.center.y - mouseDownPosiion.y); + + gaps[i] = bookmarkWidth; + + + data.RecordUndo(); + + data.bookmarks.Remove(draggedBookmark); + + } + + void acceptFromOutside() + { + if (!draggingBookmark) return; + if (!curEvent.isDragPerform) return; + if (!bookmarksRect.IsHovered()) return; + + DragAndDrop.AcceptDrag(); + curEvent.Use(); + + data.RecordUndo(); + + accept(); + + data.Dirty(); + + } + void acceptFromInside() + { + if (!draggingBookmark) return; + if (!curEvent.isMouseUp) return; + if (!bookmarksRect.IsHovered()) return; + + curEvent.Use(); + EditorGUIUtility.hotControl = 0; + + DragAndDrop.PrepareStartDrag(); // fixes phantom dragged component indicator after reordering bookmarks + + data.RecordUndo(); + data.Dirty(); + + accept(); + + } + void accept() + { + draggingBookmark = false; + draggingBookmarkFromInside = false; + mousePressed = false; + + data.bookmarks.AddAt(draggedBookmark, insertDraggedBookmarkAtIndex); + + gaps[insertDraggedBookmarkAtIndex] -= bookmarkWidth; + gaps.AddAt(0, insertDraggedBookmarkAtIndex); + + droppedBookmark = draggedBookmark; + + droppedBookmarkX = curEvent.mousePosition.x + draggedBookmarkHoldOffset.x; + droppedBookmarkXDerivative = 0; + animatingDroppedBookmark = true; + + draggedBookmark = null; + + EditorGUIUtility.hotControl = 0; + + } + + void cancelFromOutside() + { + if (!draggingBookmark) return; + if (draggingBookmarkFromInside) return; + if (bookmarksRect.IsHovered()) return; + + draggingBookmark = false; + mousePressed = false; + + } + void cancelFromInsideAndDelete() + { + if (!draggingBookmark) return; + if (!curEvent.isMouseUp) return; + if (bookmarksRect.IsHovered()) return; + + draggingBookmark = false; + + DragAndDrop.PrepareStartDrag(); // fixes phantom dragged component indicator after reordering bookmarks + + data.Dirty(); + + } + + void update() + { + if (!draggingBookmark) return; + + DragAndDrop.visualMode = DragAndDropVisualMode.Generic; + + if (draggingBookmarkFromInside) // otherwise it breaks vTabs dragndrop + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + + + insertDraggedBookmarkAtIndex = GetBookmarkIndex(curEvent.mousePosition.x + draggedBookmarkHoldOffset.x); + + } + + + initFromOutside(); + initFromInside(); + + acceptFromOutside(); + acceptFromInside(); + + cancelFromOutside(); + cancelFromInsideAndDelete(); + + update(); + + + } + + bool draggingBookmark; + bool draggingBookmarkFromInside; + + int insertDraggedBookmarkAtIndex; + + Vector2 draggedBookmarkHoldOffset; + + Bookmark draggedBookmark; + Bookmark droppedBookmark; + + + + + + + void BookmarksAnimations() + { + if (!curEvent.isLayout) return; + + void gaps_() + { + var makeSpaceForDraggedBookmark = draggingBookmark && bookmarksRect.IsHovered(); + + var lerpSpeed = 12; + + for (int i = 0; i < gaps.Count; i++) + if (makeSpaceForDraggedBookmark && i == insertDraggedBookmarkAtIndex) + gaps[i] = MathUtil.Lerp(gaps[i], bookmarkWidth, lerpSpeed, editorDeltaTime); + else + gaps[i] = MathUtil.Lerp(gaps[i], 0, lerpSpeed, editorDeltaTime); + + + + for (int i = 0; i < gaps.Count; i++) + if (gaps[i].Approx(0)) + gaps[i] = 0; + + + + animatingGaps = gaps.Any(r => r > .1f); + + + } + void droppedBookmark_() + { + if (!animatingDroppedBookmark) return; + + var lerpSpeed = 8; + + var targX = GetBookmarkCenterX(data.bookmarks.IndexOf(droppedBookmark), includeGaps: false); + + MathUtil.SmoothDamp(ref droppedBookmarkX, targX, lerpSpeed, ref droppedBookmarkXDerivative, editorDeltaTime); + + if ((droppedBookmarkX - targX).Abs() < .5f) + animatingDroppedBookmark = false; + + } + void tooltip() + { + if (!mouseHoversBookmark || lastHoveredBookmark != lastClickedBookmark) + hideTooltip = false; + + + var lerpSpeed = UnityEditorInternal.InternalEditorUtility.isApplicationActive ? 15 : 12321; + + if (mouseHoversBookmark && !draggingBookmark && !hideTooltip) + MathUtil.SmoothDamp(ref tooltipOpacity, 1, lerpSpeed, ref tooltipOpacityDerivative, editorDeltaTime); + else + MathUtil.SmoothDamp(ref tooltipOpacity, 0, lerpSpeed, ref tooltipOpacityDerivative, editorDeltaTime); + + + if (tooltipOpacity > .99f) + tooltipOpacity = 1; + + if (tooltipOpacity < .01f) + tooltipOpacity = 0; + + + animatingTooltip = tooltipOpacity != 0 && tooltipOpacity != 1; + + } + + gaps_(); + droppedBookmark_(); + tooltip(); + + } + + float droppedBookmarkX; + float droppedBookmarkXDerivative; + + float tooltipOpacity; + float tooltipOpacityDerivative; + + bool animatingDroppedBookmark; + bool animatingGaps; + bool animatingTooltip; + bool animatingBookmarks => animatingDroppedBookmark || animatingGaps; + + bool hideTooltip; + + List gaps + { + get + { + while (_gaps.Count < data.bookmarks.Count + 1) _gaps.Add(0); + while (_gaps.Count > data.bookmarks.Count + 1) _gaps.RemoveLast(); + + return _gaps; + + } + } + List _gaps = new(); + + Bookmark lastClickedBookmark; + + + + + + + + + + + + + + public VHierarchyNavbar(EditorWindow window) => this.window = window; + + public EditorWindow window; + + public VHierarchyController controller => VHierarchy.controllers_byWindow[window]; + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyNavbar.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyNavbar.cs.meta new file mode 100644 index 0000000..611fae8 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyNavbar.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0951d821533c4f9486bb1430540ef3b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPalette.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPalette.cs new file mode 100644 index 0000000..9c2395e --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPalette.cs @@ -0,0 +1,221 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditorInternal; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VHierarchy +{ + public class VHierarchyPalette : ScriptableObject + { + public List colors = new(); + + public bool colorsEnabled; + + public float colorSaturation = 1; + public float colorBrightness = 1; + public bool colorGradientsEnabled = true; + + public void ResetColors() + { + colors.Clear(); + + for (int i = 0; i < colorsCount; i++) + colors.Add(GetDefaultColor(i)); + + colorsEnabled = true; + + this.Dirty(); + + } + + public static Color GetDefaultColor(int colorIndex) + { + Color color = default; + + void grey() + { + if (colorIndex >= greyColorsCount) return; + +#if UNITY_2022_1_OR_NEWER + color = Greyscale(isDarkTheme ? .16f : .9f); +#else + color = Greyscale(isDarkTheme ? .315f : .9f); +#endif + + } + void rainbowDarkTheme() + { + if (colorIndex < greyColorsCount) return; + if (!isDarkTheme) return; + + color = ColorUtils.HSLToRGB((colorIndex - greyColorsCount.ToFloat()) / rainbowColorsCount, .45f, .35f); + + if (colorIndex == 1) + color *= 1.2f; + + if (colorIndex == 2) + color *= 1.1f; + + if (colorIndex == 6) + color *= 1.35f; + + if (colorIndex == 7) + color *= 1.3f; + + if (colorIndex == 8) + color *= 1.05f; + + + color.a = .1f; + + } + void rainbowLightTheme() + { + if (colorIndex < greyColorsCount) return; + if (isDarkTheme) return; + + color = ColorUtils.HSLToRGB((colorIndex - greyColorsCount.ToFloat()) / rainbowColorsCount, .62f, .8f); + + color.a = .1f; + + } + + grey(); + rainbowDarkTheme(); + rainbowLightTheme(); + + return color; + + } + + public static int greyColorsCount = 1; + public static int rainbowColorsCount = 8; + public static int colorsCount => greyColorsCount + rainbowColorsCount; + + + + + public List iconRows = new(); + + [System.Serializable] + public class IconRow + { + public List builtinIcons = new(); // names + public List customIcons = new(); // names or guids + + public bool enabled = true; + + public bool isCustom => !builtinIcons.Any() || customIcons.Any(); + public bool isEmpty => !builtinIcons.Any() && !customIcons.Any(); + public int iconCount => builtinIcons.Count + customIcons.Count; + + public IconRow(string[] builtinIcons) => this.builtinIcons = builtinIcons.ToList(); + public IconRow() { } + + } + + public void ResetIcons() + { + iconRows.Clear(); + + iconRows.Add(new IconRow(new[] + { + "Folder Icon", + "Canvas Icon", + "AvatarMask On Icon", + "cs Script Icon", + "StandaloneInputModule Icon", + "EventSystem Icon", + "Terrain Icon", + "ScriptableObject Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "Camera Icon", + "ParticleSystem Icon", + "TrailRenderer Icon", + "Material Icon", + "ReflectionProbe Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "Light Icon", + "DirectionalLight Icon", + "LightmapParameters Icon", + "LightProbes Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "Rigidbody Icon", + "BoxCollider Icon", + "SphereCollider Icon", + "CapsuleCollider Icon", + "WheelCollider Icon", + "MeshCollider Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "AudioSource Icon", + "AudioClip Icon", + "AudioListener Icon", + "AudioEchoFilter Icon", + "AudioReverbZone Icon", + + })); + iconRows.Add(new IconRow(new[] + { + "PreMatCube", + "PreMatSphere", + "PreMatCylinder", + "PreMatQuad", + "Favorite", + #if UNITY_2021_3_OR_NEWER + "Settings Icon", + #endif + + })); + + this.Dirty(); + + } + + + + + [ContextMenu("Export palette")] + public void Export() + { + var packagePath = EditorUtility.SaveFilePanel("Export vHierarchy Palette", "", this.GetPath().GetFilename(withExtension: false), "unitypackage"); + + var iconPaths = iconRows.SelectMany(r => r.customIcons).Select(r => r.ToPath()).Where(r => !r.IsNullOrEmpty()); + + AssetDatabase.ExportPackage(iconPaths.Append(this.GetPath()).ToArray(), packagePath); + + EditorUtility.RevealInFinder(packagePath); + + } + + + + + void Reset() { ResetColors(); ResetIcons(); } + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPalette.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPalette.cs.meta new file mode 100644 index 0000000..bec7d1c --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPalette.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 61290d425f1c94a8cbfb56f754ca6757 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteEditor.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteEditor.cs new file mode 100644 index 0000000..4add5a8 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteEditor.cs @@ -0,0 +1,1374 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEditorInternal; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; +using static VHierarchy.VHierarchyPalette; + + +namespace VHierarchy +{ + [CustomEditor(typeof(VHierarchyPalette))] + class VHierarchyPaletteEditor : Editor + { + + public override void OnInspectorGUI() + { + void colors() + { + var rowRect = ExpandWidthLabelRect(cellSize).SetX(rowsOffsetX).SetWidth(rowWidth + 16); + + void backgroundHovered() + { + if (!rowRect.IsHovered()) return; + if (pickingColor) return; + if (draggingRow) return; + + rowRect.Draw(hoveredRowBackground); + + } + void toggle() + { + var toggleRect = rowRect.SetWidth(16).MoveX(5); + + var prevEnabled = palette.colorsEnabled; + var newEnabled = EditorGUI.Toggle(toggleRect, palette.colorsEnabled); + + if (prevEnabled != newEnabled) + palette.RecordUndo(); + + palette.colorsEnabled = newEnabled; + + if (prevEnabled != newEnabled) + palette.Dirty(); + + } + void crossIcon() + { + var crossIconRect = rowRect.SetX(rowsOffsetX + iconsOffsetX + iconSpacing / 2).SetWidth(iconSize).SetHeightFromMid(iconSize); + + SetGUIColor(palette.colorsEnabled ? Color.white : disabledRowTint); + + GUI.DrawTexture(crossIconRect, EditorIcons.GetIcon("CrossIcon")); + + ResetGUIColor(); + + } + void color(int i) + { + var cellRect = rowRect.MoveX(iconsOffsetX + (i + 1) * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + void backgroundPicking() + { + if (!pickingColor) return; + if (i != pickingColorAtIndex) return; + + cellRect.DrawRounded(pickingBackground, 2); + + } + void backgroundHovered_andStartPickingColor() + { + if (pickingColor) return; + if (!cellRect.IsHovered()) return; + + + SetGUIColor(Color.clear); + + var clicked = GUI.Button(cellRect.Resize(1), ""); + + ResetGUIColor(); + + + + + var isPressed = GUIUtility.hotControl == typeof(EditorGUIUtility).GetFieldValue("s_LastControlID"); + + cellRect.DrawRounded(Greyscale(isPressed ? .39f : .43f), 2); + + + + + if (!clicked) return; + + colorPickerWindow = EditorUtils.OpenColorPicker((c) => { palette.RecordUndo(); palette.Dirty(); palette.colors[i] = c; }, palette.colors[i], true, false); + + colorPickerWindow.MoveTo(EditorGUIUtility.GUIToScreenPoint(cellRect.Move(-3, 50).position)); + + pickingColor = true; + pickingColorAtIndex = i; + + } + void colorOutline() + { + var outlineColor = i < VHierarchyPalette.greyColorsCount ? Greyscale(.0f, .4f) : Greyscale(.15f, .2f); + + if (!palette.colorsEnabled) + outlineColor *= disabledRowTint; + + + cellRect.Resize(3).DrawRounded(outlineColor, 4); + + } + void color() + { + var brightness = palette.colorBrightness; + var saturation = palette.colorSaturation; + var drawGradients = palette.colorGradientsEnabled; + + if (!palette.colorGradientsEnabled) + brightness *= isDarkTheme ? .75f : .97f; + + if (i < VHierarchyPalette.greyColorsCount) + { + saturation = brightness = 1; + drawGradients = false; + } + + + var colorRaw = palette.colors[i]; + + var color = MathUtil.Lerp(Greyscale(.2f), colorRaw, brightness); + + Color.RGBToHSV(color, out float h, out float s, out float v); + color = Color.HSVToRGB(h, s * saturation, v); + + color = MathUtil.Lerp(color, colorRaw, .5f).SetAlpha(1); + + if (!palette.colorsEnabled) + color *= disabledRowTint; + + if (i >= VHierarchyPalette.greyColorsCount && isDarkTheme) + color *= 1.41f; + + + + + cellRect.Resize(4).DrawRounded(color, 3); + + if (drawGradients) + cellRect.Resize(4).AddWidthFromRight(-2).DrawCurtainLeft(GUIColors.windowBackground.SetAlpha(.45f)); + + } + void updatePickingColor() + { + if (!pickingColor) return; + + EditorApplication.RepaintHierarchyWindow(); + + } + void stopPickingColor() + { + if (!pickingColor) return; + if (colorPickerWindow) return; + + pickingColor = false; + + } + + + cellRect.MarkInteractive(); + + + backgroundPicking(); + backgroundHovered_andStartPickingColor(); + + colorOutline(); + color(); + + updatePickingColor(); + stopPickingColor(); + + } + void adjustColorsButton() + { + var cellRect = rowRect.MoveX(iconsOffsetX + (palette.colors.Count + 1) * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize).MoveX(-1f); + + + var iconSize = 16; + var iconName = "Preset.Context"; + var iconColor = Greyscale(.75f, palette.colorsEnabled ? (isDarkTheme ? 1 : .8f) : .5f); + + if (!IconButton(cellRect, iconName, iconSize, iconColor)) return; + + + if (adjustColorsWindow) { adjustColorsWindow.Close(); return; } + + var windowX = 107f.Min(this.GetMemberValue("propertyViewer").position.width - 310); + var windowY = cellRect.y + 25; + var windowWidth = 270; + var windowHeight = 92; + + adjustColorsWindow = ScriptableObject.CreateInstance(); + adjustColorsWindow.palette = palette; + adjustColorsWindow.paletteEditor = this; + + adjustColorsWindow.ShowPopup(); + adjustColorsWindow.Focus(); + + adjustColorsWindow.position = EditorGUIUtility.GUIToScreenRect(new Rect(windowX, windowY, windowWidth, windowHeight)); + + } + + + backgroundHovered(); + toggle(); + crossIcon(); + + for (int i = 0; i < palette.colors.Count; i++) + color(i); + + adjustColorsButton(); + + Space(rowSpacing - 2); + + } + void icons() + { + void row(Rect rowRect, IconRow row) + { + var isLastRow = row == palette.iconRows.Last(); + var isDraggedRow = row == draggedRow; + var spaceForCrossIcon = 0f; + + + void updatePickingIcon() + { + if (!pickingIcon) return; + if (pickingIconAtRow != row) return; + if (pickingIconAtIndex >= row.customIcons.Count) return; // somehow happens if RecordUndo is used + + palette.RecordUndo(); + palette.Dirty(); + + row.customIcons[pickingIconAtIndex] = addIconWindow.hoveredIconName; + + } + void stopPickingIcon() + { + if (!pickingIcon) return; + if (pickingIconAtRow != row) return; + if (addIconWindow) return; + + if (pickingIconAtIndex < row.customIcons.Count) + if (row.customIcons[pickingIconAtIndex] == null) + row.customIcons.RemoveAt(pickingIconAtIndex); + + pickingIcon = false; + + } + void dragndrop() + { + if (!rowRect.IsHovered()) return; + if (!row.isCustom) return; + + if (curEvent.isDragUpdate && DragAndDrop.objectReferences.First() is Texture2D) + DragAndDrop.visualMode = DragAndDropVisualMode.Copy; + + if (!curEvent.isDragPerform) return; + if (!(DragAndDrop.objectReferences.Any(r => r is Texture2D))) return; + + DragAndDrop.AcceptDrag(); + + palette.RecordUndo(); + palette.Dirty(); + + foreach (var icon in DragAndDrop.objectReferences.Where(r => r is Texture2D)) + row.customIcons.Add(icon.GetPath().ToGuid()); + + } + + void calcSpaceForCrossIcon() + { + if (row == curFirstEnabledRow) + spaceForCrossIcon = crossIconAnimationT * cellSize; + + if (row == crossIconAnimationSourceRow) + spaceForCrossIcon = (1 - crossIconAnimationT) * cellSize; + + } + + void backgroundHovered() + { + if (!rowRect.IsHovered()) return; + if (pickingColor) return; + if (pickingIcon) return; + if (draggingRow) return; + if (DragAndDrop.objectReferences.Any() && !row.isCustom) return; + + + rowRect.Draw(hoveredRowBackground); + + } + void backgroundDragged() + { + if (!isDraggedRow) return; + + rowRect.DrawBlurred(Greyscale(0, .3f), 12); + rowRect.Draw(draggedRowBackground); + + } + void toggle() + { + var prevEnabled = row.enabled; + var newEnabled = EditorGUI.Toggle(rowRect.SetWidth(16).MoveX(5), row.enabled); + + if (prevEnabled != newEnabled) + palette.RecordUndo(); + + row.enabled = newEnabled; + + if (prevEnabled != newEnabled) + palette.Dirty(); + + } + void addIconButton() + { + if (!row.isCustom) return; + if (pickingIcon && pickingIconAtRow == row) return; + + + var cellRect = rowRect.MoveX(iconsOffsetX + row.customIcons.Count * cellSize + spaceForCrossIcon).SetWidth(cellSize).SetHeightFromMid(cellSize); + + var iconSize = 16; + var iconName = "Toolbar Plus"; + var iconColor = Greyscale(.73f, row.enabled ? (isDarkTheme ? 1 : .65f) : .5f); + + if (!IconButton(cellRect, iconName, iconSize, iconColor)) return; + + + + palette.RecordUndo(); + + row.customIcons.Add(null); + + + + var windowX = 15; + var windowY = cellRect.y + 23; + var windowWidth = (this.GetMemberValue("propertyViewer").position.width - 26).Min(679); + var windowHeight = windowWidth * 1.2f; + + windowWidth = (windowWidth / AddIconWindow.cellSize).FloorToInt() * AddIconWindow.cellSize - 3; + + + addIconWindow = ScriptableObject.CreateInstance(); + addIconWindow.palette = palette; + addIconWindow.paletteEditor = this; + + addIconWindow.ShowPopup(); + addIconWindow.Focus(); + + addIconWindow.position = EditorGUIUtility.GUIToScreenRect(new Rect(windowX, windowY, windowWidth, windowHeight)); + + addIconWindow.Init(); + + + pickingIcon = true; + pickingIconAtIndex = row.customIcons.Count - 1; + pickingIconAtRow = row; + + + } + void icon(int i) + { + var cellRect = rowRect.MoveX(iconsOffsetX + spaceForCrossIcon + i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + + void backgroundPicking() + { + if (!pickingIcon) return; + if (pickingIconAtRow != row) return; + if (pickingIconAtIndex != i) return; + + cellRect.DrawRounded(pickingBackground, 2); + + } + void backgroundHovered_andEditIconButton() + { + if (!row.isCustom) return; + if (pickingIcon) return; + if (!cellRect.IsHovered()) return; + if (DragAndDrop.objectReferences.Any()) return; + + + SetGUIColor(Color.clear); + + var clicked = GUI.Button(cellRect.Resize(1), ""); + + ResetGUIColor(); + + + + + var isPressed = GUIUtility.hotControl == typeof(EditorGUIUtility).GetFieldValue("s_LastControlID"); + + cellRect.DrawRounded(Greyscale(isPressed ? .39f : .45f), 2); + + + + + if (!clicked) return; + + GenericMenu menu = new(); + + menu.AddItem(new GUIContent("Move left"), false, i == 0 ? null : () => + { + palette.RecordUndo(); + palette.Dirty(); + + var icon = row.customIcons[i]; + + row.customIcons.RemoveAt(i); + row.customIcons.AddAt(icon, i - 1); + + }); + menu.AddItem(new GUIContent("Move right"), false, i == row.customIcons.Count - 1 ? null : () => + { + palette.RecordUndo(); + palette.Dirty(); + + var icon = row.customIcons[i]; + + row.customIcons.RemoveAt(i); + row.customIcons.AddAt(icon, i + 1); + + }); + + menu.AddSeparator(""); + + menu.AddItem(new GUIContent("Remove icon"), false, () => { palette.RecordUndo(); row.customIcons.RemoveAt(i); palette.Dirty(); }); + + + menu.ShowAsContext(); + + } + + void drawIcon() + { + var iconNameOrGuid = row.isCustom ? row.customIcons[i] : row.builtinIcons[i]; + + if (iconNameOrGuid == null) return; + + var iconNameOrPath = iconNameOrGuid.Length == 32 ? iconNameOrGuid.ToPath() : iconNameOrGuid; + var icon = EditorIcons.GetIcon(iconNameOrPath) ?? Texture2D.blackTexture; + + + var cellRect = rowRect.MoveX(iconsOffsetX + spaceForCrossIcon + i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + var iconRect = cellRect.SetSizeFromMid(iconSize); + + if (icon.width < icon.height) iconRect = iconRect.SetWidthFromMid(iconRect.height * icon.width / icon.height); + if (icon.height < icon.width) iconRect = iconRect.SetHeightFromMid(iconRect.width * icon.height / icon.width); + + + + SetGUIColor(row.enabled ? Color.white : disabledRowTint); + + GUI.DrawTexture(iconRect, icon); + + ResetGUIColor(); + + } + + + cellRect.MarkInteractive(); + + backgroundPicking(); + backgroundHovered_andEditIconButton(); + + drawIcon(); + + } + + + rowRect.MarkInteractive(); + + updatePickingIcon(); + stopPickingIcon(); + dragndrop(); + + calcSpaceForCrossIcon(); + backgroundHovered(); + backgroundDragged(); + toggle(); + addIconButton(); + + for (int i = 0; i < row.iconCount; i++) + icon(i); + + } + + void updateRowsCount() + { + palette.iconRows.RemoveAll(r => r.isEmpty && r != palette.iconRows.Last()); + + if (!palette.iconRows.Last().isEmpty) + palette.iconRows.Add(new IconRow()); + + } + void updateRowGapsCount() + { + while (rowGaps.Count < palette.iconRows.Count) + rowGaps.Add(0); + + while (rowGaps.Count > palette.iconRows.Count) + rowGaps.RemoveLast(); + + } + + void normalRow(int i) + { + Space(rowGaps[i] * (cellSize + rowSpacing)); + + if (i == 0 && lastRect.y != 0) + firstRowY = lastRect.y; + + Space(cellSize + rowSpacing); + + var rowRect = Rect.zero.SetPos(rowsOffsetX, lastRect.y).SetSize(rowWidth, cellSize); + + if (curEvent.isRepaint) + if (rowRect.IsHovered()) + hoveredRow = palette.iconRows[i]; + + + row(rowRect, palette.iconRows[i]); + + } + void draggedRow_() + { + if (!draggingRow) return; + + draggedRowY = (curEvent.mousePosition.y + draggedRowHoldOffset).Clamp(firstRowY, firstRowY + (palette.iconRows.Count - 1) * (cellSize + rowSpacing)); + + var rowRect = Rect.zero.SetPos(rowsOffsetX, draggedRowY).SetSize(rowWidth, cellSize); + + row(rowRect, draggedRow); + + } + void crossIcon() + { + if (!palette.iconRows.Any(r => r.enabled)) return; + + var rect = Rect.zero.SetPos(rowsOffsetX + iconsOffsetX, crossIconY).SetSize(cellSize, cellSize).Resize(iconSpacing / 2); + + GUI.DrawTexture(rect, EditorIcons.GetIcon("CrossIcon")); + + } + + + updateRowsCount(); + updateRowGapsCount(); + + + if (curEvent.isRepaint) + hoveredRow = null; + + for (int i = 0; i < palette.iconRows.Count; i++) + normalRow(i); + + crossIcon(); + + draggedRow_(); + + + } + void tutor() + { + SetGUIEnabled(false); + + + Space(4); + GUILayout.Label("Add icons with drag-and-drop or by clicking '+'"); + + Space(4); + GUILayout.Label("Click added icon to move or remove it"); + + Space(4); + GUILayout.Label("Drag rows to reorder them"); + + + ResetGUIEnabled(); + + } + + + Space(15); + colors(); + + Space(15); + icons(); + + Space(22); + tutor(); + + UpdateAnimations(); + + UpdateDragging(); + + palette.Dirty(); + + if (draggingRow || animatingCrossIcon) + Repaint(); + + } + + float iconSize => 14; + float iconSpacing => 6; + float cellSize => iconSize + iconSpacing; + float rowSpacing = 1; + float rowsOffsetX => 14; + float iconsOffsetX => 27; + + Color hoveredRowBackground => Greyscale(isDarkTheme ? 1 : 0, .05f); + Color draggedRowBackground => Greyscale(isDarkTheme ? .3f : .9f); + Color pickingBackground => Greyscale(1, .17f); + Color disabledRowTint => Greyscale(1, .45f); + + float rowWidth => cellSize * Mathf.Max(palette.colors.Count, palette.iconRows.Max(r => r.iconCount + 1)) + 55; + + bool pickingColor; + int pickingColorAtIndex; + EditorWindow colorPickerWindow; + + bool pickingIcon; + int pickingIconAtIndex; + IconRow pickingIconAtRow; + AddIconWindow addIconWindow; + + IconRow hoveredRow; + + float firstRowY = 51; + + static AdjustColorsWindow adjustColorsWindow; + + + + + + void UpdateAnimations() + { + void lerpRowGaps() + { + if (!curEvent.isLayout) return; + + var lerpSpeed = draggingRow ? 12 : 12321; + + for (int i = 0; i < rowGaps.Count; i++) + rowGaps[i] = MathUtil.Lerp(rowGaps[i], draggingRow && i == insertDraggedRowAtIndex ? 1 : 0, lerpSpeed, editorDeltaTime); + + for (int i = 0; i < rowGaps.Count; i++) + if (rowGaps[i].Approx(0)) + rowGaps[i] = 0; + else if (rowGaps[i].Approx(1)) + rowGaps[i] = 1; + + + } + + void lerpCrossIconAnimationT() + { + if (!curEvent.isLayout) return; + + var lerpSpeed = 12; + + MathUtil.Lerp(ref crossIconAnimationT, 1, lerpSpeed, editorDeltaTime); + + } + void startCrossIconAnimation() + { + if (prevFirstEnabledRow == null) { prevFirstEnabledRow = curFirstEnabledRow; return; } + if (prevFirstEnabledRow == curFirstEnabledRow) return; + + crossIconAnimationT = 0; + crossIconAnimationSourceRow = prevFirstEnabledRow; + + prevFirstEnabledRow = curFirstEnabledRow; + + } + void stopCrossIconAnimation() + { + if (!crossIconAnimationT.Approx(1)) return; + + crossIconAnimationT = 1; + crossIconAnimationSourceRow = null; + + } + void calcCrossIconY() + { + var indexOfFirstEnabled = palette.iconRows.IndexOfFirst(r => r.enabled); + var yOfFirstEnabled = firstRowY + indexOfFirstEnabled * (cellSize + rowSpacing); + for (int i = 0; i < indexOfFirstEnabled + 1; i++) + yOfFirstEnabled += rowGaps[i] * (cellSize + rowSpacing); + + + var indexOfSourceRow = palette.iconRows.IndexOf(crossIconAnimationSourceRow); + var yOfSourceRow = firstRowY + indexOfSourceRow * (cellSize + rowSpacing); + for (int i = 0; i < indexOfSourceRow + 1; i++) + yOfSourceRow += rowGaps[i] * (cellSize + rowSpacing); + + if (crossIconAnimationSourceRow == draggedRow) + yOfSourceRow = draggedRowY; + + + crossIconY = MathUtil.Lerp(yOfSourceRow, yOfFirstEnabled, crossIconAnimationT); + + if (indexOfFirstEnabled == indexOfSourceRow) + crossIconAnimationT = 1; + + } + + + lerpRowGaps(); + + lerpCrossIconAnimationT(); + startCrossIconAnimation(); + stopCrossIconAnimation(); + calcCrossIconY(); + + } + + List rowGaps = new(); + + float crossIconY = 51; + float crossIconAnimationT = 1; + IconRow crossIconAnimationSourceRow; + bool animatingCrossIcon => crossIconAnimationT != 1; + + [System.NonSerialized] IconRow prevFirstEnabledRow; + IconRow curFirstEnabledRow => palette.iconRows.FirstOrDefault(r => r.enabled); + + + + + + + void UpdateDragging() + { + void startDragging() + { + if (draggingRow) return; + if (!curEvent.isMouseDrag) return; + if (hoveredRow == null) return; + if (hoveredRow == palette.iconRows.Last()) return; + + palette.RecordUndo(); + + draggingRow = true; + draggedRow = hoveredRow; + draggingRowFromIndex = palette.iconRows.IndexOf(hoveredRow); + draggedRowHoldOffset = firstRowY + draggingRowFromIndex * (cellSize + rowSpacing) - curEvent.mousePosition.y; + + palette.iconRows.Remove(hoveredRow); + rowGaps[draggingRowFromIndex] = 1; + + } + void updateDragging() + { + if (!draggingRow) return; + + insertDraggedRowAtIndex = ((curEvent.mousePosition.y - firstRowY) / (cellSize + rowSpacing)).FloorToInt().Clamp(0, palette.iconRows.Count - 1); + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + } + void stopDragging() + { + if (!draggingRow) return; + if (!curEvent.isMouseUp) return; + + palette.RecordUndo(); + palette.Dirty(); + + palette.iconRows.AddAt(draggedRow, insertDraggedRowAtIndex); + + rowGaps[insertDraggedRowAtIndex] = 0; + + draggingRow = false; + draggedRow = null; + + EditorGUIUtility.hotControl = 0; + + } + + + startDragging(); + updateDragging(); + stopDragging(); + + } + + IconRow draggedRow; + bool draggingRow; + int draggingRowFromIndex; + float draggedRowHoldOffset; + float draggedRowY; + int insertDraggedRowAtIndex; + + + + + + + VHierarchyPalette palette => target as VHierarchyPalette; + + } + + + class AddIconWindow : EditorWindow + { + + void OnGUI() + { + void header() + { + var headerRect = Rect.zero.SetHeight(20).SetWidth(position.width); + var closeButtonRect = headerRect.SetWidthFromRight(16).SetHeightFromMid(16).Move(-3, -.5f); + + void background() + { + headerRect.Draw(EditorGUIUtility.isProSkin ? Greyscale(.18f) : Greyscale(.7f)); + } + void title() + { + SetGUIColor(Greyscale(.8f)); + SetLabelAlignmentCenter(); + + GUI.Label(headerRect.MoveY(-1), "Add icon"); + + ResetLabelStyle(); + ResetGUIColor(); + + } + void closeButton() + { + var colorNormal = isDarkTheme ? Greyscale(.55f) : Greyscale(.35f); + var colorHovered = isDarkTheme ? Greyscale(.9f) : colorNormal; + + var iconSize = 14; + + if (IconButton(closeButtonRect, "CrossIcon", iconSize, colorNormal, colorHovered)) + Close(); + + } + void escHint() + { + if (!closeButtonRect.IsHovered()) return; + + var textRect = headerRect.SetWidthFromRight(42).MoveY(-.5f).MoveX(1); + var fontSize = 11; + var color = Greyscale(.65f); + + + SetLabelFontSize(fontSize); + SetGUIColor(color); + + GUI.Label(textRect, "Esc"); + + ResetGUIColor(); + ResetLabelStyle(); + + } + + background(); + title(); + closeButton(); + escHint(); + + Space(headerRect.height); + + } + void search() + { + var backgroundRect = ExpandWidthLabelRect(height: 21).SetWidthFromMid(position.width); + var backgroundColor = isDarkTheme ? Greyscale(.25f) : Greyscale(.8f); + + backgroundRect.Draw(backgroundColor); + + + var lineRect = backgroundRect.SetHeightFromBottom(1).MoveY(.5f); + var lineColor = isDarkTheme ? Greyscale(.15f) : Greyscale(.7f); + + lineRect.Draw(lineColor); + + + var searchRect = backgroundRect.Resize(2); + + EditorGUI.BeginChangeCheck(); + + searchString = searchField.OnGUI(searchRect, searchString); + + if (EditorGUI.EndChangeCheck()) + { + GenerateRows(); + FilterIconsBySearch(); + GenerateRows(); + } + + } + void icons() + { + void row(int i) + { + var rowRect = position.SetPos(0, 0).SetHeight(cellSize).MoveY(i * rowHeight); + var iconNames = rows[i]; + + void icon(int i) + { + var iconName = iconNames[i]; + var icon = EditorIcons.GetIcon(iconName); + + var cellRect = rowRect.SetWidth(cellSize).MoveX(i * cellSize + paddingLeft); + var iconRect = cellRect.SetSizeFromMid(iconSize); + + if (icon.width < icon.height) iconRect = iconRect.SetWidthFromMid(iconRect.height * icon.width / icon.height); + if (icon.height < icon.width) iconRect = iconRect.SetHeightFromMid(iconRect.width * icon.height / icon.width); + + + + var hoverRect = cellRect.AddHeightFromMid(rowSpacing); + + hoverRect.MarkInteractive(); + + if (hoverRect.IsHovered()) + cellRect.Draw(Greyscale(isDarkTheme ? .42f : .69f)); + + + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon(iconName)); + + + + if (hoverRect.IsHovered()) + hoveredIconName = iconName; + + if (hoverRect.IsHovered() && curEvent.isMouseDown) + Close(); + + } + + for (int ii = 0; ii < iconNames.Count; ii++) + icon(ii); + } + + + if (curEvent.isRepaint) + hoveredIconName = null; + + + scrollPos = GUILayout.BeginScrollView(new Vector2(0, scrollPos)).y; + + GUILayout.Space(rows.Count * rowHeight + 23); + + + var i0 = (scrollPos / rowHeight).FloorToInt(); + var i1 = (i0 + ((position.height - 30) / rowHeight).CeilToInt()).Min(rows.Count); + + for (int ii = i0; ii < i1; ii++) + row(ii); + + + GUILayout.EndScrollView(); + + } + void hoveredIconLabel() + { + if (hoveredIconName == null) return; + + + var nameRect = position.SetPos(0, 0).SetHeightFromBottom(18).SetWidth(hoveredIconName.GetLabelWidth() + 6).Move(1, -1); + + var shadowRect = nameRect.AddWidthFromRight(10).AddHeight(10); + + + shadowRect.DrawBlurred(GUIColors.windowBackground, 12); + shadowRect.DrawBlurred(GUIColors.windowBackground.SetAlpha(.4f), 8); + + + SetLabelAlignmentCenter(); + + GUI.Label(nameRect, hoveredIconName); + + ResetLabelStyle(); + + } + void closeOnEsc() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + hoveredIconName = null; + + Close(); + + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + + header(); + search(); + icons(); + hoveredIconLabel(); + closeOnEsc(); + outline(); + + paletteEditor.Repaint(); + + if (EditorWindow.focusedWindow != this) + Close(); + + } + + public static float iconSize => 16; + public static float iconSpacing => 6; + public static float cellSize => iconSize + iconSpacing; + public static float rowSpacing = 1; + + public static float rowHeight => cellSize + rowSpacing; + + public static float paddingLeft => 3; + public static float paddingRight => 3; + + public string hoveredIconName; + + static string searchString = ""; + static float scrollPos; + + + + + + void LoadAllIcons() + { + if (allIconNames != null) return; + + + (float h, float s, float v, float a) GetAverageColor(Texture2D texture) + { + + var readableTexture = new Texture2D(texture.width, texture.height, texture.format, texture.mipmapCount > 1); + + Graphics.CopyTexture(texture, readableTexture); + + var pixels = readableTexture.GetPixels32(); + + readableTexture.DestroyImmediate(); + + + float hSum = 0; + float sSum = 0; + float vSum = 0; + + int nonTransparentPxCount = pixels.Length; + int coloredPxCount = pixels.Length; + + for (var i = 0; i < pixels.Length; i++) + { + if (pixels[i].a <= .1f) { nonTransparentPxCount--; coloredPxCount--; continue; } + + Color.RGBToHSV(pixels[i], out float h, out float s, out float v); + + if (s > .1f) + hSum += h; + else + coloredPxCount--; + + sSum += s; + + vSum += v; + + } + + var hAvg = hSum / coloredPxCount; + var sAvg = sSum / nonTransparentPxCount; + var vAvg = vSum / nonTransparentPxCount; + var aAvg = nonTransparentPxCount / pixels.Length.ToFloat(); + + if (coloredPxCount == 0) + hAvg = -1; + + + return (hAvg, sAvg, vAvg, aAvg); + + } + + + + var editorAssetBundle = typeof(EditorGUIUtility).InvokeMethod("GetEditorAssetBundle"); + + allIconNames = ( + + from path in editorAssetBundle.GetAllAssetNames() + + + let icon = editorAssetBundle.LoadAsset(path) + + where icon + + + where path.StartsWith("icons/") + where !path.Contains("avatarinspector") + + where !icon.name.ToLower().StartsWith("d_") + + where !icon.name.ToLower().EndsWith(".small") + where !icon.name.ToLower().EndsWith("_sml") + + where !icon.name.Contains("@") + where !icon.name.Contains("TreeEditor") + where !icon.name.Contains("scene-template") + where !icon.name.Contains("StateMachineEditor.Background") + where !icon.name.Contains("SpeedTree") + where !icon.name.Contains("TextMesh") + where !icon.name.Contains("Profiler.Instrumentation") + where !icon.name.Contains("Profiler.Record") + where !icon.name.Contains("SocialNetworks") + where !icon.name.Contains("Groove") + + + + let avgColor = GetAverageColor(icon) + + where avgColor.a > .1f + + + + orderby avgColor.h * -3f + + avgColor.s * .09f + + avgColor.v * 0f, icon.name + + + + select icon.name + + ).ToHashSet() + .ToList(); + + } + + static List allIconNames; + + + + void FilterIconsBySearch() + { + filteredIcons = ( + + from iconName in allIconNames + + where iconName.ToLower().Contains(searchString.ToLower()) + + orderby iconName.ToLower().IndexOf(searchString.ToLower(), System.StringComparison.Ordinal) + + select iconName + + ).ToList(); + } + + static List filteredIcons; + + + + void GenerateRows() + { + + var iconsPerRow = ((position.width - paddingLeft - paddingRight) / cellSize).FloorToInt(); + + rows = new(); + + var curRow = new List(); + + foreach (var icon in filteredIcons) + { + curRow.Add(icon); + + if (curRow.Count == iconsPerRow) + { + rows.Add(curRow); + curRow = new(); + } + } + + if (curRow.Any()) + rows.Add(curRow); + + } + + static List> rows; + + + + + + + public void Init() + { + LoadAllIcons(); + FilterIconsBySearch(); + GenerateRows(); + + searchField = new(); + + } + + SearchField searchField; + + + + + + public VHierarchyPalette palette; + public VHierarchyPaletteEditor paletteEditor; + + } + + class AdjustColorsWindow : EditorWindow + { + void OnGUI() + { + void header() + { + var headerRect = Rect.zero.SetHeight(20).SetWidth(position.width); + var closeButtonRect = headerRect.SetWidthFromRight(16).SetHeightFromMid(16).Move(-3, -.5f); + + void background() + { + headerRect.Draw(EditorGUIUtility.isProSkin ? Greyscale(.18f) : Greyscale(.7f)); + } + void title() + { + SetGUIColor(Greyscale(.8f)); + SetLabelAlignmentCenter(); + + GUI.Label(headerRect.MoveY(-1), "Adjust colors"); + + ResetLabelStyle(); + ResetGUIColor(); + + } + void closeButton() + { + var colorNormal = isDarkTheme ? Greyscale(.55f) : Greyscale(.35f); + var colorHovered = isDarkTheme ? Greyscale(.9f) : colorNormal; + + var iconSize = 14; + + if (IconButton(closeButtonRect, "CrossIcon", iconSize, colorNormal, colorHovered)) + Close(); + + } + void escHint() + { + if (!closeButtonRect.IsHovered()) return; + + var textRect = headerRect.SetWidthFromRight(42).MoveY(-.5f).MoveX(1); + var fontSize = 11; + var color = Greyscale(.65f); + + + SetLabelFontSize(fontSize); + SetGUIColor(color); + + GUI.Label(textRect, "Esc"); + + ResetGUIColor(); + ResetLabelStyle(); + + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + + background(); + title(); + closeButton(); + escHint(); + outline(); + + Space(headerRect.height); + + } + void body() + { + EditorGUIUtility.labelWidth = 85; + EditorGUIUtility.keyboardControl = 0; + + palette.RecordUndo(); + + + EditorGUI.BeginChangeCheck(); + + palette.colorBrightness = (EditorGUILayout.Slider("Brightness", palette.colorBrightness, 0, 2) / .1f).RoundToInt() * .1f; + palette.colorSaturation = (EditorGUILayout.Slider("Saturation", palette.colorSaturation, 0, 2) / .1f).RoundToInt() * .1f; + + palette.colorGradientsEnabled = EditorGUILayout.Toggle("Gradients", palette.colorGradientsEnabled); + + if (EditorGUI.EndChangeCheck()) + { + VHierarchy.goInfoCache.Clear(); + + paletteEditor.Repaint(); + EditorApplication.RepaintHierarchyWindow(); + palette.Dirty(); + } + + + EditorGUIUtility.labelWidth = 0; + + } + void closeOnEsc() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + Close(); + + } + + + header(); + + Space(7); + BeginIndent(10); + + body(); + + EndIndent(5); + + + + closeOnEsc(); + + if (EditorWindow.focusedWindow != this) + Close(); + + Repaint(); // for undo + + } + + public VHierarchyPalette palette; + public VHierarchyPaletteEditor paletteEditor; + + } + +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteEditor.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteEditor.cs.meta new file mode 100644 index 0000000..a1fcf36 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteEditor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b7dce55e9e9b476fb5d1d669c006123 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteWindow.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteWindow.cs new file mode 100644 index 0000000..1751200 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteWindow.cs @@ -0,0 +1,711 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VHierarchy.VHierarchy; +using static VHierarchy.VHierarchyData; +using static VHierarchy.VHierarchyPalette; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + + +namespace VHierarchy +{ + public class VHierarchyPaletteWindow : EditorWindow + { + + void OnGUI() + { + if (!palette) { Close(); return; } + + int hoveredColorIndex = -1; + string hoveredIconNameOrGuid = null; + + void background() + { + position.SetPos(0, 0).Draw(windowBackground); + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + void colors() + { + if (!palette.colorsEnabled) return; + + var rowRect = this.position.SetPos(paddingX, paddingY).SetHeight(cellSize); + + + void color(int i) + { + var cellRect = rowRect.MoveX(i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + void backgroundSelected() + { + if (!colorIndexes_initial.Contains(i)) return; + + cellRect.Resize(1).DrawRounded(selectedBackground, 2); + + } + void backgroundHovered() + { + if (!cellRect.IsHovered()) return; + + cellRect.Resize(1).DrawRounded(this.hoveredBackground, 2); + + } + void crossIcon() + { + if (i != 0) return; + + GUI.DrawTexture(cellRect.SetSizeFromMid(iconSize), EditorIcons.GetIcon("CrossIcon")); + + } + void colorOutline() + { + if (i == 0) return; + + var outlineColor = i <= VHierarchyPalette.greyColorsCount ? Greyscale(.0f, .4f) : Greyscale(.15f, .2f); + + cellRect.Resize(3).DrawRounded(outlineColor, 4); + + } + void color() + { + if (i == 0) return; + + var brightness = palette.colorBrightness; + var saturation = palette.colorSaturation; + var drawGradients = palette.colorGradientsEnabled; + + if (!palette.colorGradientsEnabled) + brightness *= isDarkTheme ? .75f : .97f; + + if (i <= VHierarchyPalette.greyColorsCount) + { + saturation = brightness = 1; + drawGradients = false; + } + + + var colorRaw = palette ? palette.colors[i - 1] : VHierarchyPalette.GetDefaultColor(i - 1); + + var color = MathUtil.Lerp(Greyscale(.2f), colorRaw, brightness); + + Color.RGBToHSV(color, out float h, out float s, out float v); + color = Color.HSVToRGB(h, s * saturation, v); + + color = MathUtil.Lerp(color, colorRaw, .5f).SetAlpha(1); + + if (i > VHierarchyPalette.greyColorsCount && isDarkTheme) + color *= 1.41f; + + + + + cellRect.Resize(4).DrawRounded(color, 3); + + if (drawGradients) + cellRect.Resize(4).AddWidthFromRight(-2).DrawCurtainLeft(GUIColors.windowBackground.SetAlpha(.45f)); + + } + void recursiveIndicator() + { + if (!curEvent.isRepaint) return; + + + var isRecursive = goDatas.First().colorIndex == i && goDatas.First().isColorRecursive; + + if (!isRecursive) return; + + + + var iconRect = cellRect.SetSizeFromMid(16).Move(-6, -7); + var shadowRect = iconRect.Resize(3).Move(2, 1).AddWidthFromRight(3); + var shadowRadius = 4; + + shadowRect.DrawBlurred(GUIColors.windowBackground, shadowRadius); + + + SetGUIColor(Color.white * 2); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon("UnityEditor.SceneHierarchyWindow@2x")); + + ResetGUIColor(); + + + } + + void setHovered() + { + if (!cellRect.IsHovered()) return; + + hoveredColorIndex = i; + + } + void closeOnClick() + { + if (!cellRect.IsHovered()) return; + if (!curEvent.isMouseUp) return; + + curEvent.Use(); + + Close(); + + } + + + + cellRect.MarkInteractive(); + + backgroundSelected(); + backgroundHovered(); + crossIcon(); + colorOutline(); + color(); + recursiveIndicator(); + + setHovered(); + closeOnClick(); + + } + + + for (int i = 0; i < palette.colors.Count + 1; i++) + color(i); + + } + void icons() + { + void row(int i, IconRow iconRow) + { + var rowRect = this.position.SetPos(paddingX, paddingY).SetHeight(cellSize).MoveY(palette.colorsEnabled ? cellSize + spaceAfterColors : 0).MoveY(i * (cellSize + rowSpacing)); + + var isFirstEnabledRow = palette.iconRows.First(r => r.enabled) == iconRow; + + + void icon(int i) + { + var cellRect = rowRect.MoveX(i * cellSize).SetWidth(cellSize).SetHeightFromMid(cellSize); + + var isCrossIcon = isFirstEnabledRow && i == 0; + var actualIconIndex = isFirstEnabledRow ? i - 1 : i; + var isCustomIcon = !isCrossIcon && actualIconIndex >= iconRow.builtinIcons.Count; + var iconNameOrGuid = isCrossIcon ? "" : isCustomIcon ? iconRow.customIcons[actualIconIndex - iconRow.builtinIcons.Count] : iconRow.builtinIcons[actualIconIndex]; + + + void backgroundSelected() + { + if (!iconNamesOrGuids_initial.Contains(iconNameOrGuid)) return; + + cellRect.Resize(1).DrawRounded(selectedBackground, 2); + + } + void backgroundHovered() + { + if (!cellRect.IsHovered()) return; + + cellRect.Resize(1).DrawRounded(this.hoveredBackground, 2); + + } + void crossIcon() + { + if (!isCrossIcon) return; + + GUI.DrawTexture(cellRect.SetSizeFromMid(iconSize), EditorIcons.GetIcon("CrossIcon")); + + } + void normalIcon() + { + if (isCrossIcon) return; + + var iconNameOrPath = iconNameOrGuid?.Length == 32 ? iconNameOrGuid.ToPath() : iconNameOrGuid; + var icon = EditorIcons.GetIcon(iconNameOrPath) ?? Texture2D.blackTexture; + + var iconRect = cellRect.SetSizeFromMid(iconSize); + + if (icon.width < icon.height) iconRect = iconRect.SetWidthFromMid(iconRect.height * icon.width / icon.height); + if (icon.height < icon.width) iconRect = iconRect.SetHeightFromMid(iconRect.width * icon.height / icon.width); + + + GUI.DrawTexture(iconRect, icon); + + } + void recursiveIndicator() + { + if (!curEvent.isRepaint) return; + + + var isRecursive = goDatas.First().iconNameOrGuid == iconNameOrGuid && goDatas.First().isIconRecursive; + + if (!isRecursive) return; + + + + var iconRect = cellRect.SetSizeFromMid(16).Move(-6, -7); + var shadowRect = iconRect.Resize(3).Move(2, 1).AddWidthFromRight(3); + var shadowRadius = 4; + + shadowRect.DrawBlurred(GUIColors.windowBackground, shadowRadius); + + + + SetGUIColor(Color.white * 2); + + GUI.DrawTexture(iconRect, EditorIcons.GetIcon("UnityEditor.SceneHierarchyWindow@2x")); + + ResetGUIColor(); + + + } + + void setHovered() + { + if (!cellRect.IsHovered()) return; + + hoveredIconNameOrGuid = iconNameOrGuid; + + } + void closeOnClick() + { + if (!cellRect.IsHovered()) return; + if (!curEvent.isMouseUp) return; + + curEvent.Use(); + + Close(); + + } + + + + cellRect.MarkInteractive(); + + backgroundSelected(); + backgroundHovered(); + crossIcon(); + normalIcon(); + recursiveIndicator(); + + setHovered(); + closeOnClick(); + + } + + + for (int j = 0; j < iconRow.iconCount + (isFirstEnabledRow ? 1 : 0); j++) + icon(j); + + } + + + var i = 0; + + foreach (var iconRow in palette.iconRows) + { + if (!iconRow.enabled) continue; + if (iconRow.isEmpty) continue; + + row(i, iconRow); + + i++; + } + + } + void editPaletteButton() + { + var buttonRect = position.SetPos(0, 0).SetWidthFromRight(16).SetHeightFromBottom(16).Move(-14, -14); + var buttonColor = isDarkTheme ? Greyscale(.6f) : Greyscale(1, .6f); + + if (!IconButton(buttonRect, "Toolbar Plus", 16, buttonColor)) return; + + + palette.SelectInInspector(frameInProject: false); + + EditorWindow.GetWindow(typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow"))?.Show(); + + this.Close(); + + } + + void setColorsAndIcons() + { + if (!curEvent.isLayout) return; + + + if (palette.iconRows.Any(r => r.enabled)) + if (hoveredIconNameOrGuid != null) + SetIcon(hoveredIconNameOrGuid, isRecursive: curEvent.holdingAlt); + else + SetInitialIcons(); + + + if (palette.colorsEnabled) + if (hoveredColorIndex != -1) + SetColor(hoveredColorIndex, isRecursive: curEvent.holdingAlt); + else + SetInitialColors(); + + } + void updatePosition() + { + if (!curEvent.isLayout) return; + + void calcDeltaTime() + { + deltaTime = (float)(EditorApplication.timeSinceStartup - lastLayoutTime); + + if (deltaTime > .05f) + deltaTime = .0166f; + + lastLayoutTime = EditorApplication.timeSinceStartup; + + } + void resetCurPos() + { + if (currentPosition != default) return; + + currentPosition = position.position; // position.position is always int, which can't be used for lerping + + } + void lerpCurPos() + { + var speed = 9; + + MathUtil.SmoothDamp(ref currentPosition, targetPosition, speed, ref positionDeriv, deltaTime); + // MathfUtils.Lerp(ref currentPosition, targetPosition, speed, deltaTime); + + } + void setCurPos() + { + position = position.SetPos(currentPosition); + } + + calcDeltaTime(); + resetCurPos(); + lerpCurPos(); + setCurPos(); + + if (!currentPosition.magnitude.Approx(targetPosition.magnitude)) + Repaint(); + + } + void closeOnEscape() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + SetInitialColors(); + SetInitialIcons(); + + Close(); + + } + + + RecordUndoOnDatas(); + + background(); + outline(); + colors(); + icons(); + editPaletteButton(); + + setColorsAndIcons(); + updatePosition(); + closeOnEscape(); + + + VHierarchy.goInfoCache.Clear(); + + EditorApplication.RepaintHierarchyWindow(); + + } + + static float iconSize => 14; + static float iconSpacing => 6; + static float cellSize => iconSize + iconSpacing; + static float spaceAfterColors => 13; + public float rowSpacing = 1; + static float paddingX => 12; + static float paddingY => 12; + + Color windowBackground => isDarkTheme ? Greyscale(.23f) : Greyscale(.75f); + Color selectedBackground => isDarkTheme ? new Color(.3f, .5f, .7f, .8f) : new Color(.3f, .5f, .7f, .6f) * 1.25f; + Color hoveredBackground => isDarkTheme ? Greyscale(1, .3f) : Greyscale(0, .1f); + + public Vector2 targetPosition; + public Vector2 currentPosition; + Vector2 positionDeriv; + float deltaTime; + double lastLayoutTime; + + + + + + + void SetIcon(string iconNameOrGuid, bool isRecursive) + { + foreach (var r in goDatas) + { + r.isIconRecursive = isRecursive; // setting it firstbecause iconNameOrGuid setter relies on isIconRecursive + r.iconNameOrGuid = iconNameOrGuid; + } + } + void SetColor(int colorIndex, bool isRecursive) + { + foreach (var r in goDatas) + { + r.isColorRecursive = isRecursive; + r.colorIndex = colorIndex; + } + } + + void SetInitialIcons() + { + for (int i = 0; i < goDatas.Count; i++) + { + goDatas[i].isIconRecursive = isIconRecursives_initial[i]; + goDatas[i].iconNameOrGuid = iconNamesOrGuids_initial[i]; + } + } + void SetInitialColors() + { + for (int i = 0; i < goDatas.Count; i++) + { + goDatas[i].isColorRecursive = isColorRecursives_initial[i]; + goDatas[i].colorIndex = colorIndexes_initial[i]; + } + } + + void RemoveEmptyGoDatas() + { + var toRemove = goDatas.Where(r => r.iconNameOrGuid == "" && r.colorIndex == 0); + + foreach (var goData in toRemove) + goData.sceneData.goDatas_byGlobalId.RemoveValue(goData); + + if (toRemove.Any()) + Undo.CollapseUndoOperations(Undo.GetCurrentGroup() - 1); + + } + + void RecordUndoOnDatas() + { + if (usingDataSO) + if (data) + data.RecordUndo(); + + foreach (var r in usedDataComponents) + r.RecordUndo(); + + } + void MarkDatasDirty() + { + if (usingDataSO) + if (data) + data.Dirty(); + + foreach (var r in usedDataComponents) + r.Dirty(); + } + void SaveData() + { + // if (usingDataSO) + // data.Save(); + } + + bool usingDataSO => !gameObjects.Select(r => r.scene).All(r => VHierarchy.dataComponents_byScene.GetValueOrDefault(r) != null); + IEnumerable usedDataComponents => VHierarchy.dataComponents_byScene.Where(kvp => kvp.Value && gameObjects.Select(r => r.scene).Contains(kvp.Key)).Select(kvp => kvp.Value); + + + + + + + + void OnLostFocus() + { + if (curEvent.holdingAlt && EditorWindow.focusedWindow?.GetType().Name == "SceneHierarchyWindow") + CloseNextFrameIfNotRefocused(); + else + Close(); + + } + + void CloseNextFrameIfNotRefocused() + { + EditorApplication.delayCall += () => { if (EditorWindow.focusedWindow != this) Close(); }; + } + + + + + static void RepaintOnAlt() // Update + { + if (curEvent.holdingAlt != wasHoldingAlt) + if (EditorWindow.mouseOverWindow is VHierarchyPaletteWindow paletteWindow) + paletteWindow.Repaint(); + + wasHoldingAlt = curEvent.holdingAlt; + + } + + static bool wasHoldingAlt; + + + + + + + + + + public void Init(List gameObjects) + { + void createData() + { + if (VHierarchy.data) return; + + VHierarchy.data = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(VHierarchy.data, GetScriptPath("VHierarchy").GetParentPath().CombinePath("vHierarchy Data.asset")); + + } + void createPalette() + { + if (VHierarchy.palette) return; + + VHierarchy.palette = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(VHierarchy.palette, GetScriptPath("VHierarchy").GetParentPath().CombinePath("vHierarchy Palette.asset")); + + } + void setSize() + { + if (!palette.colorsEnabled && !palette.iconRows.Any(r => r.enabled && !r.isEmpty)) // somehow happened on first palette window opening in 2022.3.50 + palette.InvokeMethod("Reset"); + + + + var rowCellCounts = new List(); + + if (palette.colorsEnabled) + rowCellCounts.Add(palette.colors.Count + 1); + + foreach (var r in palette.iconRows.Where(r => r.enabled && !r.isEmpty)) + rowCellCounts.Add(r.iconCount + (r == palette.iconRows.First(r => r.enabled) ? 1 : 0)); + + var width = paddingX + + rowCellCounts.Max() * cellSize + + (rowCellCounts.Last() == rowCellCounts.Max() ? cellSize : 0) + + paddingX; + + + + var iconRowCount = palette.iconRows.Count(r => r.enabled && !r.isEmpty); + + var height = paddingY + + (palette.colorsEnabled ? cellSize : 0) + + (palette.colorsEnabled && palette.iconRows.Any(r => r.enabled && !r.isEmpty) ? spaceAfterColors : 0) + + cellSize * iconRowCount + + rowSpacing * (iconRowCount - 1) + + paddingY; + + + position = position.SetSize(width, height).SetPos(targetPosition); + + } + void getInfos() + { + goDatas.Clear(); + + foreach (var r in gameObjects) + goDatas.Add(VHierarchy.GetGameObjectData(r, createDataIfDoesntExist: true)); + + } + void getInitialState() + { + iconNamesOrGuids_initial = goDatas.Select(r => r.iconNameOrGuid).ToList(); + colorIndexes_initial = goDatas.Select(r => r.colorIndex).ToList(); + + isIconRecursives_initial = goDatas.Select(r => r.isIconRecursive).ToList(); + isColorRecursives_initial = goDatas.Select(r => r.isColorRecursive).ToList(); + + + } + + + this.gameObjects = gameObjects; + + RecordUndoOnDatas(); + + createData(); + createPalette(); + setSize(); + getInfos(); + getInitialState(); + + EditorApplication.update -= RepaintOnAlt; + EditorApplication.update += RepaintOnAlt; + + } + + void OnDestroy() + { + RemoveEmptyGoDatas(); + MarkDatasDirty(); + SaveData(); + + EditorApplication.update -= RepaintOnAlt; + + } + + public List gameObjects = new(); + public List goDatas = new(); + + public List iconNamesOrGuids_initial = new(); + public List colorIndexes_initial = new(); + + public List isIconRecursives_initial = new(); + public List isColorRecursives_initial = new(); + + static VHierarchyPalette palette => VHierarchy.palette; + static VHierarchyData data => VHierarchy.data; + + + + + + + + public static void CreateInstance(Vector2 position) + { + instance = ScriptableObject.CreateInstance(); + + instance.ShowPopup(); + + instance.position = instance.position.SetPos(position).SetSize(200, 300); + instance.targetPosition = position; + + } + + public static VHierarchyPaletteWindow instance; + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteWindow.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteWindow.cs.meta new file mode 100644 index 0000000..c978574 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchyPaletteWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8ae240588f29744208e627125db9c9e4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchySceneSelectorWindow.cs b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchySceneSelectorWindow.cs new file mode 100644 index 0000000..4b4e06c --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchySceneSelectorWindow.cs @@ -0,0 +1,1189 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEditor.IMGUI.Controls; +using System.Diagnostics; +using Type = System.Type; +using Delegate = System.Delegate; +using Action = System.Action; +using static VHierarchy.VHierarchy; +using static VHierarchy.Libs.VUtils; +using static VHierarchy.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VHierarchy +{ + public class VHierarchySceneSelectorWindow : EditorWindow + { + + void OnGUI() + { + + void background() + { + windowRect.Draw(windowBackground); + + } + void closeOnEscape() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + Close(); + + EditorApplication.RepaintHierarchyWindow(); + + GUIUtility.ExitGUI(); + + } + void addTabOnEnter() + { + // if (!curEvent.isKeyDown) return; // searchfield steals fpcus + if (curEvent.keyCode != KeyCode.Return) return; + + if (keyboardFocusedRowIndex == -1) return; + if (keyboardFocusedEntry == null) return; + + OpenScene(keyboardFocusedEntry, curEvent.holdingAlt); + + this.Close(); + + } + void arrowNavigation() + { + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.UpArrow && curEvent.keyCode != KeyCode.DownArrow) return; + + curEvent.Use(); + + + if (curEvent.keyCode == KeyCode.UpArrow) + if (keyboardFocusedRowIndex == 0) + keyboardFocusedRowIndex = rowCount - 1; + else + keyboardFocusedRowIndex--; + + if (curEvent.keyCode == KeyCode.DownArrow) + if (keyboardFocusedRowIndex == rowCount - 1) + keyboardFocusedRowIndex = 0; + else + keyboardFocusedRowIndex++; + + + keyboardFocusedRowIndex = keyboardFocusedRowIndex.Clamp(0, rowCount - 1); + + } + void updateSearch() + { + if (searchString == prevSearchString) return; + + prevSearchString = searchString; + + + if (searchString == "") { keyboardFocusedRowIndex = -1; return; } + + UpdateSearch(); + + keyboardFocusedRowIndex = 0; + + } + + + void searchField_() + { + var searchRect = windowRect.SetHeight(18).MoveY(1).AddWidthFromMid(-2); + + + if (searchField == null) + { + searchField = new SearchField(); + searchField.SetFocus(); + + } + + + searchString = searchField.OnGUI(searchRect, searchString); + + } + void rows() + { + void bookmarked() + { + if (searchString != "") return; + if (!bookmarkedEntries.Any()) return; + + bookmarksRect = windowRect.SetHeight(bookmarkedEntries.Count * rowHeight + gaps.Sum()); + + BookmarksGUI(); + + } + void divider() + { + if (searchString != "") return; + if (!bookmarkedEntries.Any()) return; + + var splitterColor = Greyscale(.36f); + var splitterRect = bookmarksRect.SetHeightFromBottom(0).SetHeight(dividerHeight).SetHeightFromMid(1).AddWidthFromMid(-10); + + splitterRect.Draw(splitterColor); + + } + void notBookmarked() + { + if (searchString != "") return; + + if (bookmarkedEntries.Any()) + nextRowY = bookmarksRect.yMax + dividerHeight; + + foreach (var entry in allEntries) + { + if (bookmarkedEntries.Contains(entry)) continue; + if (entry == draggedBookmark) continue; + + RowGUI(windowRect.SetHeight(rowHeight).SetY(nextRowY), entry); + + nextRowY += rowHeight; + nextRowIndex++; + + } + + } + void searched() + { + if (searchString == "") return; + + foreach (var entry in searchedEntries) + { + RowGUI(windowRect.SetHeight(rowHeight).SetY(nextRowY), entry); + + nextRowY += rowHeight; + nextRowIndex++; + + } + + } + + + scrollPos = GUI.BeginScrollView(windowRect.AddHeightFromBottom(-firstRowOffsetTop), Vector2.up * scrollPos, windowRect.SetHeight(scrollAreaHeight), GUIStyle.none, GUIStyle.none).y; + + nextRowY = 0; + nextRowIndex = 0; + + bookmarked(); + divider(); + notBookmarked(); + searched(); + + scrollAreaHeight = nextRowY + 23; + rowCount = nextRowIndex; + + GUI.EndScrollView(); + + } + void noResults() + { + if (searchString == "") return; + if (searchedEntries.Any()) return; + + + GUI.enabled = false; + GUI.skin.label.alignment = TextAnchor.MiddleCenter; + + GUI.Label(windowRect.AddHeightFromBottom(-14), "No results"); + + GUI.skin.label.alignment = TextAnchor.MiddleLeft; + GUI.enabled = true; + + } + + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + // void resizing() + // { + + // } + + + background(); + closeOnEscape(); + addTabOnEnter(); + arrowNavigation(); + updateSearch(); + + searchField_(); + rows(); + noResults(); + + outline(); + + + if (draggingBookmark || animatingDroppedBookmark || animatingGaps) + this.Repaint(); + + } + + Rect windowRect => position.SetPos(0, 0); + Rect bookmarksRect; + + SearchField searchField; + + Color windowBackground => Greyscale(isDarkTheme ? .23f : .8f); + + string searchString = ""; + string prevSearchString = ""; + + float scrollPos; + + float rowHeight => 22; + float dividerHeight => 11; + float firstRowOffsetTop => bookmarkedEntries.Any() && searchString == "" ? 21 : 20; + + int nextRowIndex; + float nextRowY; + + float scrollAreaHeight = 1232; + int rowCount = 123; + + int keyboardFocusedRowIndex = -1; + + + + + + + + + + void RowGUI(Rect rowRect, SceneEntry entry) + { + + var isHovered = rowRect.IsHovered(); + var isPressed = entry == pressedEntry; + var isDragged = draggingBookmark && draggedBookmark == entry; + var isDropped = animatingDroppedBookmark && droppedBookmark == entry; + var isFocused = entry == keyboardFocusedEntry; + var isBookmarked = bookmarkedEntries.Contains(entry) || entry == draggedBookmark; + + if (isDropped) + isHovered = rowRect.SetY(droppedBookmarkYTarget).IsHovered(); + + + var showBlueBackground = isFocused || isPressed || isDragged; + var showAdditiveIndicator = curEvent.holdingAlt && ((keyboardFocusedEntry?.scenePath).IsNullOrEmpty() ? isHovered : isFocused); + var showStarButton = (isHovered || (isBookmarked && !isFocused)) && !showAdditiveIndicator; + var showEnterHint = (isFocused && !isHovered && isDarkTheme) && !showAdditiveIndicator; + + + void draggedShadow() + { + if (!curEvent.isRepaint) return; + if (!isDragged) return; + + var shadowRect = rowRect.AddHeightFromMid(-4); + + var shadowOpacity = .3f; + var shadowRadius = 13; + + shadowRect.DrawBlurred(Greyscale(0, shadowOpacity), shadowRadius); + + } + void blueBackground() + { + if (!curEvent.isRepaint) return; + if (!showBlueBackground) return; + + + var backgroundRect = rowRect.AddHeightFromMid(-3); + + backgroundRect.Draw(GUIColors.selectedBackground); + + + } + void icon() + { + if (!curEvent.isRepaint) return; + + + Texture iconTexture = EditorIcons.GetIcon("SceneAsset Icon"); + + if (!iconTexture) return; + + + var iconRect = rowRect.SetWidth(16).SetHeightFromMid(16).MoveX(4 + 1); + + iconRect = iconRect.SetWidthFromMid(iconRect.height * iconTexture.width / iconTexture.height); + + + GUI.DrawTexture(iconRect, iconTexture); + + } + void name() + { + if (!curEvent.isRepaint) return; + + + var nameRect = rowRect.MoveX(21 + 1); + + var nameText = searchString != "" ? namesFormattedForFuzzySearch_byEntry[entry] + : entry.sceneName; + + + if (entry.scenePath == sceneToReplace.path) + { + SetLabelBold(); + SetGUIColor(Greyscale(1.15f)); + + GUI.skin.label.richText = true; + + GUI.Label(nameRect, nameText); + + GUI.skin.label.richText = false; + + ResetGUIColor(); + ResetLabelStyle(); + + } + + else + { + var color = showBlueBackground ? Greyscale(123, 123) + : isHovered && !isPressed ? Greyscale(1.1f) + : Greyscale(1); + SetGUIColor(color); + + GUI.skin.label.richText = true; + + GUI.Label(nameRect, nameText); + + GUI.skin.label.richText = false; + + ResetGUIColor(); + } + + } + void curtain() + { + if (!curEvent.isRepaint) return; + + + var curtainWidth = 20; + + var curtainColor = showBlueBackground ? GUIColors.selectedBackground : windowBackground; + + var curtainXMax = rowRect.xMax - (showStarButton ? 21 : showEnterHint ? 33 : showAdditiveIndicator ? 80 : 0); + + + rowRect.SetHeightFromMid(18).SetXMax(curtainXMax).SetWidthFromRight(curtainWidth).DrawCurtainLeft(curtainColor); + + rowRect.SetHeightFromMid(18).SetX(curtainXMax).SetXMax(rowRect.xMax).Draw(curtainColor); + + } + void starButton() + { + if (!showStarButton) return; + + + var buttonRect = rowRect.SetWidthFromRight(16).MoveX(-6 + 1).SetSizeFromMid(rowHeight); + + + var iconName = isBookmarked ^ buttonRect.IsHovered() ? "Star" : "Star Hollow"; + var iconSize = 16; + var colorNormal = Greyscale(isDarkTheme ? (isBookmarked ? .5f : .7f) : .3f); + var colorHovered = Greyscale(isDarkTheme ? (isBookmarked ? .9f : 1) : 0f); + var colorPressed = Greyscale(isDarkTheme ? .75f : .5f); + var colorDisabled = Greyscale(isDarkTheme ? .53f : .55f); + + + if (!IconButton(buttonRect, iconName, iconSize, colorNormal, colorHovered, colorPressed)) return; + + if (isBookmarked) + bookmarkedEntries.Remove(entry); + else + bookmarkedEntries.Add(entry); + + } + void enterHint() + { + if (!curEvent.isRepaint) return; + if (!showEnterHint) return; + + + var hintRect = rowRect.SetWidthFromRight(33); + + + SetLabelFontSize(10); + SetGUIColor(Greyscale(.9f)); + + GUI.Label(hintRect, "Enter"); + + ResetGUIColor(); + ResetLabelStyle(); + + + } + void additiveIndicator() + { + if (!curEvent.isRepaint) return; + if (!showAdditiveIndicator) return; + + + var indicatorRect = rowRect.SetWidthFromRight(81); + + + SetLabelFontSize(10); + SetGUIColor(Greyscale(.9f)); + + GUI.Label(indicatorRect, "Load additively"); + + ResetGUIColor(); + ResetLabelStyle(); + + } + void hoverHighlight() + { + if (!curEvent.isRepaint) return; + if (!isHovered) return; + if (isPressed || isDragged) return; + + + var backgroundRect = rowRect.AddHeightFromMid(-2); + + var backgroundColor = Greyscale(isDarkTheme ? 1 : 0, isPressed ? .085f : .12f); + + + backgroundRect.Draw(backgroundColor); + + } + + void mouse() + { + void down() + { + if (!curEvent.isMouseDown) return; + if (!rowRect.IsHovered()) return; + + isMousePressedOnEntry = true; + pressedEntry = entry; + + mouseDownPosition = curEvent.mousePosition; + + this.Repaint(); + + } + void up() + { + if (!curEvent.isMouseUp) return; + + isMousePressedOnEntry = false; + pressedEntry = null; + + this.Repaint(); + + + if (!isHovered) return; + if (draggingBookmark) return; + if ((curEvent.mousePosition - mouseDownPosition).magnitude > 2) return; + + curEvent.Use(); + + OpenScene(entry, curEvent.holdingAlt); + + this.Close(); + + } + + down(); + up(); + + } + void setFocusedEntry() + { + var rowIndex = (rowRect.y / rowHeight).FloorToInt(); + + if (rowIndex == keyboardFocusedRowIndex) + keyboardFocusedEntry = entry; + + } + + + rowRect.MarkInteractive(); + + draggedShadow(); + blueBackground(); + icon(); + name(); + curtain(); + starButton(); + enterHint(); + additiveIndicator(); + hoverHighlight(); + mouse(); + setFocusedEntry(); + + } + + SceneEntry pressedEntry; + + bool isMousePressedOnEntry; + + Vector2 mouseDownPosition; + + SceneEntry keyboardFocusedEntry; + + + + + void OpenScene(SceneEntry entry, bool openAdditive) + { + if (!Application.isPlaying) + if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return; // if clicked cancel + + + void openScene(string path, bool additive) + { + if (!EditorApplication.isPlaying) + EditorSceneManager.OpenScene(path, additive ? OpenSceneMode.Additive : OpenSceneMode.Single); + else + SceneManager.LoadScene(path, additive ? LoadSceneMode.Additive : LoadSceneMode.Single); + + } + + + + if (openAdditive) + openScene(entry.scenePath, additive: true); + + else if (EditorSceneManager.sceneCount == 1) + openScene(entry.scenePath, additive: false); + + else + { + var openedScene = EditorSceneManager.OpenScene(entry.scenePath, OpenSceneMode.Additive); + + EditorSceneManager.MoveSceneAfter(openedScene, sceneToReplace); + + if (EditorSceneManager.GetActiveScene() == sceneToReplace) + EditorSceneManager.SetActiveScene(openedScene); + + EditorSceneManager.CloseScene(sceneToReplace, removeScene: true); + + } + + + } + + + + + + + + + + + + + public void BookmarksGUI() + { + void normalBookmark(int i) + { + if (bookmarkedEntries[i] == droppedBookmark && animatingDroppedBookmark) return; + + var bookmarkRect = bookmarksRect.SetHeight(rowHeight) + .SetY(GetBookmarY(i)); + + RowGUI(bookmarkRect, bookmarkedEntries[i]); + + } + void normalBookmarks() + { + for (int i = 0; i < bookmarkedEntries.Count; i++) + normalBookmark(i); + + } + void draggedBookmark_() + { + if (!draggingBookmark) return; + + + var bookmarkRect = bookmarksRect.SetHeight(rowHeight) + .SetY(draggedBookmarkY); + + RowGUI(bookmarkRect, draggedBookmark); + + } + void droppedBookmark_() + { + if (!animatingDroppedBookmark) return; + + var bookmarkRect = bookmarksRect.SetHeight(rowHeight) + .SetY(droppedBookmarkY); + + RowGUI(bookmarkRect, droppedBookmark); + + } + + + BookmarksDragging(); + BookmarksAnimations(); + + normalBookmarks(); + draggedBookmark_(); + droppedBookmark_(); + + } + + int GetBookmarkIndex(float mouseY) + { + return ((mouseY - bookmarksRect.y) / rowHeight).FloorToInt(); + } + + float GetBookmarY(int i, bool includeGaps = true) + { + var centerY = bookmarksRect.y + + i * rowHeight + + (includeGaps ? gaps.Take(i + 1).Sum() : 0); + + + return centerY; + + } + + + + + + + + + + void BookmarksDragging() + { + void init() + { + if (draggingBookmark) return; + if ((curEvent.mousePosition - mouseDownPosition).magnitude <= 2) return; + + if (!isMousePressedOnEntry) return; + if (!bookmarkedEntries.Contains(pressedEntry)) return; + + + var i = GetBookmarkIndex(mouseDownPosition.y); + + if (i >= bookmarkedEntries.Count) return; + if (i < 0) return; + + + animatingDroppedBookmark = false; + + draggingBookmark = true; + + draggedBookmark = bookmarkedEntries[i]; + draggedBookmarkHoldOffsetY = GetBookmarY(i) - mouseDownPosition.y; + + gaps[i] = rowHeight; + + + this.RecordUndo(); + + bookmarkedEntries.Remove(draggedBookmark); + + } + void update() + { + if (!draggingBookmark) return; + + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + draggedBookmarkY = (curEvent.mousePosition.y + draggedBookmarkHoldOffsetY).Clamp(0, bookmarksRect.yMax - rowHeight); + + insertDraggedBookmarkAtIndex = GetBookmarkIndex(curEvent.mousePosition.y + draggedBookmarkHoldOffsetY + rowHeight / 2).Clamp(0, bookmarkedEntries.Count); + + } + void accept() + { + if (!draggingBookmark) return; + if (!curEvent.isMouseUp && !curEvent.isIgnore) return; + + curEvent.Use(); + EditorGUIUtility.hotControl = 0; + + // DragAndDrop.PrepareStartDrag(); // fixes phantom dragged component indicator after reordering bookmarks + + this.RecordUndo(); + + draggingBookmark = false; + isMousePressedOnEntry = false; + + bookmarkedEntries.AddAt(draggedBookmark, insertDraggedBookmarkAtIndex); + + gaps[insertDraggedBookmarkAtIndex] -= rowHeight; + gaps.AddAt(0, insertDraggedBookmarkAtIndex); + + droppedBookmark = draggedBookmark; + + droppedBookmarkY = draggedBookmarkY; + droppedBookmarkYDerivative = 0; + animatingDroppedBookmark = true; + + draggedBookmark = null; + pressedEntry = null; + + EditorGUIUtility.hotControl = 0; + + } + + init(); + accept(); + update(); + + } + + bool draggingBookmark; + + float draggedBookmarkHoldOffsetY; + + float draggedBookmarkY; + int insertDraggedBookmarkAtIndex; + + SceneEntry draggedBookmark; + SceneEntry droppedBookmark; + + + + + + + void BookmarksAnimations() + { + if (!curEvent.isLayout) return; + + void gaps_() + { + var makeSpaceForDraggedBookmark = draggingBookmark; + + // var lerpSpeed = 1; + var lerpSpeed = 11; + + for (int i = 0; i < gaps.Count; i++) + if (makeSpaceForDraggedBookmark && i == insertDraggedBookmarkAtIndex) + gaps[i] = MathUtil.Lerp(gaps[i], rowHeight, lerpSpeed, editorDeltaTime); + else + gaps[i] = MathUtil.Lerp(gaps[i], 0, lerpSpeed, editorDeltaTime); + + + + for (int i = 0; i < gaps.Count; i++) + if (gaps[i].Approx(0)) + gaps[i] = 0; + + + + animatingGaps = gaps.Any(r => r > .1f); + + + } + void droppedBookmark_() + { + if (!animatingDroppedBookmark) return; + + // var lerpSpeed = 1; + var lerpSpeed = 8; + + droppedBookmarkYTarget = GetBookmarY(bookmarkedEntries.IndexOf(droppedBookmark), includeGaps: false); + + MathUtil.SmoothDamp(ref droppedBookmarkY, droppedBookmarkYTarget, lerpSpeed, ref droppedBookmarkYDerivative, editorDeltaTime); + + if ((droppedBookmarkY - droppedBookmarkYTarget).Abs() < .5f) + animatingDroppedBookmark = false; + + } + + gaps_(); + droppedBookmark_(); + + } + + float droppedBookmarkY; + float droppedBookmarkYTarget; + float droppedBookmarkYDerivative; + + bool animatingDroppedBookmark; + bool animatingGaps; + + List gaps + { + get + { + while (_gaps.Count < bookmarkedEntries.Count + 1) _gaps.Add(0); + while (_gaps.Count > bookmarkedEntries.Count + 1) _gaps.RemoveLast(); + + return _gaps; + + } + } + List _gaps = new(); + + + + + + + + + + + + + + + + + + + + + public static void UpdateAllEntries() + { + void fillWithAllScenes() + { + + allEntries.Clear(); + + allEntries = AssetDatabase.FindAssets("t:scene") + .Select(r => new SceneEntry() { scenePath = r.ToPath() }) + .ToList(); + + allEntries.SortBy(r => r.scenePath.GetFilename()); + + } + // void removeAllOpen() + // { + // for (int i = 0; i < EditorSceneManager.sceneCount; i++) + // allEntries.RemoveAll(r => r.scenePath == EditorSceneManager.GetSceneAt(i).path); + // } + + fillWithAllScenes(); + // removeAllOpen(); + + } + + static List allEntries = new(); + + [System.Serializable] + public class SceneEntry + { + public string scenePath = ""; + + public string sceneName => scenePath.GetFilename(withExtension: false); + + } + + + + void GetBookmarkedEntries() + { + bookmarkedEntries = data.bookmarkedScenePaths.Select(r => allEntries.FirstOrDefault(rr => rr.scenePath == r)) + .Where(r => r != null) + .ToList(); + } + void SaveBookmarkedEntries() + { + data.bookmarkedScenePaths = bookmarkedEntries.Select(r => r.scenePath).ToList(); + + data.Dirty(); + + } + + List bookmarkedEntries = new(); + + + + void OnEnable() { UpdateAllEntries(); GetBookmarkedEntries(); } + + void OnDisable() { SaveBookmarkedEntries(); } + + + + + + + + + + + void UpdateSearch() + { + + bool tryMatch(string name, string query, int[] matchIndexes, ref float cost) + { + + var wordInitialsIndexes = new List { 0 }; + + for (int i = 1; i < name.Length; i++) + { + var separators = new[] { ' ', '-', '_', '.', '(', ')', '[', ']', }; + + var prevChar = name[i - 1]; + var curChar = name[i]; + var nextChar = i + 1 < name.Length ? name[i + 1] : default(char); + + var isSeparatedWordStart = separators.Contains(prevChar) && !separators.Contains(curChar); + var isCamelcaseHump = (curChar.IsUpper() && prevChar.IsLower()) || (curChar.IsUpper() && nextChar.IsLower()); + var isNumberStart = curChar.IsDigit() && (!prevChar.IsDigit() || prevChar == '0'); + var isAfterNumber = prevChar.IsDigit() && !curChar.IsDigit(); + + if (isSeparatedWordStart || isCamelcaseHump || isNumberStart || isAfterNumber) + wordInitialsIndexes.Add(i); + + } + + + + var nextWordInitialsIndexMap = new int[name.Length]; + + var nextWordIndex = 0; + + for (int i = 0; i < name.Length; i++) + { + if (i == wordInitialsIndexes[nextWordIndex]) + if (nextWordIndex + 1 < wordInitialsIndexes.Count) + nextWordIndex++; + else break; + + nextWordInitialsIndexMap[i] = wordInitialsIndexes[nextWordIndex]; + + } + + + + + + var iName = 0; + var iQuery = 0; + + var prevMatchIndex = -1; + + void registerMatch(int matchIndex) + { + matchIndexes[iQuery] = matchIndex; + iQuery++; + + iName = matchIndex + 1; + + prevMatchIndex = matchIndex; + + + } + + + cost = 0; + + while (iName < name.Length && iQuery < query.Length) + { + var curQuerySymbol = query[iQuery].ToLower(); + var curNameSymbol = name[iName].ToLower(); + + if (curNameSymbol == curQuerySymbol) + { + var gapLength = iName - prevMatchIndex - 1; + + cost += gapLength; + + + registerMatch(iName); + + continue; + + // consecutive matches cost 0 + // distance between index 0 and first match also counts as a gap + + } + + + + var nextWordInitialIndex = nextWordInitialsIndexMap[iName]; // wordInitialsIndexes.FirstOrDefault(i => i > iName); + var nextWordInitialSymbol = nextWordInitialIndex == default ? default : name[nextWordInitialIndex].ToLower(); + + if (nextWordInitialSymbol == curQuerySymbol) + { + var gapLength = nextWordInitialIndex - prevMatchIndex - 1; + + cost += (gapLength * .01f).ClampMax(.9f); + + + registerMatch(nextWordInitialIndex); + + continue; + + // word-initial match costs less than a gap (1+) + // but more than a consecutive match (0) + + } + + + + iName++; + + } + + + + + + + var allCharsMatched = iQuery >= query.Length; + + return allCharsMatched; + + + + // this search works great in practice + // but fails in more theoretical scenarios, mostly when user skips first letters of words + // eg searching "arn" won't find "barn_a" because search will jump to last a (word-initial) and fail afterwards + // so unity search is used as a fallback + + } + bool tryMatch_unitySearch(string name, string query, int[] matchIndexes, ref float cost) + { + long score = 0; + + List matchIndexesList = new(); + + + var matched = UnityEditor.Search.FuzzySearch.FuzzyMatch(searchString, name, ref score, matchIndexesList); + + + for (int i = 0; i < matchIndexesList.Count; i++) + matchIndexes[i] = matchIndexesList[i]; + + cost = 123212 - score; + + + return matched; + + + // this search is fast but isn't tuned for real use cases + // quering "vis" ranks "Invisible" higher than "VInspectorState" + // quering "lst" ranks "SmallShadowTemp" higher than "List" + // also sometimes it favors matches that are further away from zeroth index + + } + + string formatName(string name, IEnumerable matchIndexes) + { + var formattedName = ""; + + for (int i = 0; i < name.Length; i++) + if (matchIndexes.Contains(i)) + formattedName += "" + name[i] + ""; + else + formattedName += name[i]; + + + return formattedName; + + } + + + + var costs_byEntry = new Dictionary(); + + var matchIndexes = new int[searchString.Length]; + var matchCost = 0f; + + + foreach (var entry in allEntries) + if (tryMatch(entry.sceneName, searchString, matchIndexes, ref matchCost) || tryMatch_unitySearch(entry.sceneName, searchString, matchIndexes, ref matchCost)) + { + costs_byEntry[entry] = matchCost; + namesFormattedForFuzzySearch_byEntry[entry] = formatName(entry.sceneName, matchIndexes); + } + + + searchedEntries = costs_byEntry.Keys.OrderBy(r => costs_byEntry[r]) + .ThenBy(r => r.sceneName) + .ToList(); + } + + List searchedEntries = new(); + + Dictionary namesFormattedForFuzzySearch_byEntry = new(); + + + + + + + + void OnLostFocus() + { + EditorApplication.delayCall += () => + { + if (EditorWindow.focusedWindow != this) + { + EditorApplication.RepaintHierarchyWindow(); + + Close(); + } + }; + + // delay is needed to prevent reopening after clicking + button for the second time + } + + + + public static void Open(Vector2 rowPos, Scene sceneToReplace) + { + if (!VHierarchy.data) + { + VHierarchy.data = ScriptableObject.CreateInstance(); + + AssetDatabase.CreateAsset(VHierarchy.data, GetScriptPath("VHierarchy").GetParentPath().CombinePath("vHierarchy Data.asset")); + } + + + instance = ScriptableObject.CreateInstance(); + + instance.ShowPopup(); + instance.Focus(); + + + + var width = 190; + var height = 296; + + var offsetX = -14; + var offsetY = 18; + + instance.position = instance.position.SetPos(rowPos + new Vector2(offsetX, offsetY)).SetSize(width, height); + + + + instance.sceneToReplace = sceneToReplace; + + } + + public Scene sceneToReplace; + + public static VHierarchySceneSelectorWindow instance; + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchySceneSelectorWindow.cs.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchySceneSelectorWindow.cs.meta new file mode 100644 index 0000000..6751b5c --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/VHierarchySceneSelectorWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d50f2aa4a50724ddbaf21dca1fd4b816 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Data.asset b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Data.asset new file mode 100644 index 0000000..de8c70b --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Data.asset @@ -0,0 +1,53 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 3a9752b0c8e144801967e6897679604b, type: 3} + m_Name: vHierarchy Data + m_EditorClassIdentifier: + sceneDatas_byGuid: + keys: + - f6bb9d76e96b15f4e9cc842d12308e59 + values: + - goDatas_byGlobalId: + keys: + - globalObjectIdString: GlobalObjectId_V1-2-f6bb9d76e96b15f4e9cc842d12308e59-1364312246-0 + - globalObjectIdString: GlobalObjectId_V1-2-f6bb9d76e96b15f4e9cc842d12308e59-2075842263-0 + - globalObjectIdString: GlobalObjectId_V1-2-f6bb9d76e96b15f4e9cc842d12308e59-2071633267-0 + - globalObjectIdString: GlobalObjectId_V1-2-f6bb9d76e96b15f4e9cc842d12308e59-94633464-0 + - globalObjectIdString: GlobalObjectId_V1-2-f6bb9d76e96b15f4e9cc842d12308e59-1783470267-0 + - globalObjectIdString: GlobalObjectId_V1-2-f6bb9d76e96b15f4e9cc842d12308e59-519969443-0 + values: + - colorIndex: 4 + iconNameOrGuid: + isIconRecursive: 0 + isColorRecursive: 0 + - colorIndex: 6 + iconNameOrGuid: + isIconRecursive: 0 + isColorRecursive: 0 + - colorIndex: 2 + iconNameOrGuid: + isIconRecursive: 0 + isColorRecursive: 0 + - colorIndex: 9 + iconNameOrGuid: + isIconRecursive: 0 + isColorRecursive: 0 + - colorIndex: 2 + iconNameOrGuid: + isIconRecursive: 0 + isColorRecursive: 0 + - colorIndex: 7 + iconNameOrGuid: + isIconRecursive: 0 + isColorRecursive: 0 + bookmarks: [] + bookmarkedScenePaths: [] diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Data.asset.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Data.asset.meta new file mode 100644 index 0000000..3920dd7 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Data.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1fd2c46161d242446a2bf772facad359 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Palette.asset b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Palette.asset new file mode 100644 index 0000000..3bb67e9 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Palette.asset @@ -0,0 +1,81 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 61290d425f1c94a8cbfb56f754ca6757, type: 3} + m_Name: vHierarchy Palette + m_EditorClassIdentifier: + colors: + - {r: 0.16, g: 0.16, b: 0.16, a: 1} + - {r: 0.609, g: 0.231, b: 0.23100014, a: 0.1} + - {r: 0.55825, g: 0.471625, b: 0.21175, a: 0.1} + - {r: 0.34999996, g: 0.5075, b: 0.1925, a: 0.1} + - {r: 0.1925, g: 0.5075, b: 0.27124998, a: 0.1} + - {r: 0.1925, g: 0.50750005, b: 0.5075, a: 0.1} + - {r: 0.259875, g: 0.36618757, b: 0.685125, a: 0.1} + - {r: 0.4550001, g: 0.25024998, b: 0.65975, a: 0.1} + - {r: 0.53287494, g: 0.20212498, b: 0.4501876, a: 0.1} + colorsEnabled: 1 + colorSaturation: 1 + colorBrightness: 1 + colorGradientsEnabled: 1 + iconRows: + - builtinIcons: + - Folder Icon + - Canvas Icon + - AvatarMask On Icon + - cs Script Icon + - StandaloneInputModule Icon + - EventSystem Icon + - Terrain Icon + - ScriptableObject Icon + customIcons: [] + enabled: 1 + - builtinIcons: + - Camera Icon + - ParticleSystem Icon + - TrailRenderer Icon + - Material Icon + - ReflectionProbe Icon + customIcons: [] + enabled: 1 + - builtinIcons: + - Light Icon + - DirectionalLight Icon + - LightmapParameters Icon + - LightProbes Icon + customIcons: [] + enabled: 1 + - builtinIcons: + - Rigidbody Icon + - BoxCollider Icon + - SphereCollider Icon + - CapsuleCollider Icon + - WheelCollider Icon + - MeshCollider Icon + customIcons: [] + enabled: 1 + - builtinIcons: + - AudioSource Icon + - AudioClip Icon + - AudioListener Icon + - AudioEchoFilter Icon + - AudioReverbZone Icon + customIcons: [] + enabled: 1 + - builtinIcons: + - PreMatCube + - PreMatSphere + - PreMatCylinder + - PreMatQuad + - Favorite + - Settings Icon + customIcons: [] + enabled: 1 diff --git a/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Palette.asset.meta b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Palette.asset.meta new file mode 100644 index 0000000..cc4acf7 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vHierarchy/vHierarchy Palette.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dba10c20202881040b64e11560990009 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector.meta b/Assets/OtherAssets/DEV/PAID/vInspector.meta new file mode 100644 index 0000000..0030194 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f42d0374f4587401395165a2788ee07c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/Read me.pdf b/Assets/OtherAssets/DEV/PAID/vInspector/Read me.pdf new file mode 100644 index 0000000..5ca2440 Binary files /dev/null and b/Assets/OtherAssets/DEV/PAID/vInspector/Read me.pdf differ diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/Read me.pdf.meta b/Assets/OtherAssets/DEV/PAID/vInspector/Read me.pdf.meta new file mode 100644 index 0000000..f6094d5 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/Read me.pdf.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5bc65026e2b5e458b838d06e4ef39409 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.asmdef b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.asmdef new file mode 100644 index 0000000..5fe6cf9 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.asmdef @@ -0,0 +1,14 @@ +{ + "name": "VInspector", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.asmdef.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.asmdef.meta new file mode 100644 index 0000000..a272da7 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: de13f8b1675b449e6a6af1cc0f7842ec +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.cs new file mode 100644 index 0000000..35b71d8 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.cs @@ -0,0 +1,1574 @@ +#if UNITY_EDITOR +using UnityEngine; +using UnityEngine.UIElements; +using UnityEditor.UIElements; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using UnityEditor.SceneManagement; +using UnityEngine.SceneManagement; +using System.Reflection; +using System.Linq; +using System.Text.RegularExpressions; +using Type = System.Type; +using static VInspector.VInspectorState; +using static VInspector.VInspectorData; +using static VInspector.Libs.VUtils; +using static VInspector.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VInspector +{ + public static class VInspector + { + + static void UpdateNavbars() // update + { + void updateNavbar(EditorWindow window) + { + if (!window) return; + if (!window.hasFocus) return; + + + var hasNavbar = navbars_byWindow.ContainsKey(window); + var shouldHaveNavbar = VInspectorMenu.navigationBarEnabled && !window.GetMemberValue("isLocked"); + + + if (!hasNavbar && shouldHaveNavbar) + createNavbar(window); + + if (hasNavbar && !shouldHaveNavbar) + destroyNavbar(window); + + } + + void createNavbar(EditorWindow window) + { + var navbar = new IMGUIContainer(); + + navbar.name = "vInspector-navbar"; + + navbar.style.width = Length.Percent(100); + navbar.style.height = 28; + + var navbarGui = new VInspectorNavbar(window); + navbar.onGUIHandler = () => navbarGui.OnGUI(navbar.contentRect); + + navbar.style.position = Position.Absolute; + + window.rootVisualElement.Add(navbar); + + + + var navbarSpacer = new VisualElement(); + + navbarSpacer.name = "vInspector-navbar-spacer"; + + navbar.style.width = Length.Percent(100); + navbarSpacer.style.height = 28 + 1; + + window.rootVisualElement.Insert(0, navbarSpacer); + + + + navbars_byWindow[window] = navbar; + navbarSpacers_byWindow[window] = navbarSpacer; + + } + void destroyNavbar(EditorWindow window) + { + var navbar = window.rootVisualElement.Q("vInspector-navbar"); + var navbarSpacer = window.rootVisualElement.Q("vInspector-navbar-spacer"); + + navbar.RemoveFromHierarchy(); + navbarSpacer.RemoveFromHierarchy(); + + + navbars_byWindow.Remove(window); + navbarSpacers_byWindow.Remove(window); + + } + + + foreach (var inspector in allInspectors) + updateNavbar(inspector); + + } + + static Dictionary navbars_byWindow = new(); + static Dictionary navbarSpacers_byWindow = new(); + + + + + static void PasteButton_OnGUI(EditorWindow window, Rect rect) + { + if (!goEditors_byWindow.TryGetValue(window, out var editor)) return; + + var isActive = VInspectorClipboard.CanComponentsBePastedTo(editor.targets.Cast()); + var copiedDatas = VInspectorClipboard.instance.copiedComponetDatas; + var text = copiedDatas.Count > 1 ? $"Paste {copiedDatas.Count} components" : "Paste Component"; + + void pasteButton_active() + { + if (!isActive) return; + + if (!GUI.Button(rect, text)) return; + + foreach (var target in editor.targets) + foreach (var data in copiedDatas) + VInspectorClipboard.PasteComponentAsNew(data, target as GameObject); + + if (!curEvent.holdingAlt) + VInspectorClipboard.ClearCopiedDatas(); + + } + void pasteButton_inactive() + { + if (isActive) return; + + SetGUIEnabled(false); + + GUI.Button(rect, text); + + ResetGUIEnabled(); + + } + void cancelButton() + { + if (!rect.IsHovered()) return; + + var buttonRect = rect.SetWidthFromRight(rect.height).MoveX(-1); + var iconSize = 12; + var colorNormal = Greyscale(rect.IsHovered() ? .7f : .6f) * (isActive ? 1 : .9f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .25f) * (isActive ? 1 : .9f); + var colorPressed = Greyscale(isDarkTheme ? .7f : .65f) * (isActive ? 1 : .9f); + + + if (!IconButton(buttonRect, "CrossIcon", iconSize, colorNormal, colorHovered, colorPressed)) return; + + VInspectorClipboard.ClearCopiedDatas(); + + } + void escHint() + { + if (!rect.SetWidthFromRight(rect.height).MoveX(-1).IsHovered()) return; + + var textRect = rect.SetWidthFromRight(39).MoveY(-.5f); + var fontSize = 10; + var color = Greyscale(isDarkTheme ? .9f : 1f) * (isActive ? 1 : .9f); + + + SetLabelFontSize(fontSize); + SetGUIColor(color); + + GUI.Label(textRect, "Esc"); + + ResetGUIColor(); + ResetLabelStyle(); + + } + + + if (!curEvent.isRepaint) + cancelButton(); + + pasteButton_active(); + pasteButton_inactive(); + escHint(); + + if (curEvent.isRepaint) + cancelButton(); + } + + static void UpdatePasteButtons() // update and selectionChanged + { + void updateButton(EditorWindow window) + { + if (!window) return; + if (!window.hasFocus) return; + + + var hasCopiedComponents = VInspectorClipboard.instance.copiedComponetDatas.Any(); + var inspectingGameObjects = window.GetType() == t_InspectorWindow ? window.InvokeMethod("GetInspectedObjects")?.All(r => r is GameObject) == true : + window.GetType() == t_PropertyEditor ? propertyEditorsInspectingGameObjects.Contains(window) : true; + + var hasButton = pasteButtons_byWindow.ContainsKey(window); + var shouldHaveButton = hasCopiedComponents && inspectingGameObjects; + + + if (!hasButton && shouldHaveButton) + createButton(window); + + if (hasButton && !shouldHaveButton) + destroyButton(window); + + } + + void createButton(EditorWindow window) + { + var addComponentButton = window.rootVisualElement.Q(className: "unity-inspector-add-component-button"); + + if (addComponentButton == null) return; + + + var buttonHolder = new VisualElement(); + + buttonHolder.name = "vInspector-paste-component-button-holder"; + + buttonHolder.style.flexDirection = FlexDirection.Row; + buttonHolder.style.justifyContent = Justify.Center; + + + var button = new IMGUIContainer(); + + button.style.width = 230f; + button.style.height = 25f; + button.style.marginLeft = 2f; + button.style.marginRight = 2f; + button.style.marginTop = -3f; + button.style.marginBottom = 15f; + + + button.onGUIHandler = () => PasteButton_OnGUI(window, button.contentRect); + + + + addComponentButton.parent.Add(buttonHolder); + + buttonHolder.Add(button); + + + + pasteButtons_byWindow[window] = button; ; + + } + void destroyButton(EditorWindow window) + { + var buttonHolder = pasteButtons_byWindow[window].parent; + + buttonHolder.RemoveFromHierarchy(); + + + pasteButtons_byWindow.Remove(window); + + } + + + foreach (var inspector in allInspectors) + updateButton(inspector); + + foreach (var propertyEditor in propertyEditorsInspectingGameObjects) + updateButton(propertyEditor); + + } + + static Dictionary pasteButtons_byWindow = new(); + + + + + static void FillCollections(Editor editor) // finishedDefaultHeaderGUI + { + if (editor.GetMemberValue("propertyViewer") is not EditorWindow window) return; + if (editor.target is not GameObject) return; + + goEditors_byWindow[window] = editor; + + + if (window.GetType() != t_PropertyEditor) return; + if (propertyEditorsInspectingGameObjects.Contains(window)) return; + + propertyEditorsInspectingGameObjects.Add(window); + + } + + static List propertyEditorsInspectingGameObjects = new(); + static Dictionary goEditors_byWindow = new(); + + + + + + + + + + + + + + + + static void ComponentShortcuts() // globalEventHandler + { + if (EditorWindow.mouseOverWindow is not EditorWindow hoveredWindow) return; + if (!hoveredWindow) return; + if (hoveredWindow.GetType() != t_InspectorWindow && hoveredWindow.GetType() != t_PropertyEditor) return; + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode == KeyCode.None) return; + + + void expandOrCollapseHovered() + { + if (curEvent.holdingAnyModifierKey) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.E) return; + if (Tools.viewTool == ViewTool.FPS) return; + if (!VInspectorMenu.toggleExpandedEnabled) return; + if (hoveredComponentHeader == null) return; + + + + ToggleComponentExpanded(hoveredComponent, hoveredWindow); + + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + + + if (!Application.unityVersion.Contains("2022")) return; + + var curTransformTool = Tools.current; + + toCallNextUpdate += () => Tools.current = curTransformTool; + + // E shortcut changes transform tool in 2022 + // here we undo this + + } + void expandOrCollapseAll() + { + if (curEvent.modifiers != (EventModifiers.Shift | EventModifiers.Command) && curEvent.modifiers != (EventModifiers.Shift | EventModifiers.Control)) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.E) return; + if (!VInspectorMenu.collapseEverythingEnabled) return; + + + ToggleAllComponentsExpanded(hoveredWindow); + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + } + void collapseEverythingElse() + { + if (curEvent.modifiers != EventModifiers.Shift) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.E) return; + if (!VInspectorMenu.collapseEverythingElseEnabled) return; + if (hoveredComponentHeader == null) return; + + + + CollapseOtherComponents(hoveredComponent, hoveredWindow); + + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + } + void toggleActive() + { + if (curEvent.holdingAnyModifierKey) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.A) return; + if (Tools.viewTool == ViewTool.FPS) return; + if (!VInspectorMenu.toggleActiveEnabled) return; + if (hoveredComponentHeader == null) return; + + if (EditorUtility.GetObjectEnabled(hoveredComponent) == -1) return; + + + var components = hoveredComponentHeader.editingMultiselection ? hoveredComponentHeader.multiselectedComponents : new List { hoveredComponent }; + + var anyComponentsEnabled = components.Any(r => EditorUtility.GetObjectEnabled(r) == 1); + + + foreach (var r in components) + r.RecordUndo(); + + foreach (var r in components) + EditorUtility.SetObjectEnabled(r, !anyComponentsEnabled); + + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + } + void delete() + { + if (curEvent.holdingAnyModifierKey) return; + if (!curEvent.isKeyDown || curEvent.keyCode != KeyCode.X) return; + if (!VInspectorMenu.deleteEnabled) return; + if (hoveredComponentHeader == null) return; + + + + Component requiredByComponent = null; + + foreach (var otherComponent in hoveredComponent.gameObject.GetComponents()) + if (otherComponent.GetType().GetCustomAttributes().Any(r => (r.m_Type0 ?? r.m_Type1 ?? r.m_Type2)?.IsAssignableFrom(hoveredComponent.GetType()) ?? false)) + requiredByComponent = otherComponent; + + if (requiredByComponent != null && hoveredComponent is not Transform) + Debug.Log($"Can't delete {hoveredComponent.GetType().Name.Decamelcase()} because it is required by {requiredByComponent.GetType().Name.Decamelcase()}"); + + + + if (requiredByComponent == null && hoveredComponent is not Transform) + if (VInspectorMenu.componentAnimationsEnabled) + DeleteComponent_withAnimation(hoveredWindow, hoveredComponentHeader.editingMultiselection ? hoveredComponentHeader.multiselectedComponents : new List { hoveredComponent }); + else + DeleteComponent_withoutAnimation(hoveredComponentHeader.editingMultiselection ? hoveredComponentHeader.multiselectedComponents : new List { hoveredComponent }); + + + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + + + if (!Application.unityVersion.Contains("2022")) return; + + var curPivotRotation = Tools.pivotRotation; + + toCallNextUpdate += () => Tools.pivotRotation = curPivotRotation; + + // X shortcut changes Tools.pivotRotation in 2022 + // here we undo this + + } + + void clearCopiedDatas() + { + if (curEvent.modifiers != EventModifiers.None) return; + if (!curEvent.isKeyDown) return; + if (curEvent.keyCode != KeyCode.Escape) return; + + + VInspectorClipboard.ClearCopiedDatas(); + + + hoveredWindow.Repaint(); + + curEvent.Use(); + + } + void moveBack() + { + if (!curEvent.isMouseDown) return; + if (curEvent.mouseButton != 3) return; + if (hoveredWindow.GetType() != t_InspectorWindow) return; + + if (!VInspectorSelectionHistory.instance.prevStates.Any()) return; + + VInspectorSelectionHistory.instance.MoveBack(); + + } + void moveForward() + { + if (!curEvent.isMouseDown) return; + if (curEvent.mouseButton != 4) return; + if (hoveredWindow.GetType() != t_InspectorWindow) return; + + if (!VInspectorSelectionHistory.instance.nextStates.Any()) return; + + VInspectorSelectionHistory.instance.MoveForward(); + } + + expandOrCollapseHovered(); + expandOrCollapseAll(); + collapseEverythingElse(); + toggleActive(); + delete(); + + clearCopiedDatas(); + moveBack(); + moveForward(); + + } + + public static VInspectorComponentHeader hoveredComponentHeader; + public static Component hoveredComponent => hoveredComponentHeader?.component; + + + + static void UpdateComponentAnimations() // update + { + void set_deltaTime() + { + deltaTime = (float)(EditorApplication.timeSinceStartup - lastLayoutTime); + + // if (deltaTime > .05f) + // deltaTime = .0166f; + + lastLayoutTime = EditorApplication.timeSinceStartup; + + } + void updateExpandAnimationsQueue() + { + if (!queuedExpandAnimations.Any()) { lastActivatedQueuedAnimation = null; return; } + + void unqueue(ExpandAnimation animation) + { + queuedExpandAnimations.Remove(animation); + + animation.Start(); + + lastActivatedQueuedAnimation = animation; + + } + + if (lastActivatedQueuedAnimation == null) + unqueue(queuedExpandAnimations.First()); + + else if (!lastActivatedQueuedAnimation.expandedInspectorHeightUnknown) + if ((lastActivatedQueuedAnimation.currentInspectorHeight - lastActivatedQueuedAnimation.targetInspectorHeight).Abs() < expandAnimation_unqueueAtDistance) + unqueue(queuedExpandAnimations.First()); + + } + void updateExpandAnimations() + { + foreach (var animation in activeExpandAnimations_byComponent.Values.ToList()) + animation.Update(); + + } + void updateDeleteAnimations() + { + foreach (var animation in activeDeleteAnimations.ToList()) + animation.Update(); + + } + + + toCallNextUpdate?.Invoke(); + toCallNextUpdate = null; + + set_deltaTime(); + updateExpandAnimationsQueue(); + updateExpandAnimations(); + updateDeleteAnimations(); + + } + + static float expandAnimation_lerpSpeed => 12; + static float expandAnimation_speedLimit => 4000; + static float expandAnimation_unqueueAtDistance => 90; + + static float deleteAnimation_lerpSpeed => 10; + static float deleteAnimation_speedLimit => 3000; + + static System.Action toCallNextUpdate; + + static float deltaTime; + static double lastLayoutTime; + + static Dictionary activeExpandAnimations_byComponent = new(); + static List queuedExpandAnimations = new(); + static ExpandAnimation lastActivatedQueuedAnimation; + + static List activeDeleteAnimations = new(); + + + class ExpandAnimation + { + public void Start() + { + void expand() + { + if (targetExpandedState != true) return; + + SetComponentExpanded_withoutAnimation(inspectorWindow, component, true); + + + } + void findInspectorElement() + { + var editorsList = inspectorWindow.rootVisualElement.Q(className: "unity-inspector-editors-list"); + + foreach (var someEditorElement in editorsList.Children()) + if (someEditorElement.Children().FirstOrDefault(r => r is InspectorElement) is InspectorElement someInspectorElement) + if (someInspectorElement.GetFieldValue("m_Editor") is Editor editor) + if (editor.target == component) + { + inspectorElement = someInspectorElement; + break; + + } + + } + void detachInspectorElement() + { + if (inspectorElement == null) return; + if (targetExpandedState != true) return; + + inspectorElement.style.position = Position.Absolute; + inspectorElement.style.visibility = Visibility.Hidden; + + // needed to read inspectorElement height without it affecting layout + // reattached in UpdateAnimation + + } + void createMaskElement() + { + if (inspectorElement == null) return; + + + maskElement = new IMGUIContainer(); + + maskElement.name = "vInspector-mask-for-expand-animation"; + + (maskElement as IMGUIContainer).onGUIHandler = () => new Rect(0, 0, 1232, 1232).Draw(GUIColors.windowBackground); + + inspectorElement.parent.Add(maskElement); + + } + void set_expandedInspectorHeight() + { + if (targetExpandedState == true) + expandedInspectorHeightUnknown = true; + else + expandedInspectorHeight = inspectorElement.layout.height; + + } + void set_currentInspectorHeight() + { + if (targetExpandedState == true) + currentInspectorHeight = collapsedInspectorHeight; + else + currentInspectorHeight = expandedInspectorHeight; + + } + + + expand(); + findInspectorElement(); + detachInspectorElement(); + createMaskElement(); + set_expandedInspectorHeight(); + set_currentInspectorHeight(); + + activeExpandAnimations_byComponent[component] = this; + + + } + public void Finish() + { + void collapse() + { + if (targetExpandedState != false) return; + + SetComponentExpanded_withoutAnimation(inspectorWindow, component, false); + + } + void resetInspectorElementStyle() + { + inspectorElement.style.maxHeight = StyleKeyword.Null; + inspectorElement.style.marginBottom = 0; + + } + void removeMaskElement() + { + maskElement.RemoveFromHierarchy(); + } + + + collapse(); + + toCallNextUpdate += resetInspectorElementStyle; + toCallNextUpdate += removeMaskElement; + + activeExpandAnimations_byComponent.Remove(component); + + } + + public void Update() + { + void set_expandedInspectorHeight() + { + if (!expandedInspectorHeightUnknown) return; + if (inspectorElement.layout.height == 0) return; + + expandedInspectorHeight = inspectorElement.layout.height; + expandedInspectorHeightUnknown = false; + + reattachInspectorElement(); + + } + void reattachInspectorElement() + { + inspectorElement.style.position = Position.Relative; + inspectorElement.style.visibility = Visibility.Visible; + + } + void lerp() + { + if (expandedInspectorHeightUnknown) return; + + + MathUtil.SmoothDamp(ref currentInspectorHeight, targetInspectorHeight, expandAnimation_lerpSpeed, ref currentInspectorHeightDerivative, deltaTime, expandAnimation_speedLimit); + + } + void modifyInspectorElementStyle() + { + if (expandedInspectorHeightUnknown) return; + + inspectorElement.style.maxHeight = currentInspectorHeight.Max(0); + inspectorElement.style.marginBottom = currentInspectorHeight.Min(0); + + } + void finish() + { + if ((currentInspectorHeight - targetInspectorHeight).Abs() > .5f) return; + + Finish(); + + } + + set_expandedInspectorHeight(); + lerp(); + modifyInspectorElementStyle(); + finish(); + + } + + + + public EditorWindow inspectorWindow; + + public Component component; + + public VisualElement inspectorElement; + public VisualElement maskElement; + + public float expandedInspectorHeight; + public bool expandedInspectorHeightUnknown; + + public float collapsedInspectorHeight => -7; + + public float currentInspectorHeight; + public float currentInspectorHeightDerivative; + + public float targetInspectorHeight => targetExpandedState == true ? expandedInspectorHeight : collapsedInspectorHeight; + + public bool targetExpandedState; + + } + + class DeleteAnimation + { + public void Start() + { + void findEditorElement() + { + var editorsList = inspectorWindow.rootVisualElement.Q(className: "unity-inspector-editors-list"); + + foreach (var someEditorElement in editorsList.Children()) + if (someEditorElement.Children().FirstOrDefault(r => r is InspectorElement) is InspectorElement someInspectorElement) + if (someInspectorElement.GetFieldValue("m_Editor") is Editor editor) + if (editor.target == component) + { + editorElement = someEditorElement; + break; + } + + } + void createSpacerElement() + { + spacerElement = new IMGUIContainer(); + + spacerElement.name = "vInspector-spacer-for-delete-animation"; + + (spacerElement as IMGUIContainer).onGUIHandler = () => new Rect(0, 0, 1232, 1).Draw(Greyscale(.1f)); + + editorElement.parent.Insert(editorElement.parent.IndexOf(editorElement), spacerElement); + + spacerElement.style.height = editorElement.layout.height; + + currentSpacerHeight = editorElement.layout.height; + + + } + + findEditorElement(); + createSpacerElement(); + + activeDeleteAnimations.Add(this); + + } + public void Finish() + { + spacerElement.RemoveFromHierarchy(); + + activeDeleteAnimations.Remove(this); + + } + + public void Update() + { + void lerp() + { + MathUtil.SmoothDamp(ref currentSpacerHeight, 0, deleteAnimation_lerpSpeed, ref currentSpacerHeightDerivative, deltaTime, deleteAnimation_speedLimit); + } + void modifySpacerElement() + { + spacerElement.style.height = currentSpacerHeight; + } + void finish() + { + if (currentSpacerHeight > .5f) return; + + Finish(); + + } + + lerp(); + modifySpacerElement(); + finish(); + + } + + + + public EditorWindow inspectorWindow; + + public Component component; + + public VisualElement editorElement; + public VisualElement spacerElement; + + public float currentSpacerHeight; + public float currentSpacerHeightDerivative; + + } + + + + public static void ToggleComponentExpanded(Component component, EditorWindow inspectorWindow) + { + if (VInspectorMenu.componentAnimationsEnabled) + SetComponentExpanded_withAnimation(inspectorWindow, component, newExpandedState: !GetCompnentExpanded(inspectorWindow, component), queueAnimation: false); + else + SetComponentExpanded_withoutAnimation(inspectorWindow, component, newExpandedState: !GetCompnentExpanded(inspectorWindow, component)); + + } + public static void ToggleAllComponentsExpanded(EditorWindow inspectorWindow) + { + var firstEditor = inspectorWindow.GetMemberValue("m_Tracker").activeEditors.First(); + + var allComponents = inspectorWindow.GetMemberValue("m_Tracker").activeEditors.Where(r => r.target is Component && r.targets.Length == firstEditor.targets.Length && r.target is not ParticleSystemRenderer) + .Select(r => r.target as Component); + var anyComponentsExpanded = allComponents.Any(r => GetCompnentExpanded(inspectorWindow, r)); + + + queuedExpandAnimations.Clear(); + lastActivatedQueuedAnimation = null; + + foreach (var component in !anyComponentsExpanded ? allComponents : allComponents.Reverse()) + if (VInspectorMenu.componentAnimationsEnabled) + SetComponentExpanded_withAnimation(inspectorWindow, component, newExpandedState: !anyComponentsExpanded, queueAnimation: true); + else + SetComponentExpanded_withoutAnimation(inspectorWindow, component, newExpandedState: !anyComponentsExpanded); + + } + public static void CollapseOtherComponents(Component component, EditorWindow inspectorWindow) + { + var firstEditor = inspectorWindow.GetMemberValue("m_Tracker").activeEditors.First(); + + var allComponents = inspectorWindow.GetMemberValue("m_Tracker").activeEditors.Where(r => r.target is Component && r.targets.Length == firstEditor.targets.Length && r.target is not ParticleSystemRenderer) + .Select(r => r.target as Component); + foreach (var someComponent in allComponents) + if (someComponent != component) + if (VInspectorMenu.componentAnimationsEnabled) + SetComponentExpanded_withAnimation(inspectorWindow, someComponent, newExpandedState: false, queueAnimation: true); + else + SetComponentExpanded_withoutAnimation(inspectorWindow, someComponent, newExpandedState: false); + + if (VInspectorMenu.componentAnimationsEnabled) + SetComponentExpanded_withAnimation(inspectorWindow, component, newExpandedState: true, queueAnimation: false); + else + SetComponentExpanded_withoutAnimation(inspectorWindow, component, newExpandedState: true); + + } + + static bool GetCompnentExpanded(EditorWindow hoveredWindow, Component component) + { + if (activeExpandAnimations_byComponent.TryGetValue(component, out var animation)) + if (animation.targetExpandedState == false) + return false; + + var tracker = hoveredWindow.GetMemberValue("m_Tracker"); + + var editorIndex = tracker.activeEditors.ToList().IndexOfFirst(r => r.target == component); + + if (!editorIndex.IsInRangeOf(tracker.activeEditors)) return false; + + return tracker.GetVisible(editorIndex) == 1; + + } + + public static void SetComponentExpanded_withAnimation(EditorWindow inspectorWindow, Component component, bool newExpandedState, bool queueAnimation) + { + if (activeExpandAnimations_byComponent.TryGetValue(component, out var activeAnimation)) { activeAnimation.targetExpandedState = newExpandedState; return; }; + + + var tracker = inspectorWindow.GetMemberValue("m_Tracker"); + + var visibleExpandedState = tracker.GetVisible(tracker.activeEditors.ToList().IndexOfFirst(r => r.target == component)) == 1; + + if (visibleExpandedState == newExpandedState) return; + + + + var animation = new ExpandAnimation(); + + animation.inspectorWindow = inspectorWindow; + animation.component = component; + animation.targetExpandedState = newExpandedState; + + + + if (queueAnimation) + queuedExpandAnimations.Add(animation); + else + animation.Start(); + + } + public static void SetComponentExpanded_withoutAnimation(EditorWindow inspectorWindow, Component component, bool newExpandedState) + { + // sets saved state which applies to all components of same type + UnityEditorInternal.InternalEditorUtility.SetIsInspectorExpanded(component, newExpandedState); + + + // sets visible state that lives as long as selection is unchanged + var tracker = inspectorWindow.GetMemberValue("m_Tracker"); + + var editorIndex = tracker.activeEditors.ToList().IndexOfFirst(r => r.target == component); + + if (!editorIndex.IsInRangeOf(tracker.activeEditors)) return; + + tracker.SetVisible(editorIndex, newExpandedState ? 1 : 0); + + } + + + public static void DeleteComponent_withAnimation(EditorWindow inspectorWindow, List multiselectedComponents) + { + var animation = new DeleteAnimation(); + + animation.inspectorWindow = inspectorWindow; + animation.component = multiselectedComponents.First(); + + animation.Start(); + + DeleteComponent_withoutAnimation(multiselectedComponents); + + } + public static void DeleteComponent_withoutAnimation(List multiselectedComponents) + { + foreach (var r in multiselectedComponents) + Undo.DestroyObjectImmediate(r); + + } + + + + + + + + + + + + + + + + static void UpdateComponentHeaders(Editor editor) // finishedDefaultHeaderGUI + { + if (!curEvent.isLayout) return; + if (editor.GetType() != t_GameObjectInspector) return; + if (editor.target is not GameObject gameObject) return; + // if (editor.GetMemberValue("propertyViewer") is not EditorWindow window) return; + // if (window.GetMemberValue("m_Tracker") is not ActiveEditorTracker tracker) return; + + + var components = gameObject.GetComponents().Where(r => r); + + // if (editor.targets.Length > 1) + // components = tracker.activeEditors.Where(r => r.target && r.target is Component && r.targets.Length == editor.targets.Length).Select(r => r.targets.First() as Component).ToArray(); + + void clearHeadersOnReorder() + { + var curOrderHash = components.Aggregate(17, (hash, element) => hash * 31 + (element?.GetHashCode() ?? 0)); + + if (curOrderHash != componentOrderHashes_byEditor.GetValueOrDefault(editor)) + componentHeaders_byComponent.Clear(); + + componentOrderHashes_byEditor[editor] = curOrderHash; + + } + void createHeader(Component component) + { + if (!component) return; + if (componentHeaders_byComponent.ContainsKey(component)) return; + + componentHeaders_byComponent[component] = new VInspectorComponentHeader(component, editor); + + } + + + clearHeadersOnReorder(); + + foreach (var component in components) + createHeader(component); + + foreach (var component in components) + componentHeaders_byComponent[component].Update(); + + } + + static Dictionary componentHeaders_byComponent = new(); + + static Dictionary componentOrderHashes_byEditor = new(); + + + + + + public static void UpdateHeaderButtons(Editor _) + { + + var buttons = typeof(EditorGUIUtility).GetMemberValue("s_EditorHeaderItemsMethods"); + + if (buttons == null) return; // will be checked again next gui frame + + + + var t_HeaderItemDelegate = typeof(EditorGUIUtility).GetNestedType("HeaderItemDelegate", maxBindingFlags); + + helpButton ??= buttons.Cast().FirstOrDefault(r => r.Method.Name == "HelpIconButton"); + presetsButton ??= buttons.Cast().FirstOrDefault(r => r.Method.Name == "DrawPresetButton"); + copyPasteButton ??= typeof(VInspector).GetMethod(nameof(CopyPasteButton), maxBindingFlags).CreateDelegate(t_HeaderItemDelegate); + playmodeSaveButton ??= typeof(VInspector).GetMethod(nameof(PlaymodeSaveButton), maxBindingFlags).CreateDelegate(t_HeaderItemDelegate); + + + + + buttons.Remove(helpButton); + buttons.Remove(presetsButton); + buttons.Remove(copyPasteButton); + buttons.Remove(playmodeSaveButton); + + + if (VInspectorMenu.playmodeSaveButtonEnabled) + buttons.Insert(0, playmodeSaveButton); + + if (VInspectorMenu.copyPasteButtonsEnabled) + buttons.Insert(0, copyPasteButton); + + if (!VInspectorMenu.hideHelpButtonEnabled && helpButton != null) + buttons.Insert(0, helpButton); + + if (!VInspectorMenu.hidePresetsButtonEnabled && presetsButton != null) + buttons.Insert(0, presetsButton); + + + + + Editor.finishedDefaultHeaderGUI -= UpdateHeaderButtons; + + } + + static System.Delegate helpButton; + static System.Delegate presetsButton; + static System.Delegate copyPasteButton; + static System.Delegate playmodeSaveButton; + + + static bool CopyPasteButton(Rect buttonRect, Object[] targets) + { + if (targets.First() is not Component component) return false; + + + var copiedData = VInspectorClipboard.instance.copiedComponetDatas.FirstOrDefault(r => r.sourceComponent == component); + var pastableData = VInspectorClipboard.instance.copiedComponetDatas.FirstOrDefault(r => r.sourceComponent.GetType() == component.GetType()); + + var isCopied = copiedData != null; + var canValuesBePasted = !isCopied && pastableData != null; + + GUI.Label(buttonRect, new GUIContent("", canValuesBePasted ? "Paste values" : isCopied ? "Copied" : "Copy component")); + + + + var iconName = canValuesBePasted ? "Paste values" : isCopied ? "Copied" : "Copy"; + var iconSize = 16; + var color = Greyscale(isDarkTheme ? .78f : .49f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .8f : .6f); + var colorDisabled = Greyscale(.52f); + + + + + var disabled = targets.Length > 1; + + if (disabled) { IconButton(buttonRect, iconName, iconSize, colorDisabled, colorDisabled, colorDisabled); return true; } + + + + if (!IconButton(buttonRect, iconName, iconSize, color, colorHovered, colorPressed)) return true; + + if (canValuesBePasted) + VInspectorClipboard.PasteComponentValues(pastableData, component); + else + VInspectorClipboard.CopyComponent(component); + + return true; + + } + + static bool PlaymodeSaveButton(Rect buttonRect, Object[] targets) + { + + void tryApplyingFailedToSaveDatas() + { + if (Application.isPlaying) return; + if (!VInspectorClipboard.instance.failedToSaveComponentDatas.Any()) return; + + foreach (var data in VInspectorClipboard.instance.failedToSaveComponentDatas.ToList()) + foreach (var target in targets) + if (target is Component component_) + if (target.GetGlobalID().UnpackForPrefab() == data.globalId) + { + VInspectorClipboard.ApplyComponentData(data, component_); + VInspectorClipboard.instance.failedToSaveComponentDatas.Remove(data); + } + + + // applies saved component datas that couldn't be applied on playmode exit + // such datas correspond to: + // - prefabs (because prefabs produce different (unpacked) globalId in playmode) + // - objects from scenes that weren't loaded before playmode (because datas can only be applied to loaded object) + // + // this solution applies datas once respective components become visible in inspector + // a "proper" solution would involve looping through all objects in a scene once it's opened + + } + + tryApplyingFailedToSaveDatas(); + + + + if (!Application.isPlaying) return false; + if (targets.First() is not Component component) return false; + + + var savedData = VInspectorClipboard.instance.savedComponentDatas.FirstOrDefault(r => r.sourceComponent == component); + + var isSaved = savedData != null; + + GUI.Label(buttonRect, new GUIContent("", isSaved ? "Saved" : "Save in play mode")); + + + + var iconName = isSaved ? "Saved" : "Save"; + var iconSize = 16; + var color = Greyscale(isDarkTheme ? .8f : .46f); + var colorHovered = Greyscale(isDarkTheme ? 1f : .2f); + var colorPressed = Greyscale(isDarkTheme ? .8f : .6f); + + + + + if (!IconButton(buttonRect, iconName, iconSize, color, colorHovered, colorPressed)) return true; + + if (targets.Length > 1) + foreach (var target in targets) + VInspectorClipboard.SaveComponent(target as Component); + else + VInspectorClipboard.SaveComponent(component); + + + + return true; + + } + + + + + + + + + + + + + + + + static void LoadSceneBookmarkObjects() // update + { + if (!data) return; + + + var scenesToLoadFor = unloadedSceneBookmarks_sceneGuids.Select(r => EditorSceneManager.GetSceneByPath(r.ToPath())) + .Where(r => r.isLoaded); + if (!scenesToLoadFor.Any()) return; + + + + foreach (var scene in scenesToLoadFor) + { + var bookmarksFromThisScene = data.bookmarks.Where(r => r.globalId.guid == scene.path.ToGuid()).ToList(); + + var objectsForTheseBookmarks = bookmarksFromThisScene.Select(r => !Application.isPlaying ? r.globalId + : r.globalId.UnpackForPrefab()).GetObjects(); + + for (int i = 0; i < bookmarksFromThisScene.Count; i++) + if (objectsForTheseBookmarks[i]) + bookmarksFromThisScene[i]._obj = objectsForTheseBookmarks[i]; + else + bookmarksFromThisScene[i].failedToLoadSceneObject = true; + + } + + unloadedSceneBookmarks_sceneGuids.Clear(); + + + foreach (var inspector in allInspectors) + inspector.Repaint(); + + } + + public static HashSet unloadedSceneBookmarks_sceneGuids = new(); + + + + + static void StashBookmarkObjects() // on playmode enter before awake + { + stashedBookmarkObjects_byBookmark.Clear(); + + foreach (var bookmark in data.bookmarks) + stashedBookmarkObjects_byBookmark[bookmark] = bookmark._obj; + + } + static void UnstashBookmarkObjects() // on playmode exit + { + foreach (var bookmark in data.bookmarks) + if (stashedBookmarkObjects_byBookmark.TryGetValue(bookmark, out var stashedObject)) + if (stashedObject != null) + bookmark._obj = stashedObject; + + } + + static Dictionary stashedBookmarkObjects_byBookmark = new(); + + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] + static void OnPlaymodeEnter_beforeAwake() + { + if (!data) return; + + StashBookmarkObjects(); + + } + static void OnPlaymodeExit(PlayModeStateChange state) + { + if (state != PlayModeStateChange.EnteredEditMode) return; + if (!data) return; + + + UnstashBookmarkObjects(); + + // scene objects can get recreated in playmode if the scene was reloaded + // in this case their respective bookmarks will be updated in OnSceneLoaded_inPlaymode to reference the recreated versions + // so we ensure that after playmode bookmarks reference the same objects as they did before playmode + + + + + foreach (var bookmark in data.bookmarks) + if (bookmark.globalId.guid == "00000000000000000000000000000000") + if (bookmark._obj is GameObject gameObject) + { + bookmark.globalId = new GlobalID(bookmark.globalId.ToString().Replace("00000000000000000000000000000000", gameObject.scene.path.ToGuid())); + data.Dirty(); + } + + // objects from DontDestroyOnLoad that were bookmarked in playmode have globalIds with blank scene guids + // we fix this after playmode, when scene guids become available + + } + + + + + + + + + + + + + + + + public static bool HasVInspectorAttribtues(Type type) + { + if (typesWithVInspectorAttributes != null) return typesWithVInspectorAttributes.Contains(type); + + + typesWithVInspectorAttributes = new(); + + typesWithVInspectorAttributes.UnionWith(TypeCache.GetFieldsWithAttribute().Select(r => r.DeclaringType)); + typesWithVInspectorAttributes.UnionWith(TypeCache.GetFieldsWithAttribute().Select(r => r.DeclaringType)); + + typesWithVInspectorAttributes.UnionWith(TypeCache.GetFieldsWithAttribute().Select(r => r.DeclaringType)); + typesWithVInspectorAttributes.UnionWith(TypeCache.GetFieldsWithAttribute().Select(r => r.DeclaringType)); + typesWithVInspectorAttributes.UnionWith(TypeCache.GetFieldsWithAttribute().Select(r => r.DeclaringType)); + typesWithVInspectorAttributes.UnionWith(TypeCache.GetFieldsWithAttribute().Select(r => r.DeclaringType)); + + typesWithVInspectorAttributes.UnionWith(TypeCache.GetFieldsWithAttribute().Select(r => r.DeclaringType)); + typesWithVInspectorAttributes.UnionWith(TypeCache.GetMethodsWithAttribute().Select(r => r.DeclaringType)); + + typesWithVInspectorAttributes.UnionWith(TypeCache.GetMethodsWithAttribute().Select(r => r.DeclaringType)); + + foreach (var r in typesWithVInspectorAttributes.ToHashSet()) + typesWithVInspectorAttributes.UnionWith(TypeCache.GetTypesDerivedFrom(r)); + + + + return typesWithVInspectorAttributes.Contains(type); + + } + + static HashSet typesWithVInspectorAttributes = null; + + + + public static bool HasUITKOnlyDrawers(SerializedObject serializedObject) + { + if (serializedObject.targetObject == null) return false; + + + var targetType = serializedObject.targetObject.GetType(); + + if (uitkUsage_byType.ContainsKey(targetType)) return uitkUsage_byType[targetType]; + + + + + var maxSearchDepth = 1; + + var curProperty = serializedObject.GetIterator(); + + while (curProperty.NextVisible(enterChildren: curProperty.depth < maxSearchDepth)) + { + var handler = typeof(Editor).Assembly.GetType("UnityEditor.ScriptAttributeUtility").InvokeMethod("GetHandler", curProperty); + + var propertyDrawer = handler.GetPropertyValue("propertyDrawer"); + + if (propertyDrawer == null) continue; + + + var hasUITKimplementation = propertyDrawer.CreatePropertyGUI(curProperty.Copy()) != null; + var hasIMGUIimplementation = propertyDrawer.GetType().GetMethod("OnGUI", new[] { typeof(Rect), typeof(SerializedProperty), typeof(GUIContent) }).DeclaringType == propertyDrawer.GetType(); + + if (hasUITKimplementation && !hasIMGUIimplementation) + return uitkUsage_byType[targetType] = true; + + } + + + + + return uitkUsage_byType[targetType] = false; + + } + + static Dictionary uitkUsage_byType = new(); + + + + static void OnUndoRedo() + { + if (!valueChangedCallbacks_byUndoPosition.Any()) return; + + if (valueChangedCallbacks_byUndoPosition.TryGetValue(EditorUtils.GetCurrendUndoGroupIndex(), out var callback)) + callback.Invoke(); + + } + + public static Dictionary valueChangedCallbacks_byUndoPosition = new(); + + + + + + + + + + + + + + + + [InitializeOnLoadMethod] + static void Init() + { + if (VInspectorMenu.pluginDisabled) return; + + void subscribe() + { + + Editor.finishedDefaultHeaderGUI -= UpdateComponentHeaders; + Editor.finishedDefaultHeaderGUI += UpdateComponentHeaders; + + + Editor.finishedDefaultHeaderGUI -= FillCollections; + Editor.finishedDefaultHeaderGUI += FillCollections; + + + EditorApplication.update -= UpdatePasteButtons; + EditorApplication.update += UpdatePasteButtons; + + Selection.selectionChanged -= UpdatePasteButtons; + Selection.selectionChanged += UpdatePasteButtons; + + + EditorApplication.update -= UpdateNavbars; + EditorApplication.update += UpdateNavbars; + + EditorApplication.update -= UpdateComponentAnimations; + EditorApplication.update += UpdateComponentAnimations; + + Editor.finishedDefaultHeaderGUI -= UpdateHeaderButtons; + Editor.finishedDefaultHeaderGUI += UpdateHeaderButtons; + + + Undo.undoRedoPerformed -= OnUndoRedo; + Undo.undoRedoPerformed += OnUndoRedo; + + + EditorApplication.update -= LoadSceneBookmarkObjects; + EditorApplication.update += LoadSceneBookmarkObjects; + + + + + var globalEventHandler = typeof(EditorApplication).GetFieldValue("globalEventHandler"); + typeof(EditorApplication).SetFieldValue("globalEventHandler", ComponentShortcuts + (globalEventHandler - ComponentShortcuts)); + + + EditorApplication.quitting -= VInspectorState.Save; + EditorApplication.quitting += VInspectorState.Save; + + EditorApplication.playModeStateChanged -= OnPlaymodeExit; + EditorApplication.playModeStateChanged += OnPlaymodeExit; + + EditorApplication.playModeStateChanged -= VInspectorClipboard.OnPlaymodeStateChanged; + EditorApplication.playModeStateChanged += VInspectorClipboard.OnPlaymodeStateChanged; + + + } + void loadData() + { + data = AssetDatabase.LoadAssetAtPath(ProjectPrefs.GetString("vInspector-lastKnownDataPath")); + + + if (data) return; + + data = AssetDatabase.FindAssets("t:VInspectorData").Select(guid => AssetDatabase.LoadAssetAtPath(guid.ToPath())).FirstOrDefault(); + + + if (!data) return; + + ProjectPrefs.SetString("vInspector-lastKnownDataPath", data.GetPath()); + + } + void loadDataDelayed() + { + if (data) return; + + EditorApplication.delayCall += () => EditorApplication.delayCall += loadData; + + // AssetDatabase isn't up to date at this point (it gets updated after InitializeOnLoadMethod) + // and if current AssetDatabase state doesn't contain the data - it won't be loaded during Init() + // so here we schedule an additional, delayed attempt to load the data + // this addresses reports of data loss when trying to load it on a new machine + + } + void removeDeletedBookmarks() + { + if (!data) return; + + + var toRemove = data.bookmarks.Where(r => r.isDeleted); + + if (!toRemove.Any()) return; + + + foreach (var r in toRemove.ToList()) + data.bookmarks.Remove(r); + + data.Dirty(); + + + // delayed to give bookmarks a chance to load in update + + } + + subscribe(); + loadData(); + loadDataDelayed(); + + EditorApplication.delayCall += () => EditorApplication.delayCall += () => removeDeletedBookmarks(); + + } + + public static VInspectorData data; + + + + + static IEnumerable allInspectors => _allInspectors ??= t_InspectorWindow.GetFieldValue("m_AllInspectors").Cast().Where(r => r.GetType() == t_InspectorWindow); + static IEnumerable _allInspectors; + + static Type t_InspectorWindow = typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow"); + static Type t_PropertyEditor = typeof(Editor).Assembly.GetType("UnityEditor.PropertyEditor"); + static Type t_GameObjectInspector = typeof(Editor).Assembly.GetType("UnityEditor.GameObjectInspector"); + + static Type t_VHierarchy = Type.GetType("VHierarchy.VHierarchy") ?? Type.GetType("VHierarchy.VHierarchy, VHierarchy, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"); + + public static MethodInfo mi_VHierarchy_GetIconName = t_VHierarchy?.GetMethod("GetIconName_forVInspector", maxBindingFlags); + + + + + + + + const string version = "2.0.14"; + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.cs.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.cs.meta new file mode 100644 index 0000000..8421816 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspector.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cb0378825d0df482cb66e646e57e5153 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorAttributes.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorAttributes.cs new file mode 100644 index 0000000..af77b4f --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorAttributes.cs @@ -0,0 +1,139 @@ +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using System; +using UnityEngine; +// using static VTools.VDebug; +#if UNITY_EDITOR +using static VInspector.Libs.VUtils; +#endif + + +namespace VInspector +{ + + public class FoldoutAttribute : Attribute + { + public string name; + + public FoldoutAttribute(string name) => this.name = name; + + } + public class EndFoldoutAttribute : Attribute { } + + + + public class TabAttribute : Attribute + { + public string name; + + public TabAttribute(string name) => this.name = name; + + } + public class EndTabAttribute : Attribute { } + + + + public class ButtonAttribute : Attribute + { + public string name = ""; + public int size = 30; + public int space = 0; + public string color = "Grey"; + + public ButtonAttribute() => this.name = ""; + public ButtonAttribute(string name) => this.name = name; + + } + + + + public class VariantsAttribute : PropertyAttribute + { + public object[] variants; + + public VariantsAttribute(params object[] variants) => this.variants = variants; + + } + + + + public class MinMaxSliderAttribute : PropertyAttribute + { + public float min; + public float max; + + public MinMaxSliderAttribute(float min, float max) { this.min = min; this.max = max; } + + } + + + + public abstract class IfAttribute : Attribute + { + public string variableName; + public object variableValue; + +#if UNITY_EDITOR + public bool Evaluate(object target) + { + if (target.GetType().GetFieldInfo(variableName) == null && + target.GetType().GetPropertyInfo(variableName) == null) + return false; + + var curValue = target.GetMemberValue(variableName); + + return object.Equals(curValue, variableValue); + + } +#endif + + public IfAttribute(string boolName) { this.variableName = boolName; this.variableValue = true; } + public IfAttribute(string variableName, object variableValue) { this.variableName = variableName; this.variableValue = variableValue; } + + } + public class EndIfAttribute : Attribute { } + + public class HideIfAttribute : IfAttribute + { + public HideIfAttribute(string boolName) : base(boolName) { } + public HideIfAttribute(string variableName, object variableValue) : base(variableName, variableValue) { } + } + public class ShowIfAttribute : IfAttribute + { + public ShowIfAttribute(string boolName) : base(boolName) { } + public ShowIfAttribute(string variableName, object variableValue) : base(variableName, variableValue) { } + } + public class EnableIfAttribute : IfAttribute + { + public EnableIfAttribute(string boolName) : base(boolName) { } + public EnableIfAttribute(string variableName, object variableValue) : base(variableName, variableValue) { } + } + public class DisableIfAttribute : IfAttribute + { + public DisableIfAttribute(string boolName) : base(boolName) { } + public DisableIfAttribute(string variableName, object variableValue) : base(variableName, variableValue) { } + } + + + + public class ReadOnlyAttribute : Attribute { } + + + + public class ShowInInspectorAttribute : Attribute { } + + + [AttributeUsage(AttributeTargets.Method)] + public class OnValueChangedAttribute : Attribute + { + public string[] variableOrGroupNames; + + public OnValueChangedAttribute(string name) => this.variableOrGroupNames = new[] { name }; + public OnValueChangedAttribute(params string[] names) => this.variableOrGroupNames = names; + + } + + + +} \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorAttributes.cs.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorAttributes.cs.meta new file mode 100644 index 0000000..216da3d --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorAttributes.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c8d83838ee14b4445a0abac264bf08d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorClipboard.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorClipboard.cs new file mode 100644 index 0000000..9af4e9e --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorClipboard.cs @@ -0,0 +1,231 @@ +#if UNITY_EDITOR +using UnityEngine; +using UnityEngine.UIElements; +using System.Collections; +using System.Collections.Generic; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using System.Text.RegularExpressions; +using Type = System.Type; +using static VInspector.Libs.VUtils; +using static VInspector.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VInspector +{ + public class VInspectorClipboard : ScriptableSingleton + { + public static void CopyComponent(Component component) + { + instance.RecordUndo(); + + if (instance.copiedComponetDatas.FirstOrDefault(r => r.sourceComponent == component) is ComponentData alreadyCopiedData) + { + instance.discardedComponentDatas.Add(alreadyCopiedData); + instance.copiedComponetDatas.Remove(alreadyCopiedData); + } + else + instance.copiedComponetDatas.Add(GetComponentData(component)); + + instance.Dirty(); + + } + public static void PasteComponentValues(ComponentData data, Component component) + { + component.RecordUndo(); + + ApplyComponentData(data, component); + + component.Dirty(); + + + + instance.RecordUndo(); + + instance.copiedComponetDatas.Remove(data); + instance.discardedComponentDatas.Add(data); + + instance.Dirty(); + + } + public static void PasteComponentAsNew(ComponentData data, GameObject gameObject) + { + var addedComponent = Undo.AddComponent(gameObject, data.sourceComponent.GetType()); + + ApplyComponentData(data, addedComponent); + + } + + public static void ClearCopiedDatas() + { + instance.RecordUndo(); + + instance.discardedComponentDatas.AddRange(instance.copiedComponetDatas); + instance.copiedComponetDatas.Clear(); + + instance.Dirty(); + + } + + + + public static bool CanComponentsBePastedTo(IEnumerable targetGos) + { + if (!targetGos.Any()) return false; + + foreach (var copiedData in instance.copiedComponetDatas) + if (nonDuplicableComponentTypes.Contains(copiedData.sourceComponent.GetType())) + if (targetGos.Any(r => r.TryGetComponent(copiedData.sourceComponent.GetType(), out _))) + return false; + + return true; + + } + + static Type[] nonDuplicableComponentTypes = new[] + { + typeof(Transform), + typeof(RectTransform), + typeof(MeshFilter), + typeof(MeshRenderer), + typeof(SkinnedMeshRenderer), + typeof(Camera), + typeof(AudioListener), + typeof(Rigidbody), + typeof(Rigidbody2D), + typeof(Light), + typeof(Canvas), + typeof(Animation), + typeof(Animator), + typeof(AudioSource), + typeof(ParticleSystem), + typeof(TrailRenderer), + typeof(LineRenderer), + typeof(LensFlare), + typeof(Projector), + typeof(AudioReverbZone), + typeof(AudioEchoFilter), + typeof(Terrain), + typeof(TerrainCollider), + + }; + + + + [SerializeReference] public List copiedComponetDatas = new(); + + [SerializeReference] public List discardedComponentDatas = new(); // removed datas get stashed here so they can be restored on undo/redo + + + + + + + + public static void SaveComponent(Component component) + { + instance.RecordUndo(); + + if (instance.savedComponentDatas.FirstOrDefault(r => r.sourceComponent == component) is ComponentData alreadySavedData) + { + instance.discardedComponentDatas.Add(alreadySavedData); + instance.savedComponentDatas.Remove(alreadySavedData); + } + else + instance.savedComponentDatas.Add(GetComponentData(component, saveGlobalId: true)); + + instance.Dirty(); + + } + + public static void OnPlaymodeStateChanged(PlayModeStateChange state) + { + if (state == PlayModeStateChange.EnteredPlayMode) + instance.failedToSaveComponentDatas.Clear(); + + + if (state != PlayModeStateChange.EnteredEditMode) return; + + foreach (var data in instance.savedComponentDatas) + if (EditorUtility.InstanceIDToObject(data.sourceComponent.GetInstanceID()) is Component sourceComponent) + ApplyComponentData(data, sourceComponent); + else if (data.globalId.GetObject() is Component sourceComponent_) + ApplyComponentData(data, sourceComponent_); + else + instance.failedToSaveComponentDatas.Add(data); + + instance.savedComponentDatas.Clear(); + + } + + [SerializeReference] public List savedComponentDatas = new(); + [SerializeReference] public List failedToSaveComponentDatas = new(); + + + + + + + + public static ComponentData GetComponentData(Component component, bool saveGlobalId = false) + { + var data = new ComponentData(); + + data.sourceComponent = component; + + if (saveGlobalId) + data.globalId = component.GetGlobalID(); + + + + var property = new SerializedObject(component).GetIterator(); + + if (!property.Next(true)) return data; + + + do data.serializedPropertyValues_byPath[property.propertyPath] = property.GetBoxedValue(); + while (property.NextVisible(true)); + + return data; + + } + public static void ApplyComponentData(ComponentData componentData, Component targetComponent) + { + foreach (var key in componentData.serializedPropertyValues_byPath.Keys.ToList()) + if (componentData.serializedPropertyValues_byPath[key] is Object unityObject && !unityObject) // sometimes object references become null after playmode in unity 6, so we have to restore them by instanceId + componentData.serializedPropertyValues_byPath[key] = EditorUtility.InstanceIDToObject(unityObject.GetInstanceID()); + + foreach (var kvp in componentData.serializedPropertyValues_byPath) + { + var so = new SerializedObject(targetComponent); + var property = so.FindProperty(kvp.Key); + + so.Update(); + + property.SetBoxedValue(kvp.Value); + + so.ApplyModifiedProperties(); + + targetComponent.Dirty(); + + } + + } + + [System.Serializable] + public class ComponentData + { + public Component sourceComponent; + public Dictionary serializedPropertyValues_byPath = new(); + + public GlobalID globalId; + + } + + } + +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorClipboard.cs.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorClipboard.cs.meta new file mode 100644 index 0000000..bdadaa5 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorClipboard.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e946bdceafc44945ad0c659f550d6d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentHeader.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentHeader.cs new file mode 100644 index 0000000..700b6ea --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentHeader.cs @@ -0,0 +1,463 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Linq; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; +using UnityEditor.UIElements; +using System.Reflection; +using UnityEditor; +using static VInspector.Libs.VUtils; +using static VInspector.Libs.VGUI; +// using static VTools.VDebug; + + + +namespace VInspector +{ + public class VInspectorComponentHeader + { + + void OnGUI() + { + void masks() + { + if (!curEvent.isRepaint) return; + + + Color backgroundColor; + + void set_backgroundColor() + { + var hovered = EditorGUIUtility.isProSkin ? Greyscale(.28f) : Greyscale(.84f); + var normal = EditorGUIUtility.isProSkin ? Greyscale(.244f) : Greyscale(.8f); + + backgroundColor = normal; + + if (headerRect.IsHovered() && !mousePressedOnBackground && EditorWindow.mouseOverWindow == window) + backgroundColor = hovered; + + } + void set_buttonMaskSize() + { + buttonsList ??= typeof(EditorGUIUtility).GetMemberValue("s_EditorHeaderItemsMethods"); + + var buttonsCount = 1 + buttonsList.Count; + + if (VInspectorMenu.playmodeSaveButtonEnabled && !Application.isPlaying) + buttonsCount--; + + + buttonMaskRect = headerRect.AddHeightFromMid(-2).SetXMax(headerRect.xMax - 3).SetWidthFromRight(buttonsCount * 20); + + } + + void hideArrow() + { + if (headerRect.IsHovered()) return; + if (!VInspectorMenu.minimalModeEnabled) return; + + headerRect.SetWidth(17).MoveX(-2).AddHeightFromMid(-2).AddWidthFromRight(-4).Draw(backgroundColor); + + } + void hideButtons() + { + if (headerRect.IsHovered()) return; + if (!VInspectorMenu.minimalModeEnabled) return; + + buttonMaskRect.Draw(backgroundColor); + + } + void hideScriptText() + { + if (component is not MonoBehaviour) return; + if (!VInspectorMenu.minimalModeEnabled) return; + if (imguiContainer.onGUIHandler.Method.DeclaringType.FullName.StartsWith("Sisus")) return; + + + var name = component.GetType().Name.Decamelcase(); + + var rect = headerRect.AddHeightFromMid(-2).SetWidth(60).MoveX(name.GetLabelWidth(fontSize: 12, isBold: true) + 60 - 3); + rect = rect.MoveX(-2); + + rect.xMax = rect.xMax.Min(buttonMaskRect.x).Max(rect.xMin); + + + rect.Draw(backgroundColor); + + } + void tintScriptIcon() + { + if (component is not MonoBehaviour) return; + if (!mousePressedOnScriptIcon) return; + + var iconRect = headerRect.MoveX(18).SetWidth(22); + + iconRect.Draw(backgroundColor.SetAlpha(EditorGUIUtility.isProSkin ? .3f : .45f)); + + } + void nameCurtain() + { + var rect = headerRect.AddHeightFromMid(-2).SetXMax(buttonMaskRect.x + 2).SetWidthFromRight(18); + + rect.DrawCurtainLeft(backgroundColor); + + } + + + set_backgroundColor(); + set_buttonMaskSize(); + + hideArrow(); + hideButtons(); + hideScriptText(); + tintScriptIcon(); + nameCurtain(); + + } + + void scriptIconClicks() + { + if (component is not MonoBehaviour) return; + if (curEvent.mouseButton != 0) return; + if (!VInspectorMenu.hideScriptFieldEnabled) return; + + var iconRect = headerRect.MoveX(18).SetWidth(22); + + + void mosueDown() + { + if (!curEvent.isMouseDown) return; + if (!iconRect.IsHovered()) return; + + + mousePressedOnScriptIcon = true; + mousePressedOnScriptIcon_initPos = curEvent.mousePosition; + + + var script = MonoScript.FromMonoBehaviour(component as MonoBehaviour); + + // if (curEvent.holdingAlt) + + + if (curEvent.clickCount == 2) + AssetDatabase.OpenAsset(script); + + + curEvent.Use(); + + } + void mouseUp() + { + if (!curEvent.isMouseUp) return; + if (!mousePressedOnScriptIcon) return; + + var script = MonoScript.FromMonoBehaviour(component as MonoBehaviour); + + if (curEvent.clickCount == 1) + EditorUtils.PingObject(script); + + mousePressedOnScriptIcon = false; + + window.Repaint(); + + curEvent.Use(); + + } + void startDrag() + { + if (!curEvent.isMouseDrag) return; + if (!mousePressedOnScriptIcon) return; + if ((mousePressedOnScriptIcon_initPos - curEvent.mousePosition).magnitude < 2) return; + + DragAndDrop.PrepareStartDrag(); + DragAndDrop.objectReferences = new[] { component }; + DragAndDrop.StartDrag(component.ToString()); + + mousePressedOnScriptIcon = false; + mousePressedOnBackground = false; + + } + + + mosueDown(); + mouseUp(); + startDrag(); + + } + + void expandWithAnimation() + { + if (!mousePressedOnBackground) return; + if (!curEvent.isMouseUp) return; + if (curEvent.mouseButton != 0) return; + if (headerRect.SetWidth(16).MoveX(40).IsHovered()) return; // enabled toggle + if (buttonMaskRect.IsHovered()) return; // right buttons + + + if (curEvent.holdingShift) + VInspector.CollapseOtherComponents(component, window); + + else if (VInspectorMenu.componentAnimationsEnabled) + VInspector.ToggleComponentExpanded(component, window); + + else return; + + + mousePressedOnBackground = false; + + curEvent.Use(); + + EditorGUIUtility.hotControl = 0; + + } + void createComponentWindow() + { + if (!mousePressedOnBackground) return; + if (!curEvent.isMouseDrag) return; + if (!curEvent.holdingAlt) return; + if (curEvent.mousePosition.DistanceTo(mousePressedOnBackground_initPos) < 2) return; + if (!VInspectorMenu.componentWindowsEnabled) return; + + if (VInspectorComponentWindow.draggedInstance != null) return; + + + + var position = EditorGUIUtility.GUIToScreenPoint(headerRect.position + (curEvent.mousePosition - mousePressedOnBackground_initPos)); + + VInspectorComponentWindow.CreateDraggedInstance(component, position, headerRect.width); + + + + EditorGUIUtility.hotControl = 0; + + mousePressedOnBackground = false; + + + + } + + void set_mousePressedOnBackground() + { + if (curEvent.isMouseDown && !buttonMaskRect.IsHovered()) + { + mousePressedOnBackground = true; + mousePressedOnBackground_initPos = curEvent.mousePosition; + } + + if (curEvent.isMouseUp || curEvent.isDragUpdate) + mousePressedOnBackground = false; + + if (!imguiContainer.contentRect.IsHovered()) + mousePressedOnBackground = false; + + } + void set_hoveredComponentHeader() + { + if (!curEvent.isRepaint) return; + + if (component is Transform) + VInspector.hoveredComponentHeader = null; + + if (headerRect.IsHovered()) + VInspector.hoveredComponentHeader = this; + + } + + void defaultHeaderGUI() + { + void initOffsets() + { + if (!VInspectorMenu.minimalModeEnabled) return; + if (headerContentStyle != null) return; + + headerContentStyle = typeof(EditorStyles).GetMemberValue("inspectorTitlebar"); + headerFoldoutStyle = typeof(EditorStyles).GetMemberValue("titlebarFoldout"); + + headerContentStyle_defaultLeftPadding = headerContentStyle.padding.left; + headerFoldoutStyle_defaultLeftMargin = headerFoldoutStyle.margin.left; + + } + void setAdjustedOffsets() + { + if (!VInspectorMenu.minimalModeEnabled) return; + + headerContentStyle.padding.left = headerContentStyle_defaultLeftPadding - 2; + headerFoldoutStyle.margin.left = headerFoldoutStyle_defaultLeftMargin - 1; + + } + void setDefaultOffsets() + { + if (!VInspectorMenu.minimalModeEnabled) return; + + headerContentStyle.padding.left = headerContentStyle_defaultLeftPadding; + headerFoldoutStyle.margin.left = headerFoldoutStyle_defaultLeftMargin; + + } + + initOffsets(); + setAdjustedOffsets(); + defaultHeaderGUIAction.Invoke(); + setDefaultOffsets(); + + } + + void preventKeyboardFocus() + { + if (!curEvent.isUsed) return; + if (!headerRect.IsHovered()) return; + if (imguiContainer.onGUIHandler.Method.DeclaringType.FullName.StartsWith("Sisus")) return; + + GUIUtility.keyboardControl = 0; + + // removes that annoying blue highlight after clicking on header + + } + + + + if (curEvent.isRepaint) + defaultHeaderGUI(); + + masks(); + + scriptIconClicks(); + expandWithAnimation(); + createComponentWindow(); + + set_mousePressedOnBackground(); + set_hoveredComponentHeader(); + + if (!curEvent.isRepaint) + defaultHeaderGUI(); + + preventKeyboardFocus(); + + } + + static IList buttonsList; + static Rect buttonMaskRect; + + public bool mousePressedOnBackground; + public bool mousePressedOnScriptIcon; + public Vector2 mousePressedOnBackground_initPos; + public Vector2 mousePressedOnScriptIcon_initPos; + + public Rect headerRect + { + get + { + var contentRect = imguiContainer.contentRect; + + if (contentRect.height == 42) // with extra lines like "Multi-object editing not supported" + return contentRect.SetHeight(22); + else + return contentRect.SetHeightFromBottom(22); // fixes offset on transform header in 6000 + } + } + + static GUIStyle headerContentStyle; + static GUIStyle headerFoldoutStyle; + + static int headerContentStyle_defaultLeftPadding; + static int headerFoldoutStyle_defaultLeftMargin; + + public Vector2 mouseDownPos; + + + + + + + public void Update() + { + if (imguiContainer is VisualElement v && v.panel == null) { imguiContainer.onGUIHandler = defaultHeaderGUIAction; imguiContainer = null; } + if (imguiContainer?.onGUIHandler.Method.DeclaringType == typeof(VInspectorComponentHeader)) return; + if (imguiContainer?.onGUIHandler.Method.DeclaringType.FullName.StartsWith("Sisus") == true) return; + if (typeof(ScriptableObject).IsAssignableFrom(component.GetType())) return; + if (editor.GetPropertyValue("propertyViewer") is not EditorWindow window) return; + + + this.window = window; + + void fixWrongWindow() + { + if (Application.unityVersion != "2022.3.26f1" && Application.unityVersion != "2023.2.20f1") return; + + if (!window.hasFocus) + window = window.GetMemberValue("m_Parent")?.GetMemberValue>("m_Panes")?.FirstOrDefault(r => r.hasFocus) ?? window; + + // in some versions wrong inspector may be returned by propertyViewer when there are multiple inspectors + // also the same instance of an editor may be used on all inspectors + // here we fix it for cases when multiple inspectors are in the same dock area + + } + void findHeader(VisualElement element) + { + if (element == null) return; + + if (element.GetType().Name == "EditorElement") + { + IMGUIContainer curHeaderImguiContainer = null; + + foreach (var child in element.Children()) + { + curHeaderImguiContainer ??= new[] { child as IMGUIContainer }.FirstOrDefault(r => r != null && r.name.EndsWith("Header")); + + if (curHeaderImguiContainer is null) continue; + if (child is not InspectorElement) continue; + if (child.GetFieldValue("m_Editor") is not Editor editor) continue; + if (editor.target != component) continue; + + imguiContainer = curHeaderImguiContainer; + + if (editingMultiselection = editor.targets.Count() > 1) + multiselectedComponents = editor.targets.Cast().ToList(); + + return; + + } + + } + + foreach (var r in element.Children()) + if (imguiContainer == null) + findHeader(r); + + } + void setupGUICallbacks() + { + if (imguiContainer == null) return; + + defaultHeaderGUIAction = imguiContainer.onGUIHandler; + imguiContainer.onGUIHandler = OnGUI; + } + + fixWrongWindow(); + findHeader(window.rootVisualElement); + setupGUICallbacks(); + + } + + public bool editingMultiselection; + public List multiselectedComponents; + + IMGUIContainer imguiContainer; + System.Action defaultHeaderGUIAction; + + EditorWindow window; + + + + + + + + public VInspectorComponentHeader(Component component, Editor editor) { this.component = component; this.editor = editor; } + + public Editor editor; + public Component component; + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentHeader.cs.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentHeader.cs.meta new file mode 100644 index 0000000..24d0da1 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentHeader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 69daed4ccfde7447ba633e320119de35 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentWindow.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentWindow.cs new file mode 100644 index 0000000..06f21bf --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentWindow.cs @@ -0,0 +1,498 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEditor.UIElements; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VInspector.VInspector; +using static VInspector.Libs.VUtils; +using static VInspector.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VInspector +{ + public class VInspectorComponentWindow : EditorWindow + { + + void OnGUI() + { + if (!component) component = EditorUtility.InstanceIDToObject(componentIid) as Component; + if (!component) { Close(); return; } + if (!editor) { Init(component); skipHeightUpdate = true; } + + + void background() + { + position.SetPos(0, 0).Draw(GUIColors.windowBackground); + } + void header() + { + var headerRect = ExpandWidthLabelRect(18).Resize(-1).AddWidthFromMid(6); + var closeButtonRect = headerRect.SetWidthFromRight(16).SetHeightFromMid(16).Move(-4, 0); + + var backgroundColor = isDarkTheme ? Greyscale(.25f) : GUIColors.windowBackground; + + void startDragging() + { + if (isResizingVertically) return; + if (isResizingHorizontally) return; + if (isDragged) return; + if (!curEvent.isMouseDrag) return; + if (!headerRect.IsHovered()) return; + + + draggedInstance = this; + + dragStartMousePos = curEvent.mousePosition_screenSpace; + dragStartWindowPos = position.position; + + } + void updateDragging() + { + if (!isDragged) return; + + + var draggedPosition = dragStartWindowPos + curEvent.mousePosition_screenSpace - dragStartMousePos; + + if (!curEvent.isRepaint) + position = position.SetPos(draggedPosition); + + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + } + void stopDragging() + { + if (!isDragged) return; + if (!curEvent.isMouseMove && !curEvent.isMouseUp) return; + + + draggedInstance = null; + + EditorGUIUtility.hotControl = 0; + + } + + void background() + { + headerRect.Draw(backgroundColor); + + headerRect.SetHeightFromBottom(1).Draw(isDarkTheme ? Greyscale(.2f) : Greyscale(.7f)); + + } + void icon() + { + var iconRect = headerRect.SetWidth(20).MoveX(14).MoveY(-1); + + if (!componentIcons_byType.ContainsKey(component.GetType())) + componentIcons_byType[component.GetType()] = EditorGUIUtility.ObjectContent(component, component.GetType()).image; + + GUI.Label(iconRect, componentIcons_byType[component.GetType()]); + + } + void toggle() + { + var toggleRect = headerRect.MoveX(36).SetSize(20, 20); + + + var pi_enabled = component.GetType().GetProperty("enabled") ?? + component.GetType().BaseType?.GetProperty("enabled") ?? + component.GetType().BaseType?.BaseType?.GetProperty("enabled") ?? + component.GetType().BaseType?.BaseType?.BaseType?.GetProperty("enabled"); + + + if (pi_enabled == null) return; + + var enabled = (bool)pi_enabled.GetValue(component); + + + if (GUI.Toggle(toggleRect, enabled, "") == enabled) return; + + component.RecordUndo(); + pi_enabled.SetValue(component, !enabled); + + } + void name() + { + var nameRect = headerRect.MoveX(54).MoveY(-1); + + + var s = new GUIContent(EditorGUIUtility.ObjectContent(component, component.GetType())).text; + s = s.Substring(s.LastIndexOf('(') + 1); + s = s.Substring(0, s.Length - 1); + + if (instances.Any(r => r.component.GetType() == component.GetType() && r.component != component)) + s += " - " + component.gameObject.name; + + + SetLabelBold(); + + GUI.Label(nameRect, s); + + ResetLabelStyle(); + + } + void nameCurtain() + { + var flatColorRect = headerRect.SetX(closeButtonRect.x + 3).SetXMax(headerRect.xMax); + var gradientRect = headerRect.SetXMax(flatColorRect.x).SetWidthFromRight(30); + + flatColorRect.Draw(backgroundColor); + gradientRect.DrawCurtainLeft(backgroundColor); + + } + void closeButton() + { + var iconName = "CrossIcon"; + var iconSize = 14; + var color = isDarkTheme ? Greyscale(.65f) : Greyscale(.35f); + var colorHovered = isDarkTheme ? Greyscale(.9f) : color; + var colorPressed = color; + + + if (!IconButton(closeButtonRect, iconName, iconSize, color, colorHovered, colorPressed)) return; + + Close(); + + EditorGUIUtility.ExitGUI(); + + } + void rightClick() + { + if (!curEvent.isMouseDown) return; + if (curEvent.mouseButton != 1) return; + if (!headerRect.IsHovered()) return; + + typeof(EditorUtility).InvokeMethod("DisplayObjectContextMenu", Rect.zero.SetPos(curEvent.mousePosition), component, 0); + + } + + startDragging(); + updateDragging(); + stopDragging(); + + background(); + icon(); + toggle(); + name(); + nameCurtain(); + closeButton(); + rightClick(); + + } + void body_imgui() + { + if (useUITK) return; + + + EditorGUIUtility.labelWidth = (this.position.width * .4f).Max(120); + + + scrollPosition = EditorGUILayout.BeginScrollView(Vector2.up * scrollPosition).y; + BeginIndent(17); + + + editor?.OnInspectorGUI(); + + updateHeight_imgui(); + + + EndIndent(1); + EditorGUILayout.EndScrollView(); + + + EditorGUIUtility.labelWidth = 0; + + } + void outline() + { + if (Application.platform == RuntimePlatform.OSXEditor) return; + + position.SetPos(0, 0).DrawOutline(Greyscale(.1f)); + + } + + void updateHeight_imgui() + { + if (useUITK) return; + + + ExpandWidthLabelRect(height: -5); + + if (!curEvent.isRepaint) return; + if (isResizingVertically) return; + + + targetHeight = lastRect.y + 30; + + position = position.SetHeight(targetHeight.Min(maxHeight)); + + + prevHeight = position.height; + + } + void updateHeight_uitk() + { + if (!useUITK) return; + if (!curEvent.isRepaint) return; + if (skipHeightUpdate) { skipHeightUpdate = false; return; } // crashses otherwise + + + var lastElement = inspectorElement[inspectorElement.childCount - 1]; + + targetHeight = lastElement.contentRect.yMax + 33; + + position = position.SetHeight(targetHeight); + + } + + void horizontalResize() + { + var showingScrollbar = targetHeight > maxHeight; + + var resizeArea = this.position.SetPos(0, 0).SetWidthFromRight(showingScrollbar ? 3 : 5).AddHeightFromBottom(-20); + + void startResize() + { + if (isDragged) return; + if (isResizingHorizontally) return; + if (!curEvent.isMouseDown && !curEvent.isMouseDrag) return; + if (!resizeArea.IsHovered()) return; + + isResizingHorizontally = true; + + resizeStartMousePos = curEvent.mousePosition_screenSpace; + resizeStartWindowSize = this.position.size; + + } + void updateResize() + { + if (!isResizingHorizontally) return; + + + var resizedWidth = resizeStartWindowSize.x + curEvent.mousePosition_screenSpace.x - resizeStartMousePos.x; + + var width = resizedWidth.Max(300); + + if (!curEvent.isRepaint) + position = position.SetWidth(width); + + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + // GUI.focused + + } + void stopResize() + { + if (!isResizingHorizontally) return; + if (!curEvent.isMouseUp) return; + + isResizingHorizontally = false; + + EditorGUIUtility.hotControl = 0; + + } + + + EditorGUIUtility.AddCursorRect(resizeArea, MouseCursor.ResizeHorizontal); + + startResize(); + updateResize(); + stopResize(); + + } + void verticalResize() + { + var resizeArea = this.position.SetPos(0, 0).SetHeightFromBottom(5); + + void startResize() + { + if (isDragged) return; + if (isResizingVertically) return; + if (!curEvent.isMouseDown && !curEvent.isMouseDrag) return; + if (!resizeArea.IsHovered()) return; + + isResizingVertically = true; + + resizeStartMousePos = curEvent.mousePosition_screenSpace; + resizeStartWindowSize = this.position.size; + + } + void updateResize() + { + if (!isResizingVertically) return; + + + var resizedHeight = resizeStartWindowSize.y + curEvent.mousePosition_screenSpace.y - resizeStartMousePos.y; + + var height = resizedHeight.Min(targetHeight).Max(50); + + if (!curEvent.isRepaint) + position = position.SetHeight(height); + + maxHeight = height; + + + EditorGUIUtility.hotControl = EditorGUIUtility.GetControlID(FocusType.Passive); + + } + void stopResize() + { + if (!isResizingVertically) return; + if (!curEvent.isMouseUp) return; + + isResizingVertically = false; + + EditorGUIUtility.hotControl = 0; + + } + + + EditorGUIUtility.AddCursorRect(resizeArea, MouseCursor.ResizeVertical); + + startResize(); + updateResize(); + stopResize(); + + } + + + + background(); + header(); + + + horizontalResize(); + verticalResize(); + + + Space(3); + body_imgui(); + outline(); + + Space(7); + + updateHeight_uitk(); + + + if (isDragged) + Repaint(); + + EditorApplication.delayCall -= Repaint; + EditorApplication.delayCall += Repaint; + + } + + public bool isDragged => draggedInstance == this; + public Vector2 dragStartMousePos; + public Vector2 dragStartWindowPos; + + public bool isResizingHorizontally; + public bool isResizingVertically; + public Vector2 resizeStartMousePos; + public Vector2 resizeStartWindowSize; + + public float scrollPosition; + + bool skipHeightUpdate; + + public float targetHeight; + public float maxHeight; + public float prevHeight; + + static Dictionary componentIcons_byType = new(); + + + + + + + + + public void Init(Component component) + { + if (editor) + editor.DestroyImmediate(); + + this.component = component; + this.editor = Editor.CreateEditor(component); + + this.componentIid = component.GetInstanceID(); + + hasCustomUITKEditor = editor.GetType().GetMethod("CreateInspectorGUI", maxBindingFlags) != null; + + if (!instances.Contains(this)) + instances.Add(this); + + + + if (!useUITK) return; + + inspectorElement = new InspectorElement(editor.serializedObject); + + inspectorElement.style.marginTop = 23; + + this.rootVisualElement.Add(inspectorElement); + + } + + void OnDestroy() + { + editor?.DestroyImmediate(); + + if (instances.Contains(this)) + instances.Remove(this); + + } + + public Component component; + public Editor editor; + public InspectorElement inspectorElement; + + public int componentIid; + + bool useUITK => editor.target is MonoBehaviour && (HasUITKOnlyDrawers(editor.serializedObject) || hasCustomUITKEditor); + bool hasCustomUITKEditor; + + public static List instances = new(); + + + + + + public static void CreateDraggedInstance(Component component, Vector2 windowPosition, float windowWidth) + { + draggedInstance = ScriptableObject.CreateInstance(); + + draggedInstance.ShowPopup(); + draggedInstance.Init(component); + draggedInstance.Focus(); + + + draggedInstance.wantsMouseMove = true; + + // draggedInstance.minSize = new Vector2(300, 50); // will make window resizeable on mac, but not on windows + draggedInstance.maxHeight = EditorGUIUtility.GetMainWindowPosition().height * .7f; + + + draggedInstance.position = Rect.zero.SetPos(windowPosition).SetWidth(windowWidth).SetHeight(200); + draggedInstance.prevHeight = draggedInstance.position.height; + + draggedInstance.dragStartMousePos = curEvent.mousePosition_screenSpace; + draggedInstance.dragStartWindowPos = windowPosition; + + } + + public static VInspectorComponentWindow draggedInstance; + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentWindow.cs.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentWindow.cs.meta new file mode 100644 index 0000000..81b6ecf --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorComponentWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 295e0a9b2039246c59fbe00423b2723e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorData.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorData.cs new file mode 100644 index 0000000..a453150 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorData.cs @@ -0,0 +1,156 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Serialization; +using UnityEditor; +using UnityEditor.ShortcutManagement; +using System.Reflection; +using System.Linq; +using UnityEngine.UIElements; +using UnityEngine.SceneManagement; +using UnityEditor.SceneManagement; +using Type = System.Type; +using static VInspector.VInspectorState; +using static VInspector.Libs.VUtils; +using static VInspector.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VInspector +{ + public class VInspectorData : ScriptableObject + { + + public List bookmarks = new(); + + [System.Serializable] + public class Bookmark + { + + public Object obj + { + get + { + if (isSceneGameObject && _obj == null) + { + if (!failedToLoadSceneObject) // to prevent continuous GlobalID.GetObjects() calls if object is deleted + VInspector.unloadedSceneBookmarks_sceneGuids.Add(globalId.guid); + + return null; + } + + return _obj ??= globalId.GetObject(); + + } + + } + public Object _obj; + + [System.NonSerialized] + public bool failedToLoadSceneObject; + + + + public bool isLoadable => obj != null; + + public bool isDeleted + { + get + { + if (!isSceneGameObject) + return !isLoadable; + + if (isLoadable) + return false; + + if (!AssetDatabase.LoadAssetAtPath(globalId.guid.ToPath())) + return true; + + for (int i = 0; i < EditorSceneManager.sceneCount; i++) + if (EditorSceneManager.GetSceneAt(i).path == globalId.guid.ToPath()) + return true; + + return false; + + } + } + + public string assetPath => globalId.guid.ToPath(); + + public Type type => Type.GetType(_typeString) ?? typeof(DefaultAsset); + + public string name + { + get + { + if (!obj) return "Can't load object"; + + if (assetPath.GetExtension() == ".cs") + return obj.name.Decamelcase(); + else + return obj.name; + + } + } + + + + public Bookmark(Object o) + { + globalId = o.GetGlobalID(); + + isSceneGameObject = o is GameObject go && go.scene.rootCount != 0; + isAsset = !isSceneGameObject; + + _typeString = o.GetType().AssemblyQualifiedName; + + _obj = o; + + } + + public GlobalID globalId; + + public bool isSceneGameObject; + public bool isAsset; + + public string _typeString; + + } + + + + + + + + + + + [CustomEditor(typeof(VInspectorData))] + class Editor : UnityEditor.Editor + { + public override void OnInspectorGUI() + { + var style = new GUIStyle(EditorStyles.label) { wordWrap = true }; + + + SetGUIEnabled(false); + BeginIndent(0); + + Space(10); + EditorGUILayout.LabelField("This file stores bookmarks from vInspector's navigation bar", style); + + EndIndent(10); + ResetGUIEnabled(); + + // Space(15); + // base.OnInspectorGUI(); + + } + } + + + } +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorData.cs.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorData.cs.meta new file mode 100644 index 0000000..601e74b --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7915df4cd4da490ab52fc8fa9ee9127 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorDrawers.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorDrawers.cs new file mode 100644 index 0000000..14e2ea9 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorDrawers.cs @@ -0,0 +1,429 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Linq; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; +using UnityEditor.UIElements; +using System.Reflection; +using UnityEditor; +using UnityEditorInternal; +using Type = System.Type; +using Attribute = System.Attribute; +using static VInspector.VInspectorState; +using static VInspector.Libs.VUtils; +using static VInspector.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VInspector +{ + [CustomPropertyDrawer(typeof(SerializedDictionary<,>), true)] + public class SerializedDictionaryDrawer : PropertyDrawer + { + public override void OnGUI(Rect rect, SerializedProperty prop, GUIContent label) + { + var indentedRect = EditorGUI.IndentedRect(rect); + + void header() + { + var headerRect = indentedRect.SetHeight(EditorGUIUtility.singleLineHeight); + + void foldout() + { + var fullHeaderRect = headerRect.MoveX(3).AddWidthFromRight(17); + + if (fullHeaderRect.IsHovered()) + fullHeaderRect.Draw(Greyscale(1, .07f)); + + SetGUIColor(Color.clear); + SetGUIEnabled(true); + + if (GUI.Button(fullHeaderRect.AddWidth(-50), "")) + prop.isExpanded = !prop.isExpanded; + + ResetGUIColor(); + ResetGUIEnabled(); + + + + var triangleRect = rect.SetHeight(EditorGUIUtility.singleLineHeight); + + SetGUIEnabled(true); + + EditorGUI.Foldout(triangleRect, prop.isExpanded, ""); + + ResetGUIEnabled(); + + + } + void label() + { + SetLabelBold(); + SetLabelFontSize(12); + SetGUIColor(Greyscale(.9f)); + SetGUIEnabled(true); + + GUI.Label(headerRect, prop.displayName); + + ResetGUIEnabled(); + ResetGUIColor(); + ResetLabelStyle(); + + } + void count() + { + kvpsProp_byProp[prop].arraySize = EditorGUI.DelayedIntField(headerRect.SetWidthFromRight(48 + EditorGUI.indentLevel * 15), kvpsProp_byProp[prop].arraySize); + } + void repeatedKeysWarning() + { + if (!curEvent.isRepaint) return; + + + var hasRepeatedKeys = false; + var hasNullKeys = false; + + for (int i = 0; i < kvpsProp_byProp[prop].arraySize; i++) + { + hasRepeatedKeys |= kvpsProp_byProp[prop].GetArrayElementAtIndex(i).FindPropertyRelative("isKeyRepeated").boolValue; + hasNullKeys |= kvpsProp_byProp[prop].GetArrayElementAtIndex(i).FindPropertyRelative("isKeyNull").boolValue; + } + + if (!hasRepeatedKeys && !hasNullKeys) return; + + + + var warningTextRect = headerRect.AddWidthFromRight(-prop.displayName.GetLabelWidth(isBold: true)); + var warningIconRect = warningTextRect.SetHeightFromMid(20).SetWidth(20); + + var warningText = (hasRepeatedKeys && hasNullKeys) ? "Repeated and null keys" + : hasRepeatedKeys ? "Repeated keys" + : hasNullKeys ? "Null keys" : ""; + + + + GUI.Label(warningIconRect, EditorGUIUtility.IconContent("Warning")); + + + SetGUIColor(new Color(1, .9f, .03f) * 1.1f); + + GUI.Label(warningTextRect.MoveX(16), warningText); + + ResetGUIColor(); + + } + + foldout(); + label(); + count(); + repeatedKeysWarning(); + + } + void list_() + { + if (!prop.isExpanded) return; + + SetupList(prop); + + lists_byProp[prop].DoList(indentedRect.AddHeightFromBottom(-EditorGUIUtility.singleLineHeight - 3)); + } + + + SetupProps(prop); + + header(); + list_(); + + } + + public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) + { + SetupProps(prop); + + var height = EditorGUIUtility.singleLineHeight; + + if (prop.isExpanded) + { + SetupList(prop); + height += lists_byProp[prop].GetHeight() + 3; + } + + return height; + } + + float GetListElementHeight(int index, SerializedProperty prop) + { + var kvpProp = kvpsProp_byProp[prop].GetArrayElementAtIndex(index); + var keyProp = kvpProp.FindPropertyRelative("Key"); + var valueProp = kvpProp.FindPropertyRelative("Value"); + + float propHeight(SerializedProperty prop) + { + // var height = typeof(Editor).Assembly.GetType("UnityEditor.ScriptAttributeUtility").InvokeMethod("GetHandler", prop).InvokeMethod("GetHeight", prop, GUIContent.none, true); + var height = EditorGUI.GetPropertyHeight(prop); + + if (!IsSingleLine(prop) && prop.type != "EventReference") + height -= 10; + + return height; + + } + + return Mathf.Max(propHeight(keyProp), propHeight(valueProp)); + + } + + void DrawListElement(Rect rect, int index, bool isActive, bool isFocused, SerializedProperty prop) + { + Rect keyRect; + Rect valueRect; + Rect dividerRect; + + var kvpProp = kvpsProp_byProp[prop].GetArrayElementAtIndex(index); + var keyProp = kvpProp.FindPropertyRelative("Key"); + var valueProp = kvpProp.FindPropertyRelative("Value"); + + void drawProp(Rect rect, SerializedProperty prop) + { + if (IsSingleLine(prop)) { EditorGUI.PropertyField(rect.SetHeight(EditorGUIUtility.singleLineHeight), prop, GUIContent.none); return; } + + + prop.isExpanded = true; + + GUI.BeginGroup(rect); + + if (prop.type == "EventReference") // don't hide first line for FMOD EventReference + { + EditorGUIUtility.labelWidth = 1; + EditorGUI.PropertyField(rect.SetPos(0, 0), prop, GUIContent.none); + EditorGUIUtility.labelWidth = 0; + } + else + EditorGUI.PropertyField(rect.SetPos(0, -20), prop, true); + + GUI.EndGroup(); + + } + + void rects() + { + var dividerWidh = 6f; + + var dividerPos = dividerPosProp.floatValue.Clamp(.2f, .8f); + + var fullRect = rect.AddWidthFromRight(-1).AddHeightFromMid(-2); + + keyRect = fullRect.SetWidth(fullRect.width * dividerPos - dividerWidh / 2); + valueRect = fullRect.SetWidthFromRight(fullRect.width * (1 - dividerPos) - dividerWidh / 2); + dividerRect = fullRect.MoveX(fullRect.width * dividerPos - dividerWidh / 2).SetWidth(dividerWidh).Resize(-1); + + } + void key() + { + drawProp(keyRect, keyProp); + + } + void warning() + { + var isKeyRepeated = kvpProp.FindPropertyRelative("isKeyRepeated").boolValue; + var isKeyNull = kvpProp.FindPropertyRelative("isKeyNull").boolValue; + + if (!isKeyRepeated && !isKeyNull) return; + + + var warningRect = keyRect.SetWidthFromRight(20).SetHeight(20).MoveY(-1); + + if (kvpProp.FindPropertyRelative("Key").propertyType == SerializedPropertyType.ObjectReference) + warningRect = warningRect.MoveX(-17); + + + GUI.Label(warningRect, EditorGUIUtility.IconContent("Warning")); + + } + void value() + { + drawProp(valueRect, valueProp); + } + void divider() + { + EditorGUIUtility.AddCursorRect(dividerRect, MouseCursor.ResizeHorizontal); + + if (!rect.IsHovered()) return; + + if (dividerRect.IsHovered()) + { + if (curEvent.isMouseDown) + isDividerDragged = true; + + if (curEvent.isMouseUp || curEvent.isMouseMove || curEvent.isMouseLeaveWindow) + isDividerDragged = false; + } + + if (isDividerDragged && curEvent.isMouseDrag) + dividerPosProp.floatValue += curEvent.mouseDelta.x / rect.width; + + } + + rects(); + key(); + warning(); + value(); + divider(); + + } + + void DrawDictionaryIsEmpty(Rect rect) => GUI.Label(rect, "Dictionary is empty"); + + + + IEnumerable GetChildren(SerializedProperty prop, bool enterVisibleGrandchildren) + { + var startPath = prop.propertyPath; + + var enterVisibleChildren = true; + + while (prop.NextVisible(enterVisibleChildren) && prop.propertyPath.StartsWith(startPath)) + { + yield return prop; + enterVisibleChildren = enterVisibleGrandchildren; + } + + } + + bool IsSingleLine(SerializedProperty prop) => prop.propertyType != SerializedPropertyType.Generic || !prop.hasVisibleChildren; + + + + public void SetupList(SerializedProperty prop) + { + if (lists_byProp.ContainsKey(prop)) return; + + SetupProps(prop); + + lists_byProp[prop] = new ReorderableList(kvpsProp_byProp[prop].serializedObject, kvpsProp_byProp[prop], true, false, true, true); + lists_byProp[prop].drawElementCallback = (q, w, e, r) => DrawListElement(q, w, e, r, prop); + lists_byProp[prop].elementHeightCallback = (q) => GetListElementHeight(q, prop); + lists_byProp[prop].drawNoneElementCallback = DrawDictionaryIsEmpty; + + } + + Dictionary lists_byProp = new(); + // ReorderableList list; + + bool isDividerDragged; + + + public void SetupProps(SerializedProperty prop) + { + if (kvpsProp_byProp.ContainsKey(prop)) return; + + kvpsProp_byProp[prop] = prop.FindPropertyRelative("serializedKvps"); + + this.dividerPosProp = prop.FindPropertyRelative("dividerPos"); + + + } + + Dictionary kvpsProp_byProp = new(); + // SerializedProperty kvpsProp; + + SerializedProperty dividerPosProp; + + } + + + [CustomPropertyDrawer(typeof(VariantsAttribute))] + public class VariantsDrawer : PropertyDrawer + { + public override void OnGUI(Rect rect, SerializedProperty prop, GUIContent label) + { + var variants = ((VariantsAttribute)attribute).variants; + + + EditorGUI.BeginProperty(rect, label, prop); + + var iCur = prop.hasMultipleDifferentValues ? -1 : variants.ToList().IndexOf(prop.GetBoxedValue()); + + var iNew = EditorGUI.IntPopup(rect, label.text, iCur, variants.Select(r => r.ToString()).ToArray(), Enumerable.Range(0, variants.Length).ToArray()); + + if (iNew != -1) + prop.SetBoxedValue(variants[iNew]); + else if (!prop.hasMultipleDifferentValues) + prop.SetBoxedValue(variants[0]); + + EditorGUI.EndProperty(); + + } + } + + + [CustomPropertyDrawer(typeof(MinMaxSliderAttribute))] + public class MinMaxSliderDrawer : PropertyDrawer + { + public override void OnGUI(Rect rect, SerializedProperty prop, GUIContent label) + { + var fieldWidth = 52; + + var controlsRect = rect.AddWidthFromRight(-EditorGUIUtility.labelWidth); + + var minFieldRect = controlsRect.SetWidth(fieldWidth).AddWidthFromRight(-2 + EditorGUI.indentLevel * 15); + var maxFieldRect = controlsRect.SetWidthFromRight(fieldWidth).AddWidthFromRight(-2 + EditorGUI.indentLevel * 15); + var sliderRect = controlsRect.AddWidthFromMid(-fieldWidth * 2 - 4).AddWidthFromRight(-2 + EditorGUI.indentLevel * 15); + + + var isInt = prop.propertyType == SerializedPropertyType.Vector2Int; + + var min = isInt ? prop.vector2IntValue.x : prop.vector2Value.x; + var max = isInt ? prop.vector2IntValue.y : prop.vector2Value.y; + + var minLimit = ((MinMaxSliderAttribute)attribute).min; + var maxLimit = ((MinMaxSliderAttribute)attribute).max; + + + + + EditorGUI.PrefixLabel(rect, label); + + + EditorGUI.BeginProperty(rect, label, prop); + + if (sliderRect.width > 14) + { + EditorGUI.BeginChangeCheck(); + + EditorGUI.MinMaxSlider(sliderRect, ref min, ref max, minLimit, maxLimit); + + if (EditorGUI.EndChangeCheck()) + { + var abs = (maxLimit - minLimit).Abs(); + + var decimals = abs > 190 ? + 0 : abs > 19 ? + 1 : abs > 1.9f ? + 2 : + 3; + + min = (float)System.Math.Round(min, decimals); + max = (float)System.Math.Round(max, decimals); + + // same rounding logic as for [Range] + } + + } + + min = EditorGUI.DelayedFloatField(minFieldRect, min).Max(minLimit).Min(maxLimit); + max = EditorGUI.DelayedFloatField(maxFieldRect, max).Max(min).Max(minLimit).Min(maxLimit); + + if (isInt) + prop.vector2IntValue = new Vector2Int(min.RoundToInt(), max.RoundToInt()); + else + prop.vector2Value = new Vector2(min, max); + + EditorGUI.EndProperty(); + + + } + } + +} +#endif \ No newline at end of file diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorDrawers.cs.meta b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorDrawers.cs.meta new file mode 100644 index 0000000..8544e32 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorDrawers.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 481331f9e0bfe494f98ee0fc8ebfeb84 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorEditor.cs b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorEditor.cs new file mode 100644 index 0000000..ef0d347 --- /dev/null +++ b/Assets/OtherAssets/DEV/PAID/vInspector/VInspectorEditor.cs @@ -0,0 +1,2105 @@ +#if UNITY_EDITOR +using System.Collections; +using System.Linq; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.UIElements; +using UnityEditor.UIElements; +using System.Reflection; +using UnityEditor; +using Type = System.Type; +using Attribute = System.Attribute; +using static VInspector.VInspector; +using static VInspector.VInspectorState; +using static VInspector.Libs.VUtils; +using static VInspector.Libs.VGUI; +// using static VTools.VDebug; + + +namespace VInspector +{ + class VInspectorEditor + { + + public void OnGUI() + { + var drawingTabPath = ""; + var drawingFoldoutPath = ""; + var hideField = false; + var disableField = false; + var noVariablesShown = true; + var selectedTabPath = rootTab.GetSelectedTabPath(); + + + + void drawMember(MemberInfo memberInfo) + { + + void ifs() + { + var endIfAttribute = memberInfo.GetCustomAttributeCached(); + + if (endIfAttribute != null) hideField = disableField = false; + + + var ifAttribute = memberInfo.GetCustomAttributeCached(); + + if (ifAttribute is HideIfAttribute) hideField = ifAttribute.Evaluate(target); + if (ifAttribute is ShowIfAttribute) hideField = !ifAttribute.Evaluate(target); + if (ifAttribute is DisableIfAttribute) disableField = ifAttribute.Evaluate(target); + if (ifAttribute is EnableIfAttribute) disableField = !ifAttribute.Evaluate(target); + + } + void tabs() + { + void drawSubtabs(Tab tab) + { + if (!tab.subtabs.Any()) return; + + + Space(noVariablesShown ? 2 : 6); + + var selName = TabsMultiRow(tab.selectedSubtab.name, false, 24, tab.subtabs.Select(r => r.name).ToArray()); + + Space(5); + + + if (selName != tab.selectedSubtab.name) + { + tab.selectedSubtabIndex = tab.subtabs.IndexOfFirst(r => r.name == selName); + + selectedTabPath = rootTab.GetSelectedTabPath(); + + } + + + GUI.backgroundColor = Color.white; + + tab.subtabsDrawn = true; + + } + + void endTab() + { + if (memberInfo.GetCustomAttributeCached() is not EndTabAttribute endTabAttribute) return; + + drawingTabPath = ""; + drawingFoldoutPath = ""; + hideField = false; + disableField = false; + + } + void beginTab() + { + if (memberInfo.GetCustomAttributeCached() is not TabAttribute tabAttribute) return; + + drawingTabPath = tabAttribute.name; + drawingFoldoutPath = ""; + hideField = false; + disableField = false; + + } + void ensureNeededTabsDrawn() + { + if (!selectedTabPath.StartsWith(drawingTabPath)) return; + + + var curTab = rootTab; + + foreach (var name in drawingTabPath.Split('/').Where(r => r != "")) + { + if (!curTab.subtabsDrawn) + drawSubtabs(curTab); + + curTab = curTab.subtabs.Find(r => r.name == name); + + } + + } + + + endTab(); + beginTab(); + ensureNeededTabsDrawn(); + + } + void foldouts() + { + bool drawFoldout(string name, bool isExpanded, Foldout foldout) + { + var controlRect = EditorGUILayout.GetControlRect(); + var fullRect = controlRect.AddWidthFromRight(-15 * EditorGUI.indentLevel).AddWidthFromRight(18).AddWidth(3); + + var controlId = EditorGUIUtility.GetControlID(FocusType.Passive); + var isPressed = EditorGUIUtility.hotControl == controlId; + + void name_() + { + var labelRect = controlRect.AddWidthFromRight(-15 * EditorGUI.indentLevel); + + + SetLabelBold(); + SetLabelFontSize(12); + + GUI.Label(labelRect, name); + + ResetLabelStyle(); + + + } + void triangle() + { + // return; + // if (!curEvent.isRepaint) return; + + var unityFoldoutRect = controlRect.MoveX(-.5f); + + isExpanded = EditorGUI.Foldout(unityFoldoutRect, isExpanded, ""); + + } + void highlight() + { + var hoveredColor = Greyscale(1, .06f); + var pressedColor = Greyscale(1, .04f); + + + fullRect.MarkInteractive(); + + if (isPressed) + fullRect.Draw(pressedColor); + + else if (fullRect.IsHovered()) + fullRect.Draw(hoveredColor); + + } + + void mouseDown() + { + if (!curEvent.isMouseDown) return; + if (!fullRect.IsHovered()) return; + + EditorGUIUtility.hotControl = controlId; + + curEvent.Use(); + + } + void mouseUp() + { + if (!curEvent.isMouseUp) return; + if (!isPressed) return; + + if (fullRect.IsHovered()) + isExpanded = !isExpanded; + + EditorGUIUtility.hotControl = 0; + + EditorGUIUtility.keyboardControl = 0; + + curEvent.Use(); + + } + + + // background(); + name_(); + triangle(); + highlight(); + + mouseDown(); + mouseUp(); + + + return isExpanded; + + + } + void drawButtons(Foldout foldout) + { + var noButtonsToShow = true; + + Space(10); + + foreach (var button in foldout.buttons) + drawButton(button, ref noButtonsToShow); + + + if (noButtonsToShow) + Space(-10); + else + Space(5); + + } + + void beginFoldout(string name) + { + if (!rootFoldout.IsSubfoldoutContentVisible(drawingFoldoutPath)) return; + + + + updateIndentLevel(); + + drawingFoldoutPath = drawingFoldoutPath.CombinePath(name); + + + + var foldout = rootFoldout.GetSubfoldout(drawingFoldoutPath); + + foldout.isExpanded = drawFoldout(foldout.name, foldout.isExpanded, foldout); + + } + void endFoldout() + { + var foldout = rootFoldout.GetSubfoldout(drawingFoldoutPath); + + if (foldout.isExpanded) + drawButtons(foldout); + + drawingFoldoutPath = drawingFoldoutPath.HasParentPath() ? drawingFoldoutPath.GetParentPath() : ""; + + } + + + + var newFoldoutPath = drawingFoldoutPath; + + if (memberInfo.GetCustomAttributeCached() is not null) + newFoldoutPath = ""; + + if (memberInfo.GetCustomAttributeCached() is FoldoutAttribute foldoutAttribute) + newFoldoutPath = foldoutAttribute.name; + + if (newFoldoutPath == drawingFoldoutPath) return; + + + + + var drawingPathNames = drawingFoldoutPath.Split('/').Where(r => r != "").ToList(); + + var newPathNames = newFoldoutPath.Split('/').Where(r => r != "").ToList(); + + var sharedPathNames = new List(); + + for (int i = 0; i < drawingPathNames.Count && i < newPathNames.Count; i++) + if (drawingPathNames[i] == newPathNames[i]) + sharedPathNames.Add(drawingPathNames[i]); + else break; + + + + + for (int i = drawingPathNames.Count; i > sharedPathNames.Count; i--) + endFoldout(); + + for (int i = sharedPathNames.Count; i < newPathNames.Count; i++) + beginFoldout(newPathNames[i]); + + } + + void updateIndentLevel() + { + var prev = EditorGUI.indentLevel; + + EditorGUI.indentLevel = baseIndentLevel + drawingFoldoutPath.Split('/').Where(r => r != "").Count(); + + if (prev > EditorGUI.indentLevel) + Space(7); + } + + void field() + { + var fieldInfo = memberInfo as FieldInfo; + var propertyInfo = memberInfo as PropertyInfo; + + var isSerialized = serializedProperties_byMemberInfos.TryGetValue(memberInfo, out var serializedProeprty); + var isNestedEditor = isSerialized && HasVInspectorAttribtues(fieldInfo.FieldType); + var isResettable = isSerialized && VInspectorResettableVariables.IsResettable(fieldInfo); + var isReadOnly = Attribute.IsDefined(memberInfo, typeof(ReadOnlyAttribute)) || memberInfo is PropertyInfo && !propertyInfo.CanWrite; + + void serialized_default() + { + if (!isSerialized) return; + if (isNestedEditor) return; + if (isResettable) return; + if (Attribute.IsDefined(memberInfo, typeof(ButtonAttribute))) return; + + EditorGUILayout.PropertyField(serializedProeprty, true); + + } + void serialized_resettable() + { + if (!isSerialized) return; + if (isNestedEditor) return; + if (!isResettable) return; + + + var lastControlId = typeof(EditorGUIUtility).GetFieldValue("s_LastControlID"); + + + + if (!curEvent.isRepaint) + if (fieldRects_byLastControlId.ContainsKey(lastControlId)) + VInspectorResettableVariables.ResetButtonGUI(fieldRects_byLastControlId[lastControlId], serializedProeprty, fieldInfo, targets); + + + + EditorGUILayout.PropertyField(serializedProeprty, true); + + if (curEvent.isRepaint) + fieldRects_byLastControlId[lastControlId] = lastRect.AddWidthFromRight(-EditorGUIUtility.labelWidth - 2).SetHeightFromBottom(EditorGUIUtility.singleLineHeight); + + + + if (curEvent.isRepaint) + VInspectorResettableVariables.ResetButtonGUI(fieldRects_byLastControlId[lastControlId], serializedProeprty, fieldInfo, targets); + + + + } + void serialized_nestedEditor() + { + if (!isSerialized) return; + if (!isNestedEditor) return; + + EditorGUILayout.PropertyField(serializedProeprty, false); + + + if (!serializedProeprty.isExpanded) return; + + if (!nestedEditors_byPropertyPath.TryGetValue(serializedProeprty.propertyPath, out var nestedEditor)) + nestedEditor = nestedEditors_byPropertyPath[serializedProeprty.propertyPath] = new VInspectorEditor(rootPropertyGetter: () => serializedProeprty, + targetsGetter: () => targets.Select(r => fieldInfo.GetValue(r))); + nestedEditor.baseIndentLevel = EditorGUI.indentLevel + 1; + nestedEditor.OnGUI(); + + } + void nonSerialized_field() + { + if (isSerialized) return; + if (!Attribute.IsDefined(memberInfo, typeof(ShowInInspectorAttribute))) return; + if (memberInfo is not FieldInfo fieldInfo) return; + + + var type = fieldInfo.FieldType; + var name = fieldInfo.Name.FormatVariableName(); + + var curValue = fieldInfo.GetValue(fieldInfo.IsStatic ? null : target); + + var newValue = curValue; + + if (type == typeof(int)) newValue = EditorGUILayout.IntField(name, (int)curValue); + else if (type == typeof(float)) newValue = EditorGUILayout.FloatField(name, (float)curValue); + else if (type == typeof(double)) newValue = EditorGUILayout.DoubleField(name, (float)curValue); + else if (type == typeof(string)) newValue = EditorGUILayout.TextField(name, (string)curValue); + else if (type == typeof(bool)) newValue = EditorGUILayout.Toggle(name, (bool)curValue); + else if (type == typeof(Vector2)) newValue = EditorGUILayout.Vector2Field(name, (Vector2)curValue); + else if (type == typeof(Vector3)) newValue = EditorGUILayout.Vector3Field(name, (Vector3)curValue); + else if (type == typeof(Vector4)) newValue = EditorGUILayout.Vector4Field(name, (Vector4)curValue); + else if (type == typeof(Color)) newValue = EditorGUILayout.ColorField(name, (Color)curValue); + else if (type == typeof(Rect)) newValue = EditorGUILayout.RectField(name, (Rect)curValue); + else if (type == typeof(RectInt)) newValue = EditorGUILayout.RectIntField(name, (RectInt)curValue); + else if (type == typeof(Bounds)) newValue = EditorGUILayout.BoundsField(name, (Bounds)curValue); + else if (type == typeof(BoundsInt)) newValue = EditorGUILayout.BoundsIntField(name, (BoundsInt)curValue); + else if (type == typeof(Vector2Int)) newValue = EditorGUILayout.Vector2IntField(name, (Vector2Int)curValue); + else if (type == typeof(Vector3Int)) newValue = EditorGUILayout.Vector3IntField(name, (Vector3Int)curValue); + else if (type.IsEnum) newValue = EditorGUILayout.EnumPopup(name, (System.Enum)curValue); + else if (typeof(Object).IsAssignableFrom(type)) newValue = EditorGUILayout.ObjectField(name, (Object)curValue, type, true); + else EditorGUILayout.TextField(name, curValue?.ToString()); + + + fieldInfo.SetValue(fieldInfo.IsStatic ? null : target, newValue); + + noVariablesShown = false; + + } + void nonSerialized_property() + { + if (isSerialized) return; + if (!Attribute.IsDefined(memberInfo, typeof(ShowInInspectorAttribute))) return; + if (memberInfo is not PropertyInfo propertyInfo) return; + + + var type = propertyInfo.PropertyType; + var name = propertyInfo.Name.FormatVariableName(); + + var curValue = propertyInfo.GetValue(propertyInfo.GetAccessors(true).First().IsStatic ? null : target); + + var newValue = curValue; + + if (type == typeof(int)) newValue = EditorGUILayout.IntField(name, (int)curValue); + else if (type == typeof(float)) newValue = EditorGUILayout.FloatField(name, (float)curValue); + else if (type == typeof(double)) newValue = EditorGUILayout.DoubleField(name, (float)curValue); + else if (type == typeof(string)) newValue = EditorGUILayout.TextField(name, (string)curValue); + else if (type == typeof(bool)) newValue = EditorGUILayout.Toggle(name, (bool)curValue); + else if (type == typeof(Vector2)) newValue = EditorGUILayout.Vector2Field(name, (Vector2)curValue); + else if (type == typeof(Vector3)) newValue = EditorGUILayout.Vector3Field(name, (Vector3)curValue); + else if (type == typeof(Vector4)) newValue = EditorGUILayout.Vector4Field(name, (Vector4)curValue); + else if (type == typeof(Color)) newValue = EditorGUILayout.ColorField(name, (Color)curValue); + else if (type == typeof(Rect)) newValue = EditorGUILayout.RectField(name, (Rect)curValue); + else if (type == typeof(RectInt)) newValue = EditorGUILayout.RectIntField(name, (RectInt)curValue); + else if (type == typeof(Bounds)) newValue = EditorGUILayout.BoundsField(name, (Bounds)curValue); + else if (type == typeof(BoundsInt)) newValue = EditorGUILayout.BoundsIntField(name, (BoundsInt)curValue); + else if (type == typeof(Vector2Int)) newValue = EditorGUILayout.Vector2IntField(name, (Vector2Int)curValue); + else if (type == typeof(Vector3Int)) newValue = EditorGUILayout.Vector3IntField(name, (Vector3Int)curValue); + else if (type.IsEnum) newValue = EditorGUILayout.EnumPopup(name, (System.Enum)curValue); + else if (typeof(Object).IsAssignableFrom(type)) newValue = EditorGUILayout.ObjectField(name, (Object)curValue, type, true); + else EditorGUILayout.TextField(name, curValue?.ToString()); + + + if (propertyInfo.CanWrite && !Attribute.IsDefined(propertyInfo, typeof(ReadOnlyAttribute))) + if (!object.Equals(newValue, curValue)) + propertyInfo.SetValue(propertyInfo.GetAccessors(true).First().IsStatic ? null : target, newValue); + + noVariablesShown = false; + + } + + void invokeValueChangedCallbacks() + { + if (memberInfo is not FieldInfo fieldInfo) return; + + if (!valueChangedCallbacks_byFieldInfo.TryGetValue(fieldInfo, out var methodInfos)) + if (!valueChangedCallbacks_byGroupPath.TryGetValue(drawingTabPath, out methodInfos)) + if (!valueChangedCallbacks_byGroupPath.TryGetValue(drawingFoldoutPath, out methodInfos)) return; + + + void invokeCallbacks() => methodInfos.ForEach(r => targets.ForEach(rr => r.Invoke(rr, null))); + + // invokeCallbacks(); + AbstractEditor.toCallAfterModifyingSO += invokeCallbacks; + + valueChangedCallbacks_byUndoPosition[EditorUtils.GetCurrendUndoGroupIndex()] = invokeCallbacks; + + } + + + SetGUIEnabled(!isReadOnly && !disableField); + + EditorGUI.BeginChangeCheck(); + + serialized_default(); + serialized_resettable(); + serialized_nestedEditor(); + nonSerialized_field(); + nonSerialized_property(); + + if (EditorGUI.EndChangeCheck()) + invokeValueChangedCallbacks(); + + ResetGUIEnabled(); + + } + + + + + + ifs(); + + if (hideField) return; + + GUI.enabled = !disableField; + + + + + tabs(); + + if (selectedTabPath != drawingTabPath && !selectedTabPath.StartsWith(drawingTabPath + "/") && drawingTabPath != "") return; + + noVariablesShown = false; + + + + + foldouts(); + + if (!rootFoldout.IsSubfoldoutContentVisible(drawingFoldoutPath)) return; + + + + + + updateIndentLevel(); + + field(); + + } + + void drawButton(Button button, ref bool noButtonsShown) + { + if (button.tabAttribute != null && !selectedTabPath.StartsWith(button.tabAttribute.name)) return; + + if (button.ifAttribute is HideIfAttribute && button.ifAttribute.Evaluate(target)) return; + if (button.ifAttribute is ShowIfAttribute && !button.ifAttribute.Evaluate(target)) return; + + + + var prevGuiEnabled = GUI.enabled; + if (button.ifAttribute is DisableIfAttribute && button.ifAttribute.Evaluate(target)) GUI.enabled = false; + if (button.ifAttribute is EnableIfAttribute && !button.ifAttribute.Evaluate(target)) GUI.enabled = false; + + + + + Rect buttonRect; + Color color = Color.white; + + void set_buttonRect() + { + GUILayout.BeginHorizontal(); + + GUILayout.Space(EditorGUI.indentLevel * 15); + + buttonRect = ExpandWidthLabelRect(height: button.size); + + GUILayout.EndHorizontal(); + + } + void set_color() + { + if (button.color.ToLower() == "grey" || button.color.ToLower() == "gray") return; + + + var hue = 0f; + var saturation = .6f; + var lightness = isDarkTheme ? .57f : .64f; + + if (button.color.ToLower() == "red") hue = 0; + else if (button.color.ToLower() == "orange") hue = .08f; + else if (button.color.ToLower() == "yellow") hue = .13f; + else if (button.color.ToLower() == "green") { hue = .32f; saturation = .49f; lightness = isDarkTheme ? .56f : .6f; } + else if (button.color.ToLower() == "blue") hue = .55f; + else if (button.color.ToLower() == "pink") hue = .94f; + else return; + + + + color = ColorUtils.HSLToRGB(hue, saturation, lightness); + + color *= 2f; + color.a = 1; + + } + + void argumentsBackground() + { + if (!button.parameterInfos.Any()) return; + if (!button.isExpanded) return; + if (!curEvent.isRepaint) return; + + + var backgroundColor = Greyscale(isDarkTheme ? .27f : .83f); + var outlineColor = Greyscale(isDarkTheme ? .15f : .65f); + var cornerRadius = 3; + + var backgroundRect = buttonRect.AddHeight(button.parameterInfos.Count * (EditorGUIUtility.singleLineHeight + 2) + 8); + + backgroundRect.DrawRounded(outlineColor, cornerRadius); + backgroundRect.Resize(1).DrawRounded(backgroundColor, cornerRadius - 1); + + } + void buttonItself() + { + var prevBackgroundColor = GUI.backgroundColor; + GUI.backgroundColor = button.isPressed() ? GUIColors.pressedButtonBackground : color; + + var clicked = GUI.Button(buttonRect, button.name); + + GUI.backgroundColor = prevBackgroundColor; + + + + if (!clicked) return; + + foreach (var targetObject in rootProperty.serializedObject.targetObjects) + targetObject.RecordUndo(); + + foreach (var target in targets) + button.action(target); + + foreach (var targetObject in rootProperty.serializedObject.targetObjects) + targetObject.Dirty(); + + } + void expandButton() + { + if (!button.parameterInfos.Any()) return; + + var expandButtonRect = buttonRect.SetWidth(24).MoveX(1); + + var colorNormal = Greyscale(isDarkTheme ? (buttonRect.IsHovered() ? .85f : .8f) : .7f); + var colorHovered = Greyscale(isDarkTheme ? 10f : 0f, 10f); + var colorPressed = Greyscale(.85f); + + var iconSize = 12; + + + if (!IconButton(expandButtonRect, button.isExpanded ? "d_IN_foldout_act_on" : "d_IN_foldout_act", iconSize, colorNormal, colorHovered, colorPressed)) return; + + button.isExpanded = !button.isExpanded; + + GUIUtility.keyboardControl = 0; + + } + void parameters() + { + if (!button.isExpanded) return; + if (!button.parameterInfos.Any()) return; + + void parameter(int i) + { + var type = button.parameterInfos[i].ParameterType; + var name = button.parameterInfos[i].Name.FormatVariableName(); + + var curValue = button.GetParameterValue(i); + + var newValue = curValue; + + if (type == typeof(int)) newValue = EditorGUILayout.IntField(name, (int)curValue); + else if (type == typeof(float)) newValue = EditorGUILayout.FloatField(name, (float)curValue); + else if (type == typeof(double)) newValue = EditorGUILayout.DoubleField(name, (float)curValue); + else if (type == typeof(string)) newValue = EditorGUILayout.TextField(name, (string)curValue); + else if (type == typeof(bool)) newValue = EditorGUILayout.Toggle(name, (bool)curValue); + else if (type == typeof(Vector2)) newValue = EditorGUILayout.Vector2Field(name, (Vector2)curValue); + else if (type == typeof(Vector3)) newValue = EditorGUILayout.Vector3Field(name, (Vector3)curValue); + else if (type == typeof(Vector4)) newValue = EditorGUILayout.Vector4Field(name, (Vector4)curValue); + else if (type == typeof(Color)) newValue = EditorGUILayout.ColorField(name, (Color)curValue); + else if (type == typeof(Rect)) newValue = EditorGUILayout.RectField(name, (Rect)curValue); + else if (type == typeof(RectInt)) newValue = EditorGUILayout.RectIntField(name, (RectInt)curValue); + else if (type == typeof(Bounds)) newValue = EditorGUILayout.BoundsField(name, (Bounds)curValue); + else if (type == typeof(BoundsInt)) newValue = EditorGUILayout.BoundsIntField(name, (BoundsInt)curValue); + else if (type == typeof(Vector2Int)) newValue = EditorGUILayout.Vector2IntField(name, (Vector2Int)curValue); + else if (type == typeof(Vector3Int)) newValue = EditorGUILayout.Vector3IntField(name, (Vector3Int)curValue); + else if (type.IsEnum) newValue = EditorGUILayout.EnumPopup(name, (System.Enum)curValue); + else if (typeof(Object).IsAssignableFrom(type)) newValue = EditorGUILayout.ObjectField(name, (Object)curValue, type, true); + else EditorGUILayout.PrefixLabel(name); + + + button.SetParameterValue(i, newValue); + + } + + + + BeginIndent(7); + Space(1); + + for (int i = 0; i < button.parameterInfos.Count; i++) + parameter(i); + + Space(11); + EndIndent(5); + + + } + + + GUILayout.Space(button.space - 2); + + set_buttonRect(); + set_color(); + argumentsBackground(); + + if (!curEvent.isRepaint) + expandButton(); + + buttonItself(); + + if (curEvent.isRepaint) + expandButton(); + + parameters(); + + if (button.isExpanded) + Space(6); + + + + + GUI.enabled = prevGuiEnabled; + + noButtonsShown = false; + + } + + + void scriptField() + { + if (scriptFieldProperty == null) return; + if (VInspectorMenu.hideScriptFieldEnabled) return; + + using (new EditorGUI.DisabledScope(true)) + EditorGUILayout.PropertyField(scriptFieldProperty); + + } + void topMargin() + { + if (scriptFieldProperty == null) return; + if (!VInspectorMenu.hideScriptFieldEnabled) return; + + Space(2); + + } + + void members() + { + EditorGUI.indentLevel = baseIndentLevel; + + foreach (var memberInfo in drawableMemberLists_byTargetType[targetType]) + drawMember(memberInfo); + + EditorGUI.indentLevel = baseIndentLevel; + + + } + void noVariablesToShow() + { + if (!noVariablesShown) return; + + using (new EditorGUI.DisabledScope(true)) + GUILayout.Label("No variables to show"); + + + } + void endUnendedFoldouts() + { + if (drawingFoldoutPath == "") return; + + void drawButtons(Foldout foldout) + { + var noButtonsToShow = true; + + Space(10); + + foreach (var button in foldout.buttons) + drawButton(button, ref noButtonsToShow); + + + if (noButtonsToShow) + Space(-10); + else + Space(5); + + } + + while (drawingFoldoutPath != "") + { + var foldout = rootFoldout.GetSubfoldout(drawingFoldoutPath); + + EditorGUI.indentLevel = baseIndentLevel + drawingFoldoutPath.Split('/').Where(r => r != "").Count(); + + if (rootFoldout.IsSubfoldoutContentVisible(drawingFoldoutPath)) + drawButtons(foldout); + + drawingFoldoutPath = drawingFoldoutPath.HasParentPath() ? drawingFoldoutPath.GetParentPath() : ""; + + } + + EditorGUI.indentLevel = 0; + + } + + void buttons() + { + GUI.enabled = true; + + var noButtonsToShow = true; + + foreach (var button in this.buttons) + drawButton(button, ref noButtonsToShow); + + + if (noButtonsToShow) + Space(-17); + + } + + + + + rootTab.ResetSubtabsDrawn(); + + scriptField(); + topMargin(); + + members(); + noVariablesToShow(); + endUnendedFoldouts(); + + Space(16); + buttons(); + + Space(4); + + } + + public int baseIndentLevel; + + public SerializedProperty rootProperty => rootPropertyGetter.Invoke(); + public IEnumerable targets => targetsGetter.Invoke(); + public object target => targets.FirstOrDefault(); + + public Type targetType => _targetType ??= target?.GetType(); + public Type _targetType; + + static Dictionary fieldRects_byLastControlId = new(); + + + + + + + + public VInspectorEditor(System.Func rootPropertyGetter, System.Func> targetsGetter) + { + this.rootPropertyGetter = rootPropertyGetter; + this.targetsGetter = targetsGetter; + + + void createTabs() + { + void setupTab(Tab tab, IEnumerable allSubtabPaths) + { + void refreshSubtabs() + { + var names = allSubtabPaths.Select(r => r.Split('/').First()).ToList(); + + foreach (var name in names) + if (!tab.subtabs.Any(r => r.name == name)) + tab.subtabs.Add(new Tab() { name = name }); + + foreach (var subtab in tab.subtabs.ToList()) + if (!names.Any(r => r == subtab.name)) + tab.subtabs.Remove(subtab); + + tab.subtabs.SortBy(r => names.IndexOf(r.name)); + + } + void setupSubtabs() + { + foreach (var subtab in tab.subtabs) + setupTab(subtab, allSubtabPaths.Where(r => r.StartsWith(subtab.name + "/")).Select(r => r.Remove(subtab.name + "/")).ToList()); + } + + refreshSubtabs(); + setupSubtabs(); + + } + + void findAttributes() + { + if (tabAttributes_byTargetType.ContainsKey(targetType)) return; + + var attributes = TypeCache.GetFieldsWithAttribute() + .Where(r => r.DeclaringType.IsAssignableFrom(targetType)) + .OrderBy(r => r.MetadataToken) + .Select(r => r.GetCustomAttributeCached()); + + tabAttributes_byTargetType[targetType] = attributes.ToList(); + + } + void createTabs() + { + rootTab = new Tab() { isRootTab = true }; + + var allTabPaths = tabAttributes_byTargetType[targetType].Select(r => r.name); + + setupTab(rootTab, allTabPaths); + + } + + + findAttributes(); + createTabs(); + + } + void createFoldouts() + { + void setupFoldout(Foldout foldout, IEnumerable allSubfoldoutPaths) + { + void refreshSubfoldouts() + { + var names = allSubfoldoutPaths.Select(r => r.Split('/').First()).ToList(); + + foreach (var name in names) + if (foldout.subfoldouts.Find(r => r.name == name) == null) + foldout.subfoldouts.Add(new Foldout() { name = name }); + + foreach (var subtab in foldout.subfoldouts.ToList()) + if (names.Find(r => r == subtab.name) == null) + foldout.subfoldouts.Remove(subtab); + + foldout.subfoldouts.SortBy(r => names.IndexOf(r.name)); + + } + void setupSubfoldouts() + { + foreach (var subtab in foldout.subfoldouts) + setupFoldout(subtab, allSubfoldoutPaths.Where(r => r.StartsWith(subtab.name + "/")).Select(r => r.Remove(subtab.name + "/")).ToList()); + } + + refreshSubfoldouts(); + setupSubfoldouts(); + + } + + void findAttributes() + { + if (foldoutAttributes_byTargetType.ContainsKey(targetType)) return; + + var attributes = TypeCache.GetFieldsWithAttribute() + .Where(r => r.DeclaringType.IsAssignableFrom(targetType)) + .OrderBy(r => r.MetadataToken) + .Select(r => r.GetCustomAttributeCached()); + + foldoutAttributes_byTargetType[targetType] = attributes.ToList(); + + } + void createFoldouts() + { + rootFoldout = new Foldout() { isRootFoldout = true }; + + var allFoldoutPaths = foldoutAttributes_byTargetType[targetType].Select(r => r.name); + + setupFoldout(rootFoldout, allFoldoutPaths); + + } + + + findAttributes(); + createFoldouts(); + + } + void createButtons() + { + void createButton(MemberInfo member, ButtonAttribute buttonAttribute) + { + var button = new Button(); + + button.size = buttonAttribute.size; + button.space = buttonAttribute.space; + button.color = buttonAttribute.color; + + + if (member.GetCustomAttributeCached() is TabAttribute tabAttribute) + button.tabAttribute = tabAttribute; + + if (member.GetCustomAttributeCached() is FoldoutAttribute foldoutAttribute) + button.foldoutAttribute = foldoutAttribute; + + if (member.GetCustomAttributeCached() is IfAttribute ifAttribute) + button.ifAttribute = ifAttribute; + + + if (member is FieldInfo field && field.FieldType == typeof(bool)) + { + var fieldTarget = field.IsStatic ? null : target; + + button.action = (o) => field.SetValue(o, !(bool)field.GetValue(o)); + button.name = buttonAttribute.name != "" ? buttonAttribute.name : field.Name.FormatVariableName(false); + button.isPressed = () => (bool)field.GetValue(fieldTarget); + + } + + if (member is MethodInfo method) + { + if (method.DeclaringType.IsGenericType) + method = targetType.GetMethodInfo(method.Name, method.GetParameters().Select(r => r.ParameterType).ToArray()) ?? method; // fixes wrong method when targetType inherits from method.DeclaringType and method.DeclaringType is generic + + if (!method.GetParameters().Any()) + { + var methodTarget = method.IsStatic ? null : target; + + button.action = (methodTarget) => method.Invoke(methodTarget, null); + button.name = buttonAttribute.name != "" ? buttonAttribute.name : method.Name.FormatVariableName(false); + button.isPressed = () => false; + + } + else + { + var methodTarget = method.IsStatic ? null : target; + + button.action = (methodTarget) => method.Invoke(methodTarget, Enumerable.Range(0, button.parameterInfos.Count).Select(i => button.GetParameterValue(i)).ToArray()); + button.name = buttonAttribute.name != "" ? buttonAttribute.name : method.Name.FormatVariableName(false); + button.isPressed = () => false; + + button.parameterInfos = method.GetParameters().ToList(); + + } + + } + + + + if (button.action != null) + if (button.foldoutAttribute != null && rootFoldout.GetSubfoldout(button.foldoutAttribute.name) is Foldout foldout) + foldout.buttons.Add(button); + else + this.buttons.Add(button); + + } + + void findFields() + { + if (fieldsWithButtonAttributes_byTargetType.ContainsKey(targetType)) return; + + var fields = TypeCache.GetFieldsWithAttribute() + .Where(r => r.DeclaringType.IsAssignableFrom(targetType)) + .OrderBy(r => r.MetadataToken); + + fieldsWithButtonAttributes_byTargetType[targetType] = fields.ToList(); + + } + void findMethods() + { + if (methodsWithButtonAttributes_byTargetType.ContainsKey(targetType)) return; + + var methods = TypeCache.GetMethodsWithAttribute() + .Where(r => r.DeclaringType.IsAssignableFrom(targetType) || (targetType.BaseType.IsGenericType && targetType.BaseType.GetGenericTypeDefinition() == r.DeclaringType)) + .OrderBy(r => r.MetadataToken); + + methodsWithButtonAttributes_byTargetType[targetType] = methods.ToList(); + + } + void createButtons() + { + this.buttons = new List