initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
|
@ -0,0 +1,106 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEditor.AddressableAssets.Settings;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.AddressableAssets.GUIElements
|
||||
{
|
||||
internal class DocumentationButton : VisualElement
|
||||
{
|
||||
public new class UxmlFactory : UxmlFactory<DocumentationButton, UxmlTraits> { }
|
||||
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
UxmlStringAttributeDescription m_Text = new UxmlStringAttributeDescription { name = "text", defaultValue = "" };
|
||||
UxmlStringAttributeDescription m_Page = new UxmlStringAttributeDescription { name = "page", defaultValue = "index.html" };
|
||||
// example page link: index.html or AddressableAssetSettings.html#profile
|
||||
|
||||
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new UxmlChildElementDescription(typeof(DocumentationButton));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
string page = m_Page.GetValueFromBag(bag, cc);
|
||||
string text = m_Text.GetValueFromBag(bag, cc);
|
||||
|
||||
if (string.IsNullOrEmpty(text))
|
||||
((DocumentationButton)ve).Initialise(page);
|
||||
else
|
||||
((DocumentationButton)ve).Initialise(page, text);
|
||||
}
|
||||
}
|
||||
|
||||
public override VisualElement contentContainer
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Button;
|
||||
}
|
||||
}
|
||||
|
||||
private Button m_Button;
|
||||
private string m_URL;
|
||||
|
||||
public void Initialise(string page)
|
||||
{
|
||||
InitButton(page);
|
||||
m_Button.style.minWidth = new StyleLength(17);
|
||||
m_Button.style.maxWidth = new StyleLength(17);
|
||||
m_Button.style.maxHeight = new StyleLength(17);
|
||||
m_Button.style.minHeight = new StyleLength(17);
|
||||
|
||||
string textureName = EditorGUIUtility.isProSkin ? "d__Help@2x" : "_Help@2x";
|
||||
m_Button.style.backgroundImage = new StyleBackground(EditorGUIUtility.IconContent(textureName).image as Texture2D);
|
||||
|
||||
hierarchy.Add(m_Button);
|
||||
}
|
||||
|
||||
public void Initialise(string page, string labelText)
|
||||
{
|
||||
InitButton(page);
|
||||
|
||||
VisualElement icon = new VisualElement();
|
||||
icon.AddToClassList("link-icon");
|
||||
m_Button.Add(icon);
|
||||
|
||||
Label label = new Label(labelText);
|
||||
label.AddToClassList("link-text");
|
||||
m_Button.Add(label);
|
||||
|
||||
hierarchy.Add(m_Button);
|
||||
}
|
||||
|
||||
private void InitButton(string page)
|
||||
{
|
||||
if (m_Button != null)
|
||||
hierarchy.Remove(m_Button);
|
||||
m_Button = new Button();
|
||||
m_Button.clicked += OpenDocumentation;
|
||||
SetDocumentation(page);
|
||||
|
||||
m_Button.style.borderBottomWidth = new StyleFloat(0f);
|
||||
m_Button.style.borderTopWidth = new StyleFloat(0f);
|
||||
m_Button.style.borderLeftWidth = new StyleFloat(0f);
|
||||
m_Button.style.borderRightWidth = new StyleFloat(0f);
|
||||
m_Button.style.backgroundColor = new StyleColor(Color.clear);
|
||||
m_Button.style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
|
||||
}
|
||||
|
||||
private void SetDocumentation(string page)
|
||||
{
|
||||
m_URL = AddressableAssetUtility.GenerateDocsURL(page);
|
||||
}
|
||||
|
||||
private void OpenDocumentation()
|
||||
{
|
||||
Application.OpenURL(m_URL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 240d85f3e514d2949a7aa8c95b24ef2f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
using System.Collections.Generic;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.AddressableAssets.GUIElements
|
||||
{
|
||||
internal static class GUIUtility
|
||||
{
|
||||
public static readonly string OpenManualTooltip = L10n.Tr("Open the relevant documentation entry.");
|
||||
|
||||
public const string HelpIconButtonClass = "icon-button__help-icon";
|
||||
public const string MenuIconButtonClass = "icon-button__menu-icon";
|
||||
|
||||
public const string UIToolKitAssetsPath = "Packages/com.unity.addressables/Editor/GUI/GUIElements/";
|
||||
public const string UxmlFilesPath = UIToolKitAssetsPath + "UXML/";
|
||||
public const string StyleSheetsPath = UIToolKitAssetsPath + "StyleSheets/";
|
||||
|
||||
public const string RibbonUxmlPath = UxmlFilesPath + "Ribbon.uxml";
|
||||
public const string RibbonUssPath = StyleSheetsPath + "Ribbon.uss";
|
||||
public const string RibbonDarkUssPath = StyleSheetsPath + "Ribbon_dark.uss";
|
||||
public const string RibbonLightUssPath = StyleSheetsPath + "Ribbon_light.uss";
|
||||
|
||||
public static void SetVisibility(VisualElement element, bool visible)
|
||||
{
|
||||
SetElementDisplay(element, visible);
|
||||
}
|
||||
|
||||
public static void SetElementDisplay(VisualElement element, bool value)
|
||||
{
|
||||
if (element == null)
|
||||
return;
|
||||
|
||||
element.style.display = value ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
element.style.visibility = value ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
public static VisualElement Clone(this VisualTreeAsset tree, VisualElement target = null, string styleSheetPath = null, Dictionary<string, VisualElement> slots = null)
|
||||
{
|
||||
var ret = tree.CloneTree();
|
||||
if (!string.IsNullOrEmpty(styleSheetPath))
|
||||
ret.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(styleSheetPath));
|
||||
if (target != null)
|
||||
target.Add(ret);
|
||||
ret.style.flexGrow = 1f;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void SwitchClasses(this VisualElement element, string classToAdd, string classToRemove)
|
||||
{
|
||||
if (!element.ClassListContains(classToAdd))
|
||||
element.AddToClassList(classToAdd);
|
||||
element.RemoveFromClassList(classToRemove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3d3b5d9a79a143a3bd3e0b1506282c2b
|
||||
timeCreated: 1674225117
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.AddressableAssets.GUIElements
|
||||
{
|
||||
internal class LabeledLabel : VisualElement
|
||||
{
|
||||
public new class UxmlFactory : UxmlFactory<LabeledLabel, UxmlTraits> { }
|
||||
public override VisualElement contentContainer
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_Container;
|
||||
}
|
||||
}
|
||||
|
||||
private VisualElement m_Container;
|
||||
private Label m_Label;
|
||||
private Label m_Text;
|
||||
|
||||
public bool Wrap
|
||||
{
|
||||
set
|
||||
{
|
||||
m_Text.style.flexWrap = new StyleEnum<Wrap>(value ? UnityEngine.UIElements.Wrap.Wrap : UnityEngine.UIElements.Wrap.NoWrap);
|
||||
m_Text.style.flexShrink = new StyleFloat(1);
|
||||
m_Text.style.whiteSpace = new StyleEnum<WhiteSpace>(WhiteSpace.Normal);
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
set { m_Text.text = value; }
|
||||
}
|
||||
|
||||
public string Label
|
||||
{
|
||||
set { m_Label.text = $"<b>{value}</b>"; }
|
||||
}
|
||||
|
||||
public LabeledLabel()
|
||||
{
|
||||
Initialise("", "", true);
|
||||
}
|
||||
public LabeledLabel(string label, string text, bool wrap)
|
||||
{
|
||||
Initialise(label, text, wrap);
|
||||
}
|
||||
|
||||
private void Initialise(string label, string text, bool wrap)
|
||||
{
|
||||
m_Container = new VisualElement();
|
||||
m_Container.style.flexDirection = new StyleEnum<FlexDirection>(FlexDirection.Row);
|
||||
m_Container.style.flexGrow = new StyleFloat(1);
|
||||
|
||||
m_Label = new Label($"<b>{label}</b>");
|
||||
m_Label.style.width = new StyleLength(100);
|
||||
m_Label.style.minWidth = new StyleLength(100);
|
||||
m_Label.style.maxWidth = new StyleLength(100);
|
||||
if (wrap)
|
||||
{
|
||||
m_Label.style.flexWrap = new StyleEnum<Wrap>(UnityEngine.UIElements.Wrap.Wrap);
|
||||
m_Label.style.flexShrink = new StyleFloat(1);
|
||||
m_Label.style.whiteSpace = new StyleEnum<WhiteSpace>(WhiteSpace.Normal);
|
||||
}
|
||||
|
||||
m_Label.tooltip = label;
|
||||
m_Container.Add(m_Label);
|
||||
|
||||
m_Text = new Label(text);
|
||||
m_Text.style.width = new StyleLength(StyleKeyword.Auto);
|
||||
if (wrap)
|
||||
{
|
||||
m_Text.style.flexWrap = new StyleEnum<Wrap>(UnityEngine.UIElements.Wrap.Wrap);
|
||||
m_Text.style.flexShrink = new StyleFloat(1);
|
||||
m_Text.style.whiteSpace = new StyleEnum<WhiteSpace>(WhiteSpace.Normal);
|
||||
}
|
||||
m_Container.Add(m_Text);
|
||||
|
||||
hierarchy.Add(m_Container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c73685de6a2030a48a67d78ba454753c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,249 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
[assembly: UxmlNamespacePrefix("UnityEditor.AddressableAssets.GUIElements", "AddressablesGUI")]
|
||||
namespace UnityEditor.AddressableAssets.GUIElements
|
||||
{
|
||||
internal class Ribbon : VisualElement
|
||||
{
|
||||
static class Content
|
||||
{
|
||||
// Technically Rbbon is a UI controll that could be more agnostic of this specific implementation for the Memory Profiler.
|
||||
// While this content may be reused in other places of the Memory Profiler and therefore makes sense to be located on TextContent,
|
||||
// keeping a Content class here makes it easier to keep things separated and helps when copying it out into other tools.
|
||||
public static readonly string OpenManualTooltip = GUIUtility.OpenManualTooltip;
|
||||
}
|
||||
static class Styles
|
||||
{
|
||||
public static readonly string HelpIconButtonClass = GUIUtility.HelpIconButtonClass;
|
||||
public static readonly string MenuIconButtonClass = GUIUtility.MenuIconButtonClass;
|
||||
}
|
||||
|
||||
Align m_Alignment;
|
||||
public Align Alignment
|
||||
{
|
||||
get { return m_Alignment; }
|
||||
private set
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case Align.Auto:
|
||||
case Align.FlexStart:
|
||||
m_Alignment = value;
|
||||
SetLeftAligned();
|
||||
break;
|
||||
case Align.Center:
|
||||
m_Alignment = value;
|
||||
SetCenterAligned();
|
||||
break;
|
||||
case Align.FlexEnd:
|
||||
case Align.Stretch:
|
||||
default:
|
||||
Debug.LogError("Ribbons can only be left of center aligned");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowHelpButton { get; private set; }
|
||||
public bool ShowMenuButton { get; private set; }
|
||||
public int InitialOption { get; private set; }
|
||||
|
||||
public int m_CurrentOption = 0;
|
||||
public int CurrentOption
|
||||
{
|
||||
get { return m_CurrentOption; }
|
||||
set { m_CurrentOption = value; RefreshButtonToggleStates(); }
|
||||
}
|
||||
|
||||
public override VisualElement contentContainer
|
||||
{
|
||||
get { return m_Content; }
|
||||
}
|
||||
|
||||
public event Action<int> Clicked = delegate { };
|
||||
public event Action HelpClicked = delegate { };
|
||||
public event Action MenuClicked = delegate { };
|
||||
|
||||
VisualElement m_Root;
|
||||
VisualElement m_Content;
|
||||
VisualElement m_Container;
|
||||
List<RibbonButton> m_Buttons = new List<RibbonButton>();
|
||||
VisualElement m_OptionsAndInfos;
|
||||
Button m_Menu;
|
||||
Button m_Help;
|
||||
|
||||
const string k_ContainerClassCenterAligned = "ribbon__container--centered";
|
||||
const string k_ClassCenterAligned = "ribbon--centered";
|
||||
const string k_ContainerClassLeftAligned = "ribbon__container--left-aligned";
|
||||
const string k_ClassLeftAligned = "ribbon--left-aligned";
|
||||
|
||||
public Ribbon()
|
||||
{
|
||||
VisualTreeAsset ribbonViewTree = AssetDatabase.LoadAssetAtPath(GUIUtility.RibbonUxmlPath, typeof(VisualTreeAsset)) as VisualTreeAsset;
|
||||
|
||||
m_Root = ribbonViewTree.Clone();
|
||||
|
||||
// clear out the style sheets defined in the template uxml file so they can be applied from here in the order of: 1. theming, 2. base
|
||||
m_Root.styleSheets.Clear();
|
||||
var themeStyle = AssetDatabase.LoadAssetAtPath(EditorGUIUtility.isProSkin ? GUIUtility.RibbonDarkUssPath : GUIUtility.RibbonLightUssPath, typeof(StyleSheet)) as StyleSheet;
|
||||
m_Root.styleSheets.Add(themeStyle);
|
||||
|
||||
var ribbonStyle = AssetDatabase.LoadAssetAtPath(GUIUtility.RibbonUssPath, typeof(StyleSheet)) as StyleSheet;
|
||||
m_Root.styleSheets.Add(ribbonStyle);
|
||||
|
||||
hierarchy.Add(m_Root);
|
||||
|
||||
style.flexShrink = 0;
|
||||
|
||||
m_Container = m_Root.Q("ribbon__container");
|
||||
m_Content = m_Root.Q("ribbon__buttons");
|
||||
m_OptionsAndInfos = m_Root.Q("options-and-info");
|
||||
m_Menu = m_OptionsAndInfos.Q<Button>("", Styles.MenuIconButtonClass);
|
||||
m_Help = m_OptionsAndInfos.Q<Button>("", Styles.HelpIconButtonClass);
|
||||
m_Help.tooltip = Content.OpenManualTooltip;
|
||||
m_CurrentOption = InitialOption;
|
||||
Setup();
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
m_CurrentOption = InitialOption;
|
||||
Setup();
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
GatherElements();
|
||||
|
||||
if (m_Buttons.Count > 0)
|
||||
{
|
||||
UnregisterCallback<GeometryChangedEvent>(OnPostDisplaySetup);
|
||||
SetupElements();
|
||||
}
|
||||
else
|
||||
RegisterCallback<GeometryChangedEvent>(OnPostDisplaySetup);
|
||||
}
|
||||
|
||||
void GatherElements()
|
||||
{
|
||||
m_Buttons.Clear();
|
||||
if (m_Content == null)
|
||||
return;
|
||||
for (int i = 0; i < m_Content.childCount; i++)
|
||||
{
|
||||
if (m_Content[i] is RibbonButton)
|
||||
{
|
||||
m_Buttons.Add(m_Content[i] as RibbonButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnPostDisplaySetup(GeometryChangedEvent evt)
|
||||
{
|
||||
GatherElements();
|
||||
if (m_Buttons.Count > 0)
|
||||
{
|
||||
UnregisterCallback<GeometryChangedEvent>(OnPostDisplaySetup);
|
||||
SetupElements();
|
||||
}
|
||||
}
|
||||
|
||||
void SetupElements()
|
||||
{
|
||||
if (m_Alignment == Align.Center)
|
||||
SetCenterAligned();
|
||||
else
|
||||
SetLeftAligned();
|
||||
|
||||
GUIUtility.SetVisibility(m_Help, ShowHelpButton);
|
||||
m_Help.clickable.clicked += HelpClicked;
|
||||
GUIUtility.SetVisibility(m_Menu, ShowMenuButton);
|
||||
m_Menu.clickable.clicked += MenuClicked;
|
||||
|
||||
RefreshButtonToggleStates();
|
||||
for (int i = 0; i < m_Buttons.Count; i++)
|
||||
{
|
||||
// copy index value to local scope before enclosing to ensure it isn't Count-1
|
||||
int buttonIndex = i;
|
||||
m_Buttons[i].clickable.clicked += () => ButtonClicked(buttonIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshButtonToggleStates()
|
||||
{
|
||||
m_CurrentOption = Mathf.Clamp(m_CurrentOption, 0, Mathf.Max(0, m_Buttons.Count - 1));
|
||||
for (int i = 0; i < m_Buttons.Count; i++)
|
||||
{
|
||||
m_Buttons[i].Toggled = i == m_CurrentOption;
|
||||
}
|
||||
}
|
||||
|
||||
public void ButtonClicked(int index)
|
||||
{
|
||||
m_CurrentOption = index;
|
||||
RefreshButtonToggleStates();
|
||||
Clicked(index);
|
||||
}
|
||||
|
||||
void SetCenterAligned()
|
||||
{
|
||||
m_Container.SwitchClasses(classToAdd: k_ContainerClassCenterAligned, classToRemove: k_ContainerClassLeftAligned);
|
||||
m_Content.SwitchClasses(classToAdd: k_ClassCenterAligned, classToRemove: k_ClassLeftAligned);
|
||||
}
|
||||
|
||||
void SetLeftAligned()
|
||||
{
|
||||
m_Container.SwitchClasses(classToAdd: k_ContainerClassLeftAligned, classToRemove: k_ContainerClassCenterAligned);
|
||||
m_Content.SwitchClasses(classToAdd: k_ClassLeftAligned, classToRemove: k_ClassCenterAligned);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a <see cref="Ribbon"/> using the data read from a UXML file.
|
||||
/// </summary>
|
||||
public new class UxmlFactory : UxmlFactory<Ribbon, UxmlTraits> { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <see cref="UxmlTraits"/> for the <see cref="Ribbon"/>.
|
||||
/// </summary>
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
UxmlEnumAttributeDescription<Align> m_Align = new UxmlEnumAttributeDescription<Align> { name = "alignment", defaultValue = Align.Center };
|
||||
UxmlBoolAttributeDescription m_ShowHelp = new UxmlBoolAttributeDescription { name = "show-help-button", defaultValue = false };
|
||||
UxmlBoolAttributeDescription m_ShowMenu = new UxmlBoolAttributeDescription { name = "show-menu-button", defaultValue = false };
|
||||
UxmlIntAttributeDescription m_InitialOption = new UxmlIntAttributeDescription { name = "initial-option", defaultValue = 0 };
|
||||
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
// can only contain ribbon buttons
|
||||
yield return new UxmlChildElementDescription(typeof(RibbonButton));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var ribbon = ((Ribbon)ve);
|
||||
var align = m_Align.GetValueFromBag(bag, cc);
|
||||
var help = m_ShowHelp.GetValueFromBag(bag, cc);
|
||||
var menu = m_ShowMenu.GetValueFromBag(bag, cc);
|
||||
var initialOption = m_InitialOption.GetValueFromBag(bag, cc);
|
||||
|
||||
ribbon.Alignment = align;
|
||||
ribbon.ShowHelpButton = help;
|
||||
ribbon.ShowMenuButton = menu;
|
||||
ribbon.InitialOption = initialOption;
|
||||
|
||||
((Ribbon)ve).Init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 04bd0f46978d2c345acddd9421f9db9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
[assembly: UxmlNamespacePrefix("UnityEditor.AddressableAssets.GUIElements", "AddressablesGUI")]
|
||||
namespace UnityEditor.AddressableAssets.GUIElements
|
||||
{
|
||||
internal class RibbonButton : Button
|
||||
{
|
||||
bool m_Toggled;
|
||||
public bool Toggled
|
||||
{
|
||||
get { return m_Toggled; }
|
||||
set
|
||||
{
|
||||
m_Toggled = value;
|
||||
if (m_Toggled)
|
||||
AddToClassList(StyleClassToggled);
|
||||
else
|
||||
RemoveFromClassList(StyleClassToggled);
|
||||
}
|
||||
}
|
||||
|
||||
string m_CachedOriginalTooltip;
|
||||
|
||||
public const string StyleClass = "ribbon__button";
|
||||
public const string StyleClassToggled = "ribbon__button--toggled";
|
||||
public RibbonButton()
|
||||
{
|
||||
AddToClassList(StyleClass);
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the Buttons enabled state and if a disablingTooltipReason is passed, it is set as the tool-tip on disabling and will restore the old tooltip on re-enabling later.
|
||||
/// </summary>
|
||||
/// <param name="enabled"></param>
|
||||
/// <param name="disablingTooltipReason">Provide a non-null, non-empty-string reason even when enabling to confirm that the tool-tip should be restored to what it was before toggling the enabled status off.</param>
|
||||
public void SetButtonEnabled(bool enabled, string disablingTooltipReason = null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(disablingTooltipReason))
|
||||
{
|
||||
if (!enabled)
|
||||
{
|
||||
m_CachedOriginalTooltip = tooltip;
|
||||
tooltip = disablingTooltipReason;
|
||||
}
|
||||
else if (!enabledSelf)
|
||||
{
|
||||
tooltip = m_CachedOriginalTooltip;
|
||||
}
|
||||
}
|
||||
|
||||
SetEnabled(enabled);
|
||||
}
|
||||
|
||||
void Init()
|
||||
{
|
||||
m_CachedOriginalTooltip = tooltip;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Instantiates a <see cref="Ribbon"/> using the data read from a UXML file.
|
||||
/// </summary>
|
||||
public new class UxmlFactory : UxmlFactory<RibbonButton, UxmlTraits> { }
|
||||
|
||||
/// <summary>
|
||||
/// Defines <see cref="UxmlTraits"/> for the <see cref="RibbonButton"/>.
|
||||
/// </summary>
|
||||
public new class UxmlTraits : VisualElement.UxmlTraits
|
||||
{
|
||||
UxmlStringAttributeDescription m_Text = new UxmlStringAttributeDescription { name = "text", defaultValue = "Button" };
|
||||
UxmlBoolAttributeDescription m_Toggled = new UxmlBoolAttributeDescription { name = "toggled", defaultValue = false };
|
||||
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription
|
||||
{
|
||||
get { yield break; }
|
||||
}
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
var button = ((RibbonButton)ve);
|
||||
var text = m_Text.GetValueFromBag(bag, cc);
|
||||
button.text = text;
|
||||
|
||||
var toggled = m_Toggled.GetValueFromBag(bag, cc);
|
||||
if (toggled)
|
||||
button.AddToClassList(StyleClassToggled);
|
||||
else
|
||||
button.RemoveFromClassList(StyleClassToggled);
|
||||
button.Toggled = toggled;
|
||||
|
||||
((RibbonButton)ve).Init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 6a1c8da71d6fa024fb491ef337779cf3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.AddressableAssets.GUIElements
|
||||
{
|
||||
/// <summary>
|
||||
/// string based filter with identifying char
|
||||
/// </summary>
|
||||
internal struct Filter
|
||||
{
|
||||
public char FilterIdentifier;
|
||||
public string FilterValue;
|
||||
|
||||
public Filter(char id, string value)
|
||||
{
|
||||
FilterIdentifier = id;
|
||||
FilterValue = value;
|
||||
}
|
||||
}
|
||||
|
||||
internal class FilterString
|
||||
{
|
||||
private Dictionary<string, char> m_LongHandToFilterChar = new Dictionary<string, char>();
|
||||
private string m_PreviousStringQueryValue = null;
|
||||
|
||||
private List<Filter> m_Filters = new List<Filter>();
|
||||
private List<string> m_NonFilterStringValues = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// true if the parsed string query resulted is valid search queries
|
||||
/// </summary>
|
||||
public bool IsValid => m_Filters.Count > 0 || m_NonFilterStringValues.Count > 0;
|
||||
|
||||
|
||||
public List<Filter> Filters => m_Filters;
|
||||
public List<string> StringFilters => m_NonFilterStringValues;
|
||||
|
||||
public void AddFilterLongHand(string longHand, char shortHand)
|
||||
{
|
||||
m_LongHandToFilterChar[longHand.ToLowerInvariant()] = char.ToLowerInvariant(shortHand);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
m_PreviousStringQueryValue = null;
|
||||
m_Filters.Clear();
|
||||
m_NonFilterStringValues.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Take the given string and convert to content search queries
|
||||
/// </summary>
|
||||
/// <param name="value">string to parse</param>
|
||||
public void ProcessSearchValue(string value)
|
||||
{
|
||||
if (value == m_PreviousStringQueryValue)
|
||||
return;
|
||||
|
||||
Clear();
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
return;
|
||||
m_PreviousStringQueryValue = value.Replace(',', ' ');
|
||||
|
||||
List<(int, int)> filterBlocks = new List<(int, int)>();
|
||||
int filterStart;
|
||||
int filterEnd = -1;
|
||||
|
||||
for (int i = value.IndexOf(':'); i > -1; i = value.IndexOf(':', i + 1))
|
||||
{
|
||||
if (i > 0 && filterEnd > 0 && filterEnd >= i)
|
||||
continue;
|
||||
|
||||
char filterChar = 'q';
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
filterStart = 0;
|
||||
}
|
||||
else if (i == 1)
|
||||
{
|
||||
filterChar = char.ToLowerInvariant(value[0]);
|
||||
filterStart = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int prevWhitespace = i - 1;
|
||||
while (prevWhitespace >= 0)
|
||||
{
|
||||
if (value[prevWhitespace] == ' ')
|
||||
break;
|
||||
prevWhitespace--;
|
||||
}
|
||||
|
||||
if (prevWhitespace == i - 1)
|
||||
{
|
||||
// whitespace right before : (no filter defined)
|
||||
filterStart = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
filterStart = prevWhitespace + 1;
|
||||
int length = i - (prevWhitespace + 1);
|
||||
if (length > 1)
|
||||
{
|
||||
string filterDefinitionString = value.Substring(prevWhitespace + 1, length).ToLowerInvariant();
|
||||
if (m_LongHandToFilterChar.TryGetValue(filterDefinitionString, out char shortHand))
|
||||
filterChar = shortHand;
|
||||
}
|
||||
else
|
||||
filterChar = char.ToLowerInvariant(value[i - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
int nextSpaceIndex = value.IndexOf(' ', i);
|
||||
string filterValue;
|
||||
if (nextSpaceIndex > 0)
|
||||
{
|
||||
filterEnd = nextSpaceIndex;
|
||||
filterValue = value.Substring(i + 1, nextSpaceIndex - (i + 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
filterEnd = value.Length;
|
||||
filterValue = value.Substring(i + 1);
|
||||
}
|
||||
|
||||
filterBlocks.Add((filterStart, filterEnd));
|
||||
if (filterChar == 'q' || string.IsNullOrEmpty(filterValue))
|
||||
continue;
|
||||
|
||||
m_Filters.Add(new Filter(filterChar, filterValue));
|
||||
}
|
||||
|
||||
// remaining string content is used to query the name filter
|
||||
string nameSearchString = value;
|
||||
for (int i = filterBlocks.Count - 1; i >= 0; --i)
|
||||
{
|
||||
int length = filterBlocks[i].Item2 - filterBlocks[i].Item1;
|
||||
if (length > 0)
|
||||
nameSearchString = nameSearchString.Remove(filterBlocks[i].Item1, length);
|
||||
}
|
||||
|
||||
string[] nameFilters = nameSearchString.Split(' ');
|
||||
foreach (string nameFilter in nameFilters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nameFilter))
|
||||
continue;
|
||||
m_NonFilterStringValues.Add(nameFilter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal struct NumericQuery
|
||||
{
|
||||
private enum Equality
|
||||
{
|
||||
NotSpecified = 0,
|
||||
Equal,
|
||||
GreaterThan,
|
||||
LessThan
|
||||
}
|
||||
|
||||
private const Equality k_DefaultEquality = Equality.Equal;
|
||||
private Equality m_Equality;
|
||||
private int m_Value;
|
||||
|
||||
/// <summary>
|
||||
/// True if the numeric search query has been correctly parsed
|
||||
/// </summary>
|
||||
public bool IsValid => m_Equality != Equality.NotSpecified;
|
||||
|
||||
/// <summary>
|
||||
/// Parse a numeric equality string to get the equality symbol and number value
|
||||
/// </summary>
|
||||
/// <param name="parseString">string to parse for numeric equality</param>
|
||||
/// <returns>true if the parse was successful, else false</returns>
|
||||
public bool Parse(string parseString)
|
||||
{
|
||||
if (int.TryParse(parseString, out m_Value))
|
||||
{
|
||||
// string has no equality, just number
|
||||
m_Equality = k_DefaultEquality;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parseString.Length == 1)
|
||||
{
|
||||
// not a full query
|
||||
m_Equality = Equality.NotSpecified;
|
||||
m_Value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
char firstChar = parseString[0];
|
||||
m_Equality = firstChar == '<' ? Equality.LessThan :
|
||||
firstChar == '>' ? Equality.GreaterThan :
|
||||
firstChar == '=' ? Equality.Equal : Equality.NotSpecified;
|
||||
|
||||
if (m_Equality == Equality.NotSpecified)
|
||||
{
|
||||
// not numeric or equality character at the start
|
||||
m_Value = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
string numberString = parseString.Substring(1);
|
||||
if (!int.TryParse(numberString, out m_Value))
|
||||
{
|
||||
// not a valid number, invalidate
|
||||
m_Equality = Equality.NotSpecified;
|
||||
m_Value = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates a value based on the equality set for the query
|
||||
/// </summary>
|
||||
/// <param name="value">value to check for evaluation</param>
|
||||
/// <returns>true if the evaluation is true, else false</returns>
|
||||
public bool Evaluate(int value)
|
||||
{
|
||||
switch (m_Equality)
|
||||
{
|
||||
case Equality.Equal: return value == m_Value;
|
||||
case Equality.GreaterThan: return value > m_Value;
|
||||
case Equality.LessThan: return value < m_Value;
|
||||
default: return value == m_Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// invalidates and clears the query
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_Equality = Equality.NotSpecified;
|
||||
m_Value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: bb35bed7fc253894fa5526da2f1e6605
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a4b9c96f39944b62ae70bf77aee6cff2
|
||||
timeCreated: 1674224966
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
.ribbon__container {
|
||||
flex-direction: row;
|
||||
border-top-width: 1px;
|
||||
border-bottom-width: 1px;
|
||||
}
|
||||
|
||||
.ribbon__container--centered {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.ribbon--centered {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.ribbon__container--left-aligned {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.ribbon--left-aligned {
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
flex-grow: 1;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.ribbon__options-and-info {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
border-top-width: 0;
|
||||
border-bottom-width: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.icon-button__help-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.icon-button__menu-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: fb7e7faa926f9db428cf76838c871ed5
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
.ribbon__container {
|
||||
border-top-color: rgb(40, 40, 40);
|
||||
border-bottom-color: rgb(40, 40, 40);
|
||||
}
|
||||
|
||||
.ribbon__button {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: rgba(0,0,0,0);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 8px;
|
||||
padding-right: 9px;
|
||||
margin-top: 10px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(40, 40, 40);
|
||||
color: rgb(155, 155, 155);
|
||||
border-top-color: rgba(0, 0, 0, 0);
|
||||
border-right-color: rgba(0, 0, 0, 0);
|
||||
border-left-color: rgba(0, 0, 0, 0);
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
.ribbon__button:hover {
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(204, 204, 204);
|
||||
}
|
||||
|
||||
.ribbon__button--toggled:hover {
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(14, 129, 193);
|
||||
}
|
||||
|
||||
.ribbon__button--toggled {
|
||||
color: white;
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(14, 129, 193);
|
||||
}
|
||||
|
||||
.icon-button__help-icon {
|
||||
background-image: resource('d__Help.png');
|
||||
}
|
||||
|
||||
.icon-button__menu-icon {
|
||||
background-image: resource('d__Menu.png');
|
||||
}
|
||||
|
||||
.ribbon__button {
|
||||
|
||||
}
|
||||
|
||||
.ribbon__button--toggled {
|
||||
color: white;
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(14, 129, 193);
|
||||
}
|
||||
|
||||
.ribbon__button--toggled:hover {
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(14, 129, 193);
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2e877031ea62d464aa7ab620ffe1a5ba
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
.ribbon__container {
|
||||
border-top-color: rgb(172, 172, 172);
|
||||
border-bottom-color: rgb(172, 172, 172);
|
||||
background-color: rgb(172, 172, 172);
|
||||
}
|
||||
|
||||
.ribbon__button:hover {
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(34, 34, 34);
|
||||
}
|
||||
|
||||
.ribbon__button {
|
||||
padding-bottom: 4px;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-color: rgb(172, 172, 172);
|
||||
background-color: rgb(172, 172, 172);
|
||||
color: rgb(77, 77, 77);
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-left-width: 0;
|
||||
border-right-width: 0;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: rgba(0,0,0,0);
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
padding-left: 8px;
|
||||
padding-right: 9px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.ribbon__button--toggled {
|
||||
color: black;
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(39, 159, 239);
|
||||
}
|
||||
|
||||
.ribbon__button--toggled:hover {
|
||||
padding-bottom: 3px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-color: rgb(39, 159, 239);
|
||||
}
|
||||
|
||||
.icon-button__help-icon {
|
||||
background-image: resource('_Help.png');
|
||||
}
|
||||
|
||||
.icon-button__menu-icon {
|
||||
background-image: resource('_Menu.png');
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 2725ffbeffba5ae4fa95c104293c60bf
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
|
||||
disableValidation: 0
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c48a079ac200400380bbb54b66a2ad09
|
||||
timeCreated: 1674224957
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
|
||||
<ui:VisualElement name="ribbon__container" class="ribbon__container ribbon__container--left-aligned">
|
||||
<ui:VisualElement name="ribbon__buttons" style="flex-direction: row;" />
|
||||
<ui:VisualElement name="options-and-info" class="ribbon__options-and-info">
|
||||
<ui:Button class="icon-button icon-button__help-icon" />
|
||||
<ui:Button class="icon-button icon-button__menu-icon" />
|
||||
</ui:VisualElement>
|
||||
</ui:VisualElement>
|
||||
</ui:UXML>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 95956aed0a2532747b6e9ed4aa3fd547
|
||||
ScriptedImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.AddressableAssets.GUIElements
|
||||
{
|
||||
internal class VisualElementsWrapper
|
||||
{
|
||||
private VisualElement m_Root;
|
||||
public VisualElement Root => m_Root;
|
||||
|
||||
private Dictionary<string, VisualElement> m_Elements = new Dictionary<string, VisualElement>();
|
||||
protected T GetElement<T>([CallerMemberName] string name = "") where T : VisualElement
|
||||
{
|
||||
VisualElement rtn;
|
||||
if (!m_Elements.TryGetValue(name, out rtn))
|
||||
{
|
||||
rtn = m_Root.Q(name);
|
||||
m_Elements[name] = rtn;
|
||||
}
|
||||
return rtn as T;
|
||||
}
|
||||
|
||||
public VisualElementsWrapper(VisualElement rootView)
|
||||
{
|
||||
m_Root = rootView;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 3cf68bc830910e1479a05521a6b0ba9f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Add table
Add a link
Reference in a new issue