Skip to content

Commit e220b93

Browse files
keveleighRogPodge
andcommitted
Fix speech command dropdown and update commands in ET example (#10197)
* Add custom attribute and property drawer for rendering a dropdown of speech keywords * Update inspectors to use new utility * Change voice command from start/stop recording to "record eye gaze" and "end recording" to not clash with system keywords * Also check if availableKeywords is empty for valid keywords Co-authored-by: RogPodge <[email protected]> * Add summary and minor optimization Co-authored-by: RogPodge <[email protected]>
1 parent 8c72cf5 commit e220b93

File tree

14 files changed

+313
-127
lines changed

14 files changed

+313
-127
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System;
5+
using UnityEngine;
6+
7+
namespace Microsoft.MixedReality.Toolkit
8+
{
9+
/// <summary>
10+
/// Attribute used to display a dropdown of registered keywords from the speech profile.
11+
/// </summary>
12+
[AttributeUsage(AttributeTargets.Field)]
13+
public sealed class SpeechKeywordAttribute : PropertyAttribute { }
14+
}

Assets/MRTK/Core/Attributes/SpeechKeywordAttribute.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/MRTK/Core/Definitions/InputSystem/KeywordAndResponse.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public KeywordAndResponse(string keyword, UnityEvent response)
2626

2727
[SerializeField]
2828
[Tooltip("The keyword to listen for.")]
29+
[SpeechKeyword]
2930
private string keyword;
3031

3132
/// <summary>
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Microsoft.MixedReality.Toolkit.Utilities.Editor;
5+
using UnityEditor;
6+
using UnityEngine;
7+
8+
namespace Microsoft.MixedReality.Toolkit.Input.Editor
9+
{
10+
[CustomPropertyDrawer(typeof(SpeechKeywordAttribute))]
11+
public class SpeechKeywordPropertyDrawer : PropertyDrawer
12+
{
13+
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent content) => SpeechKeywordUtility.RenderKeywords(property, rect, content);
14+
}
15+
}

Assets/MRTK/Core/Inspectors/PropertyDrawers/SpeechKeywordPropertyDrawer.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/MRTK/Core/Inspectors/Utilities/InspectorUIUtility.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using System.Linq;
77
using System.Reflection;
88
using UnityEditor;
9-
using UnityEditor.Experimental.SceneManagement;
109
using UnityEngine;
1110

1211
namespace Microsoft.MixedReality.Toolkit.Utilities.Editor
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using UnityEditor;
7+
using UnityEngine;
8+
9+
namespace Microsoft.MixedReality.Toolkit.Utilities.Editor
10+
{
11+
/// <summary>
12+
/// Provides helpers for rendering speech keyword dropdowns populated from the MRTK speech profile.
13+
/// </summary>
14+
public static class SpeechKeywordUtility
15+
{
16+
private static readonly GUIContent SpeechCommandsLabel = new GUIContent("Speech Command", "Speech keyword to use, pulled from MRTK/Input/Speech Commands Profile");
17+
18+
/// <summary>
19+
/// Renders a dropdown with all keywords in the active <see cref="Input.MixedRealitySpeechCommandsProfile"/>.
20+
/// </summary>
21+
/// <param name="property">The string property representing the selected keyword.</param>
22+
public static void RenderKeywords(SerializedProperty property, Rect rect = default(Rect), GUIContent content = null) => RenderKeywordsExcept(null, property, rect, content);
23+
24+
/// <summary>
25+
/// Renders a dropdown with all keywords in the active <see cref="Input.MixedRealitySpeechCommandsProfile"/> except those passed in.
26+
/// </summary>
27+
/// <param name="keywordsInUse">The keywords the component is currently using.</param>
28+
/// <param name="property">The string property representing the selected keyword.</param>
29+
public static void RenderKeywordsExcept(string[] keywordsInUse, SerializedProperty property, Rect rect = default(Rect), GUIContent content = null)
30+
{
31+
IEnumerable<string> availableKeywords = GetDistinctRegisteredKeywords();
32+
33+
if (keywordsInUse != null && availableKeywords != null)
34+
{
35+
availableKeywords = availableKeywords.Except(keywordsInUse.Select(keyword => keyword));
36+
}
37+
38+
bool validSpeechKeywords = availableKeywords != null && availableKeywords.Count() > 0;
39+
40+
if (rect == default(Rect))
41+
{
42+
rect = EditorGUILayout.GetControlRect();
43+
}
44+
45+
if (content == null)
46+
{
47+
content = SpeechCommandsLabel;
48+
}
49+
50+
using (new EditorGUI.DisabledScope(!validSpeechKeywords))
51+
using (new EditorGUI.PropertyScope(rect, content, property))
52+
{
53+
string[] keywordOptions;
54+
if (validSpeechKeywords)
55+
{
56+
if (string.IsNullOrWhiteSpace(property.stringValue) || availableKeywords.Contains(property.stringValue))
57+
{
58+
keywordOptions = availableKeywords.OrderBy(keyword => keyword).ToArray();
59+
}
60+
else
61+
{
62+
// if the currently selected option is not whitespace, ensure it's included in the dropdown
63+
keywordOptions = availableKeywords.Concat(new[] { property.stringValue }).OrderBy(keyword => keyword).ToArray();
64+
}
65+
}
66+
else
67+
{
68+
keywordOptions = new string[] { "Missing Speech Commands" };
69+
}
70+
71+
int previousSelection = Mathf.Clamp(ArrayUtility.IndexOf(keywordOptions, property.stringValue), 0, int.MaxValue);
72+
int currentSelection = EditorGUI.Popup(rect, content.text, previousSelection, keywordOptions);
73+
74+
if (validSpeechKeywords && currentSelection != previousSelection)
75+
{
76+
property.stringValue = currentSelection > 0 ? keywordOptions[currentSelection] : string.Empty; // Don't serialize the "(No Selection)" entry
77+
}
78+
}
79+
}
80+
81+
/// <summary>
82+
/// Look for speech commands in the MRTK speech command profile.
83+
/// Adds a "(No Selection)" value at index zero and filters out duplicate values.
84+
/// </summary>
85+
public static IEnumerable<string> GetDistinctRegisteredKeywords()
86+
{
87+
if (!MixedRealityToolkit.ConfirmInitialized() ||
88+
!MixedRealityToolkit.Instance.HasActiveProfile ||
89+
!MixedRealityToolkit.Instance.ActiveProfile.IsInputSystemEnabled ||
90+
MixedRealityToolkit.Instance.ActiveProfile.InputSystemProfile.SpeechCommandsProfile == null ||
91+
MixedRealityToolkit.Instance.ActiveProfile.InputSystemProfile.SpeechCommandsProfile.SpeechCommands.Length == 0)
92+
{
93+
return null;
94+
}
95+
96+
List<string> keywords = new List<string>
97+
{
98+
"(No Selection)"
99+
};
100+
101+
Input.SpeechCommands[] speechCommands = MixedRealityToolkit.Instance.ActiveProfile.InputSystemProfile.SpeechCommandsProfile.SpeechCommands;
102+
for (var i = 0; i < speechCommands.Length; i++)
103+
{
104+
keywords.Add(speechCommands[i].Keyword);
105+
}
106+
107+
return keywords.Distinct();
108+
}
109+
}
110+
}

Assets/MRTK/Core/Inspectors/Utilities/SpeechKeywordUtility.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/MRTK/Examples/Demos/EyeTracking/DemoVisualizer/Prefabs/RecordButton.prefab

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -914,9 +914,7 @@ MonoBehaviour:
914914
m_OnCullStateChanged:
915915
m_PersistentCalls:
916916
m_Calls: []
917-
m_TypeName: UnityEngine.UI.MaskableGraphic+CullStateChangedEvent, UnityEngine.UI,
918-
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
919-
m_text: Start Recording
917+
m_text: Record Eye Gaze
920918
m_isRightToLeft: 0
921919
m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
922920
m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2}
@@ -952,7 +950,6 @@ MonoBehaviour:
952950
m_fontSizeMax: 72
953951
m_fontStyle: 0
954952
m_textAlignment: 514
955-
m_isAlignmentEnumConverted: 1
956953
m_characterSpacing: 3
957954
m_wordSpacing: 0
958955
m_lineSpacing: 0
@@ -979,26 +976,24 @@ MonoBehaviour:
979976
m_verticalMapping: 0
980977
m_uvLineOffset: 0
981978
m_geometrySortingOrder: 0
979+
m_VertexBufferAutoSizeReduction: 1
982980
m_firstVisibleCharacter: 0
983981
m_useMaxVisibleDescender: 1
984982
m_pageToDisplay: 1
985983
m_margin: {x: 0, y: 0, z: 0, w: 0}
986984
m_textInfo:
987-
textComponent: {fileID: 0}
985+
textComponent: {fileID: 4533479808247404460}
988986
characterCount: 15
989987
spriteCount: 0
990-
spaceCount: 1
991-
wordCount: 2
988+
spaceCount: 2
989+
wordCount: 3
992990
linkCount: 0
993991
lineCount: 1
994992
pageCount: 1
995993
materialCount: 1
996-
m_havePropertiesChanged: 0
997994
m_isUsingLegacyAnimationComponent: 0
998995
m_isVolumetricText: 0
999996
m_spriteAnimator: {fileID: 0}
1000-
m_isInputParsingRequired: 0
1001-
m_inputSource: 0
1002997
m_hasFontAssetChanged: 0
1003998
m_renderer: {fileID: 4533479808247404465}
1004999
m_subTextObjects:
@@ -1307,7 +1302,7 @@ MonoBehaviour:
13071302
startDimensionIndex: 0
13081303
CanSelect: 1
13091304
CanDeselect: 1
1310-
VoiceCommand: Select
1305+
voiceCommand: Select
13111306
voiceRequiresFocus: 1
13121307
profiles:
13131308
- Target: {fileID: 4533479809219024895}
@@ -1352,8 +1347,6 @@ MonoBehaviour:
13521347
m_StringArgument:
13531348
m_BoolArgument: 0
13541349
m_CallState: 2
1355-
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
1356-
Culture=neutral, PublicKeyToken=null
13571350
Events: []
13581351
resetOnDestroy: 0
13591352
enabledOnStart: 1
@@ -1378,8 +1371,6 @@ MonoBehaviour:
13781371
onLookAtStart:
13791372
m_PersistentCalls:
13801373
m_Calls: []
1381-
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
1382-
Culture=neutral, PublicKeyToken=null
13831374
whileLookingAtTarget:
13841375
m_PersistentCalls:
13851376
m_Calls:
@@ -1394,8 +1385,6 @@ MonoBehaviour:
13941385
m_StringArgument:
13951386
m_BoolArgument: 0
13961387
m_CallState: 2
1397-
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
1398-
Culture=neutral, PublicKeyToken=null
13991388
onLookAway:
14001389
m_PersistentCalls:
14011390
m_Calls:
@@ -1410,18 +1399,18 @@ MonoBehaviour:
14101399
m_StringArgument:
14111400
m_BoolArgument: 0
14121401
m_CallState: 2
1413-
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
1414-
Culture=neutral, PublicKeyToken=null
14151402
onDwell:
14161403
m_PersistentCalls:
14171404
m_Calls: []
1418-
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
1419-
Culture=neutral, PublicKeyToken=null
14201405
onSelected:
14211406
m_PersistentCalls:
14221407
m_Calls: []
1423-
m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0,
1424-
Culture=neutral, PublicKeyToken=null
1408+
onTapDown:
1409+
m_PersistentCalls:
1410+
m_Calls: []
1411+
onTapUp:
1412+
m_PersistentCalls:
1413+
m_Calls: []
14251414
eyeCursorSnapToTargetCenter: 0
14261415
--- !u!82 &4533479808906774274
14271416
AudioSource:

Assets/MRTK/Examples/Demos/EyeTracking/General/Profiles/EyeTrackingDemoSpeechCommandsProfile.asset

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,14 @@ MonoBehaviour:
8787
description: ZoomOut
8888
axisConstraint: 0
8989
- localizationKey:
90-
keyword: Start recording
90+
keyword: Record eye gaze
9191
keyCode: 107
9292
action:
9393
id: 20
9494
description: StartRecordingData
9595
axisConstraint: 0
9696
- localizationKey:
97-
keyword: Stop recording
97+
keyword: End recording
9898
keyCode: 108
9999
action:
100100
id: 21

0 commit comments

Comments
 (0)