A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR && UNITY_INPUT_SYSTEM_PROJECT_WIDE_ACTIONS
2using System;
3using System.Linq.Expressions;
4using UnityEditor;
5using UnityEditor.UIElements;
6using UnityEngine.UIElements;
7
8namespace UnityEngine.InputSystem.Editor
9{
10 internal class StateContainer
11 {
12 public event Action<InputActionsEditorState> StateChanged;
13
14 private VisualElement m_RootVisualElement;
15 private InputActionsEditorState m_State;
16 public readonly string assetGUID;
17
18 public StateContainer(InputActionsEditorState initialState, string assetGUID)
19 {
20 m_State = initialState;
21 this.assetGUID = assetGUID;
22 }
23
24 public void Dispatch(Command command)
25 {
26 if (command == null)
27 throw new ArgumentNullException(nameof(command));
28
29 m_State = command(m_State);
30
31 // why not just invoke the state changed event immediately you ask? The Dispatch method might have
32 // been called from inside a UI element event handler and if we raised the event immediately, a view
33 // might try to redraw itself *during* execution of the event handler.
34 m_RootVisualElement.schedule.Execute(() =>
35 {
36 // catch exceptions here or the UIToolkit scheduled event will keep firing forever.
37 try
38 {
39 StateChanged?.Invoke(m_State);
40 }
41 catch (Exception e)
42 {
43 Debug.LogException(e);
44 }
45 });
46 }
47
48 public void Initialize(VisualElement rootVisualElement)
49 {
50 // We need to use a root element for the TrackSerializedObjectValue that is destroyed with the view.
51 // Using a root element from the settings window would not enable the tracking callback to be destroyed or garbage collected.
52
53 m_RootVisualElement = rootVisualElement;
54
55 m_RootVisualElement.Unbind();
56 m_RootVisualElement.TrackSerializedObjectValue(m_State.serializedObject, so =>
57 {
58 StateChanged?.Invoke(m_State);
59 });
60 StateChanged?.Invoke(m_State);
61 rootVisualElement.Bind(m_State.serializedObject);
62 }
63
64 /// <summary>
65 /// Return a copy of the state.
66 /// </summary>
67 /// <remarks>
68 /// It can sometimes be necessary to get access to the state outside of a state change event, like for example
69 /// when creating views in response to UI click events. This method is for those times.
70 /// </remarks>
71 /// <returns></returns>
72 public InputActionsEditorState GetState()
73 {
74 return m_State;
75 }
76
77 public void Bind<TValue>(Expression<Func<InputActionsEditorState, ReactiveProperty<TValue>>> expr,
78 Action<InputActionsEditorState> propertyChangedCallback)
79 {
80 WhenChanged(expr, propertyChangedCallback);
81 propertyChangedCallback(m_State);
82 }
83
84 public void Bind(Expression<Func<InputActionsEditorState, SerializedProperty>> expr,
85 Action<SerializedProperty> serializedPropertyChangedCallback)
86 {
87 var propertyGetterFunc = WhenChanged(expr, serializedPropertyChangedCallback);
88 serializedPropertyChangedCallback(propertyGetterFunc(m_State));
89 }
90
91 public Func<InputActionsEditorState, ReactiveProperty<TValue>> WhenChanged<TValue>(Expression<Func<InputActionsEditorState, ReactiveProperty<TValue>>> expr,
92 Action<InputActionsEditorState> propertyChangedCallback)
93 {
94 var func = ExpressionUtils.CreateGetter(expr);
95 if (func == null)
96 throw new ArgumentException($"Couldn't get property info from expression.");
97
98 var prop = func(m_State);
99 if (prop == null)
100 throw new InvalidOperationException($"ReactiveProperty {expr} has not been assigned.");
101
102 prop.Changed += _ => propertyChangedCallback(m_State);
103
104 return func;
105 }
106
107 public Func<InputActionsEditorState, SerializedProperty> WhenChanged(Expression<Func<InputActionsEditorState, SerializedProperty>> expr,
108 Action<SerializedProperty> serializedPropertyChangedCallback)
109 {
110 var serializedPropertyGetter = ExpressionUtils.CreateGetter(expr);
111 if (serializedPropertyGetter == null)
112 throw new ArgumentException($"Couldn't get property info from expression.");
113
114 var serializedProperty = serializedPropertyGetter(m_State);
115 if (serializedProperty == null)
116 throw new InvalidOperationException($"ReactiveProperty {expr} has not been assigned.");
117
118 m_RootVisualElement.TrackPropertyValue(serializedProperty, serializedPropertyChangedCallback);
119 return serializedPropertyGetter;
120 }
121 }
122}
123
124#endif