A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using UnityEditor;
5using UnityEngine;
6
7namespace Unity.VisualScripting
8{
9 [Canvas(typeof(StateGraph))]
10 public sealed class StateCanvas : VisualScriptingCanvas<StateGraph>
11 {
12 public StateCanvas(StateGraph graph) : base(graph) { }
13
14
15 #region View
16
17 protected override bool shouldEdgePan => base.shouldEdgePan || isCreatingTransition;
18
19 #endregion
20
21
22 #region Drawing
23
24 protected override void DrawBackground()
25 {
26 base.DrawBackground();
27
28 if (isCreatingTransition)
29 {
30 var startRect = this.Widget(transitionSource).position;
31 var end = mousePosition;
32
33 Edge startEdge, endEdge;
34
35 GraphGUI.GetConnectionEdge
36 (
37 startRect.center,
38 end,
39 out startEdge,
40 out endEdge
41 );
42
43 var start = startRect.GetEdgeCenter(startEdge);
44
45 GraphGUI.DrawConnectionArrow(Color.white, start, end, startEdge, endEdge);
46 }
47 }
48
49 #endregion
50
51
52 #region Clipboard
53
54 public override void ShrinkCopyGroup(HashSet<IGraphElement> copyGroup)
55 {
56 copyGroup.RemoveWhere(element =>
57 {
58 if (element is IStateTransition)
59 {
60 var transition = (IStateTransition)element;
61
62 if (!copyGroup.Contains(transition.source) ||
63 !copyGroup.Contains(transition.destination))
64 {
65 return true;
66 }
67 }
68
69 return false;
70 });
71 }
72
73 #endregion
74
75
76 #region Window
77
78 public override void OnToolbarGUI()
79 {
80 if (graph.states.Any(u => u.GetException(reference) != null) || graph.transitions.Any(t => t.GetException(reference) != null))
81 {
82 if (GUILayout.Button("Clear Errors", LudiqStyles.toolbarButton))
83 {
84 foreach (var state in graph.states)
85 {
86 state.SetException(reference, null);
87 }
88
89 foreach (var transition in graph.transitions)
90 {
91 transition.SetException(reference, null);
92 }
93 }
94 }
95
96 EditorGUI.BeginChangeCheck();
97
98 BoltCore.Configuration.dimInactiveNodes = GUILayout.Toggle(BoltCore.Configuration.dimInactiveNodes, "Dim", LudiqStyles.toolbarButton);
99
100 if (EditorGUI.EndChangeCheck())
101 {
102 BoltCore.Configuration.Save();
103 }
104
105 base.OnToolbarGUI();
106 }
107
108 #endregion
109
110
111 #region Context
112
113 protected override void OnContext()
114 {
115 if (isCreatingTransition)
116 {
117 CancelTransition();
118 }
119 else
120 {
121 base.OnContext();
122 }
123 }
124
125 protected override IEnumerable<DropdownOption> GetContextOptions()
126 {
127 yield return new DropdownOption((Action<Vector2>)CreateFlowState, "Create Script State");
128 yield return new DropdownOption((Action<Vector2>)CreateSuperState, "Create Super State");
129 yield return new DropdownOption((Action<Vector2>)CreateAnyState, "Create Any State");
130 yield return new DropdownOption((Action<Vector2>)(NewSticky), "Create Sticky Note");
131 foreach (var baseOption in base.GetContextOptions())
132 {
133 yield return baseOption;
134 }
135 }
136
137 private void CreateFlowState(Vector2 position)
138 {
139 var flowState = FlowState.WithEnterUpdateExit();
140
141 if (!graph.states.Any())
142 {
143 flowState.isStart = true;
144 flowState.nest.embed.title = "Start";
145 }
146
147 AddState(flowState, position);
148 }
149
150 private void CreateSuperState(Vector2 position)
151 {
152 var superState = SuperState.WithStart();
153
154 if (!graph.states.Any())
155 {
156 superState.isStart = true;
157 superState.nest.embed.title = "Start";
158 }
159
160 AddState(superState, position);
161 }
162
163 private void CreateAnyState(Vector2 position)
164 {
165 AddState(new AnyState(), position);
166 }
167
168 private void NewSticky(Vector2 position)
169 {
170 var stickyNote = new StickyNote() { position = new Rect(position, new Vector2(100, 100)) };
171 graph.elements.Add(stickyNote);
172 selection.Select(stickyNote);
173 GUI.changed = true;
174 }
175
176 public void AddState(IState state, Vector2 position)
177 {
178 UndoUtility.RecordEditedObject("Create State");
179 state.position = position;
180 graph.states.Add(state);
181 state.position -= this.Widget(state).position.size / 2;
182 state.position = state.position.PixelPerfect();
183 this.Widget(state).Reposition();
184 selection.Select(state);
185 GUI.changed = true;
186 }
187
188 #endregion
189
190
191 #region Lifecycle
192
193 public override void Close()
194 {
195 base.Close();
196
197 CancelTransition();
198 }
199
200 protected override void HandleHighPriorityInput()
201 {
202 if (isCreatingTransition)
203 {
204 if (e.IsMouseDrag(MouseButton.Left))
205 {
206 // Priority over lasso
207 e.Use();
208 }
209 else if (e.IsKeyDown(KeyCode.Escape))
210 {
211 CancelTransition();
212 e.Use();
213 }
214 if (e.IsMouseDown(MouseButton.Left) || e.IsMouseUp(MouseButton.Left))
215 {
216 CompleteTransitionToNewState();
217 e.Use();
218 }
219 }
220
221 base.HandleHighPriorityInput();
222 }
223
224 public void CompleteTransitionToNewState()
225 {
226 var startRect = this.Widget(transitionSource).position;
227 var end = mousePosition;
228
229 GraphGUI.GetConnectionEdge
230 (
231 startRect.center,
232 end,
233 out var startEdge,
234 out var endEdge
235 );
236
237 var destination = FlowState.WithEnterUpdateExit();
238 graph.states.Add(destination);
239
240 Vector2 offset;
241
242 var size = this.Widget(destination).position.size;
243
244 switch (endEdge)
245 {
246 case Edge.Left:
247 offset = new Vector2(0, -size.y / 2);
248 break;
249 case Edge.Right:
250 offset = new Vector2(-size.x, -size.y / 2);
251 break;
252 case Edge.Top:
253 offset = new Vector2(-size.x / 2, 0);
254 break;
255 case Edge.Bottom:
256 offset = new Vector2(-size.x / 2, -size.y);
257 break;
258 default:
259 throw new UnexpectedEnumValueException<Edge>(endEdge);
260 }
261
262 destination.position = mousePosition + offset;
263
264 destination.position = destination.position.PixelPerfect();
265
266 EndTransition(destination);
267 }
268
269 #endregion
270
271
272 #region Drag & Drop
273
274 public override bool AcceptsDragAndDrop()
275 {
276 return DragAndDropUtility.Is<ScriptGraphAsset>() || DragAndDropUtility.Is<StateGraphAsset>();
277 }
278
279 public override void PerformDragAndDrop()
280 {
281 if (DragAndDropUtility.Is<ScriptGraphAsset>())
282 {
283 var flowMacro = DragAndDropUtility.Get<ScriptGraphAsset>();
284 var flowState = new FlowState(flowMacro);
285 AddState(flowState, DragAndDropUtility.position);
286 }
287 else if (DragAndDropUtility.Is<StateGraphAsset>())
288 {
289 var asset = DragAndDropUtility.Get<StateGraphAsset>();
290 var superState = new SuperState(asset);
291 AddState(superState, DragAndDropUtility.position);
292 }
293 }
294
295 public override void DrawDragAndDropPreview()
296 {
297 if (DragAndDropUtility.Is<ScriptGraphAsset>())
298 {
299 GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, DragAndDropUtility.Get<ScriptGraphAsset>().name, typeof(ScriptGraphAsset).Icon());
300 }
301 else if (DragAndDropUtility.Is<StateGraphAsset>())
302 {
303 GraphGUI.DrawDragAndDropPreviewLabel(DragAndDropUtility.offsetedPosition, DragAndDropUtility.Get<StateGraphAsset>().name, typeof(StateGraphAsset).Icon());
304 }
305 }
306
307 #endregion
308
309
310 #region Transition Creation
311
312 public IState transitionSource { get; set; }
313
314 public bool isCreatingTransition => transitionSource != null;
315
316 public void StartTransition(IState source)
317 {
318 transitionSource = source;
319 window.Focus();
320 }
321
322 public void EndTransition(IState destination)
323 {
324 UndoUtility.RecordEditedObject("Create State Transition");
325
326 var transition = FlowStateTransition.WithDefaultTrigger(transitionSource, destination);
327 graph.transitions.Add(transition);
328 transitionSource = null;
329 this.Widget(transition).BringToFront();
330 selection.Select(transition);
331 GUI.changed = true;
332 }
333
334 public void CancelTransition()
335 {
336 transitionSource = null;
337 }
338
339 #endregion
340 }
341}