using System; using System.Collections.Generic; using UnityEngine; using Framework.Selection; using Framework.Services; using Framework.Support; namespace Framework.UI { /// Highlights the selection of an actor public class SelectionHighlighter : ScriptComponent { /// /// Selection tracker for which the highlighting system will highlight items /// public SelectionTracker SelectionTracker; /// Color in which highlighted objects will be shown public Color HighlightColor = Color.red; /// Color in which selected objects will be shown public Color SelectionColor = Color.white; /// Called once per visual frame protected virtual void Update() { if (this.SelectionTracker != this.usedSelectionTracker) { if (this.highlightedItems == null) { createDelegatesAndCollections(); } if (this.usedSelectionTracker != null) { detachFromAllEvents(); } this.usedSelectionTracker = this.SelectionTracker; } if (this.usedSelectionTracker != null) { resyncEventSubscriptions(); } } /// Attaches and detaches from change notifications as needed private void resyncEventSubscriptions() { if (this.usedSelectionTracker.CanHighlight != this.highlightEventsAttached) { if (this.usedSelectionTracker.CanHighlight) { attachToHighlightEvents(); } else { detachFromHighlightEvents(); } } if (this.usedSelectionTracker.CanSelect != this.selectionEventsAttached) { if (this.usedSelectionTracker.CanSelect) { attachToSelectionEvents(); } else { detachFromSelectionEvents(); } } } /// Detaches from all events of the selection tracker private void detachFromAllEvents() { if (this.highlightEventsAttached) { detachFromHighlightEvents(); } if (this.selectionEventsAttached) { detachFromSelectionEvents(); } } /// Called when the highlighter component gets enabled protected virtual void OnEnable() { if (this.usedSelectionTracker != null) { resyncEventSubscriptions(); } } /// Called when the highlighter component gets disabled protected virtual void OnDisable() { if (this.usedSelectionTracker != null) { detachFromAllEvents(); } } /// Called when the component gets removed from a game object protected virtual void OnDestroy() { // If selection tracker has been destroyed, its overloaded equals operator // will compare as equal to null. In this case, wo only need to clear out // any objects still marked as selected or highlighted if (this.usedSelectionTracker == null) { selectedItemsClearing(); highlightedItemsClearing(); } else { detachFromAllEvents(); } } /// Subscribes to the notifications for the highlighted object collection private void attachToHighlightEvents() { var observableHighlightedSet = ( (IObservableCollection) this.usedSelectionTracker.HighlightedObjects ); observableHighlightedSet.Clearing += this.highlightedItemsClearingDelegate; observableHighlightedSet.ItemAdded += this.highlightedItemAddedDelegate; observableHighlightedSet.ItemRemoved += this.highlightedItemRemovedDelegate; // If any objects are already in the highlight collection make them highlighted foreach (Selectable selectable in this.usedSelectionTracker.HighlightedObjects) { highlightedItemAdded(selectable); } this.highlightEventsAttached = true; } /// Subscribes to the notifications for the selected object collection private void attachToSelectionEvents() { var observableSelectedSet = ( (IObservableCollection)this.usedSelectionTracker.SelectedObjects ); observableSelectedSet.Clearing += this.selectedItemsClearingDelegate; observableSelectedSet.ItemAdded += this.selectedItemAddedDelegate; observableSelectedSet.ItemRemoved += this.selectedItemRemovedDelegate; // If any bjects are alreadz in the selected collection make them highlighted foreach(Selectable selectable in this.usedSelectionTracker.SelectedObjects) { selectedItemAdded(selectable); } this.selectionEventsAttached = true; } /// Unsubscribes from the notifications of the highlighted object collection private void detachFromHighlightEvents() { var observableHighlightedSet = ( (IObservableCollection)this.usedSelectionTracker.HighlightedObjects ); this.highlightedItemsClearing(); observableHighlightedSet.ItemRemoved -= this.highlightedItemRemovedDelegate; observableHighlightedSet.ItemAdded -= this.highlightedItemAddedDelegate; observableHighlightedSet.Clearing -= this.highlightedItemsClearingDelegate; this.highlightEventsAttached = false; } /// Unsubscribes from the notifications of the selected object collection private void detachFromSelectionEvents() { var observableSelectedSet = ( (IObservableCollection)this.usedSelectionTracker.SelectedObjects ); this.selectedItemsClearing(); observableSelectedSet.ItemRemoved -= this.selectedItemRemovedDelegate; observableSelectedSet.ItemAdded -= this.selectedItemAddedDelegate; observableSelectedSet.Clearing -= this.selectedItemsClearingDelegate; this.selectionEventsAttached = true; } /// Creates the permanent collections and delegates used by the instance private void createDelegatesAndCollections() { this.highlightedItems = new List(); this.selectedItems = new List(); this.highlightedItemsClearingDelegate = new Action(highlightedItemsClearing); this.highlightedItemAddedDelegate = new Action(highlightedItemAdded); this.highlightedItemRemovedDelegate = new Action(highlightedItemRemoved); this.selectedItemsClearingDelegate = new Action(selectedItemsClearing); this.selectedItemAddedDelegate = new Action(selectedItemAdded); this.selectedItemRemovedDelegate = new Action(selectedItemRemoved); this.selectionEventsAttached = false; } /// Called when the highlighted items set is being cleared private void highlightedItemsClearing() { foreach(Selectable selectable in this.usedSelectionTracker.HighlightedObjects) { highlightedItemRemoved(selectable); } } /// Called when a new item is added to the highlighted items set /// Item that has been added to the highlighted items set private void highlightedItemAdded(Selectable item) { this.highlightedItems.Add(item); #if HAVE_HIGHLIGHTING_SYSTEM HighlightingSystem.Highlighter highlighter; { highlighter = item.GetComponent(); if(highlighter == null) { highlighter = item.gameObject.AddComponent(); } } highlighter.ConstantOnImmediate(this.HighlightColor); #endif // HAVE_HIGHLIGHTING_SYSTEM } /// Called when a new item is removed from the highlighted items set /// Item that has been removed from the highlighted items set private void highlightedItemRemoved(Selectable item) { this.highlightedItems.Remove(item); // The component or its game object may have already been destroyed without // unregistering. In this case, the equals operators reports equality with null. if(item == null) { return; } #if HAVE_HIGHLIGHTING_SYSTEM HighlightingSystem.Highlighter highlighter; { highlighter = item.GetComponent(); if(highlighter != null) { highlighter.ConstantOffImmediate(); if(this.selectedItems.Contains(item)) { highlighter.ConstantOnImmediate(this.SelectionColor); } } } #endif // HAVE_HIGHLIGHTING_SYSTEM } /// Called when the selected items set is being cleared private void selectedItemsClearing() { foreach(Selectable selectable in this.usedSelectionTracker.SelectedObjects) { selectedItemRemoved(selectable); } } /// Called when a new item is added to the selected items set /// Item that has been added to the selected items set private void selectedItemAdded(Selectable item) { this.selectedItems.Add(item); #if HAVE_HIGHLIGHTING_SYSTEM HighlightingSystem.Highlighter highlighter; { highlighter = item.GetComponent(); if(highlighter == null) { highlighter = item.gameObject.AddComponent(); } } // Highlight overrides selection if(!this.highlightedItems.Contains(item)) { highlighter.ConstantOnImmediate(this.SelectionColor); } #endif // HAVE_HIGHLIGHTING_SYSTEM } /// Called when a new item is removed from the selected items set /// Item that has been removed from the selected items set private void selectedItemRemoved(Selectable item) { this.selectedItems.Remove(item); // The component or its game object may have already been destroyed without // unregistering. In this case, the equals operators reports equality with null. if(item == null) { return; } #if HAVE_HIGHLIGHTING_SYSTEM HighlightingSystem.Highlighter highlighter; { highlighter = item.GetComponent(); if(highlighter != null) { if(!this.highlightedItems.Contains(item)) { highlighter.ConstantOffImmediate(); } } } #endif // HAVE_HIGHLIGHTING_SYSTEM } /// Copy of the publicly assigned selection tracker /// > /// Theoretically, someone could assign a different selection tracker to this /// component. Then we have to remove highlighters from all selected objects /// in the old selection tracker and add highlighters to all selected objects /// in the new tracker. This field helps us know when we need these measures. /// private SelectionTracker usedSelectionTracker; /// Whether the highlighter is currently attached to the tracker private bool isAttached; /// Game objects that are currently being displayed as highlighted private IList highlightedItems; /// Game objects that are currently being displayed as selected private IList selectedItems; /// Whether the higlighting callbacks are current subscribed private bool highlightEventsAttached; /// Delegate for the method private Action highlightedItemsClearingDelegate; /// Delegate for the method private Action highlightedItemAddedDelegate; /// Delegate for the method private Action highlightedItemRemovedDelegate; /// Whether the selection callbacks are current subscribed private bool selectionEventsAttached; /// Delegate for the method private Action selectedItemsClearingDelegate; /// Delegate for the method private Action selectedItemAddedDelegate; /// Delegate for the method private Action selectedItemRemovedDelegate; } } // namespace Framework.UI