A game about forced loneliness, made by TACStudios
1#if UNITY_2023_2_OR_NEWER // UnityEngine.InputForUI Module unavailable in earlier releases
2using System;
3using System.Collections.Generic;
4using Unity.IntegerTime;
5using UnityEngine.InputSystem.Controls;
6using UnityEngine.InputForUI;
7
8namespace UnityEngine.InputSystem.Plugins.InputForUI
9{
10 using Event = UnityEngine.InputForUI.Event;
11 using EventModifiers = UnityEngine.InputForUI.EventModifiers;
12 using EventProvider = UnityEngine.InputForUI.EventProvider;
13
14 internal class InputSystemProvider : IEventProviderImpl
15 {
16 Configuration m_Cfg;
17
18 InputEventPartialProvider m_InputEventPartialProvider;
19
20 InputActionAsset m_InputActionAsset;
21
22 InputActionReference m_PointAction;
23 InputActionReference m_MoveAction;
24 InputActionReference m_SubmitAction;
25 InputActionReference m_CancelAction;
26 InputActionReference m_LeftClickAction;
27 InputActionReference m_MiddleClickAction;
28 InputActionReference m_RightClickAction;
29 InputActionReference m_ScrollWheelAction;
30
31 InputAction m_NextPreviousAction;
32
33 List<Event> m_Events = new List<Event>();
34
35 PointerState m_MouseState;
36
37 PointerState m_PenState;
38 bool m_SeenPenEvents;
39
40 PointerState m_TouchState;
41 bool m_SeenTouchEvents;
42
43 const float k_SmallestReportedMovementSqrDist = 0.01f;
44
45 NavigationEventRepeatHelper m_RepeatHelper = new();
46 bool m_ResetSeenEventsOnUpdate;
47
48#if UNITY_INPUT_SYSTEM_INPUT_MODULE_SCROLL_DELTA
49 const float kScrollUGUIScaleFactor = UIElements.WheelEvent.scrollDeltaPerTick;
50#else
51 const float kScrollUGUIScaleFactor = 3.0f;
52#endif
53
54 static Action<InputActionAsset> s_OnRegisterActions;
55
56 static InputSystemProvider()
57 {
58 // Only if InputSystem is enabled in the PlayerSettings do we set it as the provider.
59 // This includes situations where both InputManager and InputSystem are enabled.
60#if ENABLE_INPUT_SYSTEM
61 EventProvider.SetInputSystemProvider(new InputSystemProvider());
62#endif // ENABLE_INPUT_SYSTEM
63 }
64
65 [RuntimeInitializeOnLoadMethod(loadType: RuntimeInitializeLoadType.SubsystemRegistration)]
66 static void Bootstrap() {} // Empty function. Exists only to invoke the static class constructor in Runtime Players
67
68 EventModifiers m_EventModifiers => m_InputEventPartialProvider._eventModifiers;
69
70 DiscreteTime m_CurrentTime => (DiscreteTime)Time.timeAsRational;
71
72 const uint k_DefaultPlayerId = 0u;
73
74 public void Initialize()
75 {
76 m_InputEventPartialProvider ??= new InputEventPartialProvider();
77 m_InputEventPartialProvider.Initialize();
78
79 m_Events.Clear();
80
81 m_MouseState.Reset();
82
83 m_PenState.Reset();
84 m_SeenPenEvents = false;
85
86 m_TouchState.Reset();
87 m_SeenTouchEvents = false;
88
89 m_Cfg = Configuration.GetDefaultConfiguration();
90
91 RegisterActions();
92
93 InputSystem.onActionsChange += OnActionsChange;
94 }
95
96 public void Shutdown()
97 {
98 UnregisterActions();
99
100 m_InputEventPartialProvider.Shutdown();
101 m_InputEventPartialProvider = null;
102
103 InputSystem.onActionsChange -= OnActionsChange;
104 }
105
106 public void OnActionsChange()
107 {
108 UnregisterActions();
109
110 m_Cfg = Configuration.GetDefaultConfiguration();
111 RegisterActions();
112 }
113
114 public void Update()
115 {
116#if UNITY_EDITOR
117 // Ensure we are in a good (initialized) state before running updates.
118 // This could be in a bad state for a duration while the build pipeline is running
119 // when building tests to run in the Standalone Player.
120 if (m_InputActionAsset == null)
121 return;
122#endif
123
124 m_InputEventPartialProvider.Update();
125
126 // Sort events added by input actions callbacks, based on type.
127 // This is necessary to ensure that events are dispatched in the correct order.
128 // If all events are of the PointerEvents type, sorting is based on reverse order of the EventSource enum.
129 // Touch -> Pen -> Mouse.
130 m_Events.Sort((a, b) => SortEvents(a, b));
131
132 var currentTime = (DiscreteTime)Time.timeAsRational;
133
134 DirectionNavigation(currentTime);
135
136 foreach (var ev in m_Events)
137 {
138 // We need to ignore some pointer events based on priority (Touch->Pen->Mouse)
139 // This is mostly used to filter out simulated input, e.g. when pen is active it also generates mouse input
140 if (m_SeenTouchEvents && ev.type == Event.Type.PointerEvent && ev.eventSource == EventSource.Pen)
141 m_PenState.Reset();
142 else if ((m_SeenTouchEvents || m_SeenPenEvents) &&
143 ev.type == Event.Type.PointerEvent && (ev.eventSource == EventSource.Mouse || ev.eventSource == EventSource.Unspecified))
144 m_MouseState.Reset();
145 else
146 EventProvider.Dispatch(ev);
147 }
148
149 // Sometimes single lower priority events can be received when using Touch or Pen, on a different frame.
150 // To avoid dispatching them, the seen event flags aren't reset in between calls to OnPointerPerformed.
151 // Essentially, if we're moving with Touch or Pen, lower priority events aren't dispatch as well.
152 // Once OnClickPerformed is called, the seen flags are reset
153 if (m_ResetSeenEventsOnUpdate)
154 {
155 ResetSeenEvents();
156 m_ResetSeenEventsOnUpdate = false;
157 }
158
159 m_Events.Clear();
160 }
161
162 void ResetSeenEvents()
163 {
164 m_SeenTouchEvents = false;
165 m_SeenPenEvents = false;
166 }
167
168 public bool ActionAssetIsNotNull()
169 {
170 return m_InputActionAsset != null;
171 }
172
173 //TODO: Refactor as there is no need for having almost the same implementation in the IM and ISX?
174 void DirectionNavigation(DiscreteTime currentTime)
175 {
176 var(move, axesButtonWerePressed) = ReadCurrentNavigationMoveVector();
177 var direction = NavigationEvent.DetermineMoveDirection(move);
178
179 // Checks for next/previous directions if no movement was detected
180 if (direction == NavigationEvent.Direction.None)
181 {
182 direction = ReadNextPreviousDirection();
183 axesButtonWerePressed = m_NextPreviousAction.WasPressedThisFrame();
184 }
185
186 if (direction == NavigationEvent.Direction.None)
187 {
188 m_RepeatHelper.Reset();
189 }
190 else
191 {
192 if (m_RepeatHelper.ShouldSendMoveEvent(currentTime, direction, axesButtonWerePressed))
193 {
194 EventProvider.Dispatch(Event.From(new NavigationEvent
195 {
196 type = NavigationEvent.Type.Move,
197 direction = direction,
198 timestamp = currentTime,
199 eventSource = GetEventSource(GetActiveDeviceFromDirection(direction)),
200 playerId = k_DefaultPlayerId,
201 eventModifiers = m_EventModifiers
202 }));
203 }
204 }
205 }
206
207 InputDevice GetActiveDeviceFromDirection(NavigationEvent.Direction direction)
208 {
209 switch (direction)
210 {
211 case NavigationEvent.Direction.Left:
212 case NavigationEvent.Direction.Up:
213 case NavigationEvent.Direction.Right:
214 case NavigationEvent.Direction.Down:
215 if (m_MoveAction != null)
216 return m_MoveAction.action.activeControl.device;
217 break;
218 case NavigationEvent.Direction.Next:
219 case NavigationEvent.Direction.Previous:
220 if (m_NextPreviousAction != null)
221 return m_NextPreviousAction.activeControl.device;
222 break;
223 case NavigationEvent.Direction.None:
224 default:
225 break;
226 }
227
228 return Keyboard.current;
229 }
230
231 (Vector2, bool) ReadCurrentNavigationMoveVector()
232 {
233 // In case action has not been configured we return defaults
234 if (m_MoveAction == null)
235 return (default, default);
236
237 var move = m_MoveAction.action.ReadValue<Vector2>();
238 // Check if the action was "pressed" this frame to deal with repeating events
239 var axisWasPressed = m_MoveAction.action.WasPressedThisFrame();
240 return (move, axisWasPressed);
241 }
242
243 NavigationEvent.Direction ReadNextPreviousDirection()
244 {
245 if (m_NextPreviousAction.IsPressed()) // Note: never null since created through code
246 {
247 //TODO: For now it only deals with Keyboard, needs to deal with other devices if we can add bindings
248 // for Gamepad, etc
249 //TODO: An alternative could be to have an action for next and for previous since shortcut support does
250 // not work properly
251 if (m_NextPreviousAction.activeControl.device is Keyboard)
252 {
253 var keyboard = m_NextPreviousAction.activeControl.device as Keyboard;
254 // Return direction based on whether shift is pressed or not
255 return keyboard.shiftKey.isPressed ? NavigationEvent.Direction.Previous : NavigationEvent.Direction.Next;
256 }
257 }
258
259 return NavigationEvent.Direction.None;
260 }
261
262 static int SortEvents(Event a, Event b)
263 {
264 return Event.CompareType(a, b);
265 }
266
267 public void OnFocusChanged(bool focus)
268 {
269 m_InputEventPartialProvider.OnFocusChanged(focus);
270 }
271
272 public bool RequestCurrentState(Event.Type type)
273 {
274 if (m_InputEventPartialProvider.RequestCurrentState(type))
275 return true;
276
277 switch (type)
278 {
279 case Event.Type.PointerEvent:
280 {
281 if (m_TouchState.LastPositionValid)
282 EventProvider.Dispatch(Event.From(ToPointerStateEvent(m_CurrentTime, m_TouchState, EventSource.Touch)));
283 if (m_PenState.LastPositionValid)
284 EventProvider.Dispatch(Event.From(ToPointerStateEvent(m_CurrentTime, m_PenState, EventSource.Pen)));
285 if (m_MouseState.LastPositionValid)
286 EventProvider.Dispatch(Event.From(ToPointerStateEvent(m_CurrentTime, m_MouseState, EventSource.Mouse)));
287 else
288 {
289 // TODO maybe it's reasonable to poll and dispatch mouse state here anyway?
290 }
291
292 return m_TouchState.LastPositionValid ||
293 m_PenState.LastPositionValid ||
294 m_MouseState.LastPositionValid;
295 }
296 // TODO
297 case Event.Type.IMECompositionEvent:
298 default:
299 return false;
300 }
301 }
302
303 public uint playerCount => 1; // TODO
304
305 // copied from UIElementsRuntimeUtility.cs
306 static Vector2 ScreenBottomLeftToPanelPosition(Vector2 position, int targetDisplay)
307 {
308 // Flip positions Y axis between input and UITK
309 var screenHeight = Screen.height;
310 if (targetDisplay > 0 && targetDisplay < Display.displays.Length)
311 screenHeight = Display.displays[targetDisplay].systemHeight;
312 position.y = screenHeight - position.y;
313 return position;
314 }
315
316 PointerEvent ToPointerStateEvent(DiscreteTime currentTime, in PointerState state, EventSource eventSource)
317 {
318 return new PointerEvent
319 {
320 type = PointerEvent.Type.State,
321 pointerIndex = 0,
322 position = state.LastPosition,
323 deltaPosition = Vector2.zero,
324 scroll = Vector2.zero,
325 displayIndex = state.LastDisplayIndex,
326 // TODO
327 // tilt = eventSource == EventSource.Pen ? _lastPenData.tilt : Vector2.zero,
328 // twist = eventSource == EventSource.Pen ? _lastPenData.twist : 0.0f,
329 // pressure = eventSource == EventSource.Pen ? _lastPenData.pressure : 0.0f,
330 // isInverted = eventSource == EventSource.Pen && ((_lastPenData.penStatus & PenStatus.Inverted) != 0),
331 button = 0,
332 buttonsState = state.ButtonsState,
333 clickCount = 0,
334 timestamp = currentTime,
335 eventSource = eventSource,
336 playerId = k_DefaultPlayerId,
337 eventModifiers = m_EventModifiers
338 };
339 }
340
341 EventSource GetEventSource(InputAction.CallbackContext ctx)
342 {
343 var device = ctx.control.device;
344 return GetEventSource(device);
345 }
346
347 EventSource GetEventSource(InputDevice device)
348 {
349 if (device is Touchscreen)
350 return EventSource.Touch;
351 if (device is Pen)
352 return EventSource.Pen;
353 if (device is Mouse)
354 return EventSource.Mouse;
355 if (device is Keyboard)
356 return EventSource.Keyboard;
357 if (device is Gamepad)
358 return EventSource.Gamepad;
359
360 return EventSource.Unspecified;
361 }
362
363 ref PointerState GetPointerStateForSource(EventSource eventSource)
364 {
365 switch (eventSource)
366 {
367 case EventSource.Touch:
368 return ref m_TouchState;
369 case EventSource.Pen:
370 return ref m_PenState;
371 default:
372 return ref m_MouseState;
373 }
374 }
375
376 void DispatchFromCallback(in Event ev)
377 {
378 m_Events.Add(ev);
379 }
380
381 static int FindTouchFingerIndex(Touchscreen touchscreen, InputAction.CallbackContext ctx)
382 {
383 if (touchscreen == null)
384 return 0;
385
386 var asVector2Control = ctx.control is Vector2Control ? (Vector2Control)ctx.control : null;
387 var asTouchPressControl = ctx.control is TouchPressControl ? (TouchPressControl)ctx.control : null;
388 var asTouchControl = ctx.control is TouchControl ? (TouchControl)ctx.control : null;
389
390 // Finds the index of the matching control type in the Touchscreen device lost of touch controls (touches)
391 for (var i = 0; i < touchscreen.touches.Count; ++i)
392 {
393 if (asVector2Control != null && asVector2Control == touchscreen.touches[i].position)
394 return i;
395 if (asTouchPressControl != null && asTouchPressControl == touchscreen.touches[i].press)
396 return i;
397 if (asTouchControl != null && asTouchControl == touchscreen.touches[i])
398 return i;
399 }
400 return 0;
401 }
402
403 void OnPointerPerformed(InputAction.CallbackContext ctx)
404 {
405 var eventSource = GetEventSource(ctx);
406 ref var pointerState = ref GetPointerStateForSource(eventSource);
407
408 // Overall I'm not happy how leaky this is, we're using input actions to have flexibility to bind to different controls,
409 // but instead we just kinda abuse it to bind to different devices ...
410 var asPointerDevice = ctx.control.device is Pointer ? (Pointer)ctx.control.device : null;
411 var asPenDevice = ctx.control.device is Pen ? (Pen)ctx.control.device : null;
412 var asTouchscreenDevice = ctx.control.device is Touchscreen ? (Touchscreen)ctx.control.device : null;
413 var asTouchControl = ctx.control is TouchControl ? (TouchControl)ctx.control : null;
414 var pointerIndex = FindTouchFingerIndex(asTouchscreenDevice, ctx);
415
416 m_ResetSeenEventsOnUpdate = false;
417 if (asTouchControl != null || asTouchscreenDevice != null)
418 m_SeenTouchEvents = true;
419 else if (asPenDevice != null)
420 m_SeenPenEvents = true;
421
422 var positionISX = ctx.ReadValue<Vector2>();
423 var targetDisplay = asPointerDevice != null ? asPointerDevice.displayIndex.ReadValue() : (asTouchscreenDevice != null ? asTouchscreenDevice.displayIndex.ReadValue() : 0);
424 var position = ScreenBottomLeftToPanelPosition(positionISX, targetDisplay);
425 var delta = pointerState.LastPositionValid ? position - pointerState.LastPosition : Vector2.zero;
426
427 var tilt = asPenDevice != null ? asPenDevice.tilt.ReadValue() : Vector2.zero;
428 var twist = asPenDevice != null ? asPenDevice.twist.ReadValue() : 0.0f;
429 var pressure = asPenDevice != null
430 ? asPenDevice.pressure.ReadValue()
431 : (asTouchControl != null ? asTouchControl.pressure.ReadValue() : 0.0f);
432 var isInverted = asPenDevice != null
433 ? asPenDevice.eraser.isPressed
434 : false; // TODO any way to detect that pen is inverted but not touching?
435
436 if (delta.sqrMagnitude >= k_SmallestReportedMovementSqrDist)
437 {
438 DispatchFromCallback(Event.From(new PointerEvent
439 {
440 type = PointerEvent.Type.PointerMoved,
441 pointerIndex = pointerIndex,
442 position = position,
443 deltaPosition = delta,
444 scroll = Vector2.zero,
445 displayIndex = targetDisplay,
446 tilt = tilt,
447 twist = twist,
448 pressure = pressure,
449 isInverted = isInverted,
450 button = 0,
451 buttonsState = pointerState.ButtonsState,
452 clickCount = 0,
453 timestamp = m_CurrentTime,
454 eventSource = eventSource,
455 playerId = k_DefaultPlayerId,
456 eventModifiers = m_EventModifiers
457 }));
458
459 // only record if we send an event
460 pointerState.OnMove(m_CurrentTime, position, targetDisplay);
461 }
462 else if (!pointerState.LastPositionValid)
463 pointerState.OnMove(m_CurrentTime, position, targetDisplay);
464 }
465
466 void OnSubmitPerformed(InputAction.CallbackContext ctx)
467 {
468 DispatchFromCallback(Event.From(new NavigationEvent
469 {
470 type = NavigationEvent.Type.Submit,
471 direction = NavigationEvent.Direction.None,
472 timestamp = m_CurrentTime,
473 eventSource = GetEventSource(ctx),
474 playerId = k_DefaultPlayerId,
475 eventModifiers = m_EventModifiers
476 }));
477 }
478
479 void OnCancelPerformed(InputAction.CallbackContext ctx)
480 {
481 DispatchFromCallback(Event.From(new NavigationEvent
482 {
483 type = NavigationEvent.Type.Cancel,
484 direction = NavigationEvent.Direction.None,
485 timestamp = m_CurrentTime,
486 eventSource = GetEventSource(ctx),
487 playerId = k_DefaultPlayerId,
488 eventModifiers = m_EventModifiers
489 }));
490 }
491
492 void OnClickPerformed(InputAction.CallbackContext ctx, EventSource eventSource, PointerEvent.Button button)
493 {
494 ref var state = ref GetPointerStateForSource(eventSource);
495
496 var asTouchscreenDevice = ctx.control.device is Touchscreen ? (Touchscreen)ctx.control.device : null;
497 var asTouchControl = ctx.control is TouchControl ? (TouchControl)ctx.control : null;
498 var pointerIndex = FindTouchFingerIndex(asTouchscreenDevice, ctx);
499
500 m_ResetSeenEventsOnUpdate = true;
501 if (asTouchControl != null || asTouchscreenDevice != null)
502 m_SeenTouchEvents = true;
503
504 var wasPressed = state.ButtonsState.Get(button);
505 var isPressed = ctx.ReadValueAsButton();
506 state.OnButtonChange(m_CurrentTime, button, wasPressed, isPressed);
507
508 DispatchFromCallback(Event.From(new PointerEvent
509 {
510 type = isPressed ? PointerEvent.Type.ButtonPressed : PointerEvent.Type.ButtonReleased,
511 pointerIndex = pointerIndex,
512 position = state.LastPosition,
513 deltaPosition = Vector2.zero,
514 scroll = Vector2.zero,
515 displayIndex = state.LastDisplayIndex,
516 tilt = Vector2.zero,
517 twist = 0.0f,
518 pressure = 0.0f,
519 isInverted = false,
520 button = button,
521 buttonsState = state.ButtonsState,
522 clickCount = state.ClickCount,
523 timestamp = m_CurrentTime,
524 eventSource = eventSource,
525 playerId = k_DefaultPlayerId,
526 eventModifiers = m_EventModifiers
527 }));
528 }
529
530 void OnLeftClickPerformed(InputAction.CallbackContext ctx) => OnClickPerformed(ctx, GetEventSource(ctx), PointerEvent.Button.MouseLeft);
531 void OnMiddleClickPerformed(InputAction.CallbackContext ctx) => OnClickPerformed(ctx, GetEventSource(ctx), PointerEvent.Button.MouseMiddle);
532 void OnRightClickPerformed(InputAction.CallbackContext ctx) => OnClickPerformed(ctx, GetEventSource(ctx), PointerEvent.Button.MouseRight);
533
534 void OnScrollWheelPerformed(InputAction.CallbackContext ctx)
535 {
536 // ISXB-704: convert input value to uniform ticks before sending them to UI.
537 var scrollTicks = ctx.ReadValue<Vector2>() / InputSystem.scrollWheelDeltaPerTick;
538 if (scrollTicks.sqrMagnitude < k_SmallestReportedMovementSqrDist)
539 return;
540
541 var eventSource = GetEventSource(ctx);
542 ref var state = ref GetPointerStateForSource(eventSource);
543
544 var position = Vector2.zero;
545 var targetDisplay = 0;
546
547 if (state.LastPositionValid)
548 {
549 position = state.LastPosition;
550 targetDisplay = state.LastDisplayIndex;
551 }
552 else if (eventSource == EventSource.Mouse && Mouse.current != null)
553 {
554 position = Mouse.current.position.ReadValue();
555 targetDisplay = Mouse.current.displayIndex.ReadValue();
556 }
557
558 // Make scrollDelta look similar to IMGUI event scroll values.
559 var scrollDelta = new Vector2
560 {
561 x = scrollTicks.x * kScrollUGUIScaleFactor,
562 y = -scrollTicks.y * kScrollUGUIScaleFactor
563 };
564
565 DispatchFromCallback(Event.From(new PointerEvent
566 {
567 type = PointerEvent.Type.Scroll,
568 pointerIndex = 0,
569 position = position,
570 deltaPosition = Vector2.zero,
571 scroll = scrollDelta,
572 displayIndex = targetDisplay,
573 tilt = Vector2.zero,
574 twist = 0.0f,
575 pressure = 0.0f,
576 isInverted = false,
577 button = 0,
578 buttonsState = state.ButtonsState,
579 clickCount = 0,
580 timestamp = m_CurrentTime,
581 eventSource = EventSource.Mouse,
582 playerId = k_DefaultPlayerId,
583 eventModifiers = m_EventModifiers
584 }));
585 }
586
587 void RegisterNextPreviousAction()
588 {
589 m_NextPreviousAction = new InputAction(name: "nextPreviousAction", type: InputActionType.Button);
590 // TODO add more default bindings, or make them configurable
591 m_NextPreviousAction.AddBinding("<Keyboard>/tab");
592 m_NextPreviousAction.Enable();
593 }
594
595 void UnregisterFixedActions()
596 {
597 // The Next/Previous action is not part of the input actions asset
598 if (m_NextPreviousAction != null)
599 {
600 m_NextPreviousAction.Disable();
601 m_NextPreviousAction = null;
602 }
603 }
604
605 void RegisterActions()
606 {
607 m_InputActionAsset = m_Cfg.ActionAsset;
608
609 // Invoke potential lister observing registration
610 s_OnRegisterActions?.Invoke(m_InputActionAsset);
611
612 m_PointAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.PointAction));
613 m_MoveAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.MoveAction));
614 m_SubmitAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.SubmitAction));
615 m_CancelAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.CancelAction));
616 m_LeftClickAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.LeftClickAction));
617 m_MiddleClickAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.MiddleClickAction));
618 m_RightClickAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.RightClickAction));
619 m_ScrollWheelAction = InputActionReference.Create(m_InputActionAsset.FindAction(m_Cfg.ScrollWheelAction));
620
621 if (m_PointAction != null && m_PointAction.action != null)
622 m_PointAction.action.performed += OnPointerPerformed;
623
624 if (m_SubmitAction != null && m_SubmitAction.action != null)
625 m_SubmitAction.action.performed += OnSubmitPerformed;
626
627 if (m_CancelAction != null && m_CancelAction.action != null)
628 m_CancelAction.action.performed += OnCancelPerformed;
629
630 if (m_LeftClickAction != null && m_LeftClickAction.action != null)
631 m_LeftClickAction.action.performed += OnLeftClickPerformed;
632
633 if (m_MiddleClickAction != null && m_MiddleClickAction.action != null)
634 m_MiddleClickAction.action.performed += OnMiddleClickPerformed;
635
636 if (m_RightClickAction != null && m_RightClickAction.action != null)
637 m_RightClickAction.action.performed += OnRightClickPerformed;
638
639 if (m_ScrollWheelAction != null && m_ScrollWheelAction.action != null)
640 m_ScrollWheelAction.action.performed += OnScrollWheelPerformed;
641
642 // When adding new actions, don't forget to add them to UnregisterActions
643
644 if (InputSystem.actions == null)
645 {
646 // If we've not loaded a user-created set of actions, just enable the UI actions from our defaults.
647 m_InputActionAsset.FindActionMap("UI", true).Enable();
648 }
649 else
650 m_InputActionAsset.Enable();
651
652 // TODO make it configurable as it is not part of default config
653 // The Next/Previous action is not part of the input actions asset
654 RegisterNextPreviousAction();
655 }
656
657 void UnregisterActions()
658 {
659 if (m_PointAction != null && m_PointAction.action != null)
660 m_PointAction.action.performed -= OnPointerPerformed;
661
662 if (m_SubmitAction != null && m_SubmitAction.action != null)
663 m_SubmitAction.action.performed -= OnSubmitPerformed;
664
665 if (m_CancelAction != null && m_CancelAction.action != null)
666 m_CancelAction.action.performed -= OnCancelPerformed;
667
668 if (m_LeftClickAction != null && m_LeftClickAction.action != null)
669 m_LeftClickAction.action.performed -= OnLeftClickPerformed;
670
671 if (m_MiddleClickAction != null && m_MiddleClickAction.action != null)
672 m_MiddleClickAction.action.performed -= OnMiddleClickPerformed;
673
674 if (m_RightClickAction != null && m_RightClickAction.action != null)
675 m_RightClickAction.action.performed -= OnRightClickPerformed;
676
677 if (m_ScrollWheelAction != null && m_ScrollWheelAction.action != null)
678 m_ScrollWheelAction.action.performed -= OnScrollWheelPerformed;
679
680 m_PointAction = null;
681 m_MoveAction = null;
682 m_SubmitAction = null;
683 m_CancelAction = null;
684 m_LeftClickAction = null;
685 m_MiddleClickAction = null;
686 m_RightClickAction = null;
687 m_ScrollWheelAction = null;
688
689 if (m_InputActionAsset != null)
690 m_InputActionAsset.Disable();
691
692 UnregisterFixedActions();
693 }
694
695 public struct Configuration
696 {
697 public InputActionAsset ActionAsset;
698 public string PointAction;
699 public string MoveAction;
700 public string SubmitAction;
701 public string CancelAction;
702 public string LeftClickAction;
703 public string MiddleClickAction;
704 public string RightClickAction;
705 public string ScrollWheelAction;
706
707 public static Configuration GetDefaultConfiguration()
708 {
709 // Only use default actions asset configuration if (ISX-1954):
710 // - Project-wide Input Actions have not been configured, OR
711 // - Project-wide Input Actions have been configured but contains no UI action map.
712 var projectWideInputActions = InputSystem.actions;
713 var useProjectWideInputActions =
714 projectWideInputActions != null &&
715 projectWideInputActions.FindActionMap("UI") != null;
716
717 // Use InputSystem.actions (Project-wide Actions) if available, else use default asset if
718 // user didn't specifically set one, so that UI functions still work (ISXB-811).
719 return new Configuration
720 {
721 ActionAsset = useProjectWideInputActions ? InputSystem.actions : new DefaultInputActions().asset,
722 PointAction = "UI/Point",
723 MoveAction = "UI/Navigate",
724 SubmitAction = "UI/Submit",
725 CancelAction = "UI/Cancel",
726 LeftClickAction = "UI/Click",
727 MiddleClickAction = "UI/MiddleClick",
728 RightClickAction = "UI/RightClick",
729 ScrollWheelAction = "UI/ScrollWheel",
730 };
731 }
732 }
733
734 internal static void SetOnRegisterActions(Action<InputActionAsset> callback)
735 {
736 s_OnRegisterActions = callback;
737 }
738 }
739}
740#endif // UNITY_2023_2_OR_NEWER