initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
|
||||
[assembly: InternalsVisibleTo("Unity.Searcher.EditorSamples")]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4c384a0eeb10a6249a9ba87a3e550b18
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: efa7753b2a14da748b0e7802b6ac3c5f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 67e35e8d446428040b9f0580470b5b5a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d0feff57b6f6c594f86fff295ec0fe3f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,34 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 1062c701058745940ae5dca6219c64c8
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,45 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f95147a9bab9fe649b8070170fa7ebd2
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: 5e63515167650a541b309ba3bfd4db3d, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: dfb97a9307a577a41bcacba34b9a7e32, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: d93c1bc57fdf1ac4dad1bf04fac05464, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 3750c318f40cab44a97d3a81beaefeb3, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: a51a3aebb43ac044989270407476b208, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: b2ea3b3f1765e544b9eb5bd1a93a91ce, type: 3}
|
||||
- {fileID: 12800000, guid: c839255fc8f6b4b4d90bf8c1af9f9bd1, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,38 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3750c318f40cab44a97d3a81beaefeb3
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,44 @@
|
|||
fileFormatVersion: 2
|
||||
guid: dfb97a9307a577a41bcacba34b9a7e32
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: 5e63515167650a541b309ba3bfd4db3d, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: d93c1bc57fdf1ac4dad1bf04fac05464, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 3750c318f40cab44a97d3a81beaefeb3, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: a51a3aebb43ac044989270407476b208, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: b2ea3b3f1765e544b9eb5bd1a93a91ce, type: 3}
|
||||
- {fileID: 12800000, guid: c839255fc8f6b4b4d90bf8c1af9f9bd1, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,42 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c839255fc8f6b4b4d90bf8c1af9f9bd1
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: 5e63515167650a541b309ba3bfd4db3d, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 3750c318f40cab44a97d3a81beaefeb3, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: a51a3aebb43ac044989270407476b208, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: b2ea3b3f1765e544b9eb5bd1a93a91ce, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,36 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2a29e27936eb547458f646f71125b264
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,39 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5e63515167650a541b309ba3bfd4db3d
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 3750c318f40cab44a97d3a81beaefeb3, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,40 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a51a3aebb43ac044989270407476b208
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: 5e63515167650a541b309ba3bfd4db3d, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 3750c318f40cab44a97d3a81beaefeb3, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,43 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d93c1bc57fdf1ac4dad1bf04fac05464
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: 5e63515167650a541b309ba3bfd4db3d, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 3750c318f40cab44a97d3a81beaefeb3, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: a51a3aebb43ac044989270407476b208, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: b2ea3b3f1765e544b9eb5bd1a93a91ce, type: 3}
|
||||
- {fileID: 12800000, guid: c839255fc8f6b4b4d90bf8c1af9f9bd1, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,37 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 30dc370b164eefc43b95744fc796dad0
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,35 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 19a9abb883540be48b75b951a927ae03
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
|
@ -0,0 +1,41 @@
|
|||
fileFormatVersion: 2
|
||||
guid: b2ea3b3f1765e544b9eb5bd1a93a91ce
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontName: Roboto
|
||||
fontNames:
|
||||
- Roboto
|
||||
fallbackFontReferences:
|
||||
- {fileID: 12800000, guid: 1062c701058745940ae5dca6219c64c8, type: 3}
|
||||
- {fileID: 12800000, guid: 5e63515167650a541b309ba3bfd4db3d, type: 3}
|
||||
- {fileID: 12800000, guid: f232b881d5789784a8214ba5f5953786, type: 3}
|
||||
- {fileID: 12800000, guid: 4e85a9d1f1055d4418b0257981f8919e, type: 3}
|
||||
- {fileID: 12800000, guid: 0b1dd5d23c84c794a9953fcf4fd1cb43, type: 3}
|
||||
- {fileID: 12800000, guid: f3c568239fc11db4b9ca70b1c2055441, type: 3}
|
||||
- {fileID: 12800000, guid: 50f3304568e09954aa2392669f102579, type: 3}
|
||||
- {fileID: 12800000, guid: e52b49e5869bf8d44b23e1c9a8329aed, type: 3}
|
||||
- {fileID: 12800000, guid: ce278506187978b45a84b86921d6ee79, type: 3}
|
||||
- {fileID: 12800000, guid: bd36e81784e3e9b4d9998e40a42cbd05, type: 3}
|
||||
- {fileID: 12800000, guid: 3750c318f40cab44a97d3a81beaefeb3, type: 3}
|
||||
- {fileID: 12800000, guid: 19a9abb883540be48b75b951a927ae03, type: 3}
|
||||
- {fileID: 12800000, guid: 76edce3923cb946408de45fc063ac0b8, type: 3}
|
||||
- {fileID: 12800000, guid: 2a29e27936eb547458f646f71125b264, type: 3}
|
||||
- {fileID: 12800000, guid: be7a350bea3c57a4ebe4b02e5730c58c, type: 3}
|
||||
- {fileID: 12800000, guid: 30dc370b164eefc43b95744fc796dad0, type: 3}
|
||||
- {fileID: 12800000, guid: a51a3aebb43ac044989270407476b208, type: 3}
|
||||
- {fileID: 12800000, guid: b0f050aebb649494baac766cfac78682, type: 3}
|
||||
- {fileID: 12800000, guid: 307130cf4ae2c564f9cb8a7716c4bf3e, type: 3}
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -0,0 +1,112 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e0f8b9d001b669a43b3c9f7b90891d96
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: -1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
|
|
@ -0,0 +1,112 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0c2aa48d028b3134dbbb5ceb57bff5ac
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: -1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -0,0 +1,112 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f353e9d22eb9164499dd70cb41b00244
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: -1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -0,0 +1,112 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c5b36095078d8694ba9e3ae3fd867b2e
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 8
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 0
|
||||
aniso: -1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 0
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 0
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 0
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 2
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
- serializedVersion: 2
|
||||
buildTarget: iPhone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 0
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID:
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
#windowMainVisualContainer {
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
background-color: #272727;
|
||||
}
|
||||
|
||||
#windowMainVisualContainer * {
|
||||
-unity-font: resource("FlatSkin/Font/Roboto-Regular");
|
||||
}
|
||||
|
||||
#windowResizer {
|
||||
position: absolute;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
padding-left: 10px;
|
||||
padding-top: 14px;
|
||||
cursor: resize-up-left;
|
||||
}
|
||||
|
||||
#splitter
|
||||
{
|
||||
flex-direction:row;
|
||||
position: absolute;
|
||||
right:0;
|
||||
top:0;
|
||||
left:0;
|
||||
bottom:0;
|
||||
}
|
||||
|
||||
#windowResizerIcon {
|
||||
flex: 1;
|
||||
background-image:resource("WindowBottomResize");
|
||||
cursor: resize-up-left;
|
||||
}
|
||||
|
||||
#searcherVisualContainer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#windowTitleLabel {
|
||||
font-size: 14px;
|
||||
color: #FFFFFF;
|
||||
margin-top: 5px;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
#windowSearchBoxVisualContainer {
|
||||
height: 21px;
|
||||
background-color: #454545;
|
||||
margin-right: 3px;
|
||||
margin-left: 5px;
|
||||
margin-top: 0;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 6px;
|
||||
border-color: #323232;
|
||||
border-width: 1px;
|
||||
}
|
||||
|
||||
#searchIcon {
|
||||
width: 32px;
|
||||
height: 16px;
|
||||
background-image:resource("FlatSkin/SearchSmallDownOff");
|
||||
top: 2px;
|
||||
left: 4px;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
#searchIcon.Active {
|
||||
background-image:resource("FlatSkin/SearchSmallDownOn");
|
||||
}
|
||||
|
||||
#autoCompleteLabel {
|
||||
position: absolute;
|
||||
background-image: none;
|
||||
top: 2px;
|
||||
left: 36px;
|
||||
font-size: 12px;
|
||||
color: #828282;
|
||||
-unity-text-align: middle-left;
|
||||
}
|
||||
|
||||
#searchBox {
|
||||
background-image: none;
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin-bottom: 2px;
|
||||
margin-top: 2px;
|
||||
font-size: 12px;
|
||||
color: #FFFFFF;
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#searchBox .unity-base-field__input {
|
||||
top: 2px;
|
||||
font-size: 12px;
|
||||
-unity-text-align: middle-left;
|
||||
color: #FFFFFF;
|
||||
background-image: none;
|
||||
border-color: rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#windowSelectionVisualContainer {
|
||||
flex: 1;
|
||||
background-color: #383838;
|
||||
}
|
||||
|
||||
.unity-list-view.focusableScrollView {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.unity-list-view {
|
||||
background-color: #383838;
|
||||
--unity-item-height: 18;
|
||||
}
|
||||
|
||||
.unity-list-view > * #labelsContainer {
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.unity-list-view > * #labelsContainer > .unity-label {
|
||||
margin-left: 0px;
|
||||
margin-right: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
color: #C4C4C4;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.unity-list-view > * #itemIconVisualElement {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#itemToggle {
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.searcher__multiselect #itemToggle {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.searcherMultiSelectConfirmButton {
|
||||
display: none;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.searcher__multiselect .searcherMultiSelectConfirmButton {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.unity-list-view > * > .unity-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
#smartSearchItem > #itemMainVisualContainer > #labelsContainer > .unity-label.Highlighted {
|
||||
color: #FFCD62;
|
||||
}
|
||||
|
||||
.unity-list-view > * > #smartSearchItem:hover * {
|
||||
background-color: #424242;
|
||||
}
|
||||
|
||||
.unity-list-view > * > #smartSearchItem.unity-list-view__item--selected * {
|
||||
background-color: #2B5D87;
|
||||
}
|
||||
|
||||
#smartSearchItem.unity-list-view__item--selected > #itemMainVisualContainer > #labelsContainer > .unity-label {
|
||||
color : #FFFFFF;
|
||||
}
|
||||
|
||||
#smartSearchItem.unity-list-view__item--selected > #itemMainVisualContainer > #labelsContainer > .unity-label.Highlighted {
|
||||
color: #FFCD00;
|
||||
}
|
||||
|
||||
.unity-list-view > * #itemChildExpander {
|
||||
height: 18px;
|
||||
padding-top: 3px;
|
||||
margin-left: 10px;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.unity-list-view > * #itemChildExpander > #expanderIcon {
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
}
|
||||
|
||||
#smartSearchItem > * > #itemChildExpander > #expanderIcon.Collapsed {
|
||||
background-image: resource("Builtin Skins/DarkSkin/Images/IN foldout.png");
|
||||
}
|
||||
|
||||
#smartSearchItem > * > #itemChildExpander > #expanderIcon.Expanded {
|
||||
background-image: resource("Builtin Skins/DarkSkin/Images/IN foldout on.png");
|
||||
}
|
||||
|
||||
#smartSearchItem.unity-list-view__item--selected > * > #itemChildExpander > #expanderIcon.Collapsed {
|
||||
background-image: resource("Builtin Skins/DarkSkin/Images/IN foldout act.png");
|
||||
}
|
||||
|
||||
#smartSearchItem.unity-list-view__item--selected > * > #itemChildExpander > #expanderIcon.Expanded {
|
||||
background-image: resource("Builtin Skins/DarkSkin/Images/IN foldout act on.png");
|
||||
}
|
||||
|
||||
.unity-list-view > * #itemMainVisualContainer {
|
||||
flex-direction: row;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.unity-list-view > *.unity-list-view__item--selected #textLabel {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.unity-list-view > * #itemMainVisualContainer {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.unity-list-view > *.Category #textLabel {
|
||||
font-size: 12px;
|
||||
color: #676767;
|
||||
}
|
||||
|
||||
#windowDetailsVisualContainer {
|
||||
flex: 1;
|
||||
background-color: #383838;
|
||||
border-left-width: 2px;
|
||||
border-color: #272727;
|
||||
padding-left: 6px;
|
||||
padding-right: 4px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
#windowDetailsVisualContainer.hidden {
|
||||
display:none;
|
||||
}
|
||||
|
||||
#windowDetailsVisualContainer > .unity-label {
|
||||
font-size: 11px;
|
||||
color: #C4C4C4;
|
||||
white-space: normal;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4deaa574907fd7647ab0d5bff0e9ba24
|
||||
ScriptedImporter:
|
||||
fileIDToRecycleName:
|
||||
11400000: stylesheet
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<UXML xmlns:ui="UnityEngine.UIElements">
|
||||
<ui:VisualElement name="itemMainVisualContainer">
|
||||
<Style path="Searcher"/>
|
||||
<ui:VisualElement name="itemIndent"/>
|
||||
<ui:VisualElement name="itemChildExpander">
|
||||
<ui:VisualElement name="expanderIcon"/>
|
||||
</ui:VisualElement>
|
||||
<ui:Toggle name="itemToggle"/>
|
||||
<ui:VisualElement name="itemIconVisualElement"/>
|
||||
<ui:VisualElement name="labelsContainer"/>
|
||||
</ui:VisualElement>
|
||||
</UXML>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 793d6318ba59ca94381b936836fe697d
|
||||
ScriptedImporter:
|
||||
fileIDToRecycleName:
|
||||
11400000: tree
|
||||
11400002: inlineStyle
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<UXML xmlns:ui="UnityEngine.UIElements" xmlns:ed="UnityEditor.Experimental.UIElements" xmlns:lv="UnityEditor.EditorCommon.UIElements.ListView">
|
||||
<ui:VisualElement name="windowMainVisualContainer">
|
||||
<Style path="Searcher"/>
|
||||
<ed:VisualSplitter name="splitter">
|
||||
<ui:VisualElement name="searcherVisualContainer">
|
||||
<ui:VisualElement name="windowTitleContainer">
|
||||
<ui:Label name="windowTitleLabel" focusable="false"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="windowSearchBoxVisualContainer">
|
||||
<ui:VisualElement name="searchIcon"/>
|
||||
<ui:Label name="autoCompleteLabel"/>
|
||||
<ui:TextField name="searchBox"/>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="windowSelectionVisualContainer">
|
||||
<ui:ListView class="focusableScrollView" item-height="25" name="windowResultsScrollView"/>
|
||||
<ui:Button text="Confirm" class="searcherMultiSelectConfirmButton" name="confirmButton"/>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
<ui:VisualElement name="windowDetailsVisualContainer"/>
|
||||
</ed:VisualSplitter>
|
||||
<ui:VisualElement name="windowResizer" focusable="false">
|
||||
<ui:VisualElement name="windowResizerIcon" focusable="false"/>
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</UXML>
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: da37dcca55e437b4a9eeebff722ad51f
|
||||
ScriptedImporter:
|
||||
fileIDToRecycleName:
|
||||
11400000: tree
|
||||
11400002: inlineStyle
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4b1180a47f4e54c43af13ef6a4a06621
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
[PublicAPI]
|
||||
public class Searcher
|
||||
{
|
||||
public ISearcherAdapter Adapter { get; }
|
||||
public Comparison<SearcherItem> SortComparison { get; set; }
|
||||
|
||||
readonly List<SearcherDatabaseBase> m_Databases;
|
||||
|
||||
public Searcher(SearcherDatabaseBase database, string title)
|
||||
: this(new List<SearcherDatabaseBase> { database }, title, null)
|
||||
{ }
|
||||
|
||||
public Searcher(IEnumerable<SearcherDatabaseBase> databases, string title)
|
||||
: this(databases, title, null)
|
||||
{ }
|
||||
|
||||
public Searcher(SearcherDatabaseBase database, ISearcherAdapter adapter = null)
|
||||
: this(new List<SearcherDatabaseBase> { database }, adapter)
|
||||
{ }
|
||||
|
||||
public Searcher(IEnumerable<SearcherDatabaseBase> databases, ISearcherAdapter adapter = null)
|
||||
: this(databases, string.Empty, adapter)
|
||||
{ }
|
||||
|
||||
Searcher(IEnumerable<SearcherDatabaseBase> databases, string title, ISearcherAdapter adapter)
|
||||
{
|
||||
m_Databases = new List<SearcherDatabaseBase>();
|
||||
var databaseId = 0;
|
||||
foreach (var database in databases)
|
||||
{
|
||||
// This is needed for sorting items between databases.
|
||||
database.OverwriteId(databaseId);
|
||||
databaseId++;
|
||||
|
||||
m_Databases.Add(database);
|
||||
}
|
||||
|
||||
Adapter = adapter ?? new SearcherAdapter(title);
|
||||
}
|
||||
|
||||
public void BuildIndices()
|
||||
{
|
||||
foreach (var database in m_Databases)
|
||||
{
|
||||
database.BuildIndex();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<SearcherItem> Search(string query)
|
||||
{
|
||||
query = query.ToLower();
|
||||
|
||||
var results = new List<SearcherItem>();
|
||||
float maxScore = 0;
|
||||
foreach (var database in m_Databases)
|
||||
{
|
||||
var localResults = database.Search(query, out var localMaxScore);
|
||||
if (localMaxScore > maxScore)
|
||||
{
|
||||
// skip the highest scored item in the local results and
|
||||
// insert it back as the first item. The first item should always be
|
||||
// the highest scored item. The order of the other items does not matter
|
||||
// because they will be reordered to recreate the tree properly.
|
||||
if (results.Count > 0)
|
||||
{
|
||||
// backup previous best result
|
||||
results.Add(results[0]);
|
||||
// replace it with the new best result
|
||||
results[0] = localResults[0];
|
||||
// add remaining results at the end
|
||||
results.AddRange(localResults.Skip(1));
|
||||
}
|
||||
else // best result will be the first item
|
||||
results.AddRange(localResults);
|
||||
|
||||
maxScore = localMaxScore;
|
||||
}
|
||||
else // no new best result just append everything
|
||||
{
|
||||
results.AddRange(localResults);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class AnalyticsEvent
|
||||
{
|
||||
[PublicAPI]
|
||||
public enum EventType{ Pending, Picked, Cancelled }
|
||||
public readonly EventType eventType;
|
||||
public readonly string currentSearchFieldText;
|
||||
public AnalyticsEvent(EventType eventType, string currentSearchFieldText)
|
||||
{
|
||||
this.eventType = eventType;
|
||||
this.currentSearchFieldText = currentSearchFieldText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 416e1b35d3077b043be46d805db72d13
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
public enum ItemExpanderState
|
||||
{
|
||||
Hidden,
|
||||
Collapsed,
|
||||
Expanded
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public interface ISearcherAdapter
|
||||
{
|
||||
VisualElement MakeItem();
|
||||
VisualElement Bind(VisualElement target, SearcherItem item, ItemExpanderState expanderState, string text);
|
||||
|
||||
string Title { get; }
|
||||
bool HasDetailsPanel { get; }
|
||||
bool AddAllChildResults { get; }
|
||||
bool MultiSelectEnabled { get; }
|
||||
float InitialSplitterDetailRatio { get; }
|
||||
void OnSelectionChanged(IEnumerable<SearcherItem> items);
|
||||
SearcherItem OnSearchResultsFilter(IEnumerable<SearcherItem> searchResults, string searchQuery);
|
||||
void InitDetailsPanel(VisualElement detailsPanel);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public class SearcherAdapter : ISearcherAdapter
|
||||
{
|
||||
const string k_EntryName = "smartSearchItem";
|
||||
const int k_IndentDepthFactor = 15;
|
||||
|
||||
readonly VisualTreeAsset m_DefaultItemTemplate;
|
||||
public virtual string Title { get; }
|
||||
public virtual bool HasDetailsPanel => true;
|
||||
public virtual bool AddAllChildResults => true;
|
||||
public virtual bool MultiSelectEnabled => false;
|
||||
|
||||
Label m_DetailsLabel;
|
||||
public virtual float InitialSplitterDetailRatio => 1.0f;
|
||||
|
||||
public SearcherAdapter(string title)
|
||||
{
|
||||
Title = title;
|
||||
m_DefaultItemTemplate = Resources.Load<VisualTreeAsset>("SearcherItem");
|
||||
}
|
||||
|
||||
public virtual VisualElement MakeItem()
|
||||
{
|
||||
// Create a visual element hierarchy for this search result.
|
||||
var item = m_DefaultItemTemplate.CloneTree();
|
||||
return item;
|
||||
}
|
||||
|
||||
public virtual VisualElement Bind(VisualElement element, SearcherItem item, ItemExpanderState expanderState, string query)
|
||||
{
|
||||
var indent = element.Q<VisualElement>("itemIndent");
|
||||
indent.style.width = item.Depth * k_IndentDepthFactor;
|
||||
|
||||
var expander = element.Q<VisualElement>("itemChildExpander");
|
||||
|
||||
var icon = expander.Query("expanderIcon").First();
|
||||
icon.ClearClassList();
|
||||
|
||||
switch (expanderState)
|
||||
{
|
||||
case ItemExpanderState.Expanded:
|
||||
icon.AddToClassList("Expanded");
|
||||
break;
|
||||
|
||||
case ItemExpanderState.Collapsed:
|
||||
icon.AddToClassList("Collapsed");
|
||||
break;
|
||||
}
|
||||
|
||||
var nameLabelsContainer = element.Q<VisualElement>("labelsContainer");
|
||||
nameLabelsContainer.Clear();
|
||||
|
||||
var iconElement = element.Q<VisualElement>("itemIconVisualElement");
|
||||
iconElement.style.backgroundImage = item.Icon;
|
||||
if (item.Icon == null && item.CollapseEmptyIcon)
|
||||
{
|
||||
iconElement.style.display = DisplayStyle.None;
|
||||
} else
|
||||
{
|
||||
iconElement.style.display = DisplayStyle.Flex;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
nameLabelsContainer.Add(new Label(item.Name));
|
||||
else
|
||||
SearcherHighlighter.HighlightTextBasedOnQuery(nameLabelsContainer, item.Name, query);
|
||||
|
||||
element.userData = item;
|
||||
element.name = k_EntryName;
|
||||
|
||||
return expander;
|
||||
}
|
||||
|
||||
public virtual void InitDetailsPanel(VisualElement detailsPanel)
|
||||
{
|
||||
m_DetailsLabel = new Label();
|
||||
detailsPanel.Add(m_DetailsLabel);
|
||||
}
|
||||
|
||||
public virtual void OnSelectionChanged(IEnumerable<SearcherItem> items)
|
||||
{
|
||||
if (m_DetailsLabel != null)
|
||||
{
|
||||
var itemsList = items.ToList();
|
||||
m_DetailsLabel.text = itemsList.Any() ? itemsList[0].Help : "No results";
|
||||
}
|
||||
}
|
||||
|
||||
// How to handle filtering and prioritization of search results is specific to clients of the searcher window
|
||||
// This callback is meant to be implemented by child classes of SearcherAdapter as they need
|
||||
public virtual SearcherItem OnSearchResultsFilter(IEnumerable<SearcherItem> searchResults, string searchQuery)
|
||||
{
|
||||
return new SearcherItem("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3b62a4eaddacd11418765f5c2f6cb592
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,806 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
using UnityEditor.UIElements;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
class SearcherControl : VisualElement
|
||||
{
|
||||
// Window constants.
|
||||
const string k_WindowTitleLabel = "windowTitleLabel";
|
||||
const string k_WindowDetailsPanel = "windowDetailsVisualContainer";
|
||||
const string k_WindowResultsScrollViewName = "windowResultsScrollView";
|
||||
const string k_WindowSearchTextFieldName = "searchBox";
|
||||
const string k_WindowAutoCompleteLabelName = "autoCompleteLabel";
|
||||
const string k_WindowSearchIconName = "searchIcon";
|
||||
const string k_WindowResizerName = "windowResizer";
|
||||
const string kWindowSearcherPanel = "searcherVisualContainer";
|
||||
const int k_TabCharacter = 9;
|
||||
|
||||
Label m_AutoCompleteLabel;
|
||||
IEnumerable<SearcherItem> m_Results;
|
||||
List<SearcherItem> m_VisibleResults;
|
||||
HashSet<SearcherItem> m_ExpandedResults;
|
||||
HashSet<SearcherItem> m_MultiSelectSelection;
|
||||
Dictionary<SearcherItem, Toggle> m_SearchItemToVisualToggle;
|
||||
Searcher m_Searcher;
|
||||
string m_SuggestedTerm;
|
||||
string m_Text = string.Empty;
|
||||
Action<SearcherItem> m_SelectionCallback;
|
||||
Action<Searcher.AnalyticsEvent> m_AnalyticsDataCallback;
|
||||
Func<IEnumerable<SearcherItem>, string, SearcherItem> m_SearchResultsFilterCallback;
|
||||
ListView m_ListView;
|
||||
TextField m_SearchTextField;
|
||||
VisualElement m_SearchTextInput;
|
||||
VisualElement m_DetailsPanel;
|
||||
VisualElement m_SearcherPanel;
|
||||
VisualElement m_ContentContainer;
|
||||
Button m_ConfirmButton;
|
||||
|
||||
internal Label TitleLabel { get; }
|
||||
internal VisualElement Resizer { get; }
|
||||
|
||||
public SearcherControl()
|
||||
{
|
||||
// Load window template.
|
||||
var windowUxmlTemplate = Resources.Load<VisualTreeAsset>("SearcherWindow");
|
||||
|
||||
// Clone Window Template.
|
||||
var windowRootVisualElement = windowUxmlTemplate.CloneTree();
|
||||
windowRootVisualElement.AddToClassList("content");
|
||||
|
||||
windowRootVisualElement.StretchToParentSize();
|
||||
|
||||
// Add Window VisualElement to window's RootVisualContainer
|
||||
Add(windowRootVisualElement);
|
||||
|
||||
m_VisibleResults = new List<SearcherItem>();
|
||||
m_ExpandedResults = new HashSet<SearcherItem>();
|
||||
m_MultiSelectSelection = new HashSet<SearcherItem>();
|
||||
m_SearchItemToVisualToggle = new Dictionary<SearcherItem, Toggle>();
|
||||
|
||||
m_ListView = this.Q<ListView>(k_WindowResultsScrollViewName);
|
||||
|
||||
if (m_ListView != null)
|
||||
{
|
||||
m_ListView.bindItem = Bind;
|
||||
m_ListView.RegisterCallback<KeyDownEvent>(SetSelectedElementInResultsList);
|
||||
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
m_ListView.onItemsChosen += obj => OnListViewSelect((SearcherItem)obj.FirstOrDefault());
|
||||
m_ListView.onSelectionChange += selectedItems => m_Searcher.Adapter.OnSelectionChanged(selectedItems.OfType<SearcherItem>().ToList());
|
||||
#else
|
||||
m_ListView.onItemChosen += obj => OnListViewSelect((SearcherItem)obj);
|
||||
m_ListView.onSelectionChanged += selectedItems => m_Searcher.Adapter.OnSelectionChanged(selectedItems.OfType<SearcherItem>());
|
||||
#endif
|
||||
m_ListView.focusable = true;
|
||||
m_ListView.tabIndex = 1;
|
||||
}
|
||||
|
||||
m_DetailsPanel = this.Q(k_WindowDetailsPanel);
|
||||
|
||||
TitleLabel = this.Q<Label>(k_WindowTitleLabel);
|
||||
|
||||
m_SearcherPanel = this.Q(kWindowSearcherPanel);
|
||||
|
||||
m_SearchTextField = this.Q<TextField>(k_WindowSearchTextFieldName);
|
||||
if (m_SearchTextField != null)
|
||||
{
|
||||
m_SearchTextField.focusable = true;
|
||||
m_SearchTextField.RegisterCallback<InputEvent>(OnSearchTextFieldTextChanged);
|
||||
|
||||
m_SearchTextInput = m_SearchTextField.Q(TextInputBaseField<string>.textInputUssName);
|
||||
m_SearchTextInput.RegisterCallback<KeyDownEvent>(OnSearchTextFieldKeyDown);
|
||||
}
|
||||
|
||||
m_AutoCompleteLabel = this.Q<Label>(k_WindowAutoCompleteLabelName);
|
||||
|
||||
Resizer = this.Q(k_WindowResizerName);
|
||||
|
||||
m_ContentContainer = this.Q("unity-content-container");
|
||||
|
||||
m_ConfirmButton = this.Q<Button>("confirmButton");
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
m_ConfirmButton.clicked += OnConfirmMultiselect;
|
||||
#else
|
||||
m_ConfirmButton.clickable.clicked += OnConfirmMultiselect;
|
||||
#endif
|
||||
|
||||
RegisterCallback<AttachToPanelEvent>(OnEnterPanel);
|
||||
RegisterCallback<DetachFromPanelEvent>(OnLeavePanel);
|
||||
|
||||
// TODO: HACK - ListView's scroll view steals focus using the scheduler.
|
||||
EditorApplication.update += HackDueToListViewScrollViewStealingFocus;
|
||||
|
||||
style.flexGrow = 1;
|
||||
}
|
||||
|
||||
void OnConfirmMultiselect()
|
||||
{
|
||||
if (m_MultiSelectSelection.Count == 0)
|
||||
{
|
||||
m_SelectionCallback(null);
|
||||
return;
|
||||
}
|
||||
foreach (SearcherItem item in m_MultiSelectSelection)
|
||||
{
|
||||
m_SelectionCallback(item);
|
||||
}
|
||||
}
|
||||
|
||||
void HackDueToListViewScrollViewStealingFocus()
|
||||
{
|
||||
m_SearchTextInput?.Focus();
|
||||
// ReSharper disable once DelegateSubtraction
|
||||
EditorApplication.update -= HackDueToListViewScrollViewStealingFocus;
|
||||
}
|
||||
|
||||
void OnEnterPanel(AttachToPanelEvent e)
|
||||
{
|
||||
RegisterCallback<KeyDownEvent>(OnKeyDown);
|
||||
}
|
||||
|
||||
void OnLeavePanel(DetachFromPanelEvent e)
|
||||
{
|
||||
UnregisterCallback<KeyDownEvent>(OnKeyDown);
|
||||
}
|
||||
|
||||
void OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.keyCode == KeyCode.Escape)
|
||||
{
|
||||
CancelSearch();
|
||||
}
|
||||
}
|
||||
|
||||
void OnListViewSelect(SearcherItem item)
|
||||
{
|
||||
if (!m_Searcher.Adapter.MultiSelectEnabled)
|
||||
{
|
||||
m_SelectionCallback(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
ToggleItemForMultiSelect(item, !m_MultiSelectSelection.Contains(item));
|
||||
}
|
||||
}
|
||||
|
||||
void CancelSearch()
|
||||
{
|
||||
OnSearchTextFieldTextChanged(InputEvent.GetPooled(m_Text, string.Empty));
|
||||
OnListViewSelect(null);
|
||||
m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Cancelled, m_SearchTextField.value));
|
||||
}
|
||||
|
||||
public void Setup(Searcher searcher, Action<SearcherItem> selectionCallback, Action<Searcher.AnalyticsEvent> analyticsDataCallback, Func<IEnumerable<SearcherItem>, string, SearcherItem> searchResultsFilterCallback)
|
||||
{
|
||||
m_Searcher = searcher;
|
||||
m_SelectionCallback = selectionCallback;
|
||||
m_AnalyticsDataCallback = analyticsDataCallback;
|
||||
m_SearchResultsFilterCallback = searchResultsFilterCallback;
|
||||
|
||||
|
||||
if (m_Searcher.Adapter.MultiSelectEnabled) {
|
||||
AddToClassList("searcher__multiselect");
|
||||
}
|
||||
|
||||
if (m_Searcher.Adapter.HasDetailsPanel)
|
||||
{
|
||||
m_Searcher.Adapter.InitDetailsPanel(m_DetailsPanel);
|
||||
m_DetailsPanel.RemoveFromClassList("hidden");
|
||||
m_DetailsPanel.style.flexGrow = m_Searcher.Adapter.InitialSplitterDetailRatio;
|
||||
m_SearcherPanel.style.flexGrow = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_DetailsPanel.AddToClassList("hidden");
|
||||
|
||||
var splitter = m_DetailsPanel.parent;
|
||||
|
||||
splitter.parent.Insert(0,m_SearcherPanel);
|
||||
splitter.parent.Insert(1, m_DetailsPanel);
|
||||
|
||||
splitter.RemoveFromHierarchy();
|
||||
}
|
||||
|
||||
|
||||
|
||||
TitleLabel.text = m_Searcher.Adapter.Title;
|
||||
if (string.IsNullOrEmpty(TitleLabel.text))
|
||||
{
|
||||
TitleLabel.parent.style.visibility = Visibility.Hidden;
|
||||
TitleLabel.parent.style.position = Position.Absolute;
|
||||
}
|
||||
|
||||
m_Searcher.BuildIndices();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
void Refresh()
|
||||
{
|
||||
var query = m_Text;
|
||||
m_Results = m_Searcher.Search(query);
|
||||
GenerateVisibleResults();
|
||||
|
||||
// The first item in the results is always the highest scored item.
|
||||
// We want to scroll to and select this item.
|
||||
var visibleIndex = -1;
|
||||
m_SuggestedTerm = string.Empty;
|
||||
|
||||
var results = m_Results.ToList();
|
||||
if (results.Any())
|
||||
{
|
||||
SearcherItem scrollToItem = m_SearchResultsFilterCallback?.Invoke(results, query);
|
||||
if(scrollToItem == null)
|
||||
scrollToItem = results.First();
|
||||
visibleIndex = m_VisibleResults.IndexOf(scrollToItem);
|
||||
|
||||
// If we're trying to scroll to a result that is not visible in a single category,
|
||||
// we need to add that result and its hierarchy back to the visible results
|
||||
// This prevents searcher suggesting a single collapsed category that the user then needs to manually expand regardless
|
||||
if (visibleIndex == -1 && m_VisibleResults.Count() == 1)
|
||||
{
|
||||
SearcherItem currentItemRoot = scrollToItem;
|
||||
var idSet = new HashSet<SearcherItem>();
|
||||
while (currentItemRoot.Parent != null)
|
||||
{
|
||||
currentItemRoot = currentItemRoot.Parent;
|
||||
}
|
||||
idSet.Add(currentItemRoot);
|
||||
AddResultChildren(currentItemRoot, idSet);
|
||||
visibleIndex = m_VisibleResults.IndexOf(scrollToItem);
|
||||
}
|
||||
|
||||
var cursorIndex = m_SearchTextField.cursorIndex;
|
||||
|
||||
if (query.Length > 0)
|
||||
{
|
||||
var strings = scrollToItem.Name.Split(' ');
|
||||
var wordStartIndex = cursorIndex == 0 ? 0 : query.LastIndexOf(' ', cursorIndex - 1) + 1;
|
||||
var word = query.Substring(wordStartIndex, cursorIndex - wordStartIndex);
|
||||
|
||||
if (word.Length > 0)
|
||||
foreach (var t in strings)
|
||||
{
|
||||
if (t.StartsWith(word, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
m_SuggestedTerm = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_ListView.itemsSource = m_VisibleResults;
|
||||
m_ListView.makeItem = MakeItem;
|
||||
RefreshListView();
|
||||
|
||||
SetSelectedElementInResultsList(visibleIndex);
|
||||
}
|
||||
|
||||
VisualElement MakeItem()
|
||||
{
|
||||
VisualElement item = m_Searcher.Adapter.MakeItem();
|
||||
if (m_Searcher.Adapter.MultiSelectEnabled)
|
||||
{
|
||||
var selectionToggle = item.Q<Toggle>("itemToggle");
|
||||
if (selectionToggle != null)
|
||||
{
|
||||
selectionToggle.RegisterValueChangedCallback(changeEvent =>
|
||||
{
|
||||
SearcherItem searcherItem = item.userData as SearcherItem;
|
||||
ToggleItemForMultiSelect(searcherItem, changeEvent.newValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
void GenerateVisibleResults()
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_Text))
|
||||
{
|
||||
m_ExpandedResults.Clear();
|
||||
RemoveChildrenFromResults();
|
||||
return;
|
||||
}
|
||||
|
||||
RegenerateVisibleResults();
|
||||
ExpandAllParents();
|
||||
}
|
||||
|
||||
void ExpandAllParents()
|
||||
{
|
||||
m_ExpandedResults.Clear();
|
||||
foreach (var item in m_VisibleResults)
|
||||
if (item.HasChildren)
|
||||
m_ExpandedResults.Add(item);
|
||||
}
|
||||
|
||||
void RemoveChildrenFromResults()
|
||||
{
|
||||
m_VisibleResults.Clear();
|
||||
var parents = new HashSet<SearcherItem>();
|
||||
|
||||
foreach (var item in m_Results.Where(i => !parents.Contains(i)))
|
||||
{
|
||||
var currentParent = item;
|
||||
|
||||
while (true)
|
||||
{
|
||||
if (currentParent.Parent == null)
|
||||
{
|
||||
if (parents.Contains(currentParent))
|
||||
break;
|
||||
|
||||
parents.Add(currentParent);
|
||||
m_VisibleResults.Add(currentParent);
|
||||
break;
|
||||
}
|
||||
|
||||
currentParent = currentParent.Parent;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_Searcher.SortComparison != null)
|
||||
m_VisibleResults.Sort(m_Searcher.SortComparison);
|
||||
}
|
||||
|
||||
void RegenerateVisibleResults()
|
||||
{
|
||||
var idSet = new HashSet<SearcherItem>();
|
||||
m_VisibleResults.Clear();
|
||||
|
||||
foreach (var item in m_Results.Where(item => !idSet.Contains(item)))
|
||||
{
|
||||
idSet.Add(item);
|
||||
m_VisibleResults.Add(item);
|
||||
|
||||
var currentParent = item.Parent;
|
||||
while (currentParent != null)
|
||||
{
|
||||
if (!idSet.Contains(currentParent))
|
||||
{
|
||||
idSet.Add(currentParent);
|
||||
m_VisibleResults.Add(currentParent);
|
||||
}
|
||||
|
||||
currentParent = currentParent.Parent;
|
||||
}
|
||||
|
||||
AddResultChildren(item, idSet);
|
||||
}
|
||||
|
||||
var comparison = m_Searcher.SortComparison ?? ((i1, i2) =>
|
||||
{
|
||||
var result = i1.Database.Id - i2.Database.Id;
|
||||
return result != 0 ? result : i1.Id - i2.Id;
|
||||
});
|
||||
m_VisibleResults.Sort(comparison);
|
||||
}
|
||||
|
||||
void AddResultChildren(SearcherItem item, ISet<SearcherItem> idSet)
|
||||
{
|
||||
if (!item.HasChildren)
|
||||
return;
|
||||
if (m_Searcher.Adapter.AddAllChildResults)
|
||||
{
|
||||
//add all children results for current search term
|
||||
// eg "Book" will show both "Cook Book" and "Cooking" as children
|
||||
foreach (var child in item.Children)
|
||||
{
|
||||
if (!idSet.Contains(child))
|
||||
{
|
||||
idSet.Add(child);
|
||||
m_VisibleResults.Add(child);
|
||||
}
|
||||
|
||||
AddResultChildren(child, idSet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var child in item.Children)
|
||||
{
|
||||
//only add child results if the child matches the search term
|
||||
// eg "Book" will show "Cook Book" but not "Cooking" as a child
|
||||
if (!m_Results.Contains(child))
|
||||
continue;
|
||||
|
||||
if (!idSet.Contains(child))
|
||||
{
|
||||
idSet.Add(child);
|
||||
m_VisibleResults.Add(child);
|
||||
}
|
||||
|
||||
AddResultChildren(child, idSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool HasChildResult(SearcherItem item)
|
||||
{
|
||||
if (m_Results.Contains(item))
|
||||
return true;
|
||||
|
||||
foreach (var child in item.Children)
|
||||
{
|
||||
if (HasChildResult(child))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemExpanderState GetExpanderState(int index)
|
||||
{
|
||||
var item = m_VisibleResults[index];
|
||||
|
||||
foreach (var child in item.Children)
|
||||
{
|
||||
if (!m_VisibleResults.Contains(child) && !HasChildResult(child))
|
||||
continue;
|
||||
|
||||
return m_ExpandedResults.Contains(item) ? ItemExpanderState.Expanded : ItemExpanderState.Collapsed;
|
||||
}
|
||||
|
||||
return item.Children.Count != 0 ? ItemExpanderState.Collapsed : ItemExpanderState.Hidden;
|
||||
}
|
||||
|
||||
void Bind(VisualElement target, int index)
|
||||
{
|
||||
var item = m_VisibleResults[index];
|
||||
var expanderState = GetExpanderState(index);
|
||||
var expander = m_Searcher.Adapter.Bind(target, item, expanderState, m_Text);
|
||||
var selectionToggle = target.Q<Toggle>("itemToggle");
|
||||
if (selectionToggle != null)
|
||||
{
|
||||
selectionToggle.SetValueWithoutNotify(m_MultiSelectSelection.Contains(item));
|
||||
m_SearchItemToVisualToggle[item] = selectionToggle;
|
||||
}
|
||||
expander.RegisterCallback<MouseDownEvent>(ExpandOrCollapse);
|
||||
}
|
||||
|
||||
void ToggleItemForMultiSelect(SearcherItem item, bool selected)
|
||||
{
|
||||
if (selected)
|
||||
{
|
||||
m_MultiSelectSelection.Add(item);
|
||||
} else
|
||||
{
|
||||
m_MultiSelectSelection.Remove(item);
|
||||
}
|
||||
|
||||
Toggle toggle;
|
||||
if (m_SearchItemToVisualToggle.TryGetValue(item, out toggle))
|
||||
{
|
||||
toggle.SetValueWithoutNotify(selected);
|
||||
}
|
||||
|
||||
foreach (var child in item.Children)
|
||||
{
|
||||
ToggleItemForMultiSelect(child, selected);
|
||||
}
|
||||
}
|
||||
|
||||
static void GetItemsToHide(SearcherItem parent, ref HashSet<SearcherItem> itemsToHide)
|
||||
{
|
||||
if (!parent.HasChildren)
|
||||
{
|
||||
itemsToHide.Add(parent);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var child in parent.Children)
|
||||
{
|
||||
itemsToHide.Add(child);
|
||||
GetItemsToHide(child, ref itemsToHide);
|
||||
}
|
||||
}
|
||||
|
||||
void HideUnexpandedItems()
|
||||
{
|
||||
// Hide unexpanded children.
|
||||
var itemsToHide = new HashSet<SearcherItem>();
|
||||
foreach (var item in m_VisibleResults)
|
||||
{
|
||||
if (m_ExpandedResults.Contains(item))
|
||||
continue;
|
||||
|
||||
if (!item.HasChildren)
|
||||
continue;
|
||||
|
||||
if (itemsToHide.Contains(item))
|
||||
continue;
|
||||
|
||||
// We need to hide its children.
|
||||
GetItemsToHide(item, ref itemsToHide);
|
||||
}
|
||||
|
||||
foreach (var item in itemsToHide)
|
||||
m_VisibleResults.Remove(item);
|
||||
}
|
||||
|
||||
void RefreshListView()
|
||||
{
|
||||
m_SearchItemToVisualToggle.Clear();
|
||||
#if UNITY_2021_2_OR_NEWER
|
||||
m_ListView.Rebuild();
|
||||
#else
|
||||
m_ListView.Refresh();
|
||||
#endif
|
||||
}
|
||||
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
void RefreshListViewOn()
|
||||
{
|
||||
// TODO: Call ListView.Refresh() when it is fixed.
|
||||
// Need this workaround until then.
|
||||
// See: https://fogbugz.unity3d.com/f/cases/1027728/
|
||||
// And: https://gitlab.internal.unity3d.com/upm-packages/editor/com.unity.searcher/issues/9
|
||||
|
||||
var scrollView = m_ListView.Q<ScrollView>();
|
||||
|
||||
var scroller = scrollView?.Q<Scroller>("VerticalScroller");
|
||||
if (scroller == null)
|
||||
return;
|
||||
|
||||
var oldValue = scroller.value;
|
||||
scroller.value = oldValue + 1.0f;
|
||||
scroller.value = oldValue - 1.0f;
|
||||
scroller.value = oldValue;
|
||||
}
|
||||
|
||||
void Expand(SearcherItem item)
|
||||
{
|
||||
m_ExpandedResults.Add(item);
|
||||
|
||||
RegenerateVisibleResults();
|
||||
HideUnexpandedItems();
|
||||
|
||||
RefreshListView();
|
||||
}
|
||||
|
||||
void Collapse(SearcherItem item)
|
||||
{
|
||||
// if it's already collapsed or not collapsed
|
||||
if (!m_ExpandedResults.Remove(item))
|
||||
{
|
||||
// this case applies for a left arrow key press
|
||||
if (item.Parent != null)
|
||||
SetSelectedElementInResultsList(m_VisibleResults.IndexOf(item.Parent));
|
||||
|
||||
// even if it's a root item and has no parents, do nothing more
|
||||
return;
|
||||
}
|
||||
|
||||
RegenerateVisibleResults();
|
||||
HideUnexpandedItems();
|
||||
|
||||
// TODO: understand what happened
|
||||
RefreshListView();
|
||||
|
||||
// RefreshListViewOn();
|
||||
}
|
||||
|
||||
void ExpandOrCollapse(MouseDownEvent evt)
|
||||
{
|
||||
if (!(evt.target is VisualElement expanderLabel))
|
||||
return;
|
||||
|
||||
VisualElement itemElement = expanderLabel.GetFirstAncestorOfType<TemplateContainer>();
|
||||
|
||||
if (!(itemElement?.userData is SearcherItem item)
|
||||
|| !item.HasChildren
|
||||
|| !expanderLabel.ClassListContains("Expanded") && !expanderLabel.ClassListContains("Collapsed"))
|
||||
return;
|
||||
|
||||
if (!m_ExpandedResults.Contains(item))
|
||||
Expand(item);
|
||||
else
|
||||
Collapse(item);
|
||||
|
||||
evt.StopImmediatePropagation();
|
||||
}
|
||||
|
||||
void OnSearchTextFieldTextChanged(InputEvent inputEvent)
|
||||
{
|
||||
var text = inputEvent.newData;
|
||||
|
||||
if (string.Equals(text, m_Text))
|
||||
return;
|
||||
|
||||
// This is necessary due to OnTextChanged(...) being called after user inputs that have no impact on the text.
|
||||
// Ex: Moving the caret.
|
||||
m_Text = text;
|
||||
|
||||
// If backspace is pressed and no text remain, clear the suggestion label.
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
this.Q(k_WindowSearchIconName).RemoveFromClassList("Active");
|
||||
|
||||
// Display the unfiltered results list.
|
||||
Refresh();
|
||||
|
||||
m_AutoCompleteLabel.text = String.Empty;
|
||||
m_SuggestedTerm = String.Empty;
|
||||
|
||||
SetSelectedElementInResultsList(0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.Q(k_WindowSearchIconName).ClassListContains("Active"))
|
||||
this.Q(k_WindowSearchIconName).AddToClassList("Active");
|
||||
|
||||
Refresh();
|
||||
|
||||
// Calculate the start and end indexes of the word being modified (if any).
|
||||
var cursorIndex = m_SearchTextField.cursorIndex;
|
||||
|
||||
// search toward the beginning of the string starting at the character before the cursor
|
||||
// +1 because we want the char after a space, or 0 if the search fails
|
||||
var wordStartIndex = cursorIndex == 0 ? 0 : (text.LastIndexOf(' ', cursorIndex - 1) + 1);
|
||||
|
||||
// search toward the end of the string from the cursor index
|
||||
var wordEndIndex = text.IndexOf(' ', cursorIndex);
|
||||
if (wordEndIndex == -1) // no space found, assume end of string
|
||||
wordEndIndex = text.Length;
|
||||
|
||||
// Clear the suggestion term if the caret is not within a word (both start and end indexes are equal, ex: (space)caret(space))
|
||||
// or the user didn't append characters to a word at the end of the query.
|
||||
if (wordStartIndex == wordEndIndex || wordEndIndex < text.Length)
|
||||
{
|
||||
m_AutoCompleteLabel.text = string.Empty;
|
||||
m_SuggestedTerm = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
var word = text.Substring(wordStartIndex, wordEndIndex - wordStartIndex);
|
||||
|
||||
if (!string.IsNullOrEmpty(m_SuggestedTerm))
|
||||
{
|
||||
var wordSuggestion =
|
||||
word + m_SuggestedTerm.Substring(word.Length, m_SuggestedTerm.Length - word.Length);
|
||||
text = text.Remove(wordStartIndex, word.Length);
|
||||
text = text.Insert(wordStartIndex, wordSuggestion);
|
||||
m_AutoCompleteLabel.text = text;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_AutoCompleteLabel.text = String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
void OnSearchTextFieldKeyDown(KeyDownEvent keyDownEvent)
|
||||
{
|
||||
// First, check if we cancelled the search.
|
||||
if (keyDownEvent.keyCode == KeyCode.Escape)
|
||||
{
|
||||
CancelSearch();
|
||||
return;
|
||||
}
|
||||
|
||||
// For some reason the KeyDown event is raised twice when entering a character.
|
||||
// As such, we ignore one of the duplicate event.
|
||||
// This workaround was recommended by the Editor team. The cause of the issue relates to how IMGUI works
|
||||
// and a fix was not in the works at the moment of this writing.
|
||||
if (keyDownEvent.character == k_TabCharacter)
|
||||
{
|
||||
// Prevent switching focus to another visual element.
|
||||
keyDownEvent.PreventDefault();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If Tab is pressed, complete the query with the suggested term.
|
||||
if (keyDownEvent.keyCode == KeyCode.Tab)
|
||||
{
|
||||
// Used to prevent the TAB input from executing it's default behavior. We're hijacking it for auto-completion.
|
||||
keyDownEvent.PreventDefault();
|
||||
|
||||
if (!string.IsNullOrEmpty(m_SuggestedTerm))
|
||||
{
|
||||
SelectAndReplaceCurrentWord();
|
||||
m_AutoCompleteLabel.text = string.Empty;
|
||||
|
||||
// TODO: Revisit, we shouldn't need to do this here.
|
||||
m_Text = m_SearchTextField.text;
|
||||
|
||||
Refresh();
|
||||
|
||||
m_SuggestedTerm = string.Empty;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSelectedElementInResultsList(keyDownEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectAndReplaceCurrentWord()
|
||||
{
|
||||
var s = m_SearchTextField.value;
|
||||
var lastWordIndex = s.LastIndexOf(' ');
|
||||
lastWordIndex++;
|
||||
|
||||
var newText = s.Substring(0, lastWordIndex) + m_SuggestedTerm;
|
||||
|
||||
// Wait for SelectRange api to reach trunk
|
||||
//#if UNITY_2018_3_OR_NEWER
|
||||
// m_SearchTextField.value = newText;
|
||||
// m_SearchTextField.SelectRange(m_SearchTextField.value.Length, m_SearchTextField.value.Length);
|
||||
//#else
|
||||
// HACK - relies on the textfield moving the caret when being assigned a value and skipping
|
||||
// all low surrogate characters
|
||||
var magicMoveCursorToEndString = new string('\uDC00', newText.Length);
|
||||
m_SearchTextField.value = magicMoveCursorToEndString;
|
||||
m_SearchTextField.value = newText;
|
||||
|
||||
//#endif
|
||||
}
|
||||
|
||||
void SetSelectedElementInResultsList(KeyDownEvent keyDownEvent)
|
||||
{
|
||||
int index;
|
||||
switch (keyDownEvent.keyCode)
|
||||
{
|
||||
case KeyCode.Escape:
|
||||
OnListViewSelect(null);
|
||||
m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Cancelled, m_SearchTextField.value));
|
||||
break;
|
||||
case KeyCode.Return:
|
||||
case KeyCode.KeypadEnter:
|
||||
if (m_ListView.selectedIndex != -1)
|
||||
{
|
||||
OnListViewSelect((SearcherItem)m_ListView.selectedItem);
|
||||
m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Picked, m_SearchTextField.value));
|
||||
}
|
||||
else
|
||||
{
|
||||
OnListViewSelect(null);
|
||||
m_AnalyticsDataCallback?.Invoke(new Searcher.AnalyticsEvent(Searcher.AnalyticsEvent.EventType.Cancelled, m_SearchTextField.value));
|
||||
}
|
||||
break;
|
||||
case KeyCode.LeftArrow:
|
||||
index = m_ListView.selectedIndex;
|
||||
if (index >= 0 && index < m_ListView.itemsSource.Count)
|
||||
Collapse(m_ListView.selectedItem as SearcherItem);
|
||||
break;
|
||||
case KeyCode.RightArrow:
|
||||
index = m_ListView.selectedIndex;
|
||||
if (index >= 0 && index < m_ListView.itemsSource.Count)
|
||||
Expand(m_ListView.selectedItem as SearcherItem);
|
||||
break;
|
||||
|
||||
// Fixes bug: https://fogbugz.unity3d.com/f/cases/1358016/
|
||||
case KeyCode.UpArrow:
|
||||
case KeyCode.PageUp:
|
||||
if (m_ListView.selectedIndex > 0)
|
||||
SetSelectedElementInResultsList(m_ListView.selectedIndex - 1);
|
||||
break;
|
||||
|
||||
case KeyCode.DownArrow:
|
||||
case KeyCode.PageDown:
|
||||
if (m_ListView.selectedIndex < 0)
|
||||
SetSelectedElementInResultsList(0);
|
||||
else
|
||||
SetSelectedElementInResultsList(m_ListView.selectedIndex + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void SetSelectedElementInResultsList(int selectedIndex)
|
||||
{
|
||||
var newIndex = selectedIndex >= 0 && selectedIndex < m_VisibleResults.Count ? selectedIndex : -1;
|
||||
if (newIndex < 0)
|
||||
return;
|
||||
|
||||
m_ListView.selectedIndex = newIndex;
|
||||
m_ListView.ScrollToItem(m_ListView.selectedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 56c8d867edaa1c9488c77cc911d01bff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,398 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
[PublicAPI]
|
||||
public class SearcherDatabase : SearcherDatabaseBase
|
||||
{
|
||||
Dictionary<string, IReadOnlyList<ValueTuple<string, float>>> m_Index = new Dictionary<string, IReadOnlyList<ValueTuple<string, float>>>();
|
||||
|
||||
class Result
|
||||
{
|
||||
public SearcherItem item;
|
||||
public float maxScore;
|
||||
}
|
||||
|
||||
const bool k_IsParallel = true;
|
||||
|
||||
public Func<string, SearcherItem, bool> MatchFilter { get; set; }
|
||||
|
||||
public static SearcherDatabase Create(
|
||||
List<SearcherItem> items,
|
||||
string databaseDirectory,
|
||||
bool serializeToFile = true
|
||||
)
|
||||
{
|
||||
if (serializeToFile && databaseDirectory != null && !Directory.Exists(databaseDirectory))
|
||||
Directory.CreateDirectory(databaseDirectory);
|
||||
|
||||
var database = new SearcherDatabase(databaseDirectory, items);
|
||||
|
||||
if (serializeToFile)
|
||||
database.SerializeToFile();
|
||||
|
||||
database.BuildIndex();
|
||||
return database;
|
||||
}
|
||||
|
||||
public static SearcherDatabase Load(string databaseDirectory)
|
||||
{
|
||||
if (!Directory.Exists(databaseDirectory))
|
||||
throw new InvalidOperationException("databaseDirectory not found.");
|
||||
|
||||
var database = new SearcherDatabase(databaseDirectory, null);
|
||||
database.LoadFromFile();
|
||||
database.BuildIndex();
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
public SearcherDatabase(IReadOnlyCollection<SearcherItem> db)
|
||||
: this("", db)
|
||||
{
|
||||
}
|
||||
|
||||
SearcherDatabase(string databaseDirectory, IReadOnlyCollection<SearcherItem> db)
|
||||
: base(databaseDirectory)
|
||||
{
|
||||
m_ItemList = new List<SearcherItem>();
|
||||
var nextId = 0;
|
||||
|
||||
if (db != null)
|
||||
foreach (var item in db)
|
||||
AddItemToIndex(item, ref nextId, null);
|
||||
}
|
||||
|
||||
public override List<SearcherItem> Search(string query, out float localMaxScore)
|
||||
{
|
||||
// Match assumes the query is trimmed
|
||||
query = query.Trim(' ', '\t');
|
||||
localMaxScore = 0;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
if (MatchFilter == null)
|
||||
return m_ItemList;
|
||||
|
||||
// ReSharper disable once RedundantLogicalConditionalExpressionOperand
|
||||
if (k_IsParallel && m_ItemList.Count > 100)
|
||||
return FilterMultiThreaded(query);
|
||||
|
||||
return FilterSingleThreaded(query);
|
||||
}
|
||||
|
||||
var finalResults = new List<SearcherItem> { null };
|
||||
var max = new Result();
|
||||
var tokenizedQuery = new List<string>();
|
||||
foreach (var token in Tokenize(query))
|
||||
{
|
||||
tokenizedQuery.Add(token.Trim().ToLower());
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantLogicalConditionalExpressionOperand
|
||||
if (k_IsParallel && m_ItemList.Count > 100)
|
||||
SearchMultithreaded(query, tokenizedQuery, max, finalResults);
|
||||
else
|
||||
SearchSingleThreaded(query, tokenizedQuery, max, finalResults);
|
||||
|
||||
localMaxScore = max.maxScore;
|
||||
if (max.item != null)
|
||||
finalResults[0] = max.item;
|
||||
else
|
||||
finalResults.RemoveAt(0);
|
||||
|
||||
return finalResults;
|
||||
}
|
||||
|
||||
protected virtual bool Match(string query, IReadOnlyList<string> tokenizedQuery, SearcherItem item, out float score)
|
||||
{
|
||||
var filter = MatchFilter?.Invoke(query, item) ?? true;
|
||||
return Match(tokenizedQuery, item.Path, out score) && filter;
|
||||
}
|
||||
|
||||
List<SearcherItem> FilterSingleThreaded(string query)
|
||||
{
|
||||
var result = new List<SearcherItem>();
|
||||
|
||||
foreach (var searcherItem in m_ItemList)
|
||||
{
|
||||
if (!MatchFilter.Invoke(query, searcherItem))
|
||||
continue;
|
||||
|
||||
result.Add(searcherItem);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
List<SearcherItem> FilterMultiThreaded(string query)
|
||||
{
|
||||
var result = new List<SearcherItem>();
|
||||
var count = Environment.ProcessorCount;
|
||||
var tasks = new Task[count];
|
||||
var lists = new List<SearcherItem>[count];
|
||||
var itemsPerTask = (int)Math.Ceiling(m_ItemList.Count / (float)count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var i1 = i;
|
||||
tasks[i] = Task.Run(() =>
|
||||
{
|
||||
lists[i1] = new List<SearcherItem>();
|
||||
|
||||
for (var j = 0; j < itemsPerTask; j++)
|
||||
{
|
||||
var index = j + itemsPerTask * i1;
|
||||
if (index >= m_ItemList.Count)
|
||||
break;
|
||||
|
||||
var item = m_ItemList[index];
|
||||
if (!MatchFilter.Invoke(query, item))
|
||||
continue;
|
||||
|
||||
lists[i1].Add(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
result.AddRange(lists[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
readonly float k_ScoreCutOff = 0.33f;
|
||||
|
||||
void SearchSingleThreaded(string query, IReadOnlyList<string> tokenizedQuery, Result max, ICollection<SearcherItem> finalResults)
|
||||
{
|
||||
List<Result> results = new List<Result>();
|
||||
|
||||
foreach (var item in m_ItemList)
|
||||
{
|
||||
float score = 0;
|
||||
if (query.Length == 0 || Match(query, tokenizedQuery, item, out score))
|
||||
{
|
||||
if (score > max.maxScore)
|
||||
{
|
||||
max.item = item;
|
||||
max.maxScore = score;
|
||||
}
|
||||
results.Add(new Result() { item = item, maxScore = score});
|
||||
}
|
||||
}
|
||||
|
||||
PostprocessResults(results, finalResults, max);
|
||||
}
|
||||
|
||||
void SearchMultithreaded(string query, IReadOnlyList<string> tokenizedQuery, Result max, List<SearcherItem> finalResults)
|
||||
{
|
||||
var count = Environment.ProcessorCount;
|
||||
var tasks = new Task[count];
|
||||
var localResults = new Result[count];
|
||||
var queue = new ConcurrentQueue<Result>();
|
||||
var itemsPerTask = (int)Math.Ceiling(m_ItemList.Count / (float)count);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
var i1 = i;
|
||||
localResults[i1] = new Result();
|
||||
tasks[i] = Task.Run(() =>
|
||||
{
|
||||
var result = localResults[i1];
|
||||
for (var j = 0; j < itemsPerTask; j++)
|
||||
{
|
||||
var index = j + itemsPerTask * i1;
|
||||
if (index >= m_ItemList.Count)
|
||||
break;
|
||||
var item = m_ItemList[index];
|
||||
float score = 0;
|
||||
if (query.Length == 0 || Match(query, tokenizedQuery, item, out score))
|
||||
{
|
||||
if (score > result.maxScore)
|
||||
{
|
||||
result.maxScore = score;
|
||||
result.item = item;
|
||||
}
|
||||
|
||||
queue.Enqueue(new Result { item = item, maxScore = score });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Task.WaitAll(tasks);
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (localResults[i].maxScore > max.maxScore)
|
||||
{
|
||||
max.maxScore = localResults[i].maxScore;
|
||||
max.item = localResults[i].item;
|
||||
}
|
||||
}
|
||||
|
||||
PostprocessResults(queue, finalResults, max);
|
||||
}
|
||||
|
||||
void PostprocessResults(IEnumerable<Result> results, ICollection<SearcherItem> items, Result max)
|
||||
{
|
||||
foreach (var result in results)
|
||||
{
|
||||
var normalizedScore = result.maxScore / max.maxScore;
|
||||
if (result.item != null && result.item != max.item && normalizedScore > k_ScoreCutOff)
|
||||
{
|
||||
items.Add(result.item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void BuildIndex()
|
||||
{
|
||||
m_Index.Clear();
|
||||
|
||||
foreach (var item in m_ItemList)
|
||||
{
|
||||
if (!m_Index.ContainsKey(item.Path))
|
||||
{
|
||||
List<ValueTuple<string, float>> terms = new List<ValueTuple<string, float>>();
|
||||
|
||||
// If the item uses synonyms to return results for similar words/phrases, add them to the search terms
|
||||
IList<string> tokens = null;
|
||||
if (item.Synonyms == null)
|
||||
tokens = Tokenize(item.Name);
|
||||
else
|
||||
tokens = Tokenize(string.Format("{0} {1}", item.Name, string.Join(" ", item.Synonyms)));
|
||||
|
||||
// Fixes bug: https://fogbugz.unity3d.com/f/cases/1359158/
|
||||
// Without this, node names with spaces or those with Pascal casing were not added to index
|
||||
var nodeName = item.Name.ToLower().Replace(" ", String.Empty);
|
||||
tokens.Add(nodeName);
|
||||
|
||||
string tokenSuite = "";
|
||||
foreach (var token in tokens)
|
||||
{
|
||||
var t = token.ToLower();
|
||||
if (t.Length > 1)
|
||||
{
|
||||
terms.Add(new ValueTuple<string, float>(t, 0.8f));
|
||||
}
|
||||
|
||||
if (tokenSuite.Length > 0)
|
||||
{
|
||||
tokenSuite += " " + t;
|
||||
terms.Add(new ValueTuple<string, float>(tokenSuite, 1f));
|
||||
}
|
||||
else
|
||||
{
|
||||
tokenSuite = t;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a term containing all the uppercase letters (CamelCase World BBox => CCWBB)
|
||||
var initialList = Regex.Split(item.Name, @"\P{Lu}+");
|
||||
var initials = string.Concat(initialList).Trim();
|
||||
if (!string.IsNullOrEmpty(initials))
|
||||
terms.Add(new ValueTuple<string, float>(initials.ToLower(), 0.5f));
|
||||
|
||||
m_Index.Add(item.Path, terms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static IList<string> Tokenize(string s)
|
||||
{
|
||||
var knownTokens = new HashSet<string>();
|
||||
var tokens = new List<string>();
|
||||
|
||||
// Split on word boundaries
|
||||
foreach (var t in Regex.Split(s, @"\W"))
|
||||
{
|
||||
// Split camel case words
|
||||
var tt = Regex.Split(t, @"(\p{Lu}+\P{Lu}*)");
|
||||
foreach (var ttt in tt)
|
||||
{
|
||||
var tttt = ttt.Trim();
|
||||
if (!string.IsNullOrEmpty(tttt) && !knownTokens.Contains(tttt))
|
||||
{
|
||||
knownTokens.Add(tttt);
|
||||
tokens.Add(tttt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
|
||||
bool Match(IReadOnlyList<string> tokenizedQuery, string itemPath, out float score)
|
||||
{
|
||||
itemPath = itemPath.Trim();
|
||||
if (itemPath == "")
|
||||
{
|
||||
if (tokenizedQuery.Count == 0)
|
||||
{
|
||||
score = 1;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
score = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<ValueTuple<string, float>> indexTerms;
|
||||
if (!m_Index.TryGetValue(itemPath, out indexTerms))
|
||||
{
|
||||
score = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
float maxScore = 0.0f;
|
||||
foreach (var t in indexTerms)
|
||||
{
|
||||
float scoreForTerm = 0f;
|
||||
var querySuite = "";
|
||||
var querySuiteFactor = 1.25f;
|
||||
foreach (var q in tokenizedQuery)
|
||||
{
|
||||
if (t.Item1.StartsWith(q))
|
||||
{
|
||||
scoreForTerm += t.Item2 * q.Length / t.Item1.Length;
|
||||
}
|
||||
|
||||
if (querySuite.Length > 0)
|
||||
{
|
||||
querySuite += " " + q;
|
||||
if (t.Item1.StartsWith(querySuite))
|
||||
{
|
||||
scoreForTerm += t.Item2 * querySuiteFactor * querySuite.Length / t.Item1.Length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
querySuite = q;
|
||||
}
|
||||
|
||||
querySuiteFactor *= querySuiteFactor;
|
||||
}
|
||||
|
||||
maxScore = Mathf.Max(maxScore, scoreForTerm);
|
||||
}
|
||||
|
||||
score = maxScore;
|
||||
return score > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3e57f897b515d5042b83c4e88a5664e3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
[PublicAPI]
|
||||
public abstract class SearcherDatabaseBase
|
||||
{
|
||||
protected const string k_SerializedJsonFile = "/SerializedDatabase.json";
|
||||
public string DatabaseDirectory { get; set; }
|
||||
|
||||
public IList<SearcherItem> ItemList => m_ItemList;
|
||||
|
||||
// ReSharper disable once Unity.RedundantSerializeFieldAttribute
|
||||
[SerializeField]
|
||||
protected List<SearcherItem> m_ItemList;
|
||||
|
||||
protected SearcherDatabaseBase(string databaseDirectory)
|
||||
{
|
||||
DatabaseDirectory = databaseDirectory;
|
||||
}
|
||||
|
||||
public virtual void BuildIndex() { }
|
||||
|
||||
public abstract List<SearcherItem> Search(string query, out float localMaxScore);
|
||||
|
||||
internal void OverwriteId(int newId)
|
||||
{
|
||||
Id = newId;
|
||||
}
|
||||
|
||||
internal int Id { get; private set; }
|
||||
|
||||
protected void LoadFromFile()
|
||||
{
|
||||
var reader = new StreamReader(DatabaseDirectory + k_SerializedJsonFile);
|
||||
var serializedData = reader.ReadToEnd();
|
||||
reader.Close();
|
||||
|
||||
EditorJsonUtility.FromJsonOverwrite(serializedData, this);
|
||||
|
||||
foreach (var item in m_ItemList)
|
||||
{
|
||||
item.OverwriteDatabase(this);
|
||||
item.ReInitAfterLoadFromFile();
|
||||
}
|
||||
}
|
||||
|
||||
protected void SerializeToFile()
|
||||
{
|
||||
if (DatabaseDirectory == null)
|
||||
return;
|
||||
var serializedData = EditorJsonUtility.ToJson(this, true);
|
||||
var writer = new StreamWriter(DatabaseDirectory + k_SerializedJsonFile, false);
|
||||
writer.Write(serializedData);
|
||||
writer.Close();
|
||||
}
|
||||
|
||||
protected void AddItemToIndex(SearcherItem item, ref int lastId, Action<SearcherItem> action)
|
||||
{
|
||||
m_ItemList.Insert(lastId, item);
|
||||
|
||||
// We can only set the id here as we only know the final index of the item here.
|
||||
item.OverwriteId(lastId);
|
||||
item.GeneratePath();
|
||||
|
||||
action?.Invoke(item);
|
||||
|
||||
lastId++;
|
||||
|
||||
// This is used for sorting results between databases.
|
||||
item.OverwriteDatabase(this);
|
||||
|
||||
if (!item.HasChildren)
|
||||
return;
|
||||
|
||||
var childrenIds = new List<int>();
|
||||
foreach (SearcherItem child in item.Children)
|
||||
{
|
||||
AddItemToIndex(child, ref lastId, action);
|
||||
childrenIds.Add(child.Id);
|
||||
}
|
||||
|
||||
item.OverwriteChildrenIds(childrenIds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 20f869e4fe46bb44a8b56a58904e705c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
static class SearcherHighlighter
|
||||
{
|
||||
const char k_StartHighlightSeparator = '{';
|
||||
const char k_EndHighlightSeparator = '}';
|
||||
const string k_HighlightedStyleClassName = "Highlighted";
|
||||
|
||||
public static void HighlightTextBasedOnQuery(VisualElement container, string text, string query)
|
||||
{
|
||||
var formattedText = text;
|
||||
var queryParts = query.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries);
|
||||
var regex = string.Empty;
|
||||
for (var index = 0; index < queryParts.Length; index++)
|
||||
{
|
||||
var queryPart = queryParts[index];
|
||||
regex += $"({Regex.Escape(queryPart)})";
|
||||
if (index < queryParts.Length - 1)
|
||||
regex += "|";
|
||||
}
|
||||
|
||||
var matches = Regex.Matches(formattedText, regex, RegexOptions.IgnoreCase);
|
||||
foreach (Match match in matches)
|
||||
{
|
||||
formattedText = formattedText.Replace(match.Value,
|
||||
$"{k_StartHighlightSeparator}{match.Value}{k_EndHighlightSeparator}");
|
||||
}
|
||||
|
||||
BuildHighlightLabels(container, formattedText);
|
||||
}
|
||||
|
||||
static void BuildHighlightLabels(VisualElement container, string formattedHighlightText)
|
||||
{
|
||||
if (string.IsNullOrEmpty(formattedHighlightText))
|
||||
return;
|
||||
|
||||
var substring = string.Empty;
|
||||
var highlighting = false;
|
||||
var skipCount = 0;
|
||||
foreach (var character in formattedHighlightText.ToCharArray())
|
||||
{
|
||||
switch (character)
|
||||
{
|
||||
// Skip embedded separators
|
||||
// Ex:
|
||||
// Query: middle e
|
||||
// Text: Middle Eastern
|
||||
// Formatted Text: {Middl{e}} {E}ast{e}rn
|
||||
// ^ ^
|
||||
case k_StartHighlightSeparator when highlighting:
|
||||
skipCount++;
|
||||
continue;
|
||||
case k_StartHighlightSeparator: {
|
||||
highlighting = true;
|
||||
if (!string.IsNullOrEmpty(substring))
|
||||
{
|
||||
container.Add(new Label(substring));
|
||||
substring = string.Empty;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
case k_EndHighlightSeparator when skipCount > 0:
|
||||
skipCount--;
|
||||
continue;
|
||||
case k_EndHighlightSeparator: {
|
||||
var label = new Label(substring);
|
||||
label.AddToClassList(k_HighlightedStyleClassName);
|
||||
container.Add(label);
|
||||
|
||||
highlighting = false;
|
||||
substring = string.Empty;
|
||||
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
substring += character;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(substring))
|
||||
{
|
||||
var label = new Label(substring);
|
||||
if (highlighting)
|
||||
label.AddToClassList(k_HighlightedStyleClassName);
|
||||
container.Add(label);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: e4c8094ce73742d79e92d10007f8e55b
|
||||
timeCreated: 1519665202
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
[PublicAPI]
|
||||
[Serializable]
|
||||
public class SearcherItem
|
||||
{
|
||||
[SerializeField] int m_Id;
|
||||
[SerializeField] List<int> m_ChildrenIds;
|
||||
[SerializeField] string m_Name;
|
||||
[SerializeField] string m_Help;
|
||||
[SerializeField] string[] m_Synonyms;
|
||||
[SerializeField] Texture2D m_Icon;
|
||||
[SerializeField] bool m_CollapseEmptyIcon = true;
|
||||
|
||||
public int Id => m_Id;
|
||||
|
||||
public virtual string Name => m_Name;
|
||||
|
||||
public string Path { get; private set; }
|
||||
|
||||
public string Help
|
||||
{
|
||||
get => m_Help;
|
||||
set => m_Help = value;
|
||||
}
|
||||
|
||||
public string[] Synonyms { get { return m_Synonyms; } set { m_Synonyms = value; } }
|
||||
|
||||
public int Depth => Parent?.Depth + 1 ?? 0;
|
||||
|
||||
public object UserData { get; set; }
|
||||
|
||||
public Texture2D Icon { get => m_Icon; set { m_Icon = value; } }
|
||||
|
||||
public bool CollapseEmptyIcon { get => m_CollapseEmptyIcon; set { m_CollapseEmptyIcon = value; } }
|
||||
|
||||
public SearcherItem Parent { get; private set; }
|
||||
public SearcherDatabaseBase Database { get; private set; }
|
||||
|
||||
// the backing field gets serialized otherwise and triggers a "Serialization depth limit 7 exceeded" warning
|
||||
[field:NonSerialized]
|
||||
public List<SearcherItem> Children { get; private set; }
|
||||
public bool HasChildren => Children.Count > 0;
|
||||
|
||||
public SearcherItem(string name, string help = "", List<SearcherItem> children = null, object userData = null, Texture2D icon = null, bool collapseEmptyIcon = true)
|
||||
{
|
||||
m_Id = -1;
|
||||
Parent = null;
|
||||
Database = null;
|
||||
|
||||
m_Name = name;
|
||||
m_Help = help;
|
||||
m_Icon = icon;
|
||||
UserData = userData;
|
||||
m_CollapseEmptyIcon = collapseEmptyIcon;
|
||||
|
||||
Children = new List<SearcherItem>();
|
||||
if (children == null)
|
||||
return;
|
||||
|
||||
Children = children;
|
||||
foreach (var child in children)
|
||||
child.OverwriteParent(this);
|
||||
}
|
||||
|
||||
public void AddChild(SearcherItem child)
|
||||
{
|
||||
if (child == null)
|
||||
throw new ArgumentNullException(nameof(child));
|
||||
|
||||
if (Database != null)
|
||||
throw new InvalidOperationException(
|
||||
"Cannot add more children to an item that was already used in a database.");
|
||||
|
||||
if (Children == null)
|
||||
Children = new List<SearcherItem>();
|
||||
|
||||
Children.Add(child);
|
||||
child.OverwriteParent(this);
|
||||
}
|
||||
|
||||
internal void OverwriteId(int newId)
|
||||
{
|
||||
m_Id = newId;
|
||||
}
|
||||
|
||||
void OverwriteParent(SearcherItem newParent)
|
||||
{
|
||||
Parent = newParent;
|
||||
}
|
||||
|
||||
internal void OverwriteDatabase(SearcherDatabaseBase newDatabase)
|
||||
{
|
||||
Database = newDatabase;
|
||||
}
|
||||
|
||||
internal void OverwriteChildrenIds(List<int> childrenIds)
|
||||
{
|
||||
m_ChildrenIds = childrenIds;
|
||||
}
|
||||
|
||||
internal void GeneratePath()
|
||||
{
|
||||
if (Parent != null)
|
||||
Path = Parent.Path + " ";
|
||||
else
|
||||
Path = string.Empty;
|
||||
Path += Name;
|
||||
}
|
||||
|
||||
internal void ReInitAfterLoadFromFile()
|
||||
{
|
||||
if (Children == null)
|
||||
Children = new List<SearcherItem>();
|
||||
|
||||
foreach (var id in m_ChildrenIds)
|
||||
{
|
||||
var child = Database.ItemList[id];
|
||||
Children.Add(child);
|
||||
child.OverwriteParent(this);
|
||||
}
|
||||
|
||||
GeneratePath();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{nameof(Id)}: {Id}, {nameof(Name)}: {Name}, {nameof(Depth)}: {Depth}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c456286d59e79df4abf836be8d6f39f3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
// SearcherTreeUtility contains a helper function that takes a flat list of SearcherItems and constructs a SearcherItems tree using their names as paths.
|
||||
//
|
||||
// For example:
|
||||
// List<SearcherItem> items = new List<SearcherItem>();
|
||||
// items.Add(new SearcherItem("Fantasy/J. R. R. Tolkien/The Fellowship of the Ring"));
|
||||
// items.Add(new SearcherItem("Fantasy/J. R. R. Tolkien/The Two Towers"));
|
||||
// items.Add(new SearcherItem("Fantasy/J. R. R. Tolkien/The Return of the King"));
|
||||
// items.Add(new SearcherItem("Health & Fitness/Becoming a Supple Leopard"));
|
||||
// items.Add(new SearcherItem("Some Uncategorized Book"));
|
||||
//
|
||||
// List<SearcherItem> itemsTree = SearcherTreeUtility.CreateFromFlatList(items);
|
||||
//
|
||||
// Will return the follow hierarchy:
|
||||
// - Fantasy
|
||||
// - - J. R. R. Tolkien
|
||||
// - - - The Fellowship of the Ring
|
||||
// - - - The Two Towers
|
||||
// - - - The Return of the King
|
||||
// - Health & Fitness
|
||||
// - - Becoming a Supple Leopard
|
||||
// - Some Uncategorized Book
|
||||
//
|
||||
// Where the first level of SearcherItems is directly inside the list.
|
||||
// Note that this will also break the names into their final path component.
|
||||
[PublicAPI]
|
||||
public static class SearcherTreeUtility
|
||||
{
|
||||
public static List<SearcherItem> CreateFromFlatList(List<SearcherItem> items)
|
||||
{
|
||||
List<SearcherItem> searchList = new List<SearcherItem>();
|
||||
for (int i = 0; i < items.Count; ++i)
|
||||
{
|
||||
SearcherItem item = items[i];
|
||||
string[] pathParts = item.Name.Split('/');
|
||||
SearcherItem searchNode = FindNodeByName(searchList, pathParts[0]);
|
||||
if (searchNode == null)
|
||||
{
|
||||
searchNode = new SearcherItem(pathParts[0]);
|
||||
searchList.Add(searchNode);
|
||||
}
|
||||
AddItem(searchNode, item, pathParts);
|
||||
}
|
||||
return searchList;
|
||||
}
|
||||
|
||||
private static void AddItem(SearcherItem root, SearcherItem item, string[] pathParts)
|
||||
{
|
||||
string itemFullPath = item.Name;
|
||||
string itemName = pathParts[pathParts.Length - 1];
|
||||
string currentPath = string.Empty;
|
||||
SearcherItem currentSearchNode = root;
|
||||
|
||||
for (int i = 1; i < pathParts.Length; ++i)
|
||||
{
|
||||
SearcherItem node = FindNodeByName(currentSearchNode.Children, pathParts[i]);
|
||||
if (node == null)
|
||||
{
|
||||
node = new SearcherItem(pathParts[i]);
|
||||
currentSearchNode.AddChild(node);
|
||||
}
|
||||
currentSearchNode = node;
|
||||
}
|
||||
// Set the user data to the final node, which is guaranteed to correspond to the item.
|
||||
currentSearchNode.UserData = item.UserData;
|
||||
currentSearchNode.Icon = item.Icon;
|
||||
}
|
||||
|
||||
private static SearcherItem FindNodeByName(IList<SearcherItem> searchList, string name)
|
||||
{
|
||||
for (int i = 0; i < searchList.Count; ++i)
|
||||
{
|
||||
if (searchList[i].Name.Equals(name))
|
||||
{
|
||||
return searchList[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: ab5bbe7300c57674db945e46e17a4630
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,428 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.Searcher
|
||||
{
|
||||
[PublicAPI]
|
||||
public class SearcherWindow : EditorWindow
|
||||
{
|
||||
[PublicAPI]
|
||||
public struct Alignment
|
||||
{
|
||||
[PublicAPI]
|
||||
public enum Horizontal { Left = 0, Center, Right }
|
||||
[PublicAPI]
|
||||
public enum Vertical { Top = 0, Center, Bottom }
|
||||
|
||||
public readonly Vertical vertical;
|
||||
public readonly Horizontal horizontal;
|
||||
|
||||
public Alignment(Vertical v, Horizontal h)
|
||||
{
|
||||
vertical = v;
|
||||
horizontal = h;
|
||||
}
|
||||
}
|
||||
|
||||
const string k_DatabaseDirectory = "/../Library/Searcher";
|
||||
|
||||
static readonly float k_SearcherDefaultWidth = 300;
|
||||
static readonly float k_DetailsDefaultWidth = 200;
|
||||
static readonly float k_DefaultHeight = 300;
|
||||
static readonly Vector2 k_MinSize = new Vector2(300, 150);
|
||||
|
||||
static Vector2 s_Size = Vector2.zero;
|
||||
static IEnumerable<SearcherItem> s_Items;
|
||||
static Searcher s_Searcher;
|
||||
static Func<SearcherItem, bool> s_ItemSelectedDelegate;
|
||||
|
||||
Action<Searcher.AnalyticsEvent> m_AnalyticsDataDelegate;
|
||||
SearcherControl m_SearcherControl;
|
||||
Vector2 m_OriginalMousePos;
|
||||
Rect m_OriginalWindowPos;
|
||||
Rect m_NewWindowPos;
|
||||
bool m_IsMouseDownOnResizer;
|
||||
bool m_IsMouseDownOnTitle;
|
||||
Focusable m_FocusedBefore;
|
||||
|
||||
static Vector2 Size
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Size == Vector2.zero)
|
||||
{
|
||||
s_Size = s_Searcher != null && s_Searcher.Adapter.HasDetailsPanel
|
||||
? new Vector2(k_SearcherDefaultWidth + k_DetailsDefaultWidth, k_DefaultHeight)
|
||||
: new Vector2(k_SearcherDefaultWidth, k_DefaultHeight);
|
||||
}
|
||||
|
||||
return s_Size;
|
||||
}
|
||||
set => s_Size = value;
|
||||
}
|
||||
|
||||
public static void Show(
|
||||
EditorWindow host,
|
||||
IList<SearcherItem> items,
|
||||
string title,
|
||||
Func<SearcherItem, bool> itemSelectedDelegate,
|
||||
Vector2 displayPosition,
|
||||
Alignment align = default)
|
||||
{
|
||||
Show(host, items, title, Application.dataPath + k_DatabaseDirectory, itemSelectedDelegate, displayPosition, align);
|
||||
}
|
||||
|
||||
public static void Show(
|
||||
EditorWindow host,
|
||||
IList<SearcherItem> items,
|
||||
ISearcherAdapter adapter,
|
||||
Func<SearcherItem, bool> itemSelectedDelegate,
|
||||
Vector2 displayPosition,
|
||||
Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
|
||||
Alignment align = default)
|
||||
{
|
||||
Show(host, items, adapter, Application.dataPath + k_DatabaseDirectory, itemSelectedDelegate,
|
||||
displayPosition, analyticsDataDelegate, align);
|
||||
}
|
||||
|
||||
public static void Show(
|
||||
EditorWindow host,
|
||||
IList<SearcherItem> items,
|
||||
string title,
|
||||
string directoryPath,
|
||||
Func<SearcherItem, bool> itemSelectedDelegate,
|
||||
Vector2 displayPosition,
|
||||
Alignment align = default)
|
||||
{
|
||||
s_Items = items;
|
||||
var databaseDir = directoryPath;
|
||||
var database = SearcherDatabase.Create(s_Items.ToList(), databaseDir);
|
||||
s_Searcher = new Searcher(database, title);
|
||||
|
||||
Show(host, s_Searcher, itemSelectedDelegate, displayPosition, null, align);
|
||||
}
|
||||
|
||||
public static void Show(
|
||||
EditorWindow host,
|
||||
IEnumerable<SearcherItem> items,
|
||||
ISearcherAdapter adapter,
|
||||
string directoryPath,
|
||||
Func<SearcherItem, bool> itemSelectedDelegate,
|
||||
Vector2 displayPosition,
|
||||
Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
|
||||
Alignment align = default)
|
||||
{
|
||||
s_Items = items;
|
||||
var databaseDir = directoryPath;
|
||||
var database = SearcherDatabase.Create(s_Items.ToList(), databaseDir);
|
||||
s_Searcher = new Searcher(database, adapter);
|
||||
|
||||
Show(host, s_Searcher, itemSelectedDelegate, displayPosition, analyticsDataDelegate, align);
|
||||
}
|
||||
|
||||
public static void Show(
|
||||
EditorWindow host,
|
||||
Searcher searcher,
|
||||
Func<SearcherItem, bool> itemSelectedDelegate,
|
||||
Vector2 displayPosition,
|
||||
Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
|
||||
Alignment align = default)
|
||||
{
|
||||
var position = GetPosition(host, displayPosition, align);
|
||||
var rect = new Rect(GetPositionWithAlignment(position + host.position.position, Size, align), Size);
|
||||
|
||||
Show(host, searcher, itemSelectedDelegate, analyticsDataDelegate, rect);
|
||||
}
|
||||
public static void Show(
|
||||
EditorWindow host,
|
||||
Searcher searcher,
|
||||
Func<SearcherItem, bool> itemSelectedDelegate,
|
||||
Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
|
||||
Rect rect)
|
||||
{
|
||||
s_Searcher = searcher;
|
||||
s_ItemSelectedDelegate = itemSelectedDelegate;
|
||||
|
||||
var window = CreateInstance<SearcherWindow>();
|
||||
window.m_AnalyticsDataDelegate = analyticsDataDelegate;
|
||||
window.position = rect;
|
||||
window.ShowPopup();
|
||||
window.Focus();
|
||||
}
|
||||
|
||||
public static Vector2 GetPositionWithAlignment(Vector2 pos, Vector2 size, Alignment align)
|
||||
{
|
||||
var x = pos.x;
|
||||
var y = pos.y;
|
||||
|
||||
switch (align.horizontal)
|
||||
{
|
||||
case Alignment.Horizontal.Center:
|
||||
x -= size.x / 2;
|
||||
break;
|
||||
|
||||
case Alignment.Horizontal.Right:
|
||||
x -= size.x;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (align.vertical)
|
||||
{
|
||||
case Alignment.Vertical.Center:
|
||||
y -= size.y / 2;
|
||||
break;
|
||||
|
||||
case Alignment.Vertical.Bottom:
|
||||
y -= size.y;
|
||||
break;
|
||||
}
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
static Vector2 GetPosition(EditorWindow host, Vector2 displayPosition, Alignment align)
|
||||
{
|
||||
var x = displayPosition.x;
|
||||
var y = displayPosition.y;
|
||||
|
||||
// Searcher overlaps with the right boundary.
|
||||
if (x + Size.x >= host.position.size.x)
|
||||
{
|
||||
switch (align.horizontal)
|
||||
{
|
||||
case Alignment.Horizontal.Center:
|
||||
x -= Size.x / 2;
|
||||
break;
|
||||
|
||||
case Alignment.Horizontal.Right:
|
||||
x -= Size.x;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The displayPosition should be in window world space but the
|
||||
// EditorWindow.position is actually the rootVisualElement
|
||||
// rectangle, not including the tabs area. So we need to do a
|
||||
// small correction here.
|
||||
y -= host.rootVisualElement.resolvedStyle.top;
|
||||
|
||||
// Searcher overlaps with the bottom boundary.
|
||||
if (y + Size.y >= host.position.size.y)
|
||||
{
|
||||
switch (align.vertical)
|
||||
{
|
||||
case Alignment.Vertical.Center:
|
||||
y -= Size.y / 2;
|
||||
break;
|
||||
|
||||
case Alignment.Vertical.Bottom:
|
||||
y -= Size.y;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_SearcherControl = new SearcherControl();
|
||||
m_SearcherControl.Setup(s_Searcher, SelectionCallback, OnAnalyticsDataCallback, s_Searcher.Adapter.OnSearchResultsFilter);
|
||||
|
||||
m_SearcherControl.TitleLabel.RegisterCallback<MouseDownEvent>(OnTitleMouseDown);
|
||||
m_SearcherControl.TitleLabel.RegisterCallback<MouseUpEvent>(OnTitleMouseUp);
|
||||
|
||||
m_SearcherControl.Resizer.RegisterCallback<MouseDownEvent>(OnResizerMouseDown);
|
||||
m_SearcherControl.Resizer.RegisterCallback<MouseUpEvent>(OnResizerMouseUp);
|
||||
|
||||
var root = rootVisualElement;
|
||||
root.style.flexGrow = 1;
|
||||
root.Add(m_SearcherControl);
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
m_SearcherControl.TitleLabel.UnregisterCallback<MouseDownEvent>(OnTitleMouseDown);
|
||||
m_SearcherControl.TitleLabel.UnregisterCallback<MouseUpEvent>(OnTitleMouseUp);
|
||||
|
||||
m_SearcherControl.Resizer.UnregisterCallback<MouseDownEvent>(OnResizerMouseDown);
|
||||
m_SearcherControl.Resizer.UnregisterCallback<MouseUpEvent>(OnResizerMouseUp);
|
||||
}
|
||||
|
||||
void OnTitleMouseDown(MouseDownEvent evt)
|
||||
{
|
||||
if (evt.button != (int)MouseButton.LeftMouse)
|
||||
return;
|
||||
|
||||
m_IsMouseDownOnTitle = true;
|
||||
|
||||
m_NewWindowPos = position;
|
||||
m_OriginalWindowPos = position;
|
||||
m_OriginalMousePos = evt.mousePosition;
|
||||
|
||||
m_FocusedBefore = rootVisualElement.panel.focusController.focusedElement;
|
||||
|
||||
m_SearcherControl.TitleLabel.RegisterCallback<MouseMoveEvent>(OnTitleMouseMove);
|
||||
m_SearcherControl.TitleLabel.RegisterCallback<KeyDownEvent>(OnSearcherKeyDown);
|
||||
m_SearcherControl.TitleLabel.CaptureMouse();
|
||||
}
|
||||
|
||||
void OnTitleMouseUp(MouseUpEvent evt)
|
||||
{
|
||||
if (evt.button != (int)MouseButton.LeftMouse)
|
||||
return;
|
||||
|
||||
if (!m_SearcherControl.TitleLabel.HasMouseCapture())
|
||||
return;
|
||||
|
||||
FinishMove();
|
||||
}
|
||||
|
||||
void FinishMove()
|
||||
{
|
||||
m_SearcherControl.TitleLabel.UnregisterCallback<MouseMoveEvent>(OnTitleMouseMove);
|
||||
m_SearcherControl.TitleLabel.UnregisterCallback<KeyDownEvent>(OnSearcherKeyDown);
|
||||
m_SearcherControl.TitleLabel.ReleaseMouse();
|
||||
m_FocusedBefore?.Focus();
|
||||
m_IsMouseDownOnTitle = false;
|
||||
}
|
||||
|
||||
void OnTitleMouseMove(MouseMoveEvent evt)
|
||||
{
|
||||
var delta = evt.mousePosition - m_OriginalMousePos;
|
||||
|
||||
// TODO Temporary fix for Visual Scripting 1st drop. Find why position.position is 0,0 on MacOs in MouseMoveEvent
|
||||
// Bug occurs with Unity 2019.2.0a13
|
||||
#if UNITY_EDITOR_OSX
|
||||
m_NewWindowPos = new Rect(m_NewWindowPos.position + delta, position.size);
|
||||
#else
|
||||
m_NewWindowPos = new Rect(position.position + delta, position.size);
|
||||
#endif
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void OnResizerMouseDown(MouseDownEvent evt)
|
||||
{
|
||||
if (evt.button != (int)MouseButton.LeftMouse)
|
||||
return;
|
||||
|
||||
m_IsMouseDownOnResizer = true;
|
||||
|
||||
m_NewWindowPos = position;
|
||||
m_OriginalWindowPos = position;
|
||||
m_OriginalMousePos = evt.mousePosition;
|
||||
|
||||
m_FocusedBefore = rootVisualElement.panel.focusController.focusedElement;
|
||||
|
||||
m_SearcherControl.Resizer.RegisterCallback<MouseMoveEvent>(OnResizerMouseMove);
|
||||
m_SearcherControl.Resizer.RegisterCallback<KeyDownEvent>(OnSearcherKeyDown);
|
||||
m_SearcherControl.Resizer.CaptureMouse();
|
||||
}
|
||||
|
||||
void OnResizerMouseUp(MouseUpEvent evt)
|
||||
{
|
||||
if (evt.button != (int)MouseButton.LeftMouse)
|
||||
return;
|
||||
|
||||
if (!m_SearcherControl.Resizer.HasMouseCapture())
|
||||
return;
|
||||
|
||||
FinishResize();
|
||||
}
|
||||
|
||||
void FinishResize()
|
||||
{
|
||||
m_SearcherControl.Resizer.UnregisterCallback<MouseMoveEvent>(OnResizerMouseMove);
|
||||
m_SearcherControl.Resizer.UnregisterCallback<KeyDownEvent>(OnSearcherKeyDown);
|
||||
m_SearcherControl.Resizer.ReleaseMouse();
|
||||
m_FocusedBefore?.Focus();
|
||||
m_IsMouseDownOnResizer = false;
|
||||
}
|
||||
|
||||
void OnResizerMouseMove(MouseMoveEvent evt)
|
||||
{
|
||||
var delta = evt.mousePosition - m_OriginalMousePos;
|
||||
Size = m_OriginalWindowPos.size + delta;
|
||||
Size = new Vector2(Math.Max(k_MinSize.x, Size.x), Math.Max(k_MinSize.y, Size.y));
|
||||
|
||||
// TODO Temporary fix for Visual Scripting 1st drop. Find why position.position is 0,0 on MacOs in MouseMoveEvent
|
||||
// Bug occurs with Unity 2019.2.0a13
|
||||
#if UNITY_EDITOR_OSX
|
||||
m_NewWindowPos = new Rect(m_NewWindowPos.position, Size);
|
||||
#else
|
||||
m_NewWindowPos = new Rect(position.position, Size);
|
||||
#endif
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void OnSearcherKeyDown(KeyDownEvent evt)
|
||||
{
|
||||
if (evt.keyCode == KeyCode.Escape)
|
||||
{
|
||||
if (m_IsMouseDownOnTitle)
|
||||
{
|
||||
FinishMove();
|
||||
position = m_OriginalWindowPos;
|
||||
}
|
||||
else if (m_IsMouseDownOnResizer)
|
||||
{
|
||||
FinishResize();
|
||||
position = m_OriginalWindowPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if ((m_IsMouseDownOnTitle || m_IsMouseDownOnResizer) && Event.current.type == EventType.Layout)
|
||||
position = m_NewWindowPos;
|
||||
}
|
||||
|
||||
void SelectionCallback(SearcherItem item)
|
||||
{
|
||||
// Don't close the window if a category is selected (only categories/titles have children, node entries are leaf elements)
|
||||
// We want to prevent collapsing the window due to accidental double-clicks on a title entry, for instance
|
||||
if (item != null && item.HasChildren)
|
||||
return;
|
||||
|
||||
if (s_ItemSelectedDelegate == null || s_ItemSelectedDelegate(item))
|
||||
Close();
|
||||
}
|
||||
|
||||
void OnAnalyticsDataCallback(Searcher.AnalyticsEvent item)
|
||||
{
|
||||
m_AnalyticsDataDelegate?.Invoke(item);
|
||||
}
|
||||
|
||||
void OnLostFocus()
|
||||
{
|
||||
if (m_IsMouseDownOnTitle)
|
||||
{
|
||||
FinishMove();
|
||||
}
|
||||
else if (m_IsMouseDownOnResizer)
|
||||
{
|
||||
FinishResize();
|
||||
}
|
||||
|
||||
// TODO: HACK - ListView's scroll view steals focus using the scheduler.
|
||||
EditorApplication.update += HackDueToCloseOnLostFocusCrashing;
|
||||
}
|
||||
|
||||
// See: https://fogbugz.unity3d.com/f/cases/1004504/
|
||||
void HackDueToCloseOnLostFocusCrashing()
|
||||
{
|
||||
// Notify user that the searcher action was cancelled.
|
||||
s_ItemSelectedDelegate?.Invoke(null);
|
||||
|
||||
Close();
|
||||
|
||||
// ReSharper disable once DelegateSubtraction
|
||||
EditorApplication.update -= HackDueToCloseOnLostFocusCrashing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3fe161b2564a4d318026d694ffdb0520
|
||||
timeCreated: 1518808441
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Unity.Searcher.Editor",
|
||||
"references": [],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": []
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4988cf9794f41d64c884876ab6574b89
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Add table
Add a link
Reference in a new issue