#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS using System; using System.Linq.Expressions; using UnityEditor; using UnityEditor.UIElements; using UnityEngine.UIElements; namespace UnityEngine.InputSystem.Editor { internal class StateContainer { public event Action StateChanged; private VisualElement m_RootVisualElement; private InputActionsEditorState m_State; public readonly string assetGUID; public StateContainer(InputActionsEditorState initialState, string assetGUID) { m_State = initialState; this.assetGUID = assetGUID; } public void Dispatch(Command command) { if (command == null) throw new ArgumentNullException(nameof(command)); m_State = command(m_State); // why not just invoke the state changed event immediately you ask? The Dispatch method might have // been called from inside a UI element event handler and if we raised the event immediately, a view // might try to redraw itself *during* execution of the event handler. m_RootVisualElement.schedule.Execute(() => { // catch exceptions here or the UIToolkit scheduled event will keep firing forever. try { StateChanged?.Invoke(m_State); } catch (Exception e) { Debug.LogException(e); } }); } public void Initialize(VisualElement rootVisualElement) { // We need to use a root element for the TrackSerializedObjectValue that is destroyed with the view. // Using a root element from the settings window would not enable the tracking callback to be destroyed or garbage collected. m_RootVisualElement = rootVisualElement; m_RootVisualElement.Unbind(); m_RootVisualElement.TrackSerializedObjectValue(m_State.serializedObject, so => { StateChanged?.Invoke(m_State); }); StateChanged?.Invoke(m_State); rootVisualElement.Bind(m_State.serializedObject); } /// /// Return a copy of the state. /// /// /// It can sometimes be necessary to get access to the state outside of a state change event, like for example /// when creating views in response to UI click events. This method is for those times. /// /// public InputActionsEditorState GetState() { return m_State; } public void Bind(Expression>> expr, Action propertyChangedCallback) { WhenChanged(expr, propertyChangedCallback); propertyChangedCallback(m_State); } public void Bind(Expression> expr, Action serializedPropertyChangedCallback) { var propertyGetterFunc = WhenChanged(expr, serializedPropertyChangedCallback); serializedPropertyChangedCallback(propertyGetterFunc(m_State)); } public Func> WhenChanged(Expression>> expr, Action propertyChangedCallback) { var func = ExpressionUtils.CreateGetter(expr); if (func == null) throw new ArgumentException($"Couldn't get property info from expression."); var prop = func(m_State); if (prop == null) throw new InvalidOperationException($"ReactiveProperty {expr} has not been assigned."); prop.Changed += _ => propertyChangedCallback(m_State); return func; } public Func WhenChanged(Expression> expr, Action serializedPropertyChangedCallback) { var serializedPropertyGetter = ExpressionUtils.CreateGetter(expr); if (serializedPropertyGetter == null) throw new ArgumentException($"Couldn't get property info from expression."); var serializedProperty = serializedPropertyGetter(m_State); if (serializedProperty == null) throw new InvalidOperationException($"ReactiveProperty {expr} has not been assigned."); m_RootVisualElement.TrackPropertyValue(serializedProperty, serializedPropertyChangedCallback); return serializedPropertyGetter; } } } #endif