// Copyright (c) Microsoft Corporation. // Licensed under the MIT License using Microsoft.MixedReality.Toolkit.Utilities; using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; namespace Microsoft.MixedReality.Toolkit.Experimental.InteractiveElement { /// /// Manages the events contained in states within an Interaction Element. /// public class EventReceiverManager { /// /// Constructor for the event receiver manager. /// /// The state manager for this event receiver manager internal EventReceiverManager(StateManager interactiveStateManager) { stateManager = interactiveStateManager; InitializeEventReceivers(); } /// /// Dictionary of active event receivers for the state events. /// public Dictionary EventReceivers { get; protected set; } = new Dictionary(); // The state manager for this event receiver manager private StateManager stateManager = null; /// /// Initialize the event receivers for each state. /// internal void InitializeEventReceivers() { foreach (KeyValuePair state in stateManager.States) { // If an interactive element component is created via script instead of initialized in the inspector, // an instance of the event configuration needs to be created if (state.Value.EventConfiguration == null) { state.Value.EventConfiguration = CreateEventConfigurationInstance(state.Key); } // Initialize runtime event receiver classes for the states InitializeAndAddEventReceiver(state.Key); } } /// /// Invoke a state event with optional event data. /// /// The name of the state /// The event data for the state event (optional) public void InvokeStateEvent(string stateName, BaseEventData eventData = null) { if (stateManager.InteractiveElement.IsStatePresent(stateName)) { BaseEventReceiver receiver = EventReceivers[stateName]; // Only invoke state events if Interactive Element is Active if (receiver != null && stateManager.InteractiveElement.Active) { receiver.OnUpdate(stateManager, eventData); } else if (receiver == null) { Debug.LogError($"The event receiver for the {stateName} state does not exist"); } } } /// /// Get the event configuration of a state. /// /// The name of the state that contains the event configuration to be retrieved /// The Interaction Event Configuration of the state internal BaseInteractionEventConfiguration GetEventConfiguration(string stateName) { InteractionState state = stateManager.GetState(stateName); if (state == null) { Debug.LogError($"An event configuration for the {stateName} state does not exist"); } var eventConfig = state.EventConfiguration; return (BaseInteractionEventConfiguration)eventConfig; } /// /// Sets the event configuration for a given state. This method checks if the state has a valid associated event configuration, creates /// an instance of the event configuration class, and initializes the matching runtime class. /// /// This state's event configuration will be set /// The set event configuration for the state. internal BaseInteractionEventConfiguration SetEventConfiguration(InteractionState state) { var eventConfiguration = CreateEventConfigurationInstance(state.Name); if (eventConfiguration != null) { state.EventConfiguration = eventConfiguration; InitializeAndAddEventReceiver(state.Name); } else { Debug.Log($"The event configuration for the {state.Name} was not set."); } return eventConfiguration; } // Create an instance of an event configuration for a state private BaseInteractionEventConfiguration CreateEventConfigurationInstance(string stateName) { BaseInteractionEventConfiguration eventConfiguration; string subStateName = stateManager.GetState(stateName).GetSubStateName(); // Check if the state has an associated event configuration by state name. // For example, the Focus state is associated with the FocusEvents class which has BaseInteractionEventConfiguration as its base class. // The FocusEvents class contains unity events with FocusEventData. // This pattern continues with states that have events with specific event data, i.e. the Touch state // is associated with the serialized class TouchEvents which contains unity events with TouchEventData var eventConfigTypes = TypeCacheUtility.GetSubClasses(); Type eventConfigType = eventConfigTypes.Find((type) => type.Name.StartsWith(subStateName)); if (eventConfigType != null) { eventConfiguration = Activator.CreateInstance(eventConfigType) as BaseInteractionEventConfiguration; } else { // If a state does not have an associated event configuration class, then create an instance of the // StateEvents class which contains the OnStateOn and OnStateOff unity events. These unity events do not have event data. eventConfiguration = Activator.CreateInstance(typeof(StateEvents)) as BaseInteractionEventConfiguration; } eventConfiguration.StateName = stateName; return eventConfiguration; } // Initialize a runtime event receiver via the state's event configuration and add it to the event receiver dictionary private BaseEventReceiver InitializeAndAddEventReceiver(string stateName) { InteractionState state = stateManager.GetState(stateName); BaseInteractionEventConfiguration eventConfiguration = (BaseInteractionEventConfiguration)state.EventConfiguration; string subStateName = state.GetSubStateName(); // Find the associated event receiver for the state if it has one var eventReceiverTypes = TypeCacheUtility.GetSubClasses(); Type eventReceiver; try { eventReceiver = eventReceiverTypes?.Find((type) => type.Name.StartsWith(subStateName)); } catch { eventReceiver = null; } if (eventReceiver != null) { eventConfiguration.EventReceiver = Activator.CreateInstance(eventReceiver, new object[] { eventConfiguration }) as BaseEventReceiver; } else { eventConfiguration.EventReceiver = Activator.CreateInstance(typeof(StateReceiver), new object[] { eventConfiguration }) as BaseEventReceiver; } EventReceivers.Add(stateName, eventConfiguration.EventReceiver); return eventConfiguration.EventReceiver; } } }