AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
Interactive.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using UnityEngine;
5 using UnityEngine.Events;
6 using System.Collections.Generic;
8 
9 #if UNITY_WSA || UNITY_STANDALONE_WIN
10 using UnityEngine.Windows.Speech;
11 #endif
12 
13 namespace HoloToolkit.Examples.InteractiveElements
14 {
22  public class Interactive : MonoBehaviour, IInputClickHandler, IFocusable, IInputHandler
23  {
24 
25  public GameObject ParentObject;
26 
30  public bool IsEnabled = true;
31 
35  public bool HasGaze { get; protected set; }
36 
40  public bool HasDown { get; protected set; }
41 
45  public bool DetectHold = false;
46 
50  public float HoldTime = 0.5f;
51 
56  public float RollOffTime = 0.02f;
57 
61  public bool IsSelected { get; protected set; }
62 
63  [Tooltip("Set a keyword to invoke the OnSelect event")]
64  public string Keyword = "";
65 
66  [Tooltip("Gaze is required for the keyword to trigger this Interactive.")]
67  public bool KeywordRequiresGaze = true;
68 
72  public UnityEvent OnSelectEvents;
73  public UnityEvent OnDownEvent;
74  public UnityEvent OnHoldEvent;
75 
80  public enum ButtonStateEnum { Default, Focus, Press, Selected, FocusSelected, PressSelected, Disabled, DisabledSelected }
81  protected ButtonStateEnum mState = ButtonStateEnum.Default;
82 
86  protected float mRollOffTimer = 0;
87  protected float mHoldTimer = 0;
88  protected bool mCheckRollOff = false;
89  protected bool mCheckHold = false;
90 
91 #if UNITY_WSA || UNITY_STANDALONE_WIN
92  protected KeywordRecognizer mKeywordRecognizer;
93 #endif
94  protected Dictionary<string, int> mKeywordDictionary;
95  protected string[] mKeywordArray;
96 
100  protected bool mIgnoreSelect = false;
101  protected bool mCheckEnabled = false;
102  protected bool mCheckSelected = false;
103  protected bool UserInitiatedEvent = false;
104  protected bool mAllowSelection = false;
105 
106  protected List<InteractiveWidget> mInteractiveWidgets = new List<InteractiveWidget>();
107 
108  protected virtual void Awake()
109  {
110  if (ParentObject == null)
111  {
112  ParentObject = this.gameObject;
113  }
114 
115  CollectWidgets(forceCollection: true);
116  }
117 
121  protected virtual void Start()
122  {
123  if (Keyword != "")
124  {
125  mKeywordArray = new string[1] { Keyword };
126  if (Keyword.IndexOf(',') > -1)
127  {
128  mKeywordArray = Keyword.Split(',');
129 
130  mKeywordDictionary = new Dictionary<string, int>();
131  for (int i = 0; i < mKeywordArray.Length; ++i)
132  {
133  mKeywordDictionary.Add(mKeywordArray[i], i);
134  }
135  }
136 
137 #if UNITY_WSA || UNITY_STANDALONE_WIN
138  if (!KeywordRequiresGaze)
139  {
140  mKeywordRecognizer = new KeywordRecognizer(mKeywordArray);
141  mKeywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
142  mKeywordRecognizer.Start();
143  }
144 #endif
145 
146  }
147 
148  mCheckEnabled = IsEnabled;
149  mCheckSelected = IsSelected;
150 
151  UpdateEffects();
152  }
153 
157  public virtual void OnInputClicked(InputClickedEventData eventData)
158  {
159  if (!IsEnabled)
160  {
161  return;
162  }
163 
164  UserInitiatedEvent = true;
165 
166  if (mIgnoreSelect)
167  {
168  mIgnoreSelect = false;
169  return;
170  }
171 
172  UpdateEffects();
173 
174  OnSelectEvents.Invoke();
175  }
176 
180  public virtual void OnFocusEnter()
181  {
182  if (!IsEnabled)
183  {
184  return;
185  }
186 
187  HasGaze = true;
188 
189  SetKeywordListener(true);
190 
191  UpdateEffects();
192  }
193 
197  public virtual void OnFocusExit()
198  {
199  HasGaze = false;
200  EndHoldDetection();
201  mRollOffTimer = 0;
202  mCheckRollOff = true;
203  SetKeywordListener(false);
204  UpdateEffects();
205  }
206 
207  private void SetKeywordListener(bool listen)
208  {
209 #if UNITY_WSA || UNITY_STANDALONE_WIN
210  if (listen)
211  {
212  if (KeywordRequiresGaze && mKeywordArray != null)
213  {
214  if (mKeywordRecognizer == null)
215  {
216  mKeywordRecognizer = new KeywordRecognizer(mKeywordArray);
217  mKeywordRecognizer.OnPhraseRecognized += KeywordRecognizer_OnPhraseRecognized;
218  mKeywordRecognizer.Start();
219  }
220  else
221  {
222  if (!mKeywordRecognizer.IsRunning)
223  {
224  mKeywordRecognizer.Start();
225  }
226  }
227  }
228  }
229  else
230  {
231  if (mKeywordRecognizer != null && KeywordRequiresGaze)
232  {
233  if (mKeywordRecognizer.IsRunning)
234  {
235  mKeywordRecognizer.Stop();
236  mKeywordRecognizer.OnPhraseRecognized -= KeywordRecognizer_OnPhraseRecognized;
237  mKeywordRecognizer.Dispose();
238  mKeywordRecognizer = null;
239  }
240  }
241  }
242 #endif
243  }
244 
250  public void SetTitle(string title)
251  {
252  LabelTheme lblTheme = gameObject.GetComponent<LabelTheme>();
253  if (lblTheme != null)
254  {
255  lblTheme.Default = title;
256  }
257  TextMesh textMesh = gameObject.GetComponentInChildren<TextMesh>();
258  if (textMesh != null)
259  {
260  textMesh.text = title;
261  }
262  }
263 
267  public virtual void OnInputDown(InputEventData eventData)
268  {
269  if (!HasGaze)
270  {
271  return;
272  }
273 
274  HasDown = true;
275  mCheckRollOff = false;
276 
277  if (DetectHold)
278  {
279  mHoldTimer = 0;
280  mCheckHold = true;
281  }
282  UpdateEffects();
283 
284  OnDownEvent.Invoke();
285  }
286 
290  public virtual void OnInputUp(InputEventData eventData)
291  {
292  mCheckHold = false;
293  HasDown = false;
294  mIgnoreSelect = false;
295  EndHoldDetection();
296  mCheckRollOff = false;
297 
298  UpdateEffects();
299  }
300 
304  public virtual void OnHold()
305  {
306  mIgnoreSelect = true;
307  EndHoldDetection();
308 
309  UpdateEffects();
310 
311  OnHoldEvent.Invoke();
312  }
313 
318  public float GetHoldPercentage()
319  {
320  return mHoldTimer / HoldTime;
321  }
322 
323  protected void EndHoldDetection()
324  {
325  mHoldTimer = 0;
326  mCheckHold = false;
327  }
328 
329  private void CollectWidgets(bool forceCollection = false)
330  {
331  if (mInteractiveWidgets.Count == 0 || forceCollection)
332  {
333  if (ParentObject != null)
334  {
335  ParentObject.GetComponentsInChildren(mInteractiveWidgets);
336  }
337  for (int i = 0; i < mInteractiveWidgets.Count; ++i)
338  {
339  if (mInteractiveWidgets[i].InteractiveHost == null)
340  {
341  mInteractiveWidgets[i].InteractiveHost = this;
342  }
343  else
344  {
345  mInteractiveWidgets.RemoveAt(i);
346  --i;
347  }
348  }
349  }
350  }
351 
355  protected void UpdateEffects()
356  {
357  CollectWidgets();
358 
359  CompareStates();
360 
361  int interactiveCount = mInteractiveWidgets.Count;
362  for (int i = 0; i < interactiveCount; ++i)
363  {
364  InteractiveWidget widget = mInteractiveWidgets[i];
365  widget.SetState(mState);
366 
367  int currentCount = mInteractiveWidgets.Count;
368  if (currentCount < interactiveCount)
369  {
370  Debug.LogWarningFormat("Call to {0}'s SetState removed other interactive widgets. GameObject name: {1}.", widget.GetType(), widget.name);
371  interactiveCount = currentCount;
372  }
373  }
374  }
375 
376  public void RegisterWidget(InteractiveWidget widget)
377  {
378  CollectWidgets();
379  if (mInteractiveWidgets.Contains(widget))
380  {
381  return;
382  }
383 
384  mInteractiveWidgets.Add(widget);
385  widget.SetState(mState);
386  }
387 
389  {
390  if (mInteractiveWidgets != null)
391  {
392  mInteractiveWidgets.Remove(widget);
393  }
394  }
395 
396 #if UNITY_WSA || UNITY_STANDALONE_WIN
397  protected virtual void KeywordRecognizer_OnPhraseRecognized(PhraseRecognizedEventArgs args)
398  {
399 
400  // Check to make sure the recognized keyword matches, then invoke the corresponding method.
401  if (args.text == Keyword && (!KeywordRequiresGaze || HasGaze) && IsEnabled)
402  {
403  if (mKeywordDictionary == null)
404  {
405  OnInputClicked(null);
406  }
407  }
408  }
409 #endif
410 
414  protected void CompareStates()
415  {
416  if (IsEnabled)
417  {
418  // all states
419  if (IsSelected)
420  {
421  if (HasGaze)
422  {
423  if (HasDown)
424  {
425  mState = ButtonStateEnum.PressSelected;
426  }
427  else
428  {
429  mState = ButtonStateEnum.FocusSelected;
430  }
431  }
432  else
433  {
434  if (HasDown)
435  {
436  mState = ButtonStateEnum.PressSelected;
437  }
438  else
439  {
440  mState = ButtonStateEnum.Selected;
441  }
442  }
443  }
444  else
445  {
446  if (HasGaze)
447  {
448  if (HasDown)
449  {
450  mState = ButtonStateEnum.Press;
451  }
452  else
453  {
454  mState = ButtonStateEnum.Focus;
455  }
456  }
457  else
458  {
459  if (HasDown)
460  {
461  mState = ButtonStateEnum.Press;
462  }
463  else
464  {
465  mState = ButtonStateEnum.Default;
466  }
467  }
468  }
469 
470  }
471  else
472  {
473  if (IsSelected)
474  {
475  mState = ButtonStateEnum.DisabledSelected;
476  }
477  else
478  {
479  mState = ButtonStateEnum.Disabled;
480  }
481  }
482  mCheckSelected = IsSelected;
483  mCheckEnabled = IsEnabled;
484  }
485 
489  protected virtual void Update()
490  {
491 
492  if (mCheckRollOff && HasDown)
493  {
494  if (mRollOffTimer < RollOffTime)
495  {
496  mRollOffTimer += Time.deltaTime;
497  }
498  else
499  {
500  mCheckRollOff = false;
501  OnInputUp(null);
502  }
503  }
504  if (mCheckHold)
505  {
506  if (mHoldTimer < HoldTime)
507  {
508  mHoldTimer += Time.deltaTime;
509  }
510  else
511  {
512  mCheckHold = false;
513  OnHold();
514  }
515  }
516 
517  if (!UserInitiatedEvent && (mCheckEnabled != IsEnabled || mCheckSelected != IsSelected))
518  {
519  UpdateEffects();
520  }
521 
522  UserInitiatedEvent = false;
523  }
524 
525  protected virtual void OnDestroy()
526  {
527  SetKeywordListener(false);
528  }
529 
530  protected virtual void OnEnable()
531  {
532 #if UNITY_WSA || UNITY_STANDALONE_WIN
533  if (mKeywordRecognizer != null && !KeywordRequiresGaze)
534  {
535  SetKeywordListener(true);
536  }
537 #endif
538  }
539 
540  protected virtual void OnDisable()
541  {
542  OnFocusExit();
543  }
544  }
545 }
virtual void OnFocusEnter()
The gameObject received gaze
Definition: Interactive.cs:180
virtual void Update()
Run timers and check for updates
Definition: Interactive.cs:489
void UnregisterWidget(InteractiveWidget widget)
Definition: Interactive.cs:388
InteractiveState can exist on a child element of the game object containing the Interactive component...
virtual void SetState(Interactive.ButtonStateEnum state)
Interactive calls this method on state change
ButtonStateEnum
A button typically has 8 potential states. We can update visual feedback based on state change...
Definition: Interactive.cs:80
void UpdateEffects()
Loop through all InteractiveEffects on child elements and update their states
Definition: Interactive.cs:355
virtual void OnInputUp(InputEventData eventData)
All tab, hold, and gesture events are completed
Definition: Interactive.cs:290
Interface to implement to react to simple click input.
void CompareStates()
Check if any state changes have occurred, from alternate input sources
Definition: Interactive.cs:414
virtual void OnFocusExit()
The gameObject no longer has gaze
Definition: Interactive.cs:197
Interface to implement to react to focus enter/exit.
Definition: IFocusable.cs:11
A theme for handling the label string on the toggle button
Definition: LabelTheme.cs:13
virtual void OnInputDown(InputEventData eventData)
The user is initiating a tap or hold
Definition: Interactive.cs:267
Describes an input event that involves a tap.
Interface to implement to react to simple pointer-like input.
virtual void OnHold()
The hold timer has finished
Definition: Interactive.cs:304
void RegisterWidget(InteractiveWidget widget)
Definition: Interactive.cs:376
Describes an input event that has a source id and a press kind.
UnityEvent OnSelectEvents
Exposed Unity Events
Definition: Interactive.cs:72
virtual void OnInputClicked(InputClickedEventData eventData)
An OnTap event occurred
Definition: Interactive.cs:157
Interactive exposes basic button type events to the Unity Editor and receives messages from the Gestu...
Definition: Interactive.cs:22
virtual void Start()
Set default visual states on Start
Definition: Interactive.cs:121
float GetHoldPercentage()
The percentage of hold time completed
Definition: Interactive.cs:318
void SetTitle(string title)
shortcut to set title (assuming this Interactive has a LabelTheme and a TextMesh attached to it) ...
Definition: Interactive.cs:250