using UnityEngine; using UnityEngine.Events; using System.Collections.Generic; using FSA = UnityEngine.Serialization.FormerlySerializedAsAttribute; namespace Lean.Common { /// This is the base class for all object selectors. [HelpURL(LeanHelper.HelpUrlPrefix + "LeanSelect")] public class LeanSelect : MonoBehaviour { [System.Serializable] public class LeanSelectableEvent : UnityEvent {} public enum ReselectType { KeepSelected, Deselect, DeselectAndSelect, SelectAgain } public enum LimitType { Unlimited, StopAtMax, DeselectFirst } public static LinkedList Instances = new LinkedList(); [System.NonSerialized] private LinkedListNode instancesNode; /// If you attempt to select a point that has no objects underneath, should all currently selected objects be deselected? public bool DeselectWithNothing { set { deselectWithNothing = value; } get { return deselectWithNothing; } } [FSA("AutoDeselect")] [SerializeField] private bool deselectWithNothing; /// If you have selected the maximum number of objects, what should happen? /// Unlimited = Always allow selection. /// StopAtMax = Allow selection up to the MaxSelectables count, then do nothing. /// DeselectFirst = Always allow selection, but deselect the first object when the MaxSelectables count is reached. public LimitType Limit { set { limit = value; } get { return limit; } } [SerializeField] private LimitType limit; /// The maximum number of selectables that can be selected at the same time. /// 0 = Unlimited. public int MaxSelectables { set { maxSelectables = value; } get { return maxSelectables; } } [FSA("MaxSelectables")] [SerializeField] private int maxSelectables = 5; /// If you select an already selected selectable, what should happen? public ReselectType Reselect { set { reselect = value; } get { return reselect; } } [FSA("Reselect")] [SerializeField] private ReselectType reselect = ReselectType.SelectAgain; /// This stores all objects selected by this component. public List Selectables { get { if (selectables == null) selectables = new List(); return selectables; } } [SerializeField] protected List selectables; /// This is invoked when an object is selected. public LeanSelectableEvent OnSelected { get { if (onSelected == null) onSelected = new LeanSelectableEvent(); return onSelected; } } [SerializeField] private LeanSelectableEvent onSelected; /// This is invoked when an object is deselected. public LeanSelectableEvent OnDeselected { get { if (onDeselected == null) onDeselected = new LeanSelectableEvent(); return onDeselected; } } [SerializeField] private LeanSelectableEvent onDeselected; /// This is invoked when you try to select, but nothing is found. public UnityEvent OnNothing { get { if (onNothing == null) onNothing = new UnityEvent(); return onNothing; } } [SerializeField] private UnityEvent onNothing; public static event System.Action OnAnySelected; public static event System.Action OnAnyDeselected; public bool IsSelected(LeanSelectable selectable) { return selectables != null && selectables.Contains(selectable); } /// This will select the specified object and add it to this component's Selectables list, if it isn't already there. public void Select(LeanSelectable selectable) { TrySelect(selectable); } /// This remove the specified object from this component's Selectables list if present, and deselect it. public void Deselect(LeanSelectable selectable) { if (selectable != null && selectables != null) { TryDeselect(selectable); } } protected bool TrySelect(LeanSelectable selectable) { if (selectable != null && selectable.isActiveAndEnabled == true) { if (TryReselect(selectable) == true) { if (Selectables.Contains(selectable) == false) // NOTE: Property { switch (limit) { case LimitType.Unlimited: { } break; case LimitType.StopAtMax: { if (selectables.Count >= maxSelectables) { return false; } } break; case LimitType.DeselectFirst: { if (selectables.Count > 0 && selectables.Count >= maxSelectables) { TryDeselect(selectables[0]); } } break; } } selectables.Add(selectable); if (onSelected != null) onSelected.Invoke(selectable); if (OnAnySelected != null) OnAnySelected.Invoke(this, selectable); selectable.InvokeOnSelected(this); return true; } } // Nothing was selected? else { if (onNothing != null) onNothing.Invoke(); if (deselectWithNothing == true) { DeselectAll(); } } return false; } private bool TryReselect(LeanSelectable selectable) { switch (reselect) { case ReselectType.KeepSelected: { if (Selectables.Contains(selectable) == false) // NOTE: Property { return true; } } break; case ReselectType.Deselect: { if (Selectables.Contains(selectable) == false) // NOTE: Property { return true; } else { Deselect(selectable); } } break; case ReselectType.DeselectAndSelect: { if (Selectables.Contains(selectable) == true) // NOTE: Property { Deselect(selectable); } } return true; case ReselectType.SelectAgain: { } return true; } return false; } protected bool TryDeselect(LeanSelectable selectable) { if (selectables != null) { var index = selectables.IndexOf(selectable); if (index >= 0) { return TryDeselect(index); } } return false; } protected bool TryDeselect(int index) { var success = false; if (selectables != null && index >= 0 && index < selectables.Count) { var selectable = selectables[index]; selectables.RemoveAt(index); if (selectable != null) { selectable.InvokeOnDeslected(this); if (onDeselected != null) { onDeselected.Invoke(selectable); } if (OnAnyDeselected != null) { OnAnyDeselected.Invoke(this, selectable); } } success = true; } return success; } /// This will deselect all objects that were selected by this component. [ContextMenu("Deselect All")] public void DeselectAll() { if (selectables != null) { while (selectables.Count > 0) { var index = selectables.Count - 1; var selectable = selectables[index]; selectables.RemoveAt(index); selectable.InvokeOnDeslected(this); } } } /// This will deselect objects in chronological order until the selected object count reaches the specified amount. public void Cull(int maxCount) { if (selectables != null) { while (selectables.Count > 0 && selectables.Count > maxCount) { var selectable = selectables[0]; selectables.RemoveAt(0); if (selectable != null) { if (selectable != null) { Deselect(selectable); } } } } } protected virtual void OnEnable() { instancesNode = Instances.AddLast(this); } protected virtual void OnDisable() { Instances.Remove(instancesNode); instancesNode = null; } protected virtual void OnDestroy() { DeselectAll(); } } } #if UNITY_EDITOR namespace Lean.Common.Editor { using TARGET = LeanSelect; [UnityEditor.CanEditMultipleObjects] [UnityEditor.CustomEditor(typeof(TARGET))] public class LeanSelect_Editor : LeanEditor { [System.NonSerialized] TARGET tgt; [System.NonSerialized] TARGET[] tgts; protected override void OnInspector() { GetTargets(out tgt, out tgts); Draw("deselectWithNothing", "If you attempt to select a point that has no objects underneath, should all currently selected objects be deselected?"); Draw("limit", "If you have selected the maximum number of objects, what should happen?\n\nUnlimited = Always allow selection.\n\nStopAtMax = Allow selection up to the MaxSelectables count, then do nothing.\n\nDeselectFirst = Always allow selection, but deselect the first object when the MaxSelectables count is reached."); if (Any(tgts, t => t.Limit != LeanSelect.LimitType.Unlimited)) { BeginIndent(); Draw("maxSelectables", "The maximum number of selectables that can be selected at the same time.\n\n0 = Unlimited."); EndIndent(); } Draw("reselect", "If you select an already selected selectable, what should happen?"); Separator(); var select = (LeanSelectable)UnityEditor.EditorGUILayout.ObjectField(new GUIContent("Select", "Drop a selectable object here to select it."), null, typeof(LeanSelectable), true); var deselect = (LeanSelectable)UnityEditor.EditorGUILayout.ObjectField(new GUIContent("Deselect", "Drop a selectable object here to deselect it."), null, typeof(LeanSelectable), true); BeginDisabled(); Draw("selectables", "This stores all objects selected by this component."); EndDisabled(); Separator(); var showUnusedEvents = DrawFoldout("Show Unused Events", "Show all events?"); DrawEvents(showUnusedEvents); if (select != null) { Each(tgts, t => t.Select(select), true); } if (deselect != null) { Each(tgts, t => t.Deselect(deselect), true); } } protected virtual void DrawEvents(bool showUnusedEvents) { if (Any(tgts, t => t.OnSelected.GetPersistentEventCount() > 0) == true || showUnusedEvents == true) { Draw("onSelected"); } if (Any(tgts, t => t.OnDeselected.GetPersistentEventCount() > 0) == true || showUnusedEvents == true) { Draw("onDeselected"); } if (Any(tgts, t => t.OnNothing.GetPersistentEventCount() > 0) == true || showUnusedEvents == true) { Draw("onNothing"); } } } } #endif