490 lines
23 KiB
C#
490 lines
23 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor.AddressableAssets.Build;
|
|
using UnityEditor.AddressableAssets.Settings;
|
|
using UnityEngine;
|
|
using UnityEngine.TestTools;
|
|
|
|
namespace UnityEditor.AddressableAssets.GUI
|
|
{
|
|
using Object = UnityEngine.Object;
|
|
|
|
[InitializeOnLoad, ExcludeFromCoverage]
|
|
internal static class AddressableAssetInspectorGUI
|
|
{
|
|
static GUIStyle s_ToggleMixed;
|
|
static GUIContent s_AddressableAssetToggleText;
|
|
|
|
static GUIContent s_SystemSettingsGUIContent = new GUIContent("System Settings", "View Addressable Asset Settings");
|
|
|
|
static GUIContent s_GroupsDropdownLabelContent = new GUIContent("Group", "The Addressable Group that this asset is assigned to.");
|
|
|
|
static string s_GroupsDropdownControlName = nameof(AddressableAssetInspectorGUI) + ".GroupsPopupField";
|
|
static Texture s_GroupsCaretTexture = null;
|
|
static Texture s_FolderTexture = null;
|
|
|
|
static AddressableAssetInspectorGUI()
|
|
{
|
|
s_ToggleMixed = null;
|
|
s_AddressableAssetToggleText = new GUIContent("Addressable",
|
|
"Check this to mark this asset as an Addressable Asset, which includes it in the bundled data and makes it loadable via script by its address.");
|
|
Editor.finishedDefaultHeaderGUI += OnPostHeaderGUI;
|
|
}
|
|
|
|
static void SetAaEntry(AddressableAssetSettings aaSettings, List<TargetInfo> targetInfos, bool create)
|
|
{
|
|
if (create && aaSettings.DefaultGroup.ReadOnly)
|
|
{
|
|
Debug.LogError("Current default group is ReadOnly. Cannot add addressable assets to it");
|
|
return;
|
|
}
|
|
|
|
Undo.RecordObject(aaSettings, "AddressableAssetSettings");
|
|
|
|
if (!create)
|
|
{
|
|
List<AddressableAssetEntry> removedEntries = new List<AddressableAssetEntry>(targetInfos.Count);
|
|
for (int i = 0; i < targetInfos.Count; ++i)
|
|
{
|
|
AddressableAssetEntry e = aaSettings.FindAssetEntry(targetInfos[i].Guid);
|
|
AddressableAssetUtility.OpenAssetIfUsingVCIntegration(e.parentGroup);
|
|
removedEntries.Add(e);
|
|
aaSettings.RemoveAssetEntry(removedEntries[i], false);
|
|
}
|
|
|
|
if (removedEntries.Count > 0)
|
|
aaSettings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryRemoved, removedEntries, true, false);
|
|
}
|
|
else
|
|
{
|
|
AddressableAssetGroup parentGroup = aaSettings.DefaultGroup;
|
|
var resourceTargets = targetInfos.Where(ti => AddressableAssetUtility.IsInResources(ti.Path));
|
|
if (resourceTargets.Any())
|
|
{
|
|
var resourcePaths = resourceTargets.Select(t => t.Path).ToList();
|
|
var resourceGuids = resourceTargets.Select(t => t.Guid).ToList();
|
|
AddressableAssetUtility.SafeMoveResourcesToGroup(aaSettings, parentGroup, resourcePaths, resourceGuids);
|
|
}
|
|
|
|
var otherTargetInfos = targetInfos.Except(resourceTargets);
|
|
List<string> otherTargetGuids = new List<string>(targetInfos.Count);
|
|
foreach (var info in otherTargetInfos)
|
|
otherTargetGuids.Add(info.Guid);
|
|
|
|
var entriesCreated = new List<AddressableAssetEntry>();
|
|
var entriesMoved = new List<AddressableAssetEntry>();
|
|
aaSettings.CreateOrMoveEntries(otherTargetGuids, parentGroup, entriesCreated, entriesMoved, false, false);
|
|
|
|
bool openedInVC = false;
|
|
if (entriesMoved.Count > 0)
|
|
{
|
|
AddressableAssetUtility.OpenAssetIfUsingVCIntegration(parentGroup);
|
|
openedInVC = true;
|
|
aaSettings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entriesMoved, true, false);
|
|
}
|
|
|
|
if (entriesCreated.Count > 0)
|
|
{
|
|
if (!openedInVC)
|
|
AddressableAssetUtility.OpenAssetIfUsingVCIntegration(parentGroup);
|
|
aaSettings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryAdded, entriesCreated, true, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
[UnityEngine.TestTools.ExcludeFromCoverage]
|
|
static void OnPostHeaderGUI(Editor editor)
|
|
{
|
|
var aaSettings = AddressableAssetSettingsDefaultObject.Settings;
|
|
|
|
if (editor.targets.Length > 0)
|
|
{
|
|
// only display for the Prefab/Model importer not the displayed GameObjects
|
|
if (editor.targets[0].GetType() == typeof(GameObject))
|
|
return;
|
|
|
|
foreach (var t in editor.targets)
|
|
{
|
|
if (t is AddressableAssetGroupSchema)
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
GUILayout.Label("Profile: " + AddressableAssetSettingsDefaultObject.GetSettings(true).profileSettings
|
|
.GetProfileName(AddressableAssetSettingsDefaultObject.GetSettings(true).activeProfileId));
|
|
|
|
GUILayout.FlexibleSpace();
|
|
if (GUILayout.Button(s_SystemSettingsGUIContent, "MiniButton"))
|
|
{
|
|
EditorGUIUtility.PingObject(AddressableAssetSettingsDefaultObject.Settings);
|
|
Selection.activeObject = AddressableAssetSettingsDefaultObject.Settings;
|
|
}
|
|
|
|
GUILayout.EndHorizontal();
|
|
return;
|
|
}
|
|
}
|
|
|
|
List<TargetInfo> targetInfos = GatherTargetInfos(editor.targets, aaSettings);
|
|
if (targetInfos.Count == 0)
|
|
return;
|
|
|
|
bool targetHasAddressableSubObject = false;
|
|
int mainAssetsAddressable = 0;
|
|
int subAssetsAddressable = 0;
|
|
foreach (TargetInfo info in targetInfos)
|
|
{
|
|
if (info.MainAssetEntry == null)
|
|
continue;
|
|
if (info.MainAssetEntry.IsSubAsset)
|
|
subAssetsAddressable++;
|
|
else
|
|
mainAssetsAddressable++;
|
|
if (!info.IsMainAsset)
|
|
targetHasAddressableSubObject = true;
|
|
}
|
|
|
|
// Overrides a DisabledScope in the EditorElement.cs that disables GUI drawn in the header when the asset cannot be edited.
|
|
bool prevEnabledState = UnityEngine.GUI.enabled;
|
|
if (targetHasAddressableSubObject)
|
|
UnityEngine.GUI.enabled = false;
|
|
else
|
|
{
|
|
UnityEngine.GUI.enabled = true;
|
|
foreach (var info in targetInfos)
|
|
{
|
|
if (!info.IsMainAsset)
|
|
{
|
|
UnityEngine.GUI.enabled = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
int totalAddressableCount = mainAssetsAddressable + subAssetsAddressable;
|
|
if (totalAddressableCount == 0) // nothing is addressable
|
|
{
|
|
if (GUILayout.Toggle(false, s_AddressableAssetToggleText, GUILayout.ExpandWidth(false)))
|
|
SetAaEntry(AddressableAssetSettingsDefaultObject.GetSettings(true), targetInfos, true);
|
|
}
|
|
else if (totalAddressableCount == editor.targets.Length) // everything is addressable
|
|
{
|
|
var entryInfo = targetInfos[targetInfos.Count - 1];
|
|
if (entryInfo == null || entryInfo.MainAssetEntry == null)
|
|
throw new NullReferenceException("EntryInfo incorrect for Addressables content.");
|
|
|
|
GUILayout.BeginHorizontal();
|
|
|
|
if (mainAssetsAddressable > 0 && subAssetsAddressable > 0)
|
|
{
|
|
if (s_ToggleMixed == null)
|
|
s_ToggleMixed = new GUIStyle("ToggleMixed");
|
|
if (GUILayout.Toggle(false, s_AddressableAssetToggleText, s_ToggleMixed, GUILayout.ExpandWidth(false)))
|
|
SetAaEntry(aaSettings, targetInfos, true);
|
|
}
|
|
else if (mainAssetsAddressable > 0)
|
|
{
|
|
if (!GUILayout.Toggle(true, s_AddressableAssetToggleText, GUILayout.ExpandWidth(false)))
|
|
{
|
|
SetAaEntry(aaSettings, targetInfos, false);
|
|
UnityEngine.GUI.enabled = prevEnabledState;
|
|
GUIUtility.ExitGUI();
|
|
}
|
|
}
|
|
else if (GUILayout.Toggle(false, s_AddressableAssetToggleText, GUILayout.ExpandWidth(false)))
|
|
SetAaEntry(aaSettings, targetInfos, true);
|
|
|
|
if (editor.targets.Length == 1)
|
|
{
|
|
if (!entryInfo.IsMainAsset || entryInfo.MainAssetEntry.IsSubAsset)
|
|
{
|
|
bool preAddressPrevEnabledState = UnityEngine.GUI.enabled;
|
|
UnityEngine.GUI.enabled = false;
|
|
string address = entryInfo.Address + (entryInfo.IsMainAsset ? "" : $"[{entryInfo.TargetObject.name}]");
|
|
EditorGUILayout.DelayedTextField(address, GUILayout.ExpandWidth(true));
|
|
UnityEngine.GUI.enabled = preAddressPrevEnabledState;
|
|
}
|
|
else
|
|
{
|
|
string newAddress = EditorGUILayout.DelayedTextField(entryInfo.Address, GUILayout.ExpandWidth(true));
|
|
if (newAddress != entryInfo.Address)
|
|
{
|
|
if (newAddress.Contains('[') && newAddress.Contains(']'))
|
|
Debug.LogErrorFormat("Rename of address '{0}' cannot contain '[ ]'.", entryInfo.Address);
|
|
else
|
|
{
|
|
entryInfo.MainAssetEntry.address = newAddress;
|
|
AddressableAssetUtility.OpenAssetIfUsingVCIntegration(entryInfo.MainAssetEntry.parentGroup, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FindUniqueAssetGuids(targetInfos, out var uniqueAssetGuids, out var uniqueAddressableAssetGuids);
|
|
EditorGUILayout.LabelField(uniqueAddressableAssetGuids.Count + " out of " + uniqueAssetGuids.Count + " assets are addressable.");
|
|
}
|
|
|
|
DrawSelectEntriesButton(targetInfos);
|
|
GUILayout.EndHorizontal();
|
|
|
|
GUILayout.BeginHorizontal();
|
|
DrawGroupsDropdown(aaSettings, targetInfos);
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
else // mixed addressable selected
|
|
{
|
|
GUILayout.BeginHorizontal();
|
|
if (s_ToggleMixed == null)
|
|
s_ToggleMixed = new GUIStyle("ToggleMixed");
|
|
if (GUILayout.Toggle(false, s_AddressableAssetToggleText, s_ToggleMixed, GUILayout.ExpandWidth(false)))
|
|
SetAaEntry(AddressableAssetSettingsDefaultObject.GetSettings(true), targetInfos, true);
|
|
FindUniqueAssetGuids(targetInfos, out var uniqueAssetGuids, out var uniqueAddressableAssetGuids);
|
|
EditorGUILayout.LabelField(uniqueAddressableAssetGuids.Count + " out of " + uniqueAssetGuids.Count + " assets are addressable.");
|
|
DrawSelectEntriesButton(targetInfos);
|
|
GUILayout.EndHorizontal();
|
|
}
|
|
|
|
UnityEngine.GUI.enabled = prevEnabledState;
|
|
}
|
|
}
|
|
|
|
// Caching due to Gathering TargetInfos is an expensive operation
|
|
// The InspectorGUI needs to call this multiple times per layout and paint
|
|
private static AddressableAssetSettings.Cache<int, List<TargetInfo>> s_Cache = null;
|
|
|
|
internal static List<TargetInfo> GatherTargetInfos(Object[] targets, AddressableAssetSettings aaSettings)
|
|
{
|
|
int selectionHashCode = targets[0].GetHashCode();
|
|
for (int i = 1; i < targets.Length; ++i)
|
|
selectionHashCode = selectionHashCode * 31 ^ targets[i].GetHashCode();
|
|
|
|
List<TargetInfo> targetInfos = null;
|
|
if (s_Cache == null && aaSettings != null)
|
|
s_Cache = new AddressableAssetSettings.Cache<int, List<TargetInfo>>(aaSettings);
|
|
if (s_Cache != null && s_Cache.TryGetCached(selectionHashCode, out targetInfos))
|
|
return targetInfos;
|
|
|
|
targetInfos = new List<TargetInfo>(targets.Length);
|
|
AddressableAssetEntry entry;
|
|
foreach (var t in targets)
|
|
{
|
|
if (AddressableAssetUtility.TryGetPathAndGUIDFromTarget(t, out var path, out var guid))
|
|
{
|
|
var mainAssetType = AssetDatabase.GetMainAssetTypeAtPath(path);
|
|
// Is asset
|
|
if (mainAssetType != null && !BuildUtility.IsEditorAssembly(mainAssetType.Assembly))
|
|
{
|
|
bool isMainAsset = t is AssetImporter || AssetDatabase.IsMainAsset(t);
|
|
var info = new TargetInfo() {TargetObject = t, Guid = guid, Path = path, IsMainAsset = isMainAsset};
|
|
if (aaSettings != null)
|
|
{
|
|
entry = aaSettings.FindAssetEntry(guid, true);
|
|
if (entry != null)
|
|
info.MainAssetEntry = entry;
|
|
}
|
|
|
|
targetInfos.Add(info);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s_Cache != null && targetInfos != null && targetInfos.Count > 0)
|
|
s_Cache.Add(selectionHashCode, targetInfos);
|
|
return targetInfos;
|
|
}
|
|
|
|
internal static void FindUniqueAssetGuids(List<TargetInfo> targetInfos, out HashSet<string> uniqueAssetGuids, out HashSet<string> uniqueAddressableAssetGuids)
|
|
{
|
|
uniqueAssetGuids = new HashSet<string>();
|
|
uniqueAddressableAssetGuids = new HashSet<string>();
|
|
foreach (TargetInfo info in targetInfos)
|
|
{
|
|
uniqueAssetGuids.Add(info.Guid);
|
|
if (info.MainAssetEntry != null)
|
|
uniqueAddressableAssetGuids.Add(info.Guid);
|
|
}
|
|
}
|
|
|
|
static void DrawSelectEntriesButton(List<TargetInfo> targets)
|
|
{
|
|
var prevGuiEnabled = UnityEngine.GUI.enabled;
|
|
UnityEngine.GUI.enabled = true;
|
|
|
|
if (GUILayout.Button("Select"))
|
|
{
|
|
AddressableAssetsWindow.Init();
|
|
var window = EditorWindow.GetWindow<AddressableAssetsWindow>();
|
|
List<AddressableAssetEntry> entries = new List<AddressableAssetEntry>(targets.Count);
|
|
foreach (TargetInfo info in targets)
|
|
{
|
|
if (info.MainAssetEntry != null)
|
|
{
|
|
if (info.IsMainAsset == false && ProjectConfigData.ShowSubObjectsInGroupView)
|
|
{
|
|
List<AddressableAssetEntry> subs = new List<AddressableAssetEntry>();
|
|
info.MainAssetEntry.GatherAllAssets(subs, false, true, true);
|
|
foreach (AddressableAssetEntry sub in subs)
|
|
{
|
|
if (sub.TargetAsset == info.TargetObject)
|
|
{
|
|
entries.Add(sub);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
entries.Add(info.MainAssetEntry);
|
|
}
|
|
}
|
|
|
|
if (entries.Count > 0)
|
|
window.SelectAssetsInGroupEditor(entries);
|
|
}
|
|
|
|
UnityEngine.GUI.enabled = prevGuiEnabled;
|
|
}
|
|
|
|
static void DrawGroupsDropdown(AddressableAssetSettings settings, List<TargetInfo> targets)
|
|
{
|
|
bool canEditGroup = true;
|
|
bool mixedGroups = false;
|
|
AddressableAssetGroup displayGroup = null;
|
|
var entries = new List<AddressableAssetEntry>();
|
|
foreach (TargetInfo info in targets)
|
|
{
|
|
AddressableAssetEntry entry = info.MainAssetEntry;
|
|
if (entry == null)
|
|
{
|
|
canEditGroup = false;
|
|
}
|
|
else
|
|
{
|
|
entries.Add(entry);
|
|
if (entry.ReadOnly || entry.parentGroup.ReadOnly)
|
|
{
|
|
canEditGroup = false;
|
|
}
|
|
|
|
if (displayGroup == null)
|
|
displayGroup = entry.parentGroup;
|
|
else if (entry.parentGroup != displayGroup)
|
|
{
|
|
mixedGroups = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
using (new EditorGUI.DisabledScope(!canEditGroup))
|
|
{
|
|
GUILayout.Label(s_GroupsDropdownLabelContent);
|
|
if (mixedGroups)
|
|
EditorGUI.showMixedValue = true;
|
|
|
|
UnityEngine.GUI.SetNextControlName(s_GroupsDropdownControlName);
|
|
|
|
float iconHeight = EditorGUIUtility.singleLineHeight - EditorGUIUtility.standardVerticalSpacing * 3;
|
|
Vector2 iconSize = EditorGUIUtility.GetIconSize();
|
|
EditorGUIUtility.SetIconSize(new Vector2(iconHeight, iconHeight));
|
|
if (s_FolderTexture == null)
|
|
{
|
|
s_FolderTexture = EditorGUIUtility.IconContent("Folder Icon").image;
|
|
}
|
|
|
|
GUIContent groupGUIContent = new GUIContent(displayGroup.Name, s_FolderTexture);
|
|
Rect groupFieldRect = GUILayoutUtility.GetRect(groupGUIContent, EditorStyles.objectField);
|
|
EditorGUI.DropdownButton(groupFieldRect, groupGUIContent, FocusType.Keyboard, EditorStyles.objectField);
|
|
EditorGUIUtility.SetIconSize(new Vector2(iconSize.x, iconSize.y));
|
|
|
|
if (mixedGroups)
|
|
EditorGUI.showMixedValue = false;
|
|
|
|
float pickerWidth = 12f;
|
|
Rect groupFieldRectNoPicker = new Rect(groupFieldRect);
|
|
groupFieldRectNoPicker.xMax = groupFieldRect.xMax - pickerWidth * 1.33f;
|
|
|
|
Rect pickerRect = new Rect(groupFieldRectNoPicker.xMax, groupFieldRectNoPicker.y, pickerWidth, groupFieldRectNoPicker.height);
|
|
bool isPickerPressed = Event.current.clickCount == 1 && pickerRect.Contains(Event.current.mousePosition);
|
|
|
|
DrawCaret(pickerRect);
|
|
|
|
if (canEditGroup)
|
|
{
|
|
bool isEnterKeyPressed = Event.current.type == EventType.KeyDown && Event.current.isKey && (Event.current.keyCode == KeyCode.KeypadEnter || Event.current.keyCode == KeyCode.Return);
|
|
bool enterKeyRequestsPopup = isEnterKeyPressed && (s_GroupsDropdownControlName == UnityEngine.GUI.GetNameOfFocusedControl());
|
|
if (isPickerPressed || enterKeyRequestsPopup)
|
|
{
|
|
EditorWindow.GetWindow<GroupsPopupWindow>(true, "Select Addressable Group").Initialize(settings, entries, !mixedGroups, true, Event.current.mousePosition, AddressableAssetUtility.MoveEntriesToGroup);
|
|
}
|
|
|
|
bool isDragging = Event.current.type == EventType.DragUpdated && groupFieldRectNoPicker.Contains(Event.current.mousePosition);
|
|
bool isDropping = Event.current.type == EventType.DragPerform && groupFieldRectNoPicker.Contains(Event.current.mousePosition);
|
|
HandleDragAndDrop(settings, entries, isDragging, isDropping);
|
|
}
|
|
|
|
if (!mixedGroups)
|
|
{
|
|
if (Event.current.clickCount == 1 && groupFieldRectNoPicker.Contains(Event.current.mousePosition))
|
|
{
|
|
UnityEngine.GUI.FocusControl(s_GroupsDropdownControlName);
|
|
AddressableAssetsWindow.Init();
|
|
var window = EditorWindow.GetWindow<AddressableAssetsWindow>();
|
|
window.SelectGroupInGroupEditor(displayGroup, false);
|
|
}
|
|
|
|
if (Event.current.clickCount == 2 && groupFieldRectNoPicker.Contains(Event.current.mousePosition))
|
|
{
|
|
AddressableAssetsWindow.Init();
|
|
var window = EditorWindow.GetWindow<AddressableAssetsWindow>();
|
|
window.SelectGroupInGroupEditor(displayGroup, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DrawCaret(Rect pickerRect)
|
|
{
|
|
if (s_GroupsCaretTexture == null)
|
|
{
|
|
s_GroupsCaretTexture = EditorGUIUtility.IconContent("d_pick").image;
|
|
}
|
|
UnityEngine.GUI.DrawTexture(pickerRect, s_GroupsCaretTexture, ScaleMode.ScaleToFit);
|
|
}
|
|
|
|
static void HandleDragAndDrop(AddressableAssetSettings settings, List<AddressableAssetEntry> aaEntries, bool isDragging, bool isDropping)
|
|
{
|
|
var groupItems = DragAndDrop.GetGenericData("AssetEntryTreeViewItem") as List<AssetEntryTreeViewItem>;
|
|
if (isDragging)
|
|
{
|
|
bool canDragGroup = groupItems != null && groupItems.Count == 1 && groupItems[0].IsGroup && !groupItems[0].group.ReadOnly;
|
|
DragAndDrop.visualMode = canDragGroup ? DragAndDropVisualMode.Copy : DragAndDropVisualMode.Rejected;
|
|
}
|
|
else if (isDropping)
|
|
{
|
|
if (groupItems != null)
|
|
{
|
|
var group = groupItems[0].group;
|
|
AddressableAssetUtility.MoveEntriesToGroup(settings, aaEntries, group);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class TargetInfo
|
|
{
|
|
public UnityEngine.Object TargetObject;
|
|
public string Guid;
|
|
public string Path;
|
|
public bool IsMainAsset;
|
|
public AddressableAssetEntry MainAssetEntry;
|
|
|
|
public string Address
|
|
{
|
|
get
|
|
{
|
|
if (MainAssetEntry == null)
|
|
throw new NullReferenceException("No Entry set for Target info with AssetPath " + Path);
|
|
return MainAssetEntry.address;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|