// 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); /// Get a new VisualElement as a column public static VisualElement column => new VisualElement().SetName("Column").SetStyleFlexDirection(FlexDirection.Column).SetStyleFlexGrow(1); /// Get a new VisualElement as a row public static VisualElement row => new VisualElement().SetName("Row").SetStyleFlexDirection(FlexDirection.Row).SetStyleFlexGrow(1); /// Get a new VisualElement as empty flexible space 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); /// Fluid button used under headers for in-editor actions public static FluidButton SystemButton(IEnumerable textures) => FluidButton .Get() .SetIcon(textures) .SetStyleAlignSelf(Align.FlexStart) .SetElementSize(ElementSize.Tiny); /// /// Get a switch that connects to a given property and applies SetEnabled on a target container, depending on its value /// /// Target bool property /// Content that gets SetEnabled true or false, depending on the switch value changes /// Selectable color /// Prefix added to the label '{labelPrefix} Enabled' 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; } /// /// Sorts the component order in the Inspector /// Fluid button used under headers for in-editor actions /// 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)); /// /// Rename the target gameObject to the given new name (has Undo) /// Fluid button used under headers for in-editor actions /// public static FluidButton SystemButton_RenameComponent(GameObject targetGameObject, Func newName) => SystemButton(EditorSpriteSheets.EditorUI.Icons.Edit) .SetTooltip($"Rename GameObject") .SetOnClick(() => { Undo.RecordObject(targetGameObject, "Rename"); targetGameObject.name = newName.Invoke(); }); /// /// Rename the target gameObject to the given new name (has Undo) /// Fluid button used under headers for in-editor actions /// public static FluidButton SystemButton_RenameAsset(Object targetAsset, Func 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(); }); /// Get a FluidToggleButtonTab set up to be used under a ComponentHeader bar /// Tab Button icon textures /// Accent color for the tab button public static FluidToggleButtonTab GetTabButtonForComponentSection(IEnumerable 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; } /// Get a FluidToggleButtonTab, an EnabledIndicator and a container with them assembled, set up to be used under a ComponentHeader bar /// Tab Button icon textures /// Accent color for the tab button /// Accent color for the indicator public static (FluidToggleButtonTab, EnabledIndicator, VisualElement) GetTabButtonForComponentSectionWithEnabledIndicator(IEnumerable 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 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); /// /// Get a Fluid ListView for the given array property /// /// Target arrayProperty (not checked; will result in error if a property of an array is not passed) /// Title displayed on top of the list /// Descriptions displayed below the title /// public static FluidListView NewStringListView(SerializedProperty arrayProperty, string listTitle, string listDescription) { var flv = new FluidListView(); var itemsSource = new List(); 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; } /// Get a Fluid ListView for the given array property /// Target arrayProperty (not checked; will result in error if a property of an array is not passed) /// Title displayed on top of the list /// Descriptions displayed below the title public static FluidListView NewPropertyListView(SerializedProperty arrayProperty, string listTitle, string listDescription) { var flv = new FluidListView(); var itemsSource = new List(); 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; } /// Get a Fluid ListView for the given array property /// Target arrayProperty (not checked; will result in error if a property of an array is not passed) /// Title displayed on top of the list /// Descriptions displayed below the title /// Type of object this ListView handles /// 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 public static FluidListView NewObjectListView(SerializedProperty arrayProperty, string listTitle, string listDescription, Type objectType, bool allowDragAndDrop = true) { var flv = new FluidListView(); var itemsSource = new List(); 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(_ => { flv.RegisterCallback(OnDragUpdate); flv.RegisterCallback(OnDragPerformEvent); }); flv.RegisterCallback(_ => { flv.UnregisterCallback(OnDragUpdate); flv.UnregisterCallback(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); } } }