OldBlueWater/BlueWater/Assets/Doozy/Editor/EditorUI/Utils/DesignUtils.cs

837 lines
38 KiB
C#
Raw Normal View History

// Copyright (c) 2015 - 2023 Doozy Entertainment. All Rights Reserved.
// This code can only be used under the standard Unity Asset Store End User License Agreement
// A Copy of the EULA APPENDIX 1 is available at http://unity3d.com/company/legal/as_terms
using System;
using System.Collections.Generic;
using System.Linq;
using Doozy.Editor.EditorUI.Components;
using Doozy.Editor.EditorUI.Components.Internal;
using Doozy.Editor.EditorUI.ScriptableObjects.Colors;
using Doozy.Editor.UIElements;
using Doozy.Runtime.Colors;
using Doozy.Runtime.Common.Extensions;
using Doozy.Runtime.Reactor.Internal;
using Doozy.Runtime.UIElements.Extensions;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
namespace Doozy.Editor.EditorUI.Utils
{
public static class DesignUtils
{
private static Font s_defaultFont;
public static Font unityDefaultFont => s_defaultFont ? s_defaultFont : s_defaultFont = new VisualElement().GetStyleUnityFont();
public const int k_FieldLabelFontSize = 9;
public const int k_NormalLabelFontSize = 11;
public const int k_Spacing = 4;
public const int k_Spacing2X = k_Spacing * 2;
public const int k_Spacing3X = k_Spacing * 3;
public const int k_Spacing4X = k_Spacing * 4;
public const int k_EndOfLineSpacing = k_Spacing * 6;
public const int k_ToolbarHeight = 32;
public const int k_RootPadding = 6;
public static VisualElement endOfLineBlock => GetSpaceBlock(0, k_EndOfLineSpacing, "End of Line");
public static VisualElement spaceBlock => GetSpaceBlock(k_Spacing, k_Spacing);
public static VisualElement spaceBlock2X => GetSpaceBlock(k_Spacing2X, k_Spacing2X);
public static VisualElement spaceBlock3X => GetSpaceBlock(k_Spacing3X, k_Spacing3X);
public static VisualElement spaceBlock4X => GetSpaceBlock(k_Spacing4X, k_Spacing4X);
public const int k_FieldBorderRadius = 6;
public const int k_FieldNameTiny = 10;
public const int k_FieldNameSmall = 10;
public const int k_FieldNameNormal = 11;
public const int k_FieldNameLarge = 11;
public static Color placeholderColor => EditorColors.Default.Placeholder;
public static Color tabButtonColorOff => EditorColors.Default.Background;
public static Color fieldBackgroundColor => EditorColors.Default.FieldBackground;
public static Color fieldIconColor => EditorColors.Default.FieldIcon;
public static Color fieldNameTextColor => EditorColors.Default.TextSubtitle;
public static Font fieldNameTextFont => EditorFonts.Ubuntu.Light;
public static Color dividerColor => EditorColors.Default.UnityThemeInversed.WithAlpha(0.1f);
public static Color disabledTextColor => EditorColors.Default.Placeholder;
public static Color callbacksColor => EditorColors.Default.Action;
public static EditorSelectableColorInfo callbackSelectableColor => EditorSelectableColors.Default.Action;
private static VisualElement divider =>
new VisualElement()
.SetName("Divider")
.SetStyleFlexGrow(1)
.SetStyleFlexShrink(0)
.SetStyleAlignSelf(Align.Stretch)
.SetStyleMargins(k_Spacing)
.SetStyleBackgroundColor(dividerColor);
public static VisualElement dividerHorizontal =>
divider.SetStyleHeight(1, 1, 1);
public static VisualElement dividerVertical =>
divider.SetStyleWidth(1, 1, 1);
/// <summary> Get a new VisualElement as a column </summary>
public static VisualElement column =>
new VisualElement().SetName("Column").SetStyleFlexDirection(FlexDirection.Column).SetStyleFlexGrow(1);
/// <summary> Get a new VisualElement as a row </summary>
public static VisualElement row =>
new VisualElement().SetName("Row").SetStyleFlexDirection(FlexDirection.Row).SetStyleFlexGrow(1);
/// <summary> Get a new VisualElement as empty flexible space </summary>
public static VisualElement flexibleSpace =>
new VisualElement()
.SetName("Flexible Space")
.SetStyleFlexGrow(1);
public static VisualElement fieldContainer =>
new VisualElement()
.SetStylePadding(k_Spacing)
.SetStyleBackgroundColor(fieldBackgroundColor)
.SetStyleBorderRadius(k_FieldBorderRadius);
public static Label fieldLabel =>
new Label()
.ResetLayout()
.SetStyleUnityFont(fieldNameTextFont)
.SetStyleFontSize(k_FieldLabelFontSize)
.SetStyleColor(fieldNameTextColor);
public static VisualElement GetEditorRoot() =>
new VisualElement()
.SetStylePaddingRight(k_RootPadding);
public static VisualElement editorRoot
=> GetEditorRoot();
public static FluidComponentHeader editorComponentHeader =>
FluidComponentHeader.Get()
.SetElementSize(ElementSize.Large);
public static VisualElement editorToolbarContainer =>
new VisualElement()
.SetName("Toolbar Container")
.SetStyleFlexDirection(FlexDirection.Row)
.SetStyleMarginTop(-4)
.SetStyleMarginLeft(47)
.SetStyleMarginRight(4)
.SetStyleFlexGrow(1);
public static VisualElement editorContentContainer =>
new VisualElement()
.SetName("Content Container")
.SetStyleFlexGrow(1)
.SetStyleMarginLeft(43);
public static VisualElement FullScreenVisualElement() =>
new VisualElement()
.SetStylePosition(Position.Absolute)
.SetStyleLeft(0)
.SetStyleTop(0)
.SetStyleRight(0)
.SetStyleBottom(0)
.SetPickingMode(PickingMode.Ignore)
.SetStyleJustifyContent(Justify.Center)
.SetStyleAlignItems(Align.Center);
public static VisualElement GetToolbarContainer() =>
row
.SetStyleHeight(k_ToolbarHeight)
.SetStylePaddingLeft(k_Spacing)
.SetStylePaddingRight(k_Spacing)
.SetStyleAlignItems(Align.Center)
.SetStyleJustifyContent(Justify.FlexEnd)
.SetStyleBackgroundColor(EditorColors.Default.BoxBackground);
public static VisualElement GetSpaceBlock(int size, string name = "") =>
GetSpaceBlock(size, size, name);
public static VisualElement GetSpaceBlock(int width, int height, string name = "") =>
new VisualElement()
.SetName($"{name} Space Block ({width}x{height})")
.SetStyleWidth(width)
.SetStyleHeight(height)
.SetStyleAlignSelf(Align.Center)
.SetStyleFlexShrink(0);
public static VisualElement UnityEventField(string labelText, SerializedProperty property) =>
new VisualElement()
.SetName($"UnityEvent: {labelText}")
.AddChild(UnityEventLabel(labelText))
.AddChild(NewPropertyField(property));
public static Label NewFieldNameLabel(string text) =>
fieldLabel
.SetName($"Label: {text}")
.SetText(text);
public static Label UnityEventLabel(string labelText) =>
NewLabel(labelText, 10)
.SetStyleBackgroundColor(EditorColors.Default.BoxBackground)
.SetStyleBorderRadius(4, 4, 0, 0)
.SetStylePadding(8, 4, 8, 6)
.SetStyleMarginBottom(-2);
public static Label NewLabel(string text = "", int labelFontSize = k_NormalLabelFontSize) =>
new Label(text)
.SetName($"Label: {text}")
.ResetLayout()
.SetStyleTextAlign(TextAnchor.MiddleLeft)
.SetStyleFontSize(labelFontSize);
public static EnumField NewEnumField(SerializedProperty property, bool invisibleField = false) =>
NewEnumField(property.propertyPath, invisibleField);
public static EnumField NewEnumField(string bindingPath, bool invisibleField = false) =>
new EnumField().SetBindingPath(bindingPath)
.SetName($"Enum: {bindingPath}")
.ResetLayout()
.SetStyleFlexShrink(1)
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static PropertyField NewPropertyField(SerializedProperty property, bool invisibleField = false) =>
NewPropertyField(property.propertyPath, invisibleField);
public static PropertyField NewPropertyField(string bindingPath, bool invisibleField = false) =>
new PropertyField().SetBindingPath(bindingPath)
.SetName($"Property: {bindingPath}")
.ResetLayout()
.SetStyleFlexGrow(1)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static ObjectField NewObjectField(SerializedProperty property, Type objectType, bool allowSceneObjects = true, bool invisibleField = false) =>
NewObjectField(property.propertyPath, objectType, allowSceneObjects, invisibleField);
public static ObjectField NewObjectField(string bindingPath, Type objectType, bool allowSceneObjects = true, bool invisibleField = false) =>
new ObjectField
{
bindingPath = bindingPath,
objectType = objectType,
allowSceneObjects = allowSceneObjects
}
.SetName($"Object: {bindingPath}")
.ResetLayout()
.SetStyleFlexShrink(1)
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static IntegerField NewIntegerField(SerializedProperty property, bool invisibleField = false) =>
NewIntegerField(property.propertyPath, invisibleField);
public static IntegerField NewIntegerField(string bindingPath, bool invisibleField = false) =>
new IntegerField().SetBindingPath(bindingPath)
.SetName($"Int: {bindingPath}")
.ResetLayout()
.SetStyleFlexShrink(1)
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static FloatField NewFloatField(SerializedProperty property, bool invisibleField = false) =>
NewFloatField(property.propertyPath, invisibleField);
public static FloatField NewFloatField(string bindingPath, bool invisibleField = false) =>
new FloatField().SetBindingPath(bindingPath)
.SetName($"Int: {bindingPath}")
.ResetLayout()
.SetStyleFlexShrink(1)
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static Vector2Field NewVector2Field(SerializedProperty property, bool invisibleField = false) =>
NewVector2Field(property.propertyPath, invisibleField);
public static Vector2Field NewVector2Field(string bindingPath, bool invisibleField = false) =>
new Vector2Field().SetBindingPath(bindingPath)
.SetName($"Float: {bindingPath}")
.ResetLayout()
.SetStyleFlexShrink(1)
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static Vector3Field NewVector3Field(SerializedProperty property, bool invisibleField = false) =>
NewVector3Field(property.propertyPath, invisibleField);
public static Vector3Field NewVector3Field(string bindingPath, bool invisibleField = false) =>
new Vector3Field().SetBindingPath(bindingPath)
.SetName($"Float: {bindingPath}")
.ResetLayout()
.SetStyleFlexShrink(1)
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static TextField NewTextField(SerializedProperty property, bool isDelayed = false, bool invisibleField = false) =>
NewTextField(property.propertyPath, isDelayed, invisibleField);
public static TextField NewTextField(string bindingPath, bool isDelayed = false, bool invisibleField = false)
{
TextField field =
new TextField().SetBindingPath(bindingPath)
.SetName($"Text: {bindingPath}")
.ResetLayout()
.SetStyleFlexShrink(1)
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
field.isDelayed = isDelayed;
return field;
}
public static Slider NewSlider(SerializedProperty property, float start, float end) =>
NewSlider(property.propertyPath, start, end);
public static Slider NewSlider(string bindingPath, float start, float end) =>
new Slider(start, end)
.SetBindingPath(bindingPath)
.ResetLayout()
.SetStyleFlexGrow(1);
public static Toggle NewToggle(SerializedProperty property, bool invisibleField = false) =>
NewToggle(property.propertyPath, invisibleField);
public static Toggle NewToggle(string bindingPath, bool invisibleField = false) =>
new Toggle().SetBindingPath(bindingPath)
.SetName($"Toggle: {bindingPath}")
.ResetLayout()
.SetStyleAlignSelf(Align.Center)
.SetStyleDisplay(invisibleField ? DisplayStyle.None : DisplayStyle.Flex);
public static FluidToggleButtonTab NameTab() =>
FluidToggleButtonTab.Get()
.SetTabPosition(TabPosition.TabOnBottom)
.SetElementSize(ElementSize.Small)
.SetIcon(EditorSpriteSheets.EditorUI.Icons.Label)
.SetLabelText("Name")
.SetContainerColorOff(tabButtonColorOff);
/// <summary> Fluid button used under headers for in-editor actions </summary>
public static FluidButton SystemButton(IEnumerable<Texture2D> textures) =>
FluidButton
.Get()
.SetIcon(textures)
.SetStyleAlignSelf(Align.FlexStart)
.SetElementSize(ElementSize.Tiny);
/// <summary>
/// Get a switch that connects to a given property and applies SetEnabled on a target container, depending on its value
/// </summary>
/// <param name="property"> Target bool property </param>
/// <param name="content"> Content that gets SetEnabled true or false, depending on the switch value changes </param>
/// <param name="sColor"> Selectable color </param>
/// <param name="labelPrefix"> Prefix added to the label '{labelPrefix} Enabled' </param>
public static FluidToggleSwitch GetEnableDisableSwitch(SerializedProperty property, VisualElement content, EditorSelectableColorInfo sColor, string labelPrefix = "")
{
FluidToggleSwitch fluidSwitch =
FluidToggleSwitch.Get()
.SetToggleAccentColor(sColor)
.BindToProperty(property.propertyPath);
fluidSwitch.SetOnValueChanged(evt => Update(evt.newValue));
Update(property.boolValue);
void Update(bool enabled)
{
fluidSwitch.SetLabelText($"{labelPrefix}{(labelPrefix.IsNullOrEmpty() ? "" : " ")}{(enabled ? "Enabled" : "Disabled")}");
content.SetEnabled(enabled);
}
return fluidSwitch;
}
/// <summary>
/// Sorts the component order in the Inspector
/// <para/> Fluid button used under headers for in-editor actions
/// </summary>
public static FluidButton SystemButton_SortComponents(GameObject targetGameObject, params string[] customSortedComponentNames) =>
SystemButton(EditorSpriteSheets.EditorUI.Icons.SortAz)
.SetTooltip("Sort the components, on this gameObject, in a custom alphabetical order")
.SetOnClick(() => EditorUtils.SortComponents(targetGameObject, customSortedComponentNames));
/// <summary>
/// Rename the target gameObject to the given new name (has Undo)
/// <para/> Fluid button used under headers for in-editor actions
/// </summary>
public static FluidButton SystemButton_RenameComponent(GameObject targetGameObject, Func<string> newName) =>
SystemButton(EditorSpriteSheets.EditorUI.Icons.Edit)
.SetTooltip($"Rename GameObject")
.SetOnClick(() =>
{
Undo.RecordObject(targetGameObject, "Rename");
targetGameObject.name = newName.Invoke();
});
/// <summary>
/// Rename the target gameObject to the given new name (has Undo)
/// <para/> Fluid button used under headers for in-editor actions
/// </summary>
public static FluidButton SystemButton_RenameAsset(Object targetAsset, Func<string> newName) =>
SystemButton(EditorSpriteSheets.EditorUI.Icons.Edit)
.SetTooltip($"Rename Asset")
.SetOnClick(() =>
{
if (targetAsset == null) return;
if (newName == null) return;
AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(targetAsset), newName.Invoke());
EditorUtility.SetDirty(targetAsset);
AssetDatabase.SaveAssets();
});
/// <summary> Get a FluidToggleButtonTab set up to be used under a ComponentHeader bar </summary>
/// <param name="textures"> Tab Button icon textures </param>
/// <param name="selectableAccentColor"> Accent color for the tab button </param>
public static FluidToggleButtonTab GetTabButtonForComponentSection(IEnumerable<Texture2D> textures = null, EditorSelectableColorInfo selectableAccentColor = null)
{
FluidToggleButtonTab tabButton =
FluidToggleButtonTab.Get()
.SetContainerColorOff(tabButtonColorOff)
.SetTabPosition(TabPosition.TabOnBottom)
.SetElementSize(ElementSize.Normal);
if (textures != null)
tabButton.SetIcon(textures);
if (selectableAccentColor != null)
tabButton.SetToggleAccentColor(selectableAccentColor);
tabButton.iconReaction?.SetDuration(0.6f);
return tabButton;
}
/// <summary> Get a FluidToggleButtonTab, an EnabledIndicator and a container with them assembled, set up to be used under a ComponentHeader bar </summary>
/// <param name="textures"> Tab Button icon textures </param>
/// <param name="selectableAccentColor"> Accent color for the tab button </param>
/// <param name="accentColor"> Accent color for the indicator </param>
public static (FluidToggleButtonTab, EnabledIndicator, VisualElement) GetTabButtonForComponentSectionWithEnabledIndicator(IEnumerable<Texture2D> textures, EditorSelectableColorInfo selectableAccentColor, Color accentColor)
{
FluidToggleButtonTab tab = GetTabButtonForComponentSection(textures, selectableAccentColor);
EnabledIndicator indicator = EnabledIndicator.Get().SetEnabledColor(accentColor);
VisualElement container = column.SetStyleFlexGrow(0).AddChild(indicator).AddChild(tab);
return (tab, indicator, container);
}
public static FluidButton GetNewTinyButton
(
string text,
IEnumerable<Texture2D> textures,
EditorSelectableColorInfo selectableColor = null,
string tooltip = ""
) =>
FluidButton.Get()
.SetLabelText(text)
.SetIcon(textures)
.SetAccentColor(selectableColor ?? EditorSelectableColors.Default.ButtonIcon)
.SetTooltip(tooltip)
.SetButtonStyle(ButtonStyle.Contained)
.SetElementSize(ElementSize.Tiny);
/// <summary>
/// Get a Fluid ListView for the given array property
/// </summary>
/// <param name="arrayProperty"> Target arrayProperty (not checked; will result in error if a property of an array is not passed) </param>
/// <param name="listTitle"> Title displayed on top of the list </param>
/// <param name="listDescription"> Descriptions displayed below the title </param>
/// <returns></returns>
public static FluidListView NewStringListView(SerializedProperty arrayProperty, string listTitle, string listDescription)
{
var flv = new FluidListView();
var itemsSource = new List<SerializedProperty>();
flv.SetListTitle(listTitle);
flv.SetListDescription(listDescription);
flv.listView.selectionType = SelectionType.None;
flv.listView.itemsSource = itemsSource;
flv.listView.makeItem = () => new StringFluidListViewItem(flv);
flv.listView.bindItem = (element, i) =>
{
var item = (StringFluidListViewItem)element;
item.Update(i, itemsSource[i]);
item.OnRemoveButtonClick += itemProperty =>
{
int propertyIndex = 0;
for (int j = 0; j < arrayProperty.arraySize; j++)
{
if (itemProperty.propertyPath != arrayProperty.GetArrayElementAtIndex(j).propertyPath)
continue;
propertyIndex = j;
break;
}
arrayProperty.DeleteArrayElementAtIndex(propertyIndex);
arrayProperty.serializedObject.ApplyModifiedProperties();
UpdateItemsSource();
};
};
#if UNITY_2021_2_OR_NEWER
flv.listView.fixedItemHeight = 30;
flv.SetPreferredListHeight((int)flv.listView.fixedItemHeight * 6);
#else
flv.listView.itemHeight = 30;
flv.SetPreferredListHeight(flv.listView.itemHeight * 6);
#endif
flv.SetDynamicListHeight(false);
flv.HideFooterWhenEmpty(true);
flv.UseSmallEmptyListPlaceholder(true);
flv.emptyListPlaceholder.SetIcon(EditorSpriteSheets.EditorUI.Placeholders.EmptyListViewSmall);
//ADD ITEM BUTTON (plus button)
flv.AddNewItemButtonCallback += () =>
{
arrayProperty.InsertArrayElementAtIndex(0);
arrayProperty.serializedObject.ApplyModifiedProperties();
UpdateItemsSource();
};
UpdateItemsSource();
int arraySize = arrayProperty.arraySize;
flv.schedule.Execute(() =>
{
if (arrayProperty.arraySize == arraySize) return;
arraySize = arrayProperty.arraySize;
UpdateItemsSource();
}).Every(100);
void UpdateItemsSource()
{
itemsSource.Clear();
for (int i = 0; i < arrayProperty.arraySize; i++)
itemsSource.Add(arrayProperty.GetArrayElementAtIndex(i));
flv?.Update();
}
flv.schedule.Execute(flv.Update);
return flv;
}
/// <summary> Get a Fluid ListView for the given array property </summary>
/// <param name="arrayProperty"> Target arrayProperty (not checked; will result in error if a property of an array is not passed) </param>
/// <param name="listTitle"> Title displayed on top of the list </param>
/// <param name="listDescription"> Descriptions displayed below the title </param>
public static FluidListView NewPropertyListView(SerializedProperty arrayProperty, string listTitle, string listDescription)
{
var flv = new FluidListView();
var itemsSource = new List<SerializedProperty>();
flv.SetListTitle(listTitle);
flv.SetListDescription(listDescription);
flv.listView.selectionType = SelectionType.None;
flv.listView.itemsSource = itemsSource;
flv.listView.makeItem = () => new PropertyFluidListViewItem(flv);
flv.listView.bindItem = (element, i) =>
{
var item = (PropertyFluidListViewItem)element;
item.Update(i, itemsSource[i]);
item.OnRemoveButtonClick += itemProperty =>
{
int propertyIndex = 0;
for (int j = 0; j < arrayProperty.arraySize; j++)
{
if (itemProperty.propertyPath != arrayProperty.GetArrayElementAtIndex(j).propertyPath)
continue;
propertyIndex = j;
break;
}
arrayProperty.DeleteArrayElementAtIndex(propertyIndex);
arrayProperty.serializedObject.ApplyModifiedProperties();
UpdateItemsSource();
};
};
#if UNITY_2021_2_OR_NEWER
flv.listView.fixedItemHeight = 30;
flv.SetPreferredListHeight((int)flv.listView.fixedItemHeight * 6);
#else
flv.listView.itemHeight = 30;
flv.SetPreferredListHeight(flv.listView.itemHeight * 6);
#endif
flv.SetDynamicListHeight(false);
flv.HideFooterWhenEmpty(true);
flv.UseSmallEmptyListPlaceholder(true);
flv.emptyListPlaceholder.SetIcon(EditorSpriteSheets.EditorUI.Placeholders.EmptyListViewSmall);
//ADD ITEM BUTTON (plus button)
flv.AddNewItemButtonCallback += () =>
{
arrayProperty.InsertArrayElementAtIndex(0);
arrayProperty.serializedObject.ApplyModifiedProperties();
UpdateItemsSource();
};
UpdateItemsSource();
int arraySize = arrayProperty.arraySize;
flv.schedule.Execute(() =>
{
if (arrayProperty.arraySize == arraySize) return;
arraySize = arrayProperty.arraySize;
UpdateItemsSource();
}).Every(100);
void UpdateItemsSource()
{
itemsSource.Clear();
for (int i = 0; i < arrayProperty.arraySize; i++)
itemsSource.Add(arrayProperty.GetArrayElementAtIndex(i));
flv?.Update();
}
flv.schedule.Execute(flv.Update);
return flv;
}
/// <summary> Get a Fluid ListView for the given array property </summary>
/// <param name="arrayProperty"> Target arrayProperty (not checked; will result in error if a property of an array is not passed) </param>
/// <param name="listTitle"> Title displayed on top of the list </param>
/// <param name="listDescription"> Descriptions displayed below the title </param>
/// <param name="objectType"> Type of object this ListView handles </param>
/// <param name="allowDragAndDrop"> Drag and Drop functionality is added automatically if TRUE. Set FALSE if you want to no Drag and Drop or intend to implement a custom one </param>
public static FluidListView NewObjectListView(SerializedProperty arrayProperty, string listTitle, string listDescription, Type objectType, bool allowDragAndDrop = true)
{
var flv = new FluidListView();
var itemsSource = new List<SerializedProperty>();
flv.SetListTitle(listTitle);
flv.SetListDescription(listDescription);
flv.listView.selectionType = SelectionType.None;
flv.listView.itemsSource = itemsSource;
flv.listView.makeItem = () => new ObjectFluidListViewItem(flv, objectType);
flv.listView.bindItem = (element, i) =>
{
var item = (ObjectFluidListViewItem)element;
item.Update(i, itemsSource[i]);
item.OnRemoveButtonClick += itemProperty =>
{
int propertyIndex = 0;
for (int j = 0; j < arrayProperty.arraySize; j++)
{
if (itemProperty.propertyPath != arrayProperty.GetArrayElementAtIndex(j).propertyPath)
continue;
propertyIndex = j;
break;
}
//delete if not out of bounds
if (propertyIndex < arrayProperty.arraySize)
{
arrayProperty.DeleteArrayElementAtIndex(propertyIndex);
arrayProperty.serializedObject.ApplyModifiedProperties();
}
UpdateItemsSource();
};
};
#if UNITY_2021_2_OR_NEWER
flv.listView.fixedItemHeight = 30;
flv.SetPreferredListHeight((int)flv.listView.fixedItemHeight * 6);
#else
flv.listView.itemHeight = 30;
flv.SetPreferredListHeight(flv.listView.itemHeight * 6);
#endif
flv.SetDynamicListHeight(false);
flv.HideFooterWhenEmpty(true);
flv.UseSmallEmptyListPlaceholder(true);
flv.emptyListPlaceholder.SetIcon(EditorSpriteSheets.EditorUI.Placeholders.EmptyListViewSmall);
//ADD ITEM BUTTON (plus button)
flv.AddNewItemButtonCallback += () =>
{
arrayProperty.InsertArrayElementAtIndex(0);
arrayProperty.GetArrayElementAtIndex(0).objectReferenceValue = null;
arrayProperty.serializedObject.ApplyModifiedProperties();
UpdateItemsSource();
};
UpdateItemsSource();
int arraySize = arrayProperty.arraySize;
flv.schedule.Execute(() =>
{
if (arrayProperty.arraySize == arraySize) return;
arraySize = arrayProperty.arraySize;
UpdateItemsSource();
}).Every(100);
void UpdateItemsSource()
{
itemsSource.Clear();
for (int i = 0; i < arrayProperty.arraySize; i++)
itemsSource.Add(arrayProperty.GetArrayElementAtIndex(i));
flv?.Update();
}
if (!allowDragAndDrop)
{
flv.schedule.Execute(flv.Update);
return flv;
}
//Drag and Drop
{
flv.RegisterCallback<AttachToPanelEvent>(_ =>
{
flv.RegisterCallback<DragUpdatedEvent>(OnDragUpdate);
flv.RegisterCallback<DragPerformEvent>(OnDragPerformEvent);
});
flv.RegisterCallback<DetachFromPanelEvent>(_ =>
{
flv.UnregisterCallback<DragUpdatedEvent>(OnDragUpdate);
flv.UnregisterCallback<DragPerformEvent>(OnDragPerformEvent);
});
void OnDragUpdate(DragUpdatedEvent evt)
{
bool isValid = DragAndDrop.objectReferences.Any(item => item.GetType() == objectType || item.GetType().IsSubclassOf(objectType));
if (!isValid)
{
foreach (Object item in DragAndDrop.objectReferences)
{
Type itemType = item.GetType();
if (itemType == objectType)
{
isValid = true;
break;
}
if (item is GameObject go && go.GetComponent(objectType) != null)
{
isValid = true;
break;
}
}
}
if (!isValid) return;
DragAndDrop.visualMode = DragAndDropVisualMode.Generic;
}
void OnDragPerformEvent(DragPerformEvent evt)
{
var references =
DragAndDrop.objectReferences
.Where(item => item != null)
.OrderBy(item => item.name)
.ToList();
foreach (Object item in references)
{
//check if we're dragging the correct object
if (item.GetType() == objectType || item.GetType().IsSubclassOf(objectType))
{
bool canAddItem = true;
for (int i = 0; i < arrayProperty.arraySize; i++)
{
if (arrayProperty.GetArrayElementAtIndex(i).objectReferenceValue != item)
continue;
canAddItem = false;
break;
}
if (!canAddItem) continue;
arrayProperty.InsertArrayElementAtIndex(arrayProperty.arraySize);
arrayProperty.GetArrayElementAtIndex(arrayProperty.arraySize - 1).objectReferenceValue = item;
continue;
}
//if the dragged object is a GameObject, check to see if the correct object is attached to it
if (item is GameObject go)
{
Component component = go.GetComponent(objectType);
if (component == null) continue;
bool canAddComponent = true;
for (int i = 0; i < arrayProperty.arraySize; i++)
{
if (arrayProperty.GetArrayElementAtIndex(i).objectReferenceValue != component)
continue;
canAddComponent = false;
break;
}
if (!canAddComponent) continue;
arrayProperty.InsertArrayElementAtIndex(arrayProperty.arraySize);
arrayProperty.GetArrayElementAtIndex(arrayProperty.arraySize - 1).objectReferenceValue = go.GetComponent(objectType);
}
}
arrayProperty.serializedObject.ApplyModifiedProperties();
}
}
flv.schedule.Execute(flv.Update);
return flv;
}
public static FluidToggleSwitch GetDebugSwitch(SerializedProperty property) =>
FluidToggleSwitch.Get()
.SetLabelText("Debug Mode")
.SetTooltip("Enable relevant debug messages to be printed to the console")
.SetToggleAccentColor(EditorSelectableColors.EditorUI.Red)
.BindToProperty(property)
.SetStyleAlignSelf(Align.FlexEnd);
public static class Buttons
{
public static FluidButton RefreshDatabase
(
string labelText,
string tooltipText,
EditorSelectableColorInfo selectableColor,
UnityAction onClickCallback
)
{
var button = FluidButton.Get()
.SetLabelText(labelText)
.SetTooltip(tooltipText)
.SetAccentColor(selectableColor)
.SetOnClick(onClickCallback)
.SetButtonStyle(ButtonStyle.Contained)
.SetElementSize(ElementSize.Small)
.SetIcon(EditorSpriteSheets.EditorUI.Icons.Refresh)
.SetStyleMinWidth(80);
button.buttonLabel.SetStyleTextAlign(TextAnchor.MiddleRight);
return button;
}
public static FluidButton PingAsset(Object asset) =>
FluidButton.Get()
.SetElementSize(ElementSize.Small)
.SetTooltip($"{(asset == null ? "Referenced asset is missing" : AssetDatabase.GetAssetPath(asset))}")
.SetIcon(EditorSpriteSheets.EditorUI.Icons.Location)
.SetOnClick(() =>
{
if (asset == null) return;
EditorGUIUtility.PingObject(asset);
if (Selection.activeObject == asset) return;
Selection.activeObject = asset;
});
public static FluidButton OpenDatabaseButton() =>
FluidButton.Get()
.SetButtonStyle(ButtonStyle.Clear)
.SetElementSize(ElementSize.Normal)
.SetStyleAlignSelf(Align.Center)
.SetStyleMarginRight(k_Spacing2X);
}
}
}